Made network requests safe, fixed connection reestablish mem leak

This commit is contained in:
j4nk 2025-09-03 22:00:19 -04:00
parent f7075b01e3
commit db315652d1
1 changed files with 121 additions and 33 deletions

154
bot.py
View File

@ -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')