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