diff --git a/bot.py b/bot.py index fb582d1..159ecf6 100644 --- a/bot.py +++ b/bot.py @@ -39,7 +39,8 @@ class QBittorrentAPICaller(): self.username = username self.password = password self.session = requests.Session() - self.reestablish_session() + # self.reestablish_session() + self.run_session_maintainer() def __del__(self): self.logout() @@ -58,55 +59,122 @@ class QBittorrentAPICaller(): return to_ret # We need to reestablish a connection to the server periodically, this does that - def reestablish_session(self): + #def reestablish_session(self): # Every 10 minutes, run this - threading.Timer(600, self.reestablish_session).start() - self.logout() - self.login() - logging.info("Reestablished connection to QBittorrent server") + #threading.Timer(600, self.reestablish_session).start() + # self.logout() + # self.login() + # logging.info("Reestablished connection to QBittorrent server") + + def run_session_maintainer(self): + def loop(): + while True: + try: + self.logout() + self.login() + logging.info("Reestablished connection to QBittorrent server") + except Exception as e: + logging.error(f"Session maintainer error: {e}", exc_info=True) + time.sleep(600) # 10 minutes + threading.Thread(target=loop, daemon=True).start() def truncate_string(self, s, max_length=25): return s[:max_length] + '...' if len(s) > max_length else s + def safe_get(self, path, params=None): + try: + # resp = self.session.get(self.url + path, params=params, timeout=5) + resp = self.session.get(path, params=params, timeout=5) + resp.raise_for_status() + return resp + except requests.exceptions.RequestException as e: + logging.warning(f"GET {path} failed: {e}") + return None + + def safe_post(self, path, data=None): + try: + # resp = self.session.post(self.url + path, data=data, timeout=5) + resp = self.session.post(path, data=data, timeout=5) + resp.raise_for_status() + return resp + except requests.exceptions.RequestException as e: + logging.warning(f"POST {path} failed: {e}") + return None + def login(self): the_url = self.url + "/" + "api/v2/auth/login" data={"username":self.username, "password":self.password} - - resp = self.session.post(the_url, data=data) + + try: + resp = self.session.post(the_url, data=data, timeout=5) + resp.raise_for_status() + except requests.exceptions.RequestException as e: + logging.warning(f"Login failed: {e}") + #resp = self.session.post(the_url, data=data) def logout(self): the_url = self.url + "/" + "api/v2/auth/logout" - resp = self.session.post(the_url) + # resp = self.session.post(the_url) + try: + resp = self.session.post(the_url, timeout=5) + resp.raise_for_status() + except requests.exceptions.RequestException as e: + logging.warning(f"Logout failed: {e}") + + couldnt_connect_text = "Could not connect to qbittorrent server" def version(self): the_url = self.url + "/" + "api/v2/app/version" - resp = self.session.get(the_url) - if resp.status_code != 200: - return "Got status" + str(resp.status_code) - return resp.text + # resp = self.session.get(the_url) + # if resp.status_code != 200: + # return "Got status" + str(resp.status_code) + # return resp.text + resp = self.safe_get(the_url) + if not resp: + return self.couldnt_connect_text + else: + return resp.text def webapiVersion(self): the_url = self.url + "/" + "api/v2/app/webapiVersion" - resp = self.session.get(the_url) - if resp.status_code != 200: - return "Got status" + str(resp.status_code) - return resp.text + resp = self.safe_get(the_url) + if not resp: + return self.couldnt_connect_text + else: + return resp.text + # try: + # resp = self.session.get(the_url) + # resp.raise_for_status() + # return resp.text + # except requests.exceptions.RequestException as e: + # logging.warning(f"Couldn't get version") + # if resp.status_code != 200: + # return "Got status" + str(resp.status_code) def buildInfo(self): the_url = self.url + "/" + "api/v2/app/buildInfo" - resp = self.session.get(the_url) - if resp.status_code != 200: - return "Got status" + str(resp.status_code) + resp = self.safe_get(the_url) + if not resp: + return self.couldnt_connect_text + else: + return json_to_key_value_string(resp.text) + + # resp = self.session.get(the_url) + # if resp.status_code != 200: + # return "Got status" + str(resp.status_code) - return json_to_key_value_string(resp.text) + # return json_to_key_value_string(resp.text) def torrentList(self, modifier=""): the_url = self.url + "/" + "api/v2/torrents/info" to_ret = "```\n" for f in ["downloading", "completed"]: - resp = self.session.post(the_url, data={"filter":f}) - if resp.status_code != 200: - return "Got status" + str(resp.status_code) + # resp = self.session.post(the_url, data={"filter":f}) + # if resp.status_code != 200: + # return "Got status" + str(resp.status_code) + resp = self.safe_post(the_url, data={"filter":f}) + if not resp: + return self.couldnt_connect_text parsed = json.loads(resp.text) to_ret += f.upper() + "\n" @@ -122,13 +190,17 @@ class QBittorrentAPICaller(): to_ret += "```" return to_ret + # Too lazy to make this safe, hopefully will be fine idk def add(self, url, username, category="unknown"): if not url.startswith("magnet:"): return "Please supply a magnet link (begins with \"magnet:\")" the_url = self.url + "/" + "api/v2/torrents/add" - resp = self.session.post(the_url, data={"urls":url,"category":category,"tags":username}) + # resp = self.session.post(the_url, data={"urls":url,"category":category,"tags":username}) + resp = self.safe_post(the_url, data={"urls":url,"category":category,"tags":username}) + if not resp: + return self.couldnt_connect_text if (resp.status_code == 415): return "Torrent file not valid" @@ -138,7 +210,10 @@ class QBittorrentAPICaller(): # Make request to torrentlist with particular hash the_url2 = self.url + "/" + "api/v2/torrents/info" time.sleep(5) - resp2 = self.session.post(the_url2) + # resp2 = self.session.post(the_url2) + resp2 = self.safe_post(the_url2) + if not resp2: + return self.couldnt_connect_text if (resp2.status_code != 200): return "Could not verify if torrent was added" parsed = json.loads(resp2.text) @@ -153,27 +228,39 @@ class QBittorrentAPICaller(): def get_search_plugins(self): the_url = self.url + "/" + "api/v2/search/plugins" - return self.session.post(the_url) + # return self.session.post(the_url) + resp = self.safe_post(the_url) + return resp if resp else self.couldnt_connect_text def search_start(self, searchstring, category="all"): the_url = self.url + "/" + "api/v2/search/start" - return self.session.post(the_url, data={"pattern":searchstring, "plugins":"enabled", "category":category}) + resp = self.safe_post(the_url, data={"pattern":searchstring, "plugins":"enabled", "category":category}) + return resp if resp else self.couldnt_connect_text + # return self.session.post(the_url, data={"pattern":searchstring, "plugins":"enabled", "category":category}) def search_status(self, search_id): the_url = self.url + "/" + "api/v2/search/status" - return self.session.post(the_url, data={"id":search_id}) + resp = self.safe_post(the_url, data={"id":search_id}) + return resp if resp else self.couldnt_connect_text + # return self.session.post(the_url, data={"id":search_id}) def search_stop(self, search_id): the_url = self.url + "/" + "api/v2/search/stop" - return self.session.post(the_url, data={"id":search_id}) + resp = self.safe_post(the_url, data={"id":search_id}) + return resp if resp else self.couldnt_connect_text + # return self.session.post(the_url, data={"id":search_id}) def search_results(self, search_id): the_url = self.url + "/" + "api/v2/search/results" - return self.session.post(the_url, data={"id":search_id}) + resp = self.safe_post(the_url, data={"id":search_id}) + return resp if resp else self.couldnt_connect_text + # return self.session.post(the_url, data={"id":search_id}) def search_delete(self, search_id): the_url = self.url + "/" + "api/v2/search/delete" - return self.session.post(the_url, data={"id":search_id}) + resp = self.safe_post(the_url, data={"id":search_id}) + return resp if resp else self.couldnt_connect_text + # return self.session.post(the_url, data={"id":search_id}) @@ -198,7 +285,8 @@ class QBBot(slixmpp.ClientXMPP): # If you wanted more functionality, here's how to register plugins: # self.register_plugin('xep_0030') # Service Discovery - # self.register_plugin('xep_0199') # XMPP Ping + self.register_plugin('xep_0199') # XMPP Ping + self['xep_0199'].enable_keepalive(interval=60, timeout=10) # Here's how to access plugins once you've registered them: # self['xep_0030'].add_feature('echo_demo')