diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..3127bc62 --- /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/.gitignore b/.gitignore index 66df4ee1..874737c3 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 fa47ecab..5155d415 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/README.md b/README.md index b6a9cf8f..c3931153 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. > >
> @@ -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/content/classes.py b/content/classes.py index 3d1ba45c..57fd328f 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 + '.)(.*?)((? 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 @@ -1400,6 +1405,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() @@ -1550,7 +1556,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: diff --git a/content/services/plex.py b/content/services/plex.py index 20a73c20..d8d18b24 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("done") 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: @@ -529,7 +536,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 +593,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 +605,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 +639,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): @@ -791,8 +798,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 +883,12 @@ 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") + 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 132cf076..c60dd893 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/content/services/trakt.py b/content/services/trakt.py index 5af16aff..5eb7f7ef 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 @@ -220,6 +244,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 +275,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') diff --git a/debrid/services/realdebrid.py b/debrid/services/realdebrid.py index ffea7be1..504c269c 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/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..26420144 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,11 @@ +version: '3' + +services: + app: + build: . + network_mode: "host" + stdin_open: true + tty: true + volumes: + - ./settings.json:/settings.json + - ./ignored.txt:/ignored.txt diff --git a/extras/plex_debrid.service b/extras/plex_debrid.service new file mode 100644 index 00000000..3de8e8fa --- /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 7c13b9c6..e5c455b0 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) diff --git a/releases/__init__.py b/releases/__init__.py index d10e5724..75b5099f 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/requirements.txt b/requirements.txt index 8c94ee28..72b1d708 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 diff --git a/scraper/__init__.py b/scraper/__init__.py index acbdbce3..5a99ee7e 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/__init__.py b/scraper/services/__init__.py index fe45e7e3..bd609bf7 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/jackett.py b/scraper/services/jackett.py index 102ce948..a6a83fbf 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 9a2f9b71..613ecd19 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)) diff --git a/scraper/services/zilean.py b/scraper/services/zilean.py new file mode 100644 index 00000000..d76ca259 --- /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('.', ' ').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 ed619d83..db76f28c 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", @@ -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', [ @@ -412,6 +413,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 51bfe927..c115feb6 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_print.py b/ui/ui_print.py index 0b6ab7f7..d2e76644 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() diff --git a/ui/ui_settings.py b/ui/ui_settings.py index 294f9d5f..94102449 100644 --- a/ui/ui_settings.py +++ b/ui/ui_settings.py @@ -1,4 +1,5 @@ -version = ['2.95', "Settings compatible update", []] +version = ['2.96', "Settings compatible update", []] run_directly = "true" debug = "false" log = "false" +loop_interval_seconds = 1800 \ No newline at end of file