def main(): qb = Client('http://127.0.0.1:8080') qb.login() pool = eventlet.GreenPool(1) # Get Data rid = 0 while True: log.warning(time.asctime(time.localtime())) try: data = qb.sync(rid) rid = data['rid'] #pp.pprint(data.get('torrents')) check_if_torrent_finish(data.get('torrents'), pool, qb) except KeyboardInterrupt: break except: qb.login() rid = 0 continue time.sleep(5 * 60) pass
class Server: MAGNET_URI = r"magnet:\?xt=urn:btih:[a-zA-Z0-9]*" COMMAND_GET_DLS = "__listdownloaded__" COMMAND_GET_TORRENTS = "__listtorrents__" COMMAND_GET_TIME = "__gettime__" COMMAND_GET_DIRECTORIES = "__getdirectories__" COMMAND_POST_REFRESH = "__refreshplex__" def __str__(self): return \ "Handles TCP connections between this and client and manages downloads." def __init__(self, config, _logger, buffer=1024): self.config = config self.logger = _logger self.buffsize = buffer self.DEFAULT_DOWNLOADED_FILE_PATH = config.get("General", "savepath") self.plex_directories = literal_eval(config.get("Plex", "directories")) # set time as a property self._time = datetime.datetime.now() self.login() self.autoUpdater = AutoUpdater(config, _logger=self.logger) @property def time(self): return str(self._time)[:-7] @time.setter def time(self, value): self._time = value def start(self): # self.listen() # self.autoUpdater.start() server_prcs = Process(target=self.listen) updater_prcs = Process(target=self.autoUpdater.start) server_prcs.start() updater_prcs.start() def login(self): self.myPlex = myplex.MyPlexAccount( username=self.config.get("Plex", "username"), password=self.config.get("Plex", "password")) self.client = Client(self.config.get("qBittorrent", "host")) self.client.login(self.config.get("qBittorrent", "username"), self.config.get("qBittorrent", "password")) def listen(self): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.bind((self.config.get("Server", "host"), self.config.getint("Server", "port"))) print("listening on %s:%i...." % (self.config.get( "Server", "host"), self.config.getint("Server", "port"))) self.logger.log("SERVER LISTENING") try: while 1: s.listen() cnn, addr = s.accept() threading.Thread(target=self.acceptClient, args=(cnn, addr)).start() # occasional crash, restart the listen? except ConnectionResetError as e: self.logger.log(str(e)) def acceptClient(self, cnn, addr): try: while 1: data = cnn.recv(self.buffsize) decoded = data.decode() self.logger.log("CONNECTION: %s: %s" % (str(addr), decoded)) if not data: break if re.match(self.MAGNET_URI, decoded[1:]): resp = self.downloadTorrent(decoded[1:], decoded[0]) encoding_resp = bytes(resp, encoding="utf-8") cnn.sendall(encoding_resp) elif decoded == self.COMMAND_GET_DLS: resp = bytes(self.getDownloadList, encoding="utf-8") cnn.sendall(resp) elif decoded == self.COMMAND_GET_TORRENTS: resp = bytes(self.getTorrentList, encoding="utf-8") cnn.sendall(resp) elif decoded == self.COMMAND_POST_REFRESH: self.updateLibrary() cnn.sendall(b"updated plex library") elif decoded == self.COMMAND_GET_TIME: resp = bytes(self.time, encoding="utf-8") cnn.sendall(resp) elif decoded == self.COMMAND_GET_DIRECTORIES: resp = bytes(self.getPlexDirectories, encoding="utf-8") cnn.sendall(resp) else: cnn.sendall(b"invalid request") except ServerRequestError as e: cnn.sendall(b"An error has occurred please try again.") self.logger.log("SERVER ERROR - " + str(e)) except Exception as e: cnn.sendall( bytes(f"An unkown error has occurred. {str(e)}", encoding="utf-8")) self.logger.log(f"SERVER ERROR - {type(e)} - {str(e)}") def downloadTorrent(self, uri, pathIndex): def download(): self.client.download_from_link( uri, savepath=self.DEFAULT_DOWNLOADED_FILE_PATH) # return the extracted hash from the uri # hash appears before the name in the uri and is wrapped in identifiable tags # parse out any http uri syntax magnet = unquote(uri) magnet = re.sub(r'magnet:\?xt=urn:btih:', '', magnet) magnet = magnet[:magnet.index('&dn=')] # if the string now matches a hash regex if re.match('[a-zA-Z0-9]*', magnet): return magnet else: return None def getAppropriateFilePath(torrent, pathIndex): import difflib def getSeasonSubDir(name, season, path): dirs = [ f for f in os.listdir(path) if not os.path.isfile(os.path.join(path, f)) ] # find existing season subfolder for d in dirs: if ("s" + season).lower() in d.lower() or \ ("season " + season).lower() in d.lower(): return path + "\\" + d # directory doesnt exist, create and return newdir = "%s\\%s Season %s" % (path, name, season) os.mkdir(newdir) return newdir client_path = self.plex_directories[pathIndex] try: if '.' in torrent['name']: t_split = torrent['name'].split('.') elif ' ' in torrent['name']: t_split = torrent['name'].split(' ') # find if torrent has seasons. # usual syntax is SXXEXX # group1- name # group2 - season (value) # group3 - episode (value) # group4 - rest regex = re.match(r'(.*?)\.S?(\d{1,2})E?(\d{2})\.(.*)', torrent['name'].replace(' ', '.')) if regex: # find the best fitting season folder # gets the media name for i, st in enumerate(t_split): if re.match(r"[Ss](\d{1,2})[Ee](\d{1,2})", st) or st.lower() == "season": # presume name is up to this index media_name = ' '.join(t_split[:i]) self.logger.log(f"media_name: {media_name}") break # get all folders dirs = [ f for f in os.listdir(client_path) if not os.path.isfile(os.path.join(client_path, f)) ] for d in dirs: # if the ratio is acceptable if difflib.SequenceMatcher( None, a=media_name.lower(), b=d.lower()).ratio() >= 0.55: # we should try to find the right season folder tv_dir = getSeasonSubDir(media_name, regex.group(2), client_path + "\\" + d) return tv_dir return client_path + "\\" + torrent['name'] except NameError: return client_path + "\\" + torrent['name'] except Exception as e: self.logger.log("ERROR getAppropiateFilePath: %s - %s" % (e, torrent["name"])) return self.DEFAULT_DOWNLOADED_FILE_PATH def overrideFilePath(hash): # use old api for torrent.set_location from qbittorrentapi import Client as xClient xc = xClient(self.config.get("qBittorrent", "host"), self.config.get("qBittorrent", "username"), self.config.get("qBittorrent", "password")) xt = next((x for x in xc.torrents.info.downloading() if x["hash"].lower() == hash.lower()), None) if xt is not None: # print("suitable location for %s %s" % (xt["name"], new_save_path)) self.logger.log("WRITING %s TO %s" % (xt["name"], new_save_path)) xt.set_location(new_save_path) try: torrent_hash = download() self.logger.log("TORRENT ADDED: %s" % uri) self.client.sync() # use the extracted hash to fetch the just added torrent t = next((x for x in self.client.torrents() if x["hash"].lower() == torrent_hash.lower()), None) if t is not None: #kinda bugs me how this call is here new_save_path = getAppropriateFilePath(t, int(pathIndex)) # override file path. overrideFilePath(torrent_hash) return "%s\n%s" % (t["name"], new_save_path) except HTTPError as e: self.login() raise ServerRequestError(e, self.downloadTorrent.__name__) except Exception as e: raise ServerRequestError(e, self.downloadTorrent.__name__) def updateLibrary(self): res = self.myPlex.resource(self.config.get("Plex", "server")).connect() res.library.update() @property def getDownloadList(self): # list all downloaded folders paths = [f for f in os.listdir(self.DEFAULT_DOWNLOADED_FILE_PATH)] return ','.join(paths) @property def getTorrentList(self): torrents = [] try: _t = self.client.torrents() except HTTPError as e: # 403 forbiddent exception (cant find exception type) # try relogin self.login() raise ServerRequestError(e, self.getTorrentList.__name__) else: for t in _t: torrents.append( str(t["hash"]) + "~" + t["name"] + "~" + str(t["progress"]) + "~" + t["state"]) return '\n'.join(torrents) @property def getPlexDirectories(self): # ? is a protected char why not return '?'.join(self.plex_directories) def getDiskUsages(self): from shutil import disk_usage usages = [(str, float, int)] for dir in self.plex_directories: du = disk_usage(dir) # disk usage # (total, used, free) usages.append((dir, (du[2] / du[0]) * 100, du[2])) return usages