From 217a9e39f8154505e8c663f4f8941edc78a30302 Mon Sep 17 00:00:00 2001 From: jaqb Date: Wed, 26 Jul 2023 14:27:38 +0200 Subject: [PATCH 01/28] Added support for local trakt list --- content/services/trakt.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/content/services/trakt.py b/content/services/trakt.py index 5af16aff8..28a847134 100644 --- a/content/services/trakt.py +++ b/content/services/trakt.py @@ -220,6 +220,9 @@ def __init__(self): if list.startswith(user[0] + "'s private list:"): list_type = "private" break + if list.startswith("local:"): + list_type = "local" + break current_user = user if list_type == "watchlist": try: @@ -248,6 +251,34 @@ def __init__(self): except Exception as e: ui_print("[trakt error]: (exception): " + str(e), debug=ui_settings.debug) continue + elif list_type == "local": + try: + path = regex.sub("^local:\s*","",list) + local_items = json.loads(open(path).read(), object_hook=lambda d: SimpleNamespace(**d)) + for element in local_items: + if hasattr(element, 'show'): + element.show.type = 'show' + element.show.user = user + element.show.guid = element.show.ids.trakt + try: + element.show.watchlistedAt = datetime.datetime.timestamp(datetime.datetime.strptime(element.listed_at,'%Y-%m-%dT%H:%M:%S.000Z')) + except: + element.show.watchlistedAt = 0 + if not element.show in self.data: + self.data.append(show(element.show)) + elif hasattr(element, 'movie'): + element.movie.type = 'movie' + element.movie.user = user + element.movie.guid = element.movie.ids.trakt + try: + element.movie.watchlistedAt = datetime.datetime.timestamp(datetime.datetime.strptime(element.listed_at,'%Y-%m-%dT%H:%M:%S.000Z')) + except: + element.movie.watchlistedAt = 0 + if not element.movie in self.data: + self.data.append(movie(element.movie)) + except Exception as e: + ui_print("[trakt error]: (exception): " + str(e), debug=ui_settings.debug) + continue elif list_type == "collection": try: watchlist_items, header = get('https://api.trakt.tv/sync/collection/shows?extended=full') From 4719337dd82742871ad88e406b3e6a37167b4773 Mon Sep 17 00:00:00 2001 From: Kip Date: Tue, 19 Dec 2023 08:25:46 -0700 Subject: [PATCH 02/28] Added systemd service file and refactored main.py Switched main.py to use argparse for cleaner code and easier argument additions for the future Added extras folder with systemd service file example --- extras/plex_debrid.service | 14 ++++++++++++++ main.py | 20 +++++++------------- 2 files changed, 21 insertions(+), 13 deletions(-) create mode 100644 extras/plex_debrid.service diff --git a/extras/plex_debrid.service b/extras/plex_debrid.service new file mode 100644 index 000000000..3de8e8fa0 --- /dev/null +++ b/extras/plex_debrid.service @@ -0,0 +1,14 @@ +[Unit] +Description=Plex torrent streaming through Debrid Services +After=network.target + +# Adjust paths and user/group as required. +[Service] +ExecStart=/usr/bin/python3 /usr/local/plex_debrid/main.py --service --config-dir=/var/lib/plex_debrid +Restart=always +User=media +Group=media +WorkingDirectory=/var/lib/plex_debrid + +[Install] +WantedBy=multi-user.target diff --git a/main.py b/main.py index 7c13b9c6a..e5c455b0b 100644 --- a/main.py +++ b/main.py @@ -1,21 +1,15 @@ +import argparse import ui from base import * -config_dir = "" -service_mode = False +parser = argparse.ArgumentParser(description='Plex Debrid') -if os.path.exists('./settings.json'): - if os.path.getsize('./settings.json') > 0 and os.path.isfile('./settings.json'): - config_dir = "." +parser.add_argument('--config-dir', '-c', type=str, default='.', help='Configuration directory') +parser.add_argument('--service', '-s', default=True, action='store_true', help='Run in service mode') -for i,arg in enumerate(sys.argv): - if config_dir == "" and arg == "--config-dir": - config_dir = sys.argv[i+1] - if arg == "-service": - service_mode = True +args = parser.parse_args() -if config_dir == "": - config_dir = "." +settings_path = f"{args.config_dir}/settings.json" if __name__ == "__main__": - ui.run(config_dir, service_mode) \ No newline at end of file + ui.run(args.config_dir, args.service) From bf8f53eb0f51ee0906a3a60ada5e98c142e51e6b Mon Sep 17 00:00:00 2001 From: sirstudly Date: Sat, 30 Mar 2024 12:52:40 +0000 Subject: [PATCH 03/28] Fix issue 578: Reoccurrence of Issue #478: Single Episode Downloads Not Functioning Correctly --- content/classes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/classes.py b/content/classes.py index 3d1ba45c9..b928c44ae 100644 --- a/content/classes.py +++ b/content/classes.py @@ -1366,7 +1366,7 @@ def download(self, retries=0, library=[], parentReleases=[]): if len(self.Episodes) > 2: if self.season_pack(scraped_releases): debrid_downloaded, retry = self.debrid_download() - # if scraper.traditional() or debrid_downloaded: + if scraper.traditional() or debrid_downloaded: for episode in self.Episodes: episode.skip_scraping = True # If there was nothing downloaded, scrape specifically for this season From fbd83ed85a40eec30090dbccdcea070bc55863a4 Mon Sep 17 00:00:00 2001 From: sirstudly Date: Thu, 21 Mar 2024 13:49:34 +0000 Subject: [PATCH 04/28] Resolving issue https://github.com/itsToggle/plex_debrid/issues/638 --- content/classes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/classes.py b/content/classes.py index 3d1ba45c9..a99d07579 100644 --- a/content/classes.py +++ b/content/classes.py @@ -571,7 +571,7 @@ def deviation(self, year=""): title = title.replace('.' + str(self.year), '') if year != "": return '[^A-Za-z0-9]*(' + title + ':?.)\(?\[?(' + str(year) + ')' - return '[^A-Za-z0-9]*(' + title + ':?.)\(?\[?(' + str(self.year) + '|' + str(self.year - 1) + '|' + str(self.year + 1) + ')' + return '[^A-Za-z0-9]*(' + title + ':?.*)\(?\[?(' + str(self.year) + '|' + str(self.year - 1) + '|' + str(self.year + 1) + ')' else: title = title.replace('.' + str(self.year), '') return '[^A-Za-z0-9]*(' + title + ')' From 3c3b2f07a98f36da57da906f14718dca29995aa1 Mon Sep 17 00:00:00 2001 From: sirstudly Date: Fri, 29 Mar 2024 09:53:11 +0000 Subject: [PATCH 05/28] Allow movie matches with year NOT present in title Also allow .avi files eg. The.Wild.Parrots.Of.Telegraph.Hill.XviD.AC3 [29/03/24 01:46:08] scraping sources [torrentio] for query "the.wild.parrots.of.telegraph.hill.2005" ... done [29/03/24 01:46:08] accepting titles that regex match "[^A-Za-z0-9]*((the.wild.parrots.of.telegraph.hill):?.*)\(?\[?(2005)(tt0424565)?" ... done - found 3 releases [29/03/24 01:46:08] scraping sources [torrentio] for IMDB ID "tt0424565" ... done [29/03/24 01:46:08] accepting titles that regex match "(.*|tt0424565)" ... done - found 3 releases [29/03/24 01:46:13] checking cache status for scraped releases on: [Real Debrid] ... done [29/03/24 01:46:14] [realdebrid] checking and sorting all release files ... done [29/03/24 01:46:14] set release bitrate using total movie duration: 01h:23m [29/03/24 01:46:14] 1) title: The.Wild.Parrots.Of.Telegraph.Hill.XviD.AC3 | size: 1.02 | bitrate: 2.05 | cached: RD | seeders: 9 | files: +0/-0 | source: [torrentio: MagnetDL] [29/03/24 01:46:14] 2) title: Wild.Parrots.of.Telegraph.Hill.(DVD.Rip).rar | size: 0.87 | bitrate: 1.75 | cached: | seeders: 0 | files: +0/-0 | source: [torrentio: ThePirateBay] [29/03/24 01:46:14] 3) title: Debbie.Does.Dallas.Uncovered.2005.DVDRip.X264.Konnann | size: 0.79 | bitrate: 1.59 | cached: RD | seeders: 2 | files: +2/-0 | source: [torrentio: MagnetDL] [29/03/24 01:46:14] 4) title: The.Wild.Parrots.Of.Telegraph.Hill.XviD.AC3 | size: 1.02 | bitrate: 2.05 | cached: RD | seeders: 9 | files: +0/-0 | source: [torrentio: MagnetDL] [29/03/24 01:46:14] 5) title: Wild.Parrots.of.Telegraph.Hill.(DVD.Rip).rar | size: 0.87 | bitrate: 1.75 | cached: | seeders: 0 | files: +0/-0 | source: [torrentio: ThePirateBay] [29/03/24 01:46:14] 6) title: Debbie.Does.Dallas.Uncovered.2005.DVDRip.X264.Konnann | size: 0.79 | bitrate: 1.59 | cached: RD | seeders: 2 | files: +2/-0 | source: [torrentio: MagnetDL] [29/03/24 01:46:14] sorting releases for version [1080p SDR] ... done - found 2 releases [29/03/24 01:46:14] 1) title: The.Wild.Parrots.Of.Telegraph.Hill.XviD.AC3 | size: 1.02 | bitrate: 2.05 | cached: RD | seeders: 9 | files: +0/-0 | source: [torrentio: MagnetDL] [29/03/24 01:46:14] 2) title: The.Wild.Parrots.Of.Telegraph.Hill.XviD.AC3 | size: 1.02 | bitrate: 2.05 | cached: RD | seeders: 9 | files: +0/-0 | source: [torrentio: MagnetDL] [29/03/24 01:46:14] [realdebrid] error: rejecting release: "The.Wild.Parrots.Of.Telegraph.Hill.XviD.AC3" because it doesnt match the allowed deviation "[^A-Za-z0-9]*((the.wild.parrots.of.telegraph.hill):?.*)\(?\[?(2005|2004|2006)" [29/03/24 01:46:14] [realdebrid] error: rejecting release: "The.Wild.Parrots.Of.Telegraph.Hill.XviD.AC3" because it doesnt match the allowed deviation "[^A-Za-z0-9]*((the.wild.parrots.of.telegraph.hill):?.*)\(?\[?(2005|2004|2006)" [29/03/24 01:46:14] retrying download in 30min for item: the.wild.parrots.of.telegraph.hill.2005 - version/s [1080p SDR] - attempt 1/5 --- content/classes.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/content/classes.py b/content/classes.py index a99d07579..b99155e15 100644 --- a/content/classes.py +++ b/content/classes.py @@ -570,8 +570,8 @@ def deviation(self, year=""): if regex.search(str(self.year), releases.rename(self.title.replace(str(self.year), '') + ' ' + str(self.year))): title = title.replace('.' + str(self.year), '') if year != "": - return '[^A-Za-z0-9]*(' + title + ':?.)\(?\[?(' + str(year) + ')' - return '[^A-Za-z0-9]*(' + title + ':?.*)\(?\[?(' + str(self.year) + '|' + str(self.year - 1) + '|' + str(self.year + 1) + ')' + return '[^A-Za-z0-9]*(' + title + ':?.*)\(?\[?(' + str(year) + ')?' + return '[^A-Za-z0-9]*(' + title + ':?.*)\(?\[?(' + str(self.year) + '|' + str(self.year - 1) + '|' + str(self.year + 1) + ')?' else: title = title.replace('.' + str(self.year), '') return '[^A-Za-z0-9]*(' + title + ')' @@ -612,7 +612,7 @@ def deviation(self, year=""): title = title.replace('[', '\[').replace(']', '\]') if self.type == 'movie': title = title.replace('.' + str(self.year), '') - return '(.*?)(' + title + '.)(.*?)(' + str(self.year) + '|' + str(self.year - 1) + '|' + str(self.year + 1) + ')' + return '(.*?)(' + title + '.)(.*?)(' + str(self.year) + '|' + str(self.year - 1) + '|' + str(self.year + 1) + ')?' elif self.type == 'show': title = title.replace('.' + str(self.year), '') return '(.*?)(' + title + '.)(.*?)('+self.anime_count+'|(complete)|(seasons?[^0-9]?[0-9]+[^A-Z0-9]+S?[0-9]+)|(S[0-9]+[^A-Z0-9]+S?[0-9]+))' @@ -1550,7 +1550,7 @@ def debrid_download(self, force=False): def files(self): files = [] if self.type == 'movie': - files = ['(mkv|mp4)'] + files = ['(mkv|mp4|avi)'] elif self.type == 'show': for season in self.Seasons: for episode in season.Episodes: From e15dec261ed6c51d228bdda83816c654956ec4a0 Mon Sep 17 00:00:00 2001 From: sirstudly Date: Sat, 30 Mar 2024 12:52:40 +0000 Subject: [PATCH 06/28] Fix issue 578: Reoccurrence of Issue #478: Single Episode Downloads Not Functioning Correctly --- content/classes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/classes.py b/content/classes.py index b99155e15..7d96f7056 100644 --- a/content/classes.py +++ b/content/classes.py @@ -1366,7 +1366,7 @@ def download(self, retries=0, library=[], parentReleases=[]): if len(self.Episodes) > 2: if self.season_pack(scraped_releases): debrid_downloaded, retry = self.debrid_download() - # if scraper.traditional() or debrid_downloaded: + if scraper.traditional() or debrid_downloaded: for episode in self.Episodes: episode.skip_scraping = True # If there was nothing downloaded, scrape specifically for this season From b01f8ad0cd4534f5cc09bdd5ef9e22a580bf19a4 Mon Sep 17 00:00:00 2001 From: sirstudly Date: Tue, 2 Apr 2024 20:54:32 +0100 Subject: [PATCH 07/28] classes.py:deviation(): improve regex matching - handle ampersands in titles, eg. Fast.Cheap.&.Out.of.Control.a.film.by.Errol.Morris - incorrect matching at beginning of title, eg. [realdebrid] error: rejecting release: "Knjiga.rekorda.Sutke.AKA.The.Shutka.Book.Of.Records" because it doesnt match the allowed deviation "[^A-Za-z0-9]*((the.shutka.book.of.records):?.*)\(?\[?(2005|2004|2006)?" --- content/classes.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/content/classes.py b/content/classes.py index 7d96f7056..2e507b603 100644 --- a/content/classes.py +++ b/content/classes.py @@ -566,21 +566,22 @@ def deviation(self, year=""): elif self.type == 'episode': title = releases.rename(self.grandparentTitle) title = title.replace('[', '\[').replace(']', '\]') + title = title.replace(".and.", ".(and|&|\+).") # handle ampersand/plus in lieu of "and" if self.type == 'movie': if regex.search(str(self.year), releases.rename(self.title.replace(str(self.year), '') + ' ' + str(self.year))): title = title.replace('.' + str(self.year), '') if year != "": - return '[^A-Za-z0-9]*(' + title + ':?.*)\(?\[?(' + str(year) + ')?' - return '[^A-Za-z0-9]*(' + title + ':?.*)\(?\[?(' + str(self.year) + '|' + str(self.year - 1) + '|' + str(self.year + 1) + ')?' + return '(.*?)(' + title + ':?.*)\(?\[?(' + str(year) + ')?' + return '(.*?)(' + title + ':?.*)\(?\[?(' + str(self.year) + '|' + str(self.year - 1) + '|' + str(self.year + 1) + ')?' else: title = title.replace('.' + str(self.year), '') - return '[^A-Za-z0-9]*(' + title + ')' + return '(.*?)(' + title + ')' elif self.type == 'show': title = title.replace('.' + str(self.year), '') - return '[^A-Za-z0-9]*(' + title + ':?.)(series.|[^A-Za-z0-9]+)?((\(?' + str(self.year) + '\)?.)|(complete.)|(seasons?.[0-9]+.[0-9]?[0-9]?.?)|(S[0-9]+.S?[0-9]?[0-9]?.?)|(S[0-9]+E[0-9]+))' + return '(.*?)(' + title + ':?.)(series.|[^A-Za-z0-9]+)?((\(?' + str(self.year) + '\)?.)|(complete.)|(seasons?.[0-9]+.[0-9]?[0-9]?.?)|(S[0-9]+.S?[0-9]?[0-9]?.?)|(S[0-9]+E[0-9]+))' elif self.type == 'season': title = title.replace('.' + str(self.parentYear), '') - return '[^A-Za-z0-9]*(' + title + ':?.)(series.|[^A-Za-z0-9]+)?(\(?' + str(self.parentYear) + '\)?.)?(season.' + str(self.index) + '\.|season.' + str("{:02d}".format(self.index)) + '\.|S' + str("{:02d}".format(self.index)) + '\.)' + return '(.*?)(' + title + ':?.)(series.|[^A-Za-z0-9]+)?(\(?' + str(self.parentYear) + '\)?.)?(season.' + str(self.index) + '\.|season.' + str("{:02d}".format(self.index)) + '\.|S' + str("{:02d}".format(self.index)) + '\.)' elif self.type == 'episode': title = title.replace('.' + str(self.grandparentYear), '') try: @@ -594,9 +595,9 @@ def deviation(self, year=""): airdate_formats += [airdate.strftime( '(%m|%b).*%d.*(%Y|%y)').replace("0", "0?")] airdate_formats = "(" + ")|(".join(airdate_formats) + ")" - return '[^A-Za-z0-9]*(' + title + ':?.)(series.)?(\(?' + str(self.grandparentYear) + '\)?.)?(S' + str("{:02d}".format(self.parentIndex)) + 'E' + str("{:02d}".format(self.index)) + '.|'+airdate_formats+')' + return '(.*?)(' + title + ':?.)(series.)?(\(?' + str(self.grandparentYear) + '\)?.)?(S' + str("{:02d}".format(self.parentIndex)) + 'E' + str("{:02d}".format(self.index)) + '.|'+airdate_formats+')' except: - return '[^A-Za-z0-9]*(' + title + ':?.)(series.)?(\(?' + str(self.grandparentYear) + '\)?.)?(S' + str("{:02d}".format(self.parentIndex)) + 'E' + str("{:02d}".format(self.index)) + '.)' + return '(.*?)(' + title + ':?.)(series.)?(\(?' + str(self.grandparentYear) + '\)?.)?(S' + str("{:02d}".format(self.parentIndex)) + 'E' + str("{:02d}".format(self.index)) + '.)' else: if hasattr(self, 'alternate_titles'): title = '(' + '|'.join(self.alternate_titles) + ')' @@ -612,22 +613,22 @@ def deviation(self, year=""): title = title.replace('[', '\[').replace(']', '\]') if self.type == 'movie': title = title.replace('.' + str(self.year), '') - return '(.*?)(' + title + '.)(.*?)(' + str(self.year) + '|' + str(self.year - 1) + '|' + str(self.year + 1) + ')?' + return '(.*?)(' + title + ')(.*?)(' + str(self.year) + '|' + str(self.year - 1) + '|' + str(self.year + 1) + ')?' elif self.type == 'show': title = title.replace('.' + str(self.year), '') - return '(.*?)(' + title + '.)(.*?)('+self.anime_count+'|(complete)|(seasons?[^0-9]?[0-9]+[^A-Z0-9]+S?[0-9]+)|(S[0-9]+[^A-Z0-9]+S?[0-9]+))' + return '(.*?)(' + title + ')(.*?)('+self.anime_count+'|(complete)|(seasons?[^0-9]?[0-9]+[^A-Z0-9]+S?[0-9]+)|(S[0-9]+[^A-Z0-9]+S?[0-9]+))' elif self.type == 'season': n = self.index roman = 'I' if n == 1 else 'II' if n == 2 else 'III' if n == 3 else 'IV' if n == 4 else 'V' if n == 5 else 'VI' if n == 6 else 'VII' if n == 7 else 'VIII' if n == 8 else 'IX' if n == 9 else 'X' if n == 10 else str( n) title = title.replace('.' + str(self.parentYear), '') - return '(.*?)(' + title + '.)(.*?)(season[^0-9]?0*' + str(self.index) + '|S0*' + str(self.index) + '(?!E?[0-9])|'+self.anime_count+'|[^A-Z0-9]'+roman+'[^A-Z0-9])' + return '(.*?)(' + title + ')(.*?)(season[^0-9]?0*' + str(self.index) + '|S0*' + str(self.index) + '(?!E?[0-9])|'+self.anime_count+'|[^A-Z0-9]'+roman+'[^A-Z0-9])' elif self.type == 'episode': n = self.parentIndex roman = 'I' if n == 1 else 'II' if n == 2 else 'III' if n == 3 else 'IV' if n == 4 else 'V' if n == 5 else 'VI' if n == 6 else 'VII' if n == 7 else 'VIII' if n == 8 else 'IX' if n == 9 else 'X' if n == 10 else str( n) title = title.replace('.' + str(self.grandparentYear), '') - return '(.*?)(' + title + '.)(.*?)((? Date: Wed, 3 Apr 2024 18:49:41 +0100 Subject: [PATCH 08/28] Parameterized watchlist loop interval rather than hardcoded 30 minutes --- content/classes.py | 10 ++++++---- settings/__init__.py | 1 + ui/__init__.py | 2 +- ui/ui_settings.py | 1 + 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/content/classes.py b/content/classes.py index 2e507b603..87b5c4d0a 100644 --- a/content/classes.py +++ b/content/classes.py @@ -927,17 +927,19 @@ def watch(self): retries = int(float(trigger[2])) if retries == 0: return + + message = 'retrying download in ' + str( + round(int(ui_settings.loop_interval_seconds) / 60)) + 'min for item: ' + self.query() + ' - version/s [' + '],['.join( + names) + ']' if not self in media.ignore_queue: self.ignored_count = 1 media.ignore_queue += [self] - ui_print('retrying download in 30min for item: ' + self.query() + ' - version/s [' + '],['.join( - names) + '] - attempt ' + str(self.ignored_count) + '/' + str(retries)) + ui_print(message + ' - attempt ' + str(self.ignored_count) + '/' + str(retries)) else: match = next((x for x in media.ignore_queue if self == x), None) if match.ignored_count < retries: match.ignored_count += 1 - ui_print('retrying download in 30min for item: ' + self.query() + ' - version/s [' + '],['.join( - names) + '] - attempt ' + str(match.ignored_count) + '/' + str(retries)) + ui_print(message + ' - attempt ' + str(match.ignored_count) + '/' + str(retries)) else: media.ignore_queue.remove(match) ignore.add(self) diff --git a/settings/__init__.py b/settings/__init__.py index ed619d835..de36530ce 100644 --- a/settings/__init__.py +++ b/settings/__init__.py @@ -412,6 +412,7 @@ def get(self): setting('Show Menu on Startup', 'Please enter "true" or "false": ', ui_settings, 'run_directly'), setting('Debug printing', 'Please enter "true" or "false": ', ui_settings, 'debug'), setting('Log to file', 'Please enter "true" or "false": ', ui_settings, 'log'), + setting('Watchlist loop interval (sec)', 'Please enter an integer value in seconds: ', ui_settings, 'loop_interval_seconds'), setting('version', 'No snooping around! :D This is for compatability reasons.', ui_settings, 'version', hidden=True), ] diff --git a/ui/__init__.py b/ui/__init__.py index 51bfe9277..c115feb66 100644 --- a/ui/__init__.py +++ b/ui/__init__.py @@ -403,7 +403,7 @@ def threaded(stop): else: print("Type 'exit' to return to the main menu.") timeout = 5 - regular_check = 1800 + regular_check = int(ui_settings.loop_interval_seconds) timeout_counter = 0 library = content.classes.library()[0]() # get entire plex_watchlist diff --git a/ui/ui_settings.py b/ui/ui_settings.py index 294f9d5fc..dc0149a8c 100644 --- a/ui/ui_settings.py +++ b/ui/ui_settings.py @@ -2,3 +2,4 @@ run_directly = "true" debug = "false" log = "false" +loop_interval_seconds = 1800 \ No newline at end of file From f1d4413c4be9a62b73b0f8080788e0583021956f Mon Sep 17 00:00:00 2001 From: sirstudly Date: Fri, 5 Apr 2024 22:28:44 +0100 Subject: [PATCH 09/28] Fix deviation() for season release of: Land.of.the.Giants.1968.Season.1[Complete] --- content/classes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/classes.py b/content/classes.py index 87b5c4d0a..e8e50a96e 100644 --- a/content/classes.py +++ b/content/classes.py @@ -581,7 +581,7 @@ def deviation(self, year=""): return '(.*?)(' + title + ':?.)(series.|[^A-Za-z0-9]+)?((\(?' + str(self.year) + '\)?.)|(complete.)|(seasons?.[0-9]+.[0-9]?[0-9]?.?)|(S[0-9]+.S?[0-9]?[0-9]?.?)|(S[0-9]+E[0-9]+))' elif self.type == 'season': title = title.replace('.' + str(self.parentYear), '') - return '(.*?)(' + title + ':?.)(series.|[^A-Za-z0-9]+)?(\(?' + str(self.parentYear) + '\)?.)?(season.' + str(self.index) + '\.|season.' + str("{:02d}".format(self.index)) + '\.|S' + str("{:02d}".format(self.index)) + '\.)' + return '(.*?)(' + title + ':?.)(series.|[^A-Za-z0-9]+)?(\(?' + str(self.parentYear) + '\)?.)?(season.' + str(self.index) + '[^0-9]|season.' + str("{:02d}".format(self.index)) + '[^0-9]|S' + str("{:02d}".format(self.index)) + '[^0-9])' elif self.type == 'episode': title = title.replace('.' + str(self.grandparentYear), '') try: From f7772164a608fc339dcc879736e8a7672f6c4490 Mon Sep 17 00:00:00 2001 From: Peter Nham Date: Fri, 10 May 2024 13:57:17 +1000 Subject: [PATCH 10/28] Add plex server address help - Plex doesn't like extra trailing slashes and requests requires the protocol for urls --- content/services/plex.py | 8 ++++---- settings/__init__.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/content/services/plex.py b/content/services/plex.py index 20a73c20e..1a45511d6 100644 --- a/content/services/plex.py +++ b/content/services/plex.py @@ -791,8 +791,8 @@ def __new__(self,silent=False): types = ['1'] if Directory.type == "movie" else ['2', '3', '4'] sections += [[Directory.key,types]] names += [Directory.title] - except: - ui_print("[plex error]: couldnt reach local plex server at: " + library.url + " to determine library sections. Make sure the address is correct, the server is running, and youve set up at least one library.") + except Exception as e: + ui_print("[plex error]: couldnt reach local plex server at: " + library.url + " to determine library sections. Make sure the address is correct, the server is running, and youve set up at least one library. Error:" + e) if len(sections) == 0: return list_ if not silent: @@ -876,11 +876,11 @@ def __new__(self,silent=False): episode.grandparentEID = item.EID except: ui_print('done') - ui_print("[plex error]: found incorrectly matched library item : " + item.title + " - this item needs a metadata refresh (open plex webui, find item, open item menu, refresh metadata).") + ui_print("[plex error]: found incorrectly matched library item : " + item.title + " - this item needs a metadata refresh (open plex webui, find item, open item menu, refresh metadata).") ui_print('done') current_library = copy.deepcopy(list_) if first_load and updated: - store.save(current_library,"plex","metadata") + store.save(current_library,"plex","metadata") return list_ def search(query, library=[]): diff --git a/settings/__init__.py b/settings/__init__.py index ed619d835..c1d1ca4d5 100644 --- a/settings/__init__.py +++ b/settings/__init__.py @@ -348,9 +348,9 @@ def get(self): setting('Trakt library user', [''], content.services.trakt.library, 'user', hidden=True), setting('Trakt refresh user', [''], content.services.trakt.library.refresh, 'user', hidden=True), setting('Plex library refresh', [''], content.services.plex.library.refresh, 'sections', hidden=True,moveable=False), - setting('Plex library partial scan', 'Please enter "true" or "false": ', content.services.plex.library.refresh, 'partial', hidden=True, help="Specify wether or not plex_debrid should attempt to partially scan your plex libraries."), + setting('Plex library partial scan', 'Please enter "true" or "false": ', content.services.plex.library.refresh, 'partial', hidden=True, help="Specify whether or not plex_debrid should attempt to partially scan your plex libraries."), setting('Plex library refresh delay', 'Please enter a number (e.g 420 or 69.69): ', content.services.plex.library.refresh, 'delay', hidden=True, help="Specify the amount of seconds plex_debrid should wait between adding a torrent and scanning your plex libraries."), - setting('Plex server address', 'Please enter your Plex server address: ', content.services.plex.library, 'url', hidden=True), + setting('Plex server address', 'Please enter your Plex server address: ', content.services.plex.library, 'url', hidden=True, help="It must include protocol (eg. http) and not include anything trailing slashes. eg. http://my-plex-server:32400"), setting('Plex library check', [ 'Please specify a library section number that should be checked for existing content before download: '], content.services.plex.library, 'check', hidden=True, entry="section", From 3a3c85832704019eb00e22bd901513dc4a474aae Mon Sep 17 00:00:00 2001 From: David Young Date: Tue, 2 Jul 2024 19:53:50 +1200 Subject: [PATCH 11/28] "Elfify" the logo Signed-off-by: David Young --- ui/ui_print.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ui/ui_print.py b/ui/ui_print.py index 0b6ab7f79..d2e766442 100644 --- a/ui/ui_print.py +++ b/ui/ui_print.py @@ -19,6 +19,13 @@ def logo(path='',update=""): print(' / .___/_/\___/_/|_|____\__,_/\___/_.___/_/ /_/\__,_/ ') print('/_/ /_____/ [v' + ui_settings.version[0] + ']' + update) print() + print(' ______________ __ __ __ ') + print(' / ____/ / __/ / / /___ _____/ /____ ____/ / ') + print(' / __/ / / /_/ /_/ / __ \/ ___/ __/ _ \/ __ / ') + print(' / /___/ / __/ __ / /_/ (__ ) /_/ __/ /_/ / ') + print('/_____/_/_/ /_/ /_/\____/____/\__/\___/\__,_/ ') + + print(path) print() sys.stdout.flush() From c678fa1e5974a5c666b2fe70d65228c6fdfb4047 Mon Sep 17 00:00:00 2001 From: David Young Date: Fri, 12 Jul 2024 18:17:24 +1200 Subject: [PATCH 12/28] try to pass oauth details to PD using env vars Signed-off-by: David Young --- README.md | 6 +++--- content/classes.py | 2 +- content/services/plex.py | 10 +++++----- content/services/trakt.py | 32 ++++++++++++++++++++++++++++---- requirements.txt | 1 + 5 files changed, 38 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index b6a9cf8f6..bf9df4dd3 100644 --- a/README.md +++ b/README.md @@ -381,11 +381,11 @@ If github is not your cup of tea; > > >
-> Plex lables: +> Plex labels: > -> - To add automatic version and user lables to your downloaded content, navigate to '/Settings/Library Service/Library update service/Edit/' +> - To add automatic version and user labels to your downloaded content, navigate to '/Settings/Library Service/Library update service/Edit/' > - This requires a Plex library refresh to be set up aswell (see above). -> - Lables that will be added are: "From: ..." for each user that watchlisted this item, "Version: ..." for each version that was downloaded. +> - Labels that will be added are: "From: ..." for each user that watchlisted this item, "Version: ..." for each version that was downloaded. > >
> diff --git a/content/classes.py b/content/classes.py index 3d1ba45c9..2349d2b57 100644 --- a/content/classes.py +++ b/content/classes.py @@ -1078,7 +1078,7 @@ def available(self): def collect(self, refresh_=True): for refresh_service in refresh(): if refresh_service.__module__ == self.__module__ or (self.__module__ in ["content.services.trakt", "releases", "content.services.overseerr", "content.services.plex"] and refresh_service.__module__ in ["content.services.plex", "content.services.jellyfin"]): - if refresh_ or refresh_service.name == "Plex Lables": + if refresh_ or refresh_service.name == "Plex Labels": refresh_service(self) elif self.__module__ in ["content.services.plex", "content.services.overseerr"] and refresh_service.__module__ == "content.services.trakt": try: diff --git a/content/services/plex.py b/content/services/plex.py index 20a73c20e..4d2cf35d6 100644 --- a/content/services/plex.py +++ b/content/services/plex.py @@ -529,7 +529,7 @@ def __new__(cls, element): class lable(classes.refresh): - name = 'Plex Lables' + name = 'Plex Labels' def setup(cls, new=False): ui_cls("Options/Settings/Library Services/Library update services") @@ -586,7 +586,7 @@ def call(element): retries += 1 library_item = next((x for x in current_library if element == x), None) if library_item == None: - ui_print('[plex] error: couldnt add lables - item: "' + element.query() + '" could not be found on server.') + ui_print('[plex] error: couldnt add labels - item: "' + element.query() + '" could not be found on server.') return tags_string = "" for tag in tags: @@ -598,7 +598,7 @@ def call(element): response = get(url) library_item.__dict__.update(response.MediaContainer.Metadata[0].__dict__) except Exception as e: - ui_print("[plex] error: couldnt add lables! Turn on debug printing for more info.") + ui_print("[plex] error: couldnt add labels! Turn on debug printing for more info.") ui_print(str(e), debug=ui_settings.debug) def __new__(cls, element): @@ -632,12 +632,12 @@ def __new__(cls, element): if len(tags) == 0: return element.post_tags = tags - ui_print('[plex] adding lables: "' + '","'.join(tags) + '" to item: "' + element.query() + '"') + ui_print('[plex] adding labels: "' + '","'.join(tags) + '" to item: "' + element.query() + '"') results = [None] t = Thread(target=multi_init, args=(library.lable.call, element, results, 0)) t.start() except Exception as e: - ui_print("[plex] error: couldnt add lables! Turn on debug printing for more info.") + ui_print("[plex] error: couldnt add labels! Turn on debug printing for more info.") ui_print(str(e), debug=ui_settings.debug) class ignore(classes.ignore): diff --git a/content/services/trakt.py b/content/services/trakt.py index 5af16aff8..357d78042 100644 --- a/content/services/trakt.py +++ b/content/services/trakt.py @@ -4,9 +4,22 @@ from content import classes from ui.ui_print import * +from pydantic_settings import BaseSettings + +# Get Trakt oauth details from env +class Settings(BaseSettings): + client_id: str + client_secret: str + + class Config: + env_file = ".env" + env_file_encoding = "utf-8" + +trakt = Settings() + name = 'Trakt' -client_id = "0183a05ad97098d87287fe46da4ae286f434f32e8e951caad4cc147c947d79a3" -client_secret = "87109ed53fe1b4d6b0239e671f36cd2f17378384fa1ae09888a32643f83b7e6c" +client_id = trakt.client_id +client_secret = trakt.client_secret lists = [] users = [] current_user = ["", ""] @@ -169,9 +182,20 @@ def post(url, data): response = None return response +def post2(url, data): + try: + response = session.post(url, headers={ + 'Content-type': "application/json"}, data=data) + logerror(response) + response = json.loads(response.content, object_hook=lambda d: SimpleNamespace(**d)) + time.sleep(1.1) + except: + response = None + return response + def oauth(code=""): if code == "": - response = post('https://api.trakt.tv/oauth/device/code', json.dumps({'client_id': client_id})) + response = post2('https://api.trakt.tv/oauth/device/code', json.dumps({'client_id': client_id})) if not response == None: return response.device_code, response.user_code else: @@ -180,7 +204,7 @@ def oauth(code=""): else: response = None while response == None: - response = post('https://api.trakt.tv/oauth/device/token', json.dumps( + response = post2('https://api.trakt.tv/oauth/device/token', json.dumps( {'code': code, 'client_id': client_id, 'client_secret': client_secret})) time.sleep(1) return response.access_token diff --git a/requirements.txt b/requirements.txt index 8c94ee282..72b1d7080 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ bs4==0.0.1 regex==2022.9.13 requests==2.28.1 six==1.16.0 +pydantic-settings \ No newline at end of file From 232582d3b165799ec5d068443ec8e6e0b462c337 Mon Sep 17 00:00:00 2001 From: sirstudly Date: Mon, 15 Jul 2024 00:10:15 +0100 Subject: [PATCH 13/28] Added Zilean support --- scraper/services/__init__.py | 3 +- scraper/services/zilean.py | 91 ++++++++++++++++++++++++++++++++++++ settings/__init__.py | 1 + 3 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 scraper/services/zilean.py diff --git a/scraper/services/__init__.py b/scraper/services/__init__.py index fe45e7e34..bd609bf70 100644 --- a/scraper/services/__init__.py +++ b/scraper/services/__init__.py @@ -7,10 +7,11 @@ from scraper.services import orionoid from scraper.services import nyaa from scraper.services import torrentio +from scraper.services import zilean #define subclass method def __subclasses__(): - return [rarbg,x1337,jackett,prowlarr,orionoid,nyaa,torrentio] + return [rarbg,x1337,jackett,prowlarr,orionoid,nyaa,torrentio,zilean] active = ['torrentio'] overwrite = [] diff --git a/scraper/services/zilean.py b/scraper/services/zilean.py new file mode 100644 index 000000000..19320b06c --- /dev/null +++ b/scraper/services/zilean.py @@ -0,0 +1,91 @@ +# import modules +from base import * +from ui.ui_print import * +import releases + +search_url = "http://localhost:8181/dmm/search" +name = "zilean" +timeout_sec = 10 +session = requests.Session() + + +def setup(cls, new=False): + from settings import settings_list + from scraper.services import active + settings = [] + for category, allsettings in settings_list: + for setting in allsettings: + if setting.cls == cls: + settings += [setting] + if settings == []: + if not cls.name in active: + active += [cls.name] + back = False + if not new: + while not back: + print("0) Back") + indices = [] + for index, setting in enumerate(settings): + print(str(index + 1) + ') ' + setting.name) + indices += [str(index + 1)] + print() + if settings == []: + print("Nothing to edit!") + print() + time.sleep(3) + return + choice = input("Choose an action: ") + if choice in indices: + settings[int(choice) - 1].input() + if not cls.name in active: + active += [cls.name] + back = True + elif choice == '0': + back = True + else: + print() + indices = [] + for setting in settings: + if setting.name == "Zilean Search URL": + setting.setup() + if not cls.name in active: + active += [cls.name] + + +def scrape(query, altquery): + from scraper.services import active + global search_url + scraped_releases = [] + if 'zilean' in active: + if search_url.endswith('/'): + search_url = search_url[:-1] + try: + ui_print("[zilean] searching for: " + query) + response = session.post(search_url, headers={'Content-type': "application/json"}, data=json.dumps({'queryText': query}), timeout=timeout_sec) + + if not response.status_code == 200: + ui_print('[zilean] error ' + str( + response.status_code) + ': failed response from zilean. ' + response.content) + return [] + + except requests.exceptions.Timeout: + ui_print('[zilean] error: zilean request timed out.') + return [] + except: + ui_print('[zilean] error: zilean couldn\'t be reached. Make sure your zilean search url [' + search_url + '] is correctly formatted.') + return [] + + try: + response = json.loads(response.content, object_hook=lambda d: SimpleNamespace(**d)) + except: + ui_print('[zilean] error: unable to parse response:' + response.content) + return [] + + for result in response[:]: + if regex.match(r'(' + altquery.replace('.', '\.').replace("\.*", ".*") + ')', result.filename,regex.I): + links = ['magnet:?xt=urn:btih:' + result.infoHash + '&dn=&tr='] + seeders = 0 # not available + scraped_releases += [releases.release( + '[zilean]', 'torrent', result.filename, [], float(result.filesize) / 1000000000, links, seeders)] + + return scraped_releases diff --git a/settings/__init__.py b/settings/__init__.py index de36530ce..1655ec0d3 100644 --- a/settings/__init__.py +++ b/settings/__init__.py @@ -380,6 +380,7 @@ def get(self): setting('Nyaa sleep time', 'Enter a time in seconds to sleep between requests (default: "5"): ',scraper.services.nyaa, 'sleep', hidden=True), setting('Nyaa proxy', 'Enter a proxy to use for nyaa (default: "nyaa.si"): ',scraper.services.nyaa, 'proxy', hidden=True), setting('Torrentio Scraper Parameters','Please enter a valid torrentio manifest url: ',scraper.services.torrentio, 'default_opts', entry="parameter", help='This settings lets you control the torrentio scraping parameters. Visit "https://torrentio.strem.fun/configure" and configure your settings. Dont choose a debrid service. The "manifest url" will be copied to your clipboard.', hidden=True), + setting('Zilean Search URL', 'Please specify your Zilean search URL: ', scraper.services.zilean, 'search_url', hidden=True), ] ], ['Debrid Services', [ From 99aac845ae49a4586f29db34f146b33f97419eed Mon Sep 17 00:00:00 2001 From: sirstudly Date: Mon, 15 Jul 2024 00:25:57 +0100 Subject: [PATCH 14/28] Updated docker files --- .gitignore | 3 +++ Dockerfile | 2 ++ docker-compose.yml | 10 ++++++++++ 3 files changed, 15 insertions(+) create mode 100644 docker-compose.yml diff --git a/.gitignore b/.gitignore index 66df4ee11..874737c33 100644 --- a/.gitignore +++ b/.gitignore @@ -397,3 +397,6 @@ FodyWeavers.xsd # JetBrains Rider *.sln.iml settings.json + +venv*/ +.idea/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index fa47ecabf..5155d4156 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,7 @@ FROM python:3 +RUN apt-get update && apt-get install -y vim less + ADD . / ./ RUN pip install -r requirements.txt diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..312f6c0fb --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,10 @@ +version: '3' + +services: + app: + build: . + stdin_open: true + tty: true + volumes: + - ./settings.json:/settings.json + - ./ignored.txt:/ignored.txt From 611c4ab12c96826877017c8787b286904eb7e531 Mon Sep 17 00:00:00 2001 From: sirstudly Date: Mon, 15 Jul 2024 00:36:52 +0100 Subject: [PATCH 15/28] Additional comments and debugging statements --- content/classes.py | 1 + content/services/plex.py | 10 +++++++++- content/services/textfile.py | 1 + debrid/services/realdebrid.py | 7 ++++++- scraper/__init__.py | 1 + scraper/services/jackett.py | 1 + scraper/services/torrentio.py | 3 ++- 7 files changed, 21 insertions(+), 3 deletions(-) diff --git a/content/classes.py b/content/classes.py index e8e50a96e..d19946cc3 100644 --- a/content/classes.py +++ b/content/classes.py @@ -1403,6 +1403,7 @@ def download(self, retries=0, library=[], parentReleases=[]): if not debrid_downloaded: for release in self.Releases[:]: if not regex.match(self.deviation(), release.title, regex.I): + ui_print("[download (show)] " + release.title + " does not match deviation " + self.deviation()) self.Releases.remove(release) if self.season_pack(scraped_releases): debrid_downloaded, retry = self.debrid_download() diff --git a/content/services/plex.py b/content/services/plex.py index 20a73c20e..eacfb0397 100644 --- a/content/services/plex.py +++ b/content/services/plex.py @@ -30,8 +30,10 @@ def logerror(response): def get(url, timeout=60): try: + ui_print("[plex] Processing (get): " + url) response = session.get(url, headers=headers, timeout=timeout) logerror(response) + ui_print("[plex] (get) response: " + repr(response), debug=ui_settings.debug) response = json.loads(response.content, object_hook=lambda d: SimpleNamespace(**d)) return response except Exception as e: @@ -40,8 +42,10 @@ def get(url, timeout=60): def post(url, data): try: + ui_print("[plex] Processing (post): " + url) response = session.post(url, data=data, headers=headers) logerror(response) + ui_print("[plex] (post) response: " + repr(response), debug=ui_settings.debug) response = json.loads(response.content, object_hook=lambda d: SimpleNamespace(**d)) return response except Exception as e: @@ -128,6 +132,8 @@ def add(self, item, user): elif item.type == 'movie': self.data.append(movie(item.ratingKey)) + # collect all new unique watchlisted items ACROSS ALL USERS by retrieving user watchlists and adding them to self.data + # (then remove any that are no longer in the watchlist which were added in previous runs) def update(self): update = False new_watchlist = [] @@ -172,6 +178,7 @@ def __init__(self, other): self.__dict__.update(other.__dict__) self.EID = setEID(self) self.Episodes = [] + ui_print("[plex] Processing " + self.parentTitle + " " + self.title) token = users[0][1] if library.ignore.name in classes.ignore.active: for user in users: @@ -880,7 +887,8 @@ def __new__(self,silent=False): ui_print('done') current_library = copy.deepcopy(list_) if first_load and updated: - store.save(current_library,"plex","metadata") + ui_print('[plex] saving library cache.') + store.save(current_library,"plex","metadata") return list_ def search(query, library=[]): diff --git a/content/services/textfile.py b/content/services/textfile.py index 132cf0763..c60dd8930 100644 --- a/content/services/textfile.py +++ b/content/services/textfile.py @@ -24,6 +24,7 @@ def add(self): with open(library.ignore.path + "ignored.txt",'a') as f: if not self.query() + '\n' in lines: f.write(self.query() + '\n') + ui_print("[textfile] added " + self.query() + " to ignore list") f.close() if not self in classes.ignore.ignored: classes.ignore.ignored += [self] diff --git a/debrid/services/realdebrid.py b/debrid/services/realdebrid.py index ffea7be11..504c269ca 100644 --- a/debrid/services/realdebrid.py +++ b/debrid/services/realdebrid.py @@ -54,8 +54,10 @@ def post(url, data): 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36','authorization': 'Bearer ' + api_key} response = None try: + ui_print("[realdebrid] (post): " + url + " with data " + repr(data), debug=ui_settings.debug) response = session.post(url, headers=headers, data=data) logerror(response) + ui_print("[realdebrid] response: " + repr(response), debug=ui_settings.debug) response = json.loads(response.content, object_hook=lambda d: SimpleNamespace(**d)) except Exception as e: if hasattr(response,"status_code"): @@ -145,6 +147,7 @@ def download(element, stream=True, query='', force=False): ui_print('[realdebrid] error: could not add magnet for release: ' + release.title, ui_settings.debug) continue response = post('https://api.real-debrid.com/rest/1.0/torrents/selectFiles/' + torrent_id,{'files': str(','.join(cached_ids))}) + ui_print('[realdebrid] selectFiles response ' + repr(response), ui_settings.debug) response = get('https://api.real-debrid.com/rest/1.0/torrents/info/' + torrent_id) actual_title = "" if len(response.links) == len(cached_ids): @@ -171,6 +174,8 @@ def download(element, stream=True, query='', force=False): for link in release.download: try: response = post('https://api.real-debrid.com/rest/1.0/unrestrict/link',{'link': link}) + ui_print("[realdebrid] unrestrict link response " + repr(response), + ui_settings.debug) except: break release.files = version.files @@ -190,7 +195,7 @@ def download(element, stream=True, query='', force=False): except: continue else: - ui_print('[realdebrid] error: rejecting release: "' + release.title + '" because it doesnt match the allowed deviation', ui_settings.debug) + ui_print('[realdebrid] error: rejecting release: "' + release.title + '" because it doesnt match the allowed deviation "' + query + '"', ui_settings.debug) return False # (required) Check Function diff --git a/scraper/__init__.py b/scraper/__init__.py index acbdbce3d..5a99ee7ec 100644 --- a/scraper/__init__.py +++ b/scraper/__init__.py @@ -35,6 +35,7 @@ def scrape(query, altquery="(.*)"): if not result == [] and not result == None: scraped_releases += result for release in scraped_releases: + # remove any funny characters from the release title? all ascii characters should be < 512. release.title = ''.join([i if ord(i) < 512 else '' for i in release.title]) ui_print('done - found ' + str(len(scraped_releases)) + ' releases') if len(scraped_releases) > 0: diff --git a/scraper/services/jackett.py b/scraper/services/jackett.py index 102ce9487..a6a83fbf4 100644 --- a/scraper/services/jackett.py +++ b/scraper/services/jackett.py @@ -61,6 +61,7 @@ def scrape(query, altquery): base_url = base_url[:-1] url = base_url + '/api/v2.0/indexers/' + filter + '/results?apikey=' + api_key + '&Query=' + query try: + ui_print("[jackett] get url: " + url) response = session.get(url, timeout=60) except requests.exceptions.Timeout: ui_print('[jackett] error: jackett request timed out. Reduce the number of jackett indexers, make sure your indexers are healthy and enable the jackett setting "CORS".') diff --git a/scraper/services/torrentio.py b/scraper/services/torrentio.py index 9a2f9b713..613ecd190 100644 --- a/scraper/services/torrentio.py +++ b/scraper/services/torrentio.py @@ -2,7 +2,7 @@ from base import * from ui.ui_print import * import releases - +# https://github.com/TheBeastLT/torrentio-scraper/blob/master/addon/addon.js name = "torrentio" default_opts = "https://torrentio.strem.fun/sort=qualitysize|qualityfilter=480p,scr,cam/manifest.json" @@ -12,6 +12,7 @@ def get(url): try: + ui_print("[torrentio] get url: " + url) response = session.get(url, timeout=60) response = json.loads( response.content, object_hook=lambda d: SimpleNamespace(**d)) From b4d758ba3725ce60e443a4f5e1f03c586fb4a248 Mon Sep 17 00:00:00 2001 From: sirstudly Date: Mon, 15 Jul 2024 00:38:27 +0100 Subject: [PATCH 16/28] Fix missing originallyAvailableAt on classes::released --- content/classes.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/content/classes.py b/content/classes.py index d19946cc3..d99ce6084 100644 --- a/content/classes.py +++ b/content/classes.py @@ -952,6 +952,9 @@ def watched(self): def released(self): try: + if not hasattr(self, "originallyAvailableAt"): + return False # not available yet? + released = datetime.datetime.utcnow( ) - datetime.datetime.strptime(self.originallyAvailableAt, '%Y-%m-%d') if hasattr(self, "offset_airtime"): From 86c912ae6d829dd5e529f9a85347d0a7aa4bfb61 Mon Sep 17 00:00:00 2001 From: sirstudly Date: Wed, 3 Apr 2024 18:49:41 +0100 Subject: [PATCH 17/28] Parameterized watchlist loop interval rather than hardcoded 30 minutes --- content/classes.py | 10 ++++++---- settings/__init__.py | 1 + ui/__init__.py | 2 +- ui/ui_settings.py | 1 + 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/content/classes.py b/content/classes.py index 3d1ba45c9..658a57f69 100644 --- a/content/classes.py +++ b/content/classes.py @@ -926,17 +926,19 @@ def watch(self): retries = int(float(trigger[2])) if retries == 0: return + + message = 'retrying download in ' + str( + round(int(ui_settings.loop_interval_seconds) / 60)) + 'min for item: ' + self.query() + ' - version/s [' + '],['.join( + names) + ']' if not self in media.ignore_queue: self.ignored_count = 1 media.ignore_queue += [self] - ui_print('retrying download in 30min for item: ' + self.query() + ' - version/s [' + '],['.join( - names) + '] - attempt ' + str(self.ignored_count) + '/' + str(retries)) + ui_print(message + ' - attempt ' + str(self.ignored_count) + '/' + str(retries)) else: match = next((x for x in media.ignore_queue if self == x), None) if match.ignored_count < retries: match.ignored_count += 1 - ui_print('retrying download in 30min for item: ' + self.query() + ' - version/s [' + '],['.join( - names) + '] - attempt ' + str(match.ignored_count) + '/' + str(retries)) + ui_print(message + ' - attempt ' + str(match.ignored_count) + '/' + str(retries)) else: media.ignore_queue.remove(match) ignore.add(self) diff --git a/settings/__init__.py b/settings/__init__.py index ed619d835..de36530ce 100644 --- a/settings/__init__.py +++ b/settings/__init__.py @@ -412,6 +412,7 @@ def get(self): setting('Show Menu on Startup', 'Please enter "true" or "false": ', ui_settings, 'run_directly'), setting('Debug printing', 'Please enter "true" or "false": ', ui_settings, 'debug'), setting('Log to file', 'Please enter "true" or "false": ', ui_settings, 'log'), + setting('Watchlist loop interval (sec)', 'Please enter an integer value in seconds: ', ui_settings, 'loop_interval_seconds'), setting('version', 'No snooping around! :D This is for compatability reasons.', ui_settings, 'version', hidden=True), ] diff --git a/ui/__init__.py b/ui/__init__.py index 51bfe9277..c115feb66 100644 --- a/ui/__init__.py +++ b/ui/__init__.py @@ -403,7 +403,7 @@ def threaded(stop): else: print("Type 'exit' to return to the main menu.") timeout = 5 - regular_check = 1800 + regular_check = int(ui_settings.loop_interval_seconds) timeout_counter = 0 library = content.classes.library()[0]() # get entire plex_watchlist diff --git a/ui/ui_settings.py b/ui/ui_settings.py index 294f9d5fc..dc0149a8c 100644 --- a/ui/ui_settings.py +++ b/ui/ui_settings.py @@ -2,3 +2,4 @@ run_directly = "true" debug = "false" log = "false" +loop_interval_seconds = 1800 \ No newline at end of file From 29072da3d4412c19a65c00af7845bbcf684e2db6 Mon Sep 17 00:00:00 2001 From: sirstudly Date: Wed, 17 Jul 2024 22:54:14 +0100 Subject: [PATCH 18/28] Issue 529: Update default character replacement to better handle punctuation --- releases/__init__.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/releases/__init__.py b/releases/__init__.py index d10e57245..23077de43 100644 --- a/releases/__init__.py +++ b/releases/__init__.py @@ -49,6 +49,7 @@ class rename: ['à', 'a'], ['ö', 'oe'], ['ô', 'o'], + ['ō', 'o'], ['ß', 'ss'], ['é', 'e'], ['è', 'e'], @@ -56,16 +57,16 @@ class rename: ['sh!t', 'shit'], ['f**k', 'fuck'], ['f**king', 'fucking'], - [':', ''], + ['?', ''], + [':', '.?'], ['(', ''], [')', ''], ['`', ''], ['´', ''], [',', ''], - ['!', ''], - ['?', ''], + ['!', '.?'], [' - ', ' '], - ["'", ''], + ["'", '.?'], ["\u200b", ''], ['*', ''], [' ', '.'] From 808b518ad20077c3e96576eac7b1b5ebd85280bc Mon Sep 17 00:00:00 2001 From: sirstudly Date: Sun, 21 Jul 2024 01:01:19 +0100 Subject: [PATCH 19/28] Fixing zilean support --- scraper/services/zilean.py | 97 ++++++++++++++++++++++++++------------ settings/__init__.py | 2 +- 2 files changed, 69 insertions(+), 30 deletions(-) diff --git a/scraper/services/zilean.py b/scraper/services/zilean.py index 19320b06c..14c2f2deb 100644 --- a/scraper/services/zilean.py +++ b/scraper/services/zilean.py @@ -1,9 +1,11 @@ # import modules from base import * from ui.ui_print import * +import urllib.parse import releases +import re -search_url = "http://localhost:8181/dmm/search" +base_url = "http://localhost:8181" name = "zilean" timeout_sec = 10 session = requests.Session() @@ -46,7 +48,7 @@ def setup(cls, new=False): print() indices = [] for setting in settings: - if setting.name == "Zilean Search URL": + if setting.name == "Zilean Base URL": setting.setup() if not cls.name in active: active += [cls.name] @@ -54,38 +56,75 @@ def setup(cls, new=False): def scrape(query, altquery): from scraper.services import active - global search_url + ui_print("[zilean] searching for " + query + " accepting titles that regex match " + altquery) + global base_url scraped_releases = [] - if 'zilean' in active: - if search_url.endswith('/'): - search_url = search_url[:-1] - try: - ui_print("[zilean] searching for: " + query) - response = session.post(search_url, headers={'Content-type': "application/json"}, data=json.dumps({'queryText': query}), timeout=timeout_sec) + if not 'zilean' in active: + return scraped_releases - if not response.status_code == 200: - ui_print('[zilean] error ' + str( - response.status_code) + ': failed response from zilean. ' + response.content) - return [] + matches_regex = altquery + if altquery == "(.*)": + matches_regex = query + media_type = "show" if regex.search(r'(S[0-9]|complete|S\?[0-9])', matches_regex, regex.I) else "movie" - except requests.exceptions.Timeout: - ui_print('[zilean] error: zilean request timed out.') - return [] - except: - ui_print('[zilean] error: zilean couldn\'t be reached. Make sure your zilean search url [' + search_url + '] is correctly formatted.') - return [] + opts = [] + title = query + if media_type == "show": + s = (regex.search(r'(?<=S)([0-9]+)', matches_regex, regex.I).group() + if regex.search(r'(?<=S)([0-9]+)', matches_regex, regex.I) else None) + e = (regex.search(r'(?<=E)([0-9]+)', matches_regex, regex.I).group() + if regex.search(r'(?<=E)([0-9]+)', matches_regex, regex.I) else None) + if s is not None: + opts.append('season=' + str(int(s))) + title = re.sub(r'S[0-9]+', '', title, flags=re.IGNORECASE).strip() + if e is not None and int(e) != 0: + opts.append('episode=' + str(int(e))) + title = re.sub(r'E[0-9]+', '', title, flags=re.IGNORECASE).strip() + else: + # find year match at the end of the query string + year_regex = regex.search(r'(.*)\.([12][0-9]{3})$', query, regex.I) + if year_regex: + opts.append('year=' + year_regex.group(2)) + title = year_regex.group(1) + + title = title.replace('.', ' ').replace('?', ' ').strip() + opts.append('query=' + urllib.parse.quote(title)) - try: - response = json.loads(response.content, object_hook=lambda d: SimpleNamespace(**d)) - except: - ui_print('[zilean] error: unable to parse response:' + response.content) + if base_url.endswith('/'): + base_url = base_url[:-1] + search_url = base_url + "/dmm/filtered?" + '&'.join(opts) + + try: + ui_print("[zilean] using search URL: " + search_url) + response = session.get(search_url, timeout=timeout_sec) + + if not response.status_code == 200: + ui_print('[zilean] error ' + str( + response.status_code) + ': failed response from zilean. ' + response.content) return [] - for result in response[:]: - if regex.match(r'(' + altquery.replace('.', '\.').replace("\.*", ".*") + ')', result.filename,regex.I): - links = ['magnet:?xt=urn:btih:' + result.infoHash + '&dn=&tr='] - seeders = 0 # not available - scraped_releases += [releases.release( - '[zilean]', 'torrent', result.filename, [], float(result.filesize) / 1000000000, links, seeders)] + except requests.exceptions.Timeout: + ui_print('[zilean] error: zilean request timed out.') + return [] + except: + ui_print( + '[zilean] error: zilean couldn\'t be reached. Make sure your zilean base url [' + base_url + '] is correctly formatted.') + return [] + + try: + response = json.loads(response.content, object_hook=lambda d: SimpleNamespace(**d)) + except: + ui_print('[zilean] error: unable to parse response:' + response.content) + return [] + + ui_print('[zilean] ' + str(len(response)) + ' results found.') + for result in response[:]: + if regex.match(r'(' + altquery.replace('.', '\.').replace("\.*", ".*") + ')', result.title, regex.I): + links = ['magnet:?xt=urn:btih:' + result.infoHash + '&dn=&tr='] + seeders = 0 # not available + scraped_releases += [releases.release( + '[zilean]', 'torrent', result.rawTitle, [], float(result.size) / 1000000000, links, seeders)] + else: + ui_print('[zilean] skipping ' + result.rawTitle + ' because it does not match deviation ' + altquery) return scraped_releases diff --git a/settings/__init__.py b/settings/__init__.py index 1655ec0d3..61ee39ecb 100644 --- a/settings/__init__.py +++ b/settings/__init__.py @@ -380,7 +380,7 @@ def get(self): setting('Nyaa sleep time', 'Enter a time in seconds to sleep between requests (default: "5"): ',scraper.services.nyaa, 'sleep', hidden=True), setting('Nyaa proxy', 'Enter a proxy to use for nyaa (default: "nyaa.si"): ',scraper.services.nyaa, 'proxy', hidden=True), setting('Torrentio Scraper Parameters','Please enter a valid torrentio manifest url: ',scraper.services.torrentio, 'default_opts', entry="parameter", help='This settings lets you control the torrentio scraping parameters. Visit "https://torrentio.strem.fun/configure" and configure your settings. Dont choose a debrid service. The "manifest url" will be copied to your clipboard.', hidden=True), - setting('Zilean Search URL', 'Please specify your Zilean search URL: ', scraper.services.zilean, 'search_url', hidden=True), + setting('Zilean Base URL', 'Please specify your Zilean base URL: ', scraper.services.zilean, 'base_url', hidden=True), ] ], ['Debrid Services', [ From 11655d01b065ac2e41581db44c359309d00ab895 Mon Sep 17 00:00:00 2001 From: sirstudly Date: Sun, 21 Jul 2024 01:01:41 +0100 Subject: [PATCH 20/28] Allow access to host network in docker-compose.yml --- docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.yml b/docker-compose.yml index 312f6c0fb..264201446 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,6 +3,7 @@ version: '3' services: app: build: . + network_mode: "host" stdin_open: true tty: true volumes: From b226b1edbcd4936dcef1496dfa9e2ed622df34bc Mon Sep 17 00:00:00 2001 From: sirstudly Date: Sun, 21 Jul 2024 16:34:53 +0100 Subject: [PATCH 21/28] Fixing zilean alt matching --- scraper/services/zilean.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scraper/services/zilean.py b/scraper/services/zilean.py index 14c2f2deb..8b135f741 100644 --- a/scraper/services/zilean.py +++ b/scraper/services/zilean.py @@ -119,7 +119,7 @@ def scrape(query, altquery): ui_print('[zilean] ' + str(len(response)) + ' results found.') for result in response[:]: - if regex.match(r'(' + altquery.replace('.', '\.').replace("\.*", ".*") + ')', result.title, regex.I): + if regex.match(r'(' + altquery.replace('.', '\.').replace("\.*", ".*") + ')', result.rawTitle, regex.I): links = ['magnet:?xt=urn:btih:' + result.infoHash + '&dn=&tr='] seeders = 0 # not available scraped_releases += [releases.release( From b05494d5b8d8d24d9e645ef1b3408de323115c4a Mon Sep 17 00:00:00 2001 From: sirstudly Date: Sun, 21 Jul 2024 18:41:07 +0100 Subject: [PATCH 22/28] Fixing zilean alt matching --- scraper/services/zilean.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scraper/services/zilean.py b/scraper/services/zilean.py index 8b135f741..ee9362a3d 100644 --- a/scraper/services/zilean.py +++ b/scraper/services/zilean.py @@ -119,7 +119,7 @@ def scrape(query, altquery): ui_print('[zilean] ' + str(len(response)) + ' results found.') for result in response[:]: - if regex.match(r'(' + altquery.replace('.', '\.').replace("\.*", ".*") + ')', result.rawTitle, regex.I): + if regex.match(r'(' + altquery + ')', result.rawTitle, regex.I): links = ['magnet:?xt=urn:btih:' + result.infoHash + '&dn=&tr='] seeders = 0 # not available scraped_releases += [releases.release( From b43ca9ef84b590cc368d81fe4b4da753d754d5ae Mon Sep 17 00:00:00 2001 From: sirstudly Date: Sun, 21 Jul 2024 23:03:11 +0100 Subject: [PATCH 23/28] Fixing zilean alt matching --- scraper/services/zilean.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scraper/services/zilean.py b/scraper/services/zilean.py index ee9362a3d..5331c23c5 100644 --- a/scraper/services/zilean.py +++ b/scraper/services/zilean.py @@ -74,12 +74,12 @@ def scrape(query, altquery): if regex.search(r'(?<=S)([0-9]+)', matches_regex, regex.I) else None) e = (regex.search(r'(?<=E)([0-9]+)', matches_regex, regex.I).group() if regex.search(r'(?<=E)([0-9]+)', matches_regex, regex.I) else None) - if s is not None: + if s is not None and int(s) != 0: opts.append('season=' + str(int(s))) - title = re.sub(r'S[0-9]+', '', title, flags=re.IGNORECASE).strip() if e is not None and int(e) != 0: opts.append('episode=' + str(int(e))) - title = re.sub(r'E[0-9]+', '', title, flags=re.IGNORECASE).strip() + title = re.sub(r'S[0-9]+', '', title, flags=re.IGNORECASE).strip() + title = re.sub(r'E[0-9]+', '', title, flags=re.IGNORECASE).strip() else: # find year match at the end of the query string year_regex = regex.search(r'(.*)\.([12][0-9]{3})$', query, regex.I) From 9009b435ba44b7a867d7389a5065fdcf0c2c2cb9 Mon Sep 17 00:00:00 2001 From: sirstudly Date: Mon, 15 Jul 2024 00:10:15 +0100 Subject: [PATCH 24/28] Added Zilean support --- README.md | 9 +++ scraper/services/__init__.py | 3 +- scraper/services/zilean.py | 130 +++++++++++++++++++++++++++++++++++ settings/__init__.py | 1 + 4 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 scraper/services/zilean.py diff --git a/README.md b/README.md index b6a9cf8f6..bfec7b1d8 100644 --- a/README.md +++ b/README.md @@ -492,6 +492,15 @@ If github is not your cup of tea; > - You can find a full list of all possible parameters and their respective values at "https://panel.orionoid.com/" in the "Developers" menu, section "API Docs" under "Stream API". > > +> +>
+> zilean: +> +> - Zilean is a service that allows you to search for [DebridMediaManager](https://github.com/debridmediamanager/debrid-media-manager) sourced arr-less content. +> - You can integrate zilean into plex_debrid by navigating to '/Settings/Scraper/Sources/Edit/Add source/zilean'. +> - Details of this project can be found at https://github.com/iPromKnight/zilean +> +>
  ### :arrow_down_small: Debrid Services: diff --git a/scraper/services/__init__.py b/scraper/services/__init__.py index fe45e7e34..bd609bf70 100644 --- a/scraper/services/__init__.py +++ b/scraper/services/__init__.py @@ -7,10 +7,11 @@ from scraper.services import orionoid from scraper.services import nyaa from scraper.services import torrentio +from scraper.services import zilean #define subclass method def __subclasses__(): - return [rarbg,x1337,jackett,prowlarr,orionoid,nyaa,torrentio] + return [rarbg,x1337,jackett,prowlarr,orionoid,nyaa,torrentio,zilean] active = ['torrentio'] overwrite = [] diff --git a/scraper/services/zilean.py b/scraper/services/zilean.py new file mode 100644 index 000000000..5331c23c5 --- /dev/null +++ b/scraper/services/zilean.py @@ -0,0 +1,130 @@ +# import modules +from base import * +from ui.ui_print import * +import urllib.parse +import releases +import re + +base_url = "http://localhost:8181" +name = "zilean" +timeout_sec = 10 +session = requests.Session() + + +def setup(cls, new=False): + from settings import settings_list + from scraper.services import active + settings = [] + for category, allsettings in settings_list: + for setting in allsettings: + if setting.cls == cls: + settings += [setting] + if settings == []: + if not cls.name in active: + active += [cls.name] + back = False + if not new: + while not back: + print("0) Back") + indices = [] + for index, setting in enumerate(settings): + print(str(index + 1) + ') ' + setting.name) + indices += [str(index + 1)] + print() + if settings == []: + print("Nothing to edit!") + print() + time.sleep(3) + return + choice = input("Choose an action: ") + if choice in indices: + settings[int(choice) - 1].input() + if not cls.name in active: + active += [cls.name] + back = True + elif choice == '0': + back = True + else: + print() + indices = [] + for setting in settings: + if setting.name == "Zilean Base URL": + setting.setup() + if not cls.name in active: + active += [cls.name] + + +def scrape(query, altquery): + from scraper.services import active + ui_print("[zilean] searching for " + query + " accepting titles that regex match " + altquery) + global base_url + scraped_releases = [] + if not 'zilean' in active: + return scraped_releases + + matches_regex = altquery + if altquery == "(.*)": + matches_regex = query + media_type = "show" if regex.search(r'(S[0-9]|complete|S\?[0-9])', matches_regex, regex.I) else "movie" + + opts = [] + title = query + if media_type == "show": + s = (regex.search(r'(?<=S)([0-9]+)', matches_regex, regex.I).group() + if regex.search(r'(?<=S)([0-9]+)', matches_regex, regex.I) else None) + e = (regex.search(r'(?<=E)([0-9]+)', matches_regex, regex.I).group() + if regex.search(r'(?<=E)([0-9]+)', matches_regex, regex.I) else None) + if s is not None and int(s) != 0: + opts.append('season=' + str(int(s))) + if e is not None and int(e) != 0: + opts.append('episode=' + str(int(e))) + title = re.sub(r'S[0-9]+', '', title, flags=re.IGNORECASE).strip() + title = re.sub(r'E[0-9]+', '', title, flags=re.IGNORECASE).strip() + else: + # find year match at the end of the query string + year_regex = regex.search(r'(.*)\.([12][0-9]{3})$', query, regex.I) + if year_regex: + opts.append('year=' + year_regex.group(2)) + title = year_regex.group(1) + + title = title.replace('.', ' ').replace('?', ' ').strip() + opts.append('query=' + urllib.parse.quote(title)) + + if base_url.endswith('/'): + base_url = base_url[:-1] + search_url = base_url + "/dmm/filtered?" + '&'.join(opts) + + try: + ui_print("[zilean] using search URL: " + search_url) + response = session.get(search_url, timeout=timeout_sec) + + if not response.status_code == 200: + ui_print('[zilean] error ' + str( + response.status_code) + ': failed response from zilean. ' + response.content) + return [] + + except requests.exceptions.Timeout: + ui_print('[zilean] error: zilean request timed out.') + return [] + except: + ui_print( + '[zilean] error: zilean couldn\'t be reached. Make sure your zilean base url [' + base_url + '] is correctly formatted.') + return [] + + try: + response = json.loads(response.content, object_hook=lambda d: SimpleNamespace(**d)) + except: + ui_print('[zilean] error: unable to parse response:' + response.content) + return [] + + ui_print('[zilean] ' + str(len(response)) + ' results found.') + for result in response[:]: + if regex.match(r'(' + altquery + ')', result.rawTitle, regex.I): + links = ['magnet:?xt=urn:btih:' + result.infoHash + '&dn=&tr='] + seeders = 0 # not available + scraped_releases += [releases.release( + '[zilean]', 'torrent', result.rawTitle, [], float(result.size) / 1000000000, links, seeders)] + else: + ui_print('[zilean] skipping ' + result.rawTitle + ' because it does not match deviation ' + altquery) + + return scraped_releases diff --git a/settings/__init__.py b/settings/__init__.py index ed619d835..637635ba0 100644 --- a/settings/__init__.py +++ b/settings/__init__.py @@ -380,6 +380,7 @@ def get(self): setting('Nyaa sleep time', 'Enter a time in seconds to sleep between requests (default: "5"): ',scraper.services.nyaa, 'sleep', hidden=True), setting('Nyaa proxy', 'Enter a proxy to use for nyaa (default: "nyaa.si"): ',scraper.services.nyaa, 'proxy', hidden=True), setting('Torrentio Scraper Parameters','Please enter a valid torrentio manifest url: ',scraper.services.torrentio, 'default_opts', entry="parameter", help='This settings lets you control the torrentio scraping parameters. Visit "https://torrentio.strem.fun/configure" and configure your settings. Dont choose a debrid service. The "manifest url" will be copied to your clipboard.', hidden=True), + setting('Zilean Base URL', 'Please specify your Zilean base URL: ', scraper.services.zilean, 'base_url', hidden=True), ] ], ['Debrid Services', [ From 617993acfbd9d791edd798dddd16e22aa663817c Mon Sep 17 00:00:00 2001 From: David Young Date: Tue, 23 Jul 2024 13:02:19 +1200 Subject: [PATCH 25/28] Bump version to v2.96 Signed-off-by: David Young --- .github/CODEOWNERS | 3 +++ ui/ui_settings.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..3127bc62e --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,3 @@ +# Unless more-specific rules are added, all PRs require @funkypenguin's approval ;) + +* @funkypenguin diff --git a/ui/ui_settings.py b/ui/ui_settings.py index 294f9d5fc..f46f6bb1c 100644 --- a/ui/ui_settings.py +++ b/ui/ui_settings.py @@ -1,4 +1,4 @@ -version = ['2.95', "Settings compatible update", []] +version = ['2.96', "Settings compatible update", []] run_directly = "true" debug = "false" log = "false" From 401a4e0ec90bf3080ecd3e6f2be5d8078af2bf8d Mon Sep 17 00:00:00 2001 From: sirstudly Date: Tue, 23 Jul 2024 17:15:13 +0100 Subject: [PATCH 26/28] Issue 529: Update default character replacement to better handle punctuation Correct matching for: - Mission Impossible: Dead Reckoning - "Part One" may be present in name but not matched - Mr. Nobody - Allow for Mr..Nobody or Mr.Nobody - Dirk Gently's Holistic Detective Agency - Allow presence of apostrophes in title - Face/Off - Allow for slashes in title Squashed commit of the following: commit de12f022c75b0d194441f4f7690b213016b3edc8 Author: sirstudly Date: Tue Jul 23 17:08:51 2024 +0100 Character replacement for slashes in title (eg. Face/Off) Issue #529 commit 5fbb9991895180c92b27d6114062aa342b827dd5 Author: sirstudly Date: Wed Jul 17 22:54:14 2024 +0100 Issue 529: Update default character replacement to better handle punctuation commit 113caebffea6961dcf4fef4536ee494628ab20b7 Author: sirstudly Date: Thu Apr 4 18:42:42 2024 +0100 Keeping regex consistent with subsequent line (issue 529) commit fedbc673366a5b4f7ba9bd45ce4fbbfbefbe6f98 Author: sirstudly Date: Thu Mar 21 13:49:34 2024 +0000 Resolving issue https://github.com/itsToggle/plex_debrid/issues/638 --- content/classes.py | 4 ++-- releases/__init__.py | 10 ++++++---- scraper/services/zilean.py | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/content/classes.py b/content/classes.py index f17e1bc72..cf1ef0e3e 100644 --- a/content/classes.py +++ b/content/classes.py @@ -570,8 +570,8 @@ def deviation(self, year=""): if regex.search(str(self.year), releases.rename(self.title.replace(str(self.year), '') + ' ' + str(self.year))): title = title.replace('.' + str(self.year), '') if year != "": - return '[^A-Za-z0-9]*(' + title + ':?.)\(?\[?(' + str(year) + ')' - return '[^A-Za-z0-9]*(' + title + ':?.)\(?\[?(' + str(self.year) + '|' + str(self.year - 1) + '|' + str(self.year + 1) + ')' + return '[^A-Za-z0-9]*(' + title + ':?.*)\(?\[?(' + str(year) + ')' + return '[^A-Za-z0-9]*(' + title + ':?.*)\(?\[?(' + str(self.year) + '|' + str(self.year - 1) + '|' + str(self.year + 1) + ')' else: title = title.replace('.' + str(self.year), '') return '[^A-Za-z0-9]*(' + title + ')' diff --git a/releases/__init__.py b/releases/__init__.py index d10e57245..75b5099fe 100644 --- a/releases/__init__.py +++ b/releases/__init__.py @@ -49,6 +49,7 @@ class rename: ['à', 'a'], ['ö', 'oe'], ['ô', 'o'], + ['ō', 'o'], ['ß', 'ss'], ['é', 'e'], ['è', 'e'], @@ -56,16 +57,17 @@ class rename: ['sh!t', 'shit'], ['f**k', 'fuck'], ['f**king', 'fucking'], - [':', ''], + ['?', ''], + [':', '.?'], ['(', ''], [')', ''], ['`', ''], ['´', ''], [',', ''], - ['!', ''], - ['?', ''], + ['/', '.'], + ['!', '.?'], [' - ', ' '], - ["'", ''], + ["'", '.?'], ["\u200b", ''], ['*', ''], [' ', '.'] diff --git a/scraper/services/zilean.py b/scraper/services/zilean.py index 5331c23c5..d76ca2594 100644 --- a/scraper/services/zilean.py +++ b/scraper/services/zilean.py @@ -87,7 +87,7 @@ def scrape(query, altquery): opts.append('year=' + year_regex.group(2)) title = year_regex.group(1) - title = title.replace('.', ' ').replace('?', ' ').strip() + title = title.replace('.?', '').replace('.', ' ').replace('?', ' ').strip() opts.append('query=' + urllib.parse.quote(title)) if base_url.endswith('/'): From 117b372e6b4d0db576033ccb751f14116c1c0ad7 Mon Sep 17 00:00:00 2001 From: sirstudly Date: Tue, 23 Jul 2024 19:12:11 +0100 Subject: [PATCH 27/28] Reverting some regex fixes which may not be required? --- content/classes.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/content/classes.py b/content/classes.py index 85e0d98fc..9db9d107a 100644 --- a/content/classes.py +++ b/content/classes.py @@ -570,17 +570,17 @@ def deviation(self, year=""): if regex.search(str(self.year), releases.rename(self.title.replace(str(self.year), '') + ' ' + str(self.year))): title = title.replace('.' + str(self.year), '') if year != "": - return '(.*?)(' + title + ':?.*)\(?\[?(' + str(year) + ')?' - return '(.*?)(' + title + ':?.*)\(?\[?(' + str(self.year) + '|' + str(self.year - 1) + '|' + str(self.year + 1) + ')?' + return '[^A-Za-z0-9]*(' + title + ':?.*)\(?\[?(' + str(year) + ')' + return '[^A-Za-z0-9]*(' + title + ':?.*)\(?\[?(' + str(self.year) + '|' + str(self.year - 1) + '|' + str(self.year + 1) + ')' else: title = title.replace('.' + str(self.year), '') - return '(.*?)(' + title + ')' + return '[^A-Za-z0-9]*(' + title + ')' elif self.type == 'show': title = title.replace('.' + str(self.year), '') - return '(.*?)(' + title + ':?.)(series.|[^A-Za-z0-9]+)?((\(?' + str(self.year) + '\)?.)|(complete.)|(seasons?.[0-9]+.[0-9]?[0-9]?.?)|(S[0-9]+.S?[0-9]?[0-9]?.?)|(S[0-9]+E[0-9]+))' + return '[^A-Za-z0-9]*(' + title + ':?.)(series.|[^A-Za-z0-9]+)?((\(?' + str(self.year) + '\)?.)|(complete.)|(seasons?.[0-9]+.[0-9]?[0-9]?.?)|(S[0-9]+.S?[0-9]?[0-9]?.?)|(S[0-9]+E[0-9]+))' elif self.type == 'season': title = title.replace('.' + str(self.parentYear), '') - return '(.*?)(' + title + ':?.)(series.|[^A-Za-z0-9]+)?(\(?' + str(self.parentYear) + '\)?.)?(season.' + str(self.index) + '[^0-9]|season.' + str("{:02d}".format(self.index)) + '[^0-9]|S' + str("{:02d}".format(self.index)) + '[^0-9])' + return '[^A-Za-z0-9]*(' + title + ':?.)(series.|[^A-Za-z0-9]+)?(\(?' + str(self.parentYear) + '\)?.)?(season.' + str(self.index) + '\.|season.' + str("{:02d}".format(self.index)) + '\.|S' + str("{:02d}".format(self.index)) + '\.)' elif self.type == 'episode': title = title.replace('.' + str(self.grandparentYear), '') try: @@ -594,9 +594,9 @@ def deviation(self, year=""): airdate_formats += [airdate.strftime( '(%m|%b).*%d.*(%Y|%y)').replace("0", "0?")] airdate_formats = "(" + ")|(".join(airdate_formats) + ")" - return '(.*?)(' + title + ':?.)(series.)?(\(?' + str(self.grandparentYear) + '\)?.)?(S' + str("{:02d}".format(self.parentIndex)) + 'E' + str("{:02d}".format(self.index)) + '.|'+airdate_formats+')' + return '[^A-Za-z0-9]*(' + title + ':?.)(series.)?(\(?' + str(self.grandparentYear) + '\)?.)?(S' + str("{:02d}".format(self.parentIndex)) + 'E' + str("{:02d}".format(self.index)) + '.|'+airdate_formats+')' except: - return '(.*?)(' + title + ':?.)(series.)?(\(?' + str(self.grandparentYear) + '\)?.)?(S' + str("{:02d}".format(self.parentIndex)) + 'E' + str("{:02d}".format(self.index)) + '.)' + return '[^A-Za-z0-9]*(' + title + ':?.)(series.)?(\(?' + str(self.grandparentYear) + '\)?.)?(S' + str("{:02d}".format(self.parentIndex)) + 'E' + str("{:02d}".format(self.index)) + '.)' else: if hasattr(self, 'alternate_titles'): title = '(' + '|'.join(self.alternate_titles) + ')' @@ -612,22 +612,22 @@ def deviation(self, year=""): title = title.replace('[', '\[').replace(']', '\]') if self.type == 'movie': title = title.replace('.' + str(self.year), '') - return '(.*?)(' + title + ')(.*?)(' + str(self.year) + '|' + str(self.year - 1) + '|' + str(self.year + 1) + ')?' + return '(.*?)(' + title + '.)(.*?)(' + str(self.year) + '|' + str(self.year - 1) + '|' + str(self.year + 1) + ')' elif self.type == 'show': title = title.replace('.' + str(self.year), '') - return '(.*?)(' + title + ')(.*?)('+self.anime_count+'|(complete)|(seasons?[^0-9]?[0-9]+[^A-Z0-9]+S?[0-9]+)|(S[0-9]+[^A-Z0-9]+S?[0-9]+))' + return '(.*?)(' + title + '.)(.*?)('+self.anime_count+'|(complete)|(seasons?[^0-9]?[0-9]+[^A-Z0-9]+S?[0-9]+)|(S[0-9]+[^A-Z0-9]+S?[0-9]+))' elif self.type == 'season': n = self.index roman = 'I' if n == 1 else 'II' if n == 2 else 'III' if n == 3 else 'IV' if n == 4 else 'V' if n == 5 else 'VI' if n == 6 else 'VII' if n == 7 else 'VIII' if n == 8 else 'IX' if n == 9 else 'X' if n == 10 else str( n) title = title.replace('.' + str(self.parentYear), '') - return '(.*?)(' + title + ')(.*?)(season[^0-9]?0*' + str(self.index) + '|S0*' + str(self.index) + '(?!E?[0-9])|'+self.anime_count+'|[^A-Z0-9]'+roman+'[^A-Z0-9])' + return '(.*?)(' + title + '.)(.*?)(season[^0-9]?0*' + str(self.index) + '|S0*' + str(self.index) + '(?!E?[0-9])|'+self.anime_count+'|[^A-Z0-9]'+roman+'[^A-Z0-9])' elif self.type == 'episode': n = self.parentIndex roman = 'I' if n == 1 else 'II' if n == 2 else 'III' if n == 3 else 'IV' if n == 4 else 'V' if n == 5 else 'VI' if n == 6 else 'VII' if n == 7 else 'VIII' if n == 8 else 'IX' if n == 9 else 'X' if n == 10 else str( n) title = title.replace('.' + str(self.grandparentYear), '') - return '(.*?)(' + title + ')(.*?)((? Date: Tue, 23 Jul 2024 20:35:17 +0100 Subject: [PATCH 28/28] Reverting previous commit.. included in branch issue_529 --- content/classes.py | 22 +++++++++++----------- content/services/plex.py | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/content/classes.py b/content/classes.py index 9db9d107a..57fd328f7 100644 --- a/content/classes.py +++ b/content/classes.py @@ -570,17 +570,17 @@ def deviation(self, year=""): if regex.search(str(self.year), releases.rename(self.title.replace(str(self.year), '') + ' ' + str(self.year))): title = title.replace('.' + str(self.year), '') if year != "": - return '[^A-Za-z0-9]*(' + title + ':?.*)\(?\[?(' + str(year) + ')' - return '[^A-Za-z0-9]*(' + title + ':?.*)\(?\[?(' + str(self.year) + '|' + str(self.year - 1) + '|' + str(self.year + 1) + ')' + return '(.*?)(' + title + ':?.*)\(?\[?(' + str(year) + ')?' + return '(.*?)(' + title + ':?.*)\(?\[?(' + str(self.year) + '|' + str(self.year - 1) + '|' + str(self.year + 1) + ')?' else: title = title.replace('.' + str(self.year), '') - return '[^A-Za-z0-9]*(' + title + ')' + return '(.*?)(' + title + ')' elif self.type == 'show': title = title.replace('.' + str(self.year), '') - return '[^A-Za-z0-9]*(' + title + ':?.)(series.|[^A-Za-z0-9]+)?((\(?' + str(self.year) + '\)?.)|(complete.)|(seasons?.[0-9]+.[0-9]?[0-9]?.?)|(S[0-9]+.S?[0-9]?[0-9]?.?)|(S[0-9]+E[0-9]+))' + return '(.*?)(' + title + ':?.)(series.|[^A-Za-z0-9]+)?((\(?' + str(self.year) + '\)?.)|(complete.)|(seasons?.[0-9]+.[0-9]?[0-9]?.?)|(S[0-9]+.S?[0-9]?[0-9]?.?)|(S[0-9]+E[0-9]+))' elif self.type == 'season': title = title.replace('.' + str(self.parentYear), '') - return '[^A-Za-z0-9]*(' + title + ':?.)(series.|[^A-Za-z0-9]+)?(\(?' + str(self.parentYear) + '\)?.)?(season.' + str(self.index) + '\.|season.' + str("{:02d}".format(self.index)) + '\.|S' + str("{:02d}".format(self.index)) + '\.)' + return '(.*?)(' + title + ':?.)(series.|[^A-Za-z0-9]+)?(\(?' + str(self.parentYear) + '\)?.)?(season.' + str(self.index) + '[^0-9]|season.' + str("{:02d}".format(self.index)) + '[^0-9]|S' + str("{:02d}".format(self.index)) + '[^0-9])' elif self.type == 'episode': title = title.replace('.' + str(self.grandparentYear), '') try: @@ -594,9 +594,9 @@ def deviation(self, year=""): airdate_formats += [airdate.strftime( '(%m|%b).*%d.*(%Y|%y)').replace("0", "0?")] airdate_formats = "(" + ")|(".join(airdate_formats) + ")" - return '[^A-Za-z0-9]*(' + title + ':?.)(series.)?(\(?' + str(self.grandparentYear) + '\)?.)?(S' + str("{:02d}".format(self.parentIndex)) + 'E' + str("{:02d}".format(self.index)) + '.|'+airdate_formats+')' + return '(.*?)(' + title + ':?.)(series.)?(\(?' + str(self.grandparentYear) + '\)?.)?(S' + str("{:02d}".format(self.parentIndex)) + 'E' + str("{:02d}".format(self.index)) + '.|'+airdate_formats+')' except: - return '[^A-Za-z0-9]*(' + title + ':?.)(series.)?(\(?' + str(self.grandparentYear) + '\)?.)?(S' + str("{:02d}".format(self.parentIndex)) + 'E' + str("{:02d}".format(self.index)) + '.)' + return '(.*?)(' + title + ':?.)(series.)?(\(?' + str(self.grandparentYear) + '\)?.)?(S' + str("{:02d}".format(self.parentIndex)) + 'E' + str("{:02d}".format(self.index)) + '.)' else: if hasattr(self, 'alternate_titles'): title = '(' + '|'.join(self.alternate_titles) + ')' @@ -612,22 +612,22 @@ def deviation(self, year=""): title = title.replace('[', '\[').replace(']', '\]') if self.type == 'movie': title = title.replace('.' + str(self.year), '') - return '(.*?)(' + title + '.)(.*?)(' + str(self.year) + '|' + str(self.year - 1) + '|' + str(self.year + 1) + ')' + return '(.*?)(' + title + ')(.*?)(' + str(self.year) + '|' + str(self.year - 1) + '|' + str(self.year + 1) + ')?' elif self.type == 'show': title = title.replace('.' + str(self.year), '') - return '(.*?)(' + title + '.)(.*?)('+self.anime_count+'|(complete)|(seasons?[^0-9]?[0-9]+[^A-Z0-9]+S?[0-9]+)|(S[0-9]+[^A-Z0-9]+S?[0-9]+))' + return '(.*?)(' + title + ')(.*?)('+self.anime_count+'|(complete)|(seasons?[^0-9]?[0-9]+[^A-Z0-9]+S?[0-9]+)|(S[0-9]+[^A-Z0-9]+S?[0-9]+))' elif self.type == 'season': n = self.index roman = 'I' if n == 1 else 'II' if n == 2 else 'III' if n == 3 else 'IV' if n == 4 else 'V' if n == 5 else 'VI' if n == 6 else 'VII' if n == 7 else 'VIII' if n == 8 else 'IX' if n == 9 else 'X' if n == 10 else str( n) title = title.replace('.' + str(self.parentYear), '') - return '(.*?)(' + title + '.)(.*?)(season[^0-9]?0*' + str(self.index) + '|S0*' + str(self.index) + '(?!E?[0-9])|'+self.anime_count+'|[^A-Z0-9]'+roman+'[^A-Z0-9])' + return '(.*?)(' + title + ')(.*?)(season[^0-9]?0*' + str(self.index) + '|S0*' + str(self.index) + '(?!E?[0-9])|'+self.anime_count+'|[^A-Z0-9]'+roman+'[^A-Z0-9])' elif self.type == 'episode': n = self.parentIndex roman = 'I' if n == 1 else 'II' if n == 2 else 'III' if n == 3 else 'IV' if n == 4 else 'V' if n == 5 else 'VI' if n == 6 else 'VII' if n == 7 else 'VIII' if n == 8 else 'IX' if n == 9 else 'X' if n == 10 else str( n) title = title.replace('.' + str(self.grandparentYear), '') - return '(.*?)(' + title + '.)(.*?)((?