push latest changes

This commit is contained in:
Daniel S. 2021-12-13 19:11:43 +01:00
parent 7523a19d1f
commit cb2b5c2c2b
63 changed files with 3158 additions and 1552 deletions

143
api/__init__.py Normal file
View file

@ -0,0 +1,143 @@
import io
from fabric import Connection
from utils import genpw, handle_config
from .jackett import Jackett
from .jellyfin import Jellyfin
from .portainer import Portainer
from .qbittorrent import QBittorrent
from .radarr import Radarr
from .sonarr import Sonarr
class Client(object):
def __init__(self, cfg=None):
if cfg is None:
cfg = handle_config()
self.cfg = cfg
self.jackett = Jackett(cfg["jackett_url"], cfg["jackett_api_key"])
self.sonarr = Sonarr(cfg["sonarr_url"], cfg["sonarr_api_key"])
self.radarr = Radarr(cfg["radarr_url"], cfg["radarr_api_key"])
self.jellyfin = Jellyfin(
cfg["jellyfin_url"], cfg["jellyfin_user"], cfg["jellyfin_password"]
)
self.qbittorent = QBittorrent(
cfg["qbt_url"], cfg["qbt_username"], cfg["qbt_passwd"]
)
self.portainer = Portainer(
cfg["portainer_url"],
cfg["portainer_username"],
cfg["portainer_passwd"])
self.ssh = Connection("root@server")
def _get_ssh_keys(self):
res = self.ssh.get("/data/.ssh/authorized_keys", io.BytesIO())
res.local.seek(0)
ret = []
for line in str(res.local.read(), "utf8").splitlines():
if line.startswith("#"):
continue
else:
key_type, key, comment = line.split(None, 2)
ret.append((key_type, key, comment))
return ret
def add_user(self, name, ssh_key):
cfg = handle_config()
user_config = cfg["jellyfin_user_config"]
user_policy = cfg["jellyfin_user_policy"]
passwd = genpw()
res = self.ssh.get("/data/.ssh/authorized_keys", io.BytesIO())
res.local.seek(0)
keys = [
line.split(
None,
2) for line in str(
res.local.read(),
"utf8").splitlines()]
key_type, key, *_ = ssh_key.split()
keys.append([key_type, key, name])
new_keys = []
seen_keys = set()
for key_type, key, key_name in keys:
if key not in seen_keys:
seen_keys.add(key)
new_keys.append([key_type, key, key_name])
new_keys_file = "\n".join(" ".join(key) for key in new_keys)
self.ssh.put(
io.BytesIO(bytes(new_keys_file, "utf8")),
"/data/.ssh/authorized_keys",
preserve_mode=False,
)
user = self.jellyfin.post(
"Users/New",
json={
"Name": name,
"Password": passwd})
user = user.json()
self.jellyfin.post(
"Users/{Id}/Configuration".format(**user), json=user_config)
self.jellyfin.post(
"Users/{Id}/Policy".format(**user), json=user_policy)
return passwd
def queue(self, ids=[]):
ret = []
for item in self.sonarr.queue():
if not ids or item.get("seriesId") in ids:
item["type"] = "sonarr"
ret.append(item)
for item in self.radarr.queue():
item["download"] = self.qbittorent.status(item["downloadId"])
if not ids or item.get("movieId") in ids:
item["type"] = "radarr"
ret.append(item)
return ret
@staticmethod
def test(cls, cfg=None):
modules = [
(
"Jackett",
lambda cfg: Jackett(cfg["jackett_url"], cfg["jackett_api_key"]),
),
("Sonarr", lambda cfg: Sonarr(cfg["sonarr_url"], cfg["sonarr_api_key"])),
("Radarr", lambda cfg: Radarr(cfg["radarr_url"], cfg["radarr_api_key"])),
(
"QBittorrent",
lambda cfg: QBittorrent(
cfg["qbt_url"], cfg["qbt_username"], cfg["qbt_passwd"]
),
),
(
"Jellyfin",
lambda cfg: Jellyfin(
cfg["jellyfin_url"],
cfg["jellyfin_username"],
cfg["jellyfin_passwd"],
),
),
(
"Portainer",
lambda cfg: Portainer(
cfg["portainer_url"],
cfg["portainer_username"],
cfg["portainer_passwd"],
),
),
]
errors = {}
success = True
for mod, Module in modules:
try:
print("Testing", mod)
errors[mod] = Module(cfg).test()
if errors[mod]:
success = False
except Exception as e:
print(dir(e))
errors[mod] = str(e)
success = False
print(errors)
return {"success": success, "errors": errors}

45
api/jackett.py Normal file
View file

@ -0,0 +1,45 @@
import time
from urllib.parse import urljoin
import requests as RQ
class Jackett(object):
def __init__(self, url, api_key):
self.url = url
self.api_key = api_key
self.session = RQ.Session()
self.session.post("http://192.168.2.25:9117/jackett/UI/Dashboard")
def search(self, query, indexers=None):
params = {"apikey": self.api_key,
"Query": query, "_": str(int(time.time()))}
if indexers:
params["Tracker[]"] = indexers
res = self.session.get(
urljoin(self.url, "api/v2.0/indexers/all/results"), params=params
)
res.raise_for_status()
res = res.json()
for val in res["Results"]:
for prop in ["Gain", "Seeders", "Peers", "Grabs", "Files"]:
val[prop] = val.get(prop) or 0
return res
def indexers(self):
return [
(t["id"], t["name"])
for t in self.session.get(urljoin(self.url, "api/v2.0/indexers")).json()
if t.get("configured")
]
def test(self):
errors = {}
for idx, name in self.indexers():
print("Testing indexer", name)
result = self.session.post(
urljoin(self.url, "api/v2.0/indexers/{}/test".format(idx))
)
if result.text:
errors[name] = result.json()["error"]
return errors

333
api/jellyfin.py Normal file
View file

@ -0,0 +1,333 @@
import time
import base64
from urllib.parse import urljoin
from datetime import timedelta
import requests as RQ
from dateutil.parser import parse as parse_datetime
from utils import timed_cache
class Jellyfin(object):
def __init__(self, url, user, password):
self.url = url
self.session = RQ.Session()
self.device_id = str(
base64.b64encode(
"MediaDash ({})".format(
self.session.headers["User-Agent"]).encode("utf-8")),
"utf8",
)
self.auth_headers = {
"X-Emby-Authorization": 'MediaBrowser Client="MediaDash", Device="Python", DeviceId="{}", Version="{}"'.format(
self.device_id, RQ.__version__)}
self.user = None
if user is not None:
res = self.login_user(user, password)
self.api_key = res["AccessToken"]
else:
self.api_key = password
self.auth_headers = {
"X-Emby-Authorization": 'MediaBrowser Client="MediaDash", Device="Python", DeviceId="{}", Version="{}", Token="{}"'.format(
self.device_id, RQ.__version__, self.api_key)}
# ws_url=self.url.replace("http","ws").rstrip("/")+"/?"+urlencode({"api_key":self.api_key,"deviceId":self.device_id})
# self.ws = websocket.WebSocketApp(ws_url,on_open=print,on_error=print,on_message=print,on_close=print)
# self.ws_thread = Thread(target=self.ws.run_forever,daemon=True)
self.session.headers.update(
{**self.auth_headers, "X-Emby-Token": self.api_key})
self.user = self.get_self()
self.user_id = self.user["Id"]
self.playstate_commands = sorted(
[
"Stop",
"Pause",
"Unpause",
"NextTrack",
"PreviousTrack",
"Seek",
"Rewind",
"FastForward",
"PlayPause",
]
)
self.session_commands = sorted(
[
"MoveUp",
"MoveDown",
"MoveLeft",
"MoveRight",
"PageUp",
"PageDown",
"PreviousLetter",
"NextLetter",
"ToggleOsd",
"ToggleContextMenu",
"Select",
"Back",
"TakeScreenshot",
"SendKey",
"SendString",
"GoHome",
"GoToSettings",
"VolumeUp",
"VolumeDown",
"Mute",
"Unmute",
"ToggleMute",
"SetVolume",
"SetAudioStreamIndex",
"SetSubtitleStreamIndex",
"ToggleFullscreen",
"DisplayContent",
"GoToSearch",
"DisplayMessage",
"SetRepeatMode",
"ChannelUp",
"ChannelDown",
"Guide",
"ToggleStats",
"PlayMediaSource",
"PlayTrailers",
"SetShuffleQueue",
"PlayState",
"PlayNext",
"ToggleOsdMenu",
"Play",
]
)
def login_user(self, user, passwd):
res = self.post(
"Users/AuthenticateByName",
json={"Username": user, "Pw": passwd},
headers=self.auth_headers,
)
res.raise_for_status()
res = res.json()
self.session.headers.update(
{**self.auth_headers, "X-Emby-Token": res["AccessToken"]}
)
return res
def logout(self):
self.session.post(urljoin(self.url, "Sessions/Logout"))
def status(self):
res = self.session.get(urljoin(self.url, "System/Info"))
res.raise_for_status()
return res.json()
def chapter_image_url(self, item_id, chapter_num, tag):
return urljoin(
self.url,
"Items",
item_id,
"Images",
"Chapter",
chapter_num)
def rq(self, method, url, *args, **kwargs):
res = self.session.request(
method, urljoin(
self.url, url), *args, **kwargs)
res.raise_for_status()
return res
def get(self, url, *args, **kwargs):
res = self.session.get(urljoin(self.url, url), *args, **kwargs)
res.raise_for_status()
return res
def post(self, url, *args, **kwargs):
res = self.session.post(urljoin(self.url, url), *args, **kwargs)
res.raise_for_status()
return res
def sessions(self):
res = self.get("Sessions")
res.raise_for_status()
return res.json()
@timed_cache()
def season_episodes(self, item_id, season_id):
res = self.get(
"Shows/{}/Episodes".format(item_id),
params={
"UserId": self.user_id,
"seasonId": season_id,
"fields": "Overview,MediaStreams,MediaSources,ExternalUrls",
},
)
res.raise_for_status()
res = res.json()["Items"]
for episode in res:
episode["Info"] = self.media_info(episode["Id"])
return res
@timed_cache()
def seasons(self, item_id):
res = self.get(
"Shows/{}/Seasons".format(item_id),
params={
"UserId": self.user_id,
"fields": "Overview,MediaStreams,MediaSources,ExternalUrls",
},
)
res.raise_for_status()
res = res.json()["Items"]
for season in res:
season["Episodes"] = self.season_episodes(item_id, season["Id"])
return res
@timed_cache()
def media_info(self, item_id):
res = self.get(
"Users/{}/Items/{}".format(self.user_id, item_id),
)
res.raise_for_status()
res = res.json()
if res["Type"] == "Series":
res["Seasons"] = self.seasons(item_id)
return res
def system_info(self):
res = self.get("System/Info")
res.raise_for_status()
return res.json()
def __get_child_items(self, item_id):
print(item_id)
res = self.get(
"Users/{}/Items".format(self.user_id),
params={"ParentId": item_id},
)
res.raise_for_status()
return res.json()
def get_recursive(self, item_id):
for item in self.__get_child_items(item_id).get("Items", []):
yield item
yield from self.get_recursive(item["Id"])
def get_counts(self):
res = self.get("Items/Counts").json()
return res
@timed_cache(seconds=10)
def id_map(self):
res = self.get(
"Users/{}/Items".format(self.user_id),
params={
"recursive": True,
"includeItemTypes": "Movie,Series",
"collapseBoxSetItems": False,
"fields": "ProviderIds",
},
)
res.raise_for_status()
res = res.json()["Items"]
id_map = {}
for item in res:
for _, prov_id in item["ProviderIds"].items():
for prov in ["Imdb", "Tmdb", "Tvdb"]:
id_map[(prov.lower(), prov_id)] = item["Id"]
return id_map
@timed_cache()
def get_library(self):
res = self.get(
"Users/{}/Items".format(self.user_id),
params={
"recursive": True,
"includeItemTypes": "Movie,Series",
"collapseBoxSetItems": False,
},
).json()
library = {}
for item in res["Items"]:
library[item["Id"]] = item
for item in res["Items"]:
for key, value in item.copy().items():
if key != "Id" and key.endswith("Id"):
key = key[:-2]
if value in library and key not in item:
item[key] = library[value]
return library
def get_usage(self):
report = self.post(
"user_usage_stats/submit_custom_query",
params={"stamp": int(time.time())},
json={
"CustomQueryString": "SELECT * FROM PlaybackActivity",
"ReplaceUserId": True,
},
).json()
ret = []
for row in report["results"]:
rec = dict(zip(report["colums"], row))
rec["PlayDuration"] = timedelta(seconds=int(rec["PlayDuration"]))
ts = rec.pop("DateCreated")
if ts:
rec["Timestamp"] = parse_datetime(ts)
ret.append(rec)
return ret
def __db_fetch(self, endpoint):
ret = []
res = self.session.get(
urljoin(
self.url,
endpoint),
params={
"StartIndex": 0,
"IncludeItemTypes": "*",
"ReportColumns": ""},
)
res.raise_for_status()
res = res.json()
headers = [h["Name"].lower() for h in res["Headers"]]
for row in res["Rows"]:
fields = [c["Name"] for c in row["Columns"]]
ret.append(dict(zip(headers, fields)))
ret[-1]["row_type"] = row["RowType"]
return ret
def get_self(self):
res = self.get("Users/Me")
return res.json()
def get_users(self, user_id=None):
if user_id:
res = self.get("Users/{}".format(user_id))
else:
res = self.get("Users")
res.raise_for_status()
return res.json()
def activity(self):
return self.__db_fetch("Reports/Activities")
def report(self):
return self.__db_fetch("Reports/Items")
def stop_session(self, session_id):
sessions = self.get("Sessions").json()
for session in sessions:
if session["Id"] == session_id and "NowPlayingItem" in session:
s_id = session["Id"]
u_id = session["UserId"]
i_id = session["NowPlayingItem"]["Id"]
d_id = session["DeviceId"]
self.rq(
"delete",
"Videos/ActiveEncodings",
params={"deviceId": d_id, "playSessionId": s_id},
)
self.rq("delete", f"Users/{u_id}/PlayingItems/{i_id}")
self.rq("post", f"Sessions/{s_id}/Playing/Stop")
def test(self):
self.status()
return {}

75
api/portainer.py Normal file
View file

@ -0,0 +1,75 @@
import json
from urllib.parse import urljoin
import requests as RQ
class Portainer(object):
def __init__(self, url, username, passwd):
self.url = url
self.session = RQ.Session()
jwt = self.session.post(
urljoin(self.url, "api/auth"),
json={"username": username, "password": passwd},
).json()
self.session.headers.update(
{"Authorization": "Bearer {0[jwt]}".format(jwt)})
def containers(self, container_id=None):
if container_id is None:
res = self.session.get(
urljoin(self.url, "api/endpoints/1/docker/containers/json"),
params={
"all": 1,
"filters": json.dumps(
{"label": ["com.docker.compose.project=tvstack"]}
),
},
)
else:
res = self.session.get(
urljoin(
self.url,
"api/endpoints/1/docker/containers/{}/json".format(container_id),
))
res.raise_for_status()
res = res.json()
if container_id is None:
for container in res:
pass
# print("Gettings stats for",container['Id'])
# container['stats']=self.stats(container['Id'])
# container['top']=self.top(container['Id'])
else:
res["stats"] = self.stats(container_id)
res["top"] = self.top(container_id)
return res
def top(self, container_id):
res = self.session.get(
urljoin(
self.url,
"api/endpoints/1/docker/containers/{}/top".format(container_id),
))
res.raise_for_status()
res = res.json()
cols = res["Titles"]
ret = []
return res
def stats(self, container_id):
res = self.session.get(
urljoin(
self.url,
"api/endpoints/1/docker/containers/{}/stats".format(container_id),
),
params={
"stream": False},
)
res.raise_for_status()
return res.json()
def test(self):
self.containers()
return {}

155
api/qbittorrent.py Normal file
View file

@ -0,0 +1,155 @@
import time
from urllib.parse import urljoin, urlparse
import requests as RQ
class QBittorrent(object):
status_map = {
"downloading": ("Downloading", "primary"),
"uploading": ("Seeding", "success"),
"forcedDL": ("Downloading [Forced]", "primary"),
"forcedUP": ("Seeding [Forced]", "success"),
"pausedDL": ("Downloading [Paused]", "secondary"),
"pausedUP": ("Seeding [Paused]", "secondary"),
"stalledDL": ("Downloading [Stalled]", "warning"),
"stalledUP": ("Seeding [Stalled]", "warning"),
"metaDL": ("Downloading metadata", "primary"),
"error": ("Error", "danger"),
"missingFiles": ("Missing Files", "danger"),
"queuedUP": ("Seeding [Queued]", "info"),
"queuedDL": ("Downloading [Queued]", "info"),
}
tracker_status = {
0: ("Disabled", "secondary"),
1: ("Not contacted", "info"),
2: ("Working", "success"),
3: ("Updating", "warning"),
4: ("Not working", "danger"),
}
def __init__(self, url, username, passwd):
self.url = url
self.username = username
self.passwd = passwd
self.rid = int(time.time())
self.session = RQ.Session()
url = urljoin(self.url, "/api/v2/auth/login")
self.session.post(
url, data={"username": self.username, "password": self.passwd}
).raise_for_status()
def get(self, url, **kwargs):
kwargs["rid"] = self.rid
url = urljoin(self.url, url)
res = self.session.get(url, params=kwargs)
res.raise_for_status()
try:
return res.json()
except ValueError:
return res.text
def add(self, **kwargs):
self.rid += 1
url = urljoin(self.url, "/api/v2/torrents/add")
ret = self.session.post(url, data=kwargs)
return ret.text, ret.status_code
def add_trackers(self, infohash, trackers=None):
if trackers is None:
trackers = []
for tracker_list in [
"https://newtrackon.com/api/live",
"https://ngosang.github.io/trackerslist/trackers_best.txt",
]:
try:
trackers_res = RQ.get(tracker_list)
trackers_res.raise_for_status()
except Exception as e:
print("Error getting tracker list:", e)
continue
trackers += trackers_res.text.split()
url = urljoin(self.url, "/api/v2/torrents/addTrackers")
data = {"hash": infohash, "urls": "\n\n".join(trackers)}
ret = self.session.post(url, data=data)
ret.raise_for_status()
return ret.text
def poll(self, infohash=None):
if infohash:
ret = {}
res = self.get("/api/v2/torrents/info", hashes=infohash)
ret["info"] = res
for endpoint in ["properties", "trackers", "webseeds", "files"]:
url = "/api/v2/torrents/{}".format(endpoint)
res = self.get(url, hash=infohash)
if endpoint == "trackers":
for v in res:
if v["tier"] == "":
v["tier"] = -1
v["status"] = self.tracker_status.get(
v["status"], ("Unknown", "light")
)
v["total_peers"] = (
v["num_seeds"] + v["num_leeches"] + v["num_peers"]
)
for k in [
"num_seeds",
"num_leeches",
"total_peers",
"num_downloaded",
"num_peers",
]:
if v[k] < 0:
v[k] = (-1, "?")
else:
v[k] = (v[k], v[k])
ret[endpoint] = res
ret["info"] = ret["info"][0]
ret["info"]["state"] = self.status_map.get(
ret["info"]["state"], (ret["info"]["state"], "light")
)
for tracker in ret["trackers"]:
tracker["name"] = urlparse(
tracker["url"]).netloc or tracker["url"]
tracker["has_url"] = bool(urlparse(tracker["url"]).netloc)
return ret
res = self.get("/api/v2/sync/maindata")
if "torrents" in res:
for k, v in res["torrents"].items():
v["hash"] = k
v["speed"] = v["upspeed"] + v["dlspeed"]
dl_rate = v["downloaded"] / max(0, time.time() - v["added_on"])
if dl_rate > 0:
v["eta"] = max(0, (v["size"] - v["downloaded"]) / dl_rate)
else:
v["eta"] = 0
if v["time_active"] == 0:
dl_rate = 0
else:
dl_rate = v["downloaded"] / v["time_active"]
if dl_rate > 0:
v["eta_act"] = max(
0, (v["size"] - v["downloaded"]) / dl_rate)
else:
v["eta_act"] = 0
res["torrents"][k] = v
res["version"] = self.get("/api/v2/app/version")
self.rid = res["rid"]
return res
def status(self, infohash=None):
self.rid += 1
return self.poll(infohash)
def peer_log(self, limit=0):
return self.get("/api/v2/log/peers")[-limit:]
def log(self, limit=0):
return self.get("/api/v2/log/main")[-limit:]
def test(self):
self.poll()
return {}

98
api/radarr.py Normal file
View file

@ -0,0 +1,98 @@
import time
from datetime import datetime, timedelta
from urllib.parse import urljoin
import requests as RQ
from utils import timed_cache
class Radarr(object):
def __init__(self, url, api_key):
self.url = url
self.api_key = api_key
self.root_folder = self.get("api/v3/rootFolder")[0]["path"]
self.quality_profile = self.get("api/v3/qualityprofile")[0]
def get(self, url, **kwargs):
kwargs["apikey"] = self.api_key
kwargs["_"] = str(int(time.time()))
res = RQ.get(urljoin(self.url, url), params=kwargs)
res.raise_for_status()
try:
return res.json()
except Exception:
return res.text
def search(self, query):
return self.get("api/v3/movie/lookup", term=query)
def status(self):
return self.get("api/v3/system/status")
@timed_cache()
def history(self, pageSize=500):
return self.get(
"api/v3/history",
page=1,
pageSize=500,
sortDirection="descending",
sortKey="date",
)
@timed_cache()
def calendar(self, days=90):
today = datetime.today()
start = today - timedelta(days=days)
end = today + timedelta(days=days)
return self.get(
"api/v3/calendar",
unmonitored=False,
start=start.isoformat(),
end=end.isoformat(),
)
@timed_cache()
def movies(self, movie_id=None):
if movie_id is None:
return self.get("api/v3/movie")
return self.get("api/v3/movie/{}".format(movie_id))
@timed_cache(seconds=60)
def queue(self, **kwargs):
data = []
page = 1
while True:
res = self.get("api/v3/queue", page=page, pageSize=100, **kwargs)
data += res.get("records", [])
page += 1
if len(data) >= res.get("totalRecords", 0):
break
return data
def add(self, data):
data["qualityProfileId"] = self.quality_profile["id"]
data["minimumAvailability"] = 2 # InCinema
data["rootFolderPath"] = self.root_folder
data["addOptions"] = {"searchForMovie": True}
params = dict(apikey=self.api_key)
res = RQ.post(
urljoin(
self.url,
"api/v3/movie"),
json=data,
params=params)
return res.json()
def log(self, limit=0):
return self.get(
"api/v3/log",
page=1,
pageSize=(limit or 1024),
sortDirection="descending",
sortKey="time",
)
def test(self):
self.status()
return {}

116
api/sonarr.py Normal file
View file

@ -0,0 +1,116 @@
import time
from urllib.parse import urljoin
from datetime import datetime, timedelta
import requests as RQ
from utils import timed_cache
class Sonarr(object):
def __init__(self, url, api_key):
self.url = url
self.api_key = api_key
self.root_folder = self.get("api/v3/rootFolder")[0]["path"]
self.quality_profile = self.get("api/v3/qualityprofile")[0]
self.language_profile = self.get("api/v3/languageprofile")[0]
def get(self, url, **kwargs):
kwargs["apikey"] = self.api_key
kwargs["_"] = str(int(time.time()))
res = RQ.get(urljoin(self.url, url), params=kwargs)
res.raise_for_status()
try:
return res.json()
except Exception:
return res.text
def search(self, query):
return self.get("api/v3/series/lookup", term=query)
def status(self):
return self.get("api/v3/system/status")
@timed_cache()
def history(self, pageSize=500):
return self.get(
"api/v3/history",
page=1,
pageSize=500,
sortDirection="descending",
sortKey="date",
)
@timed_cache()
def calendar(self, days=30):
today = datetime.today()
start = today - timedelta(days=days)
end = today + timedelta(days=days)
return self.get(
"api/v3/calendar",
unmonitored=False,
start=start.isoformat(),
end=end.isoformat(),
)
@timed_cache()
def series(self, series_id=None, keys=None):
if series_id is None:
return self.get("api/v3/series")
ret = {}
ret["series"] = self.get("api/v3/series/{}".format(series_id))
ret["episodes"] = self.get("api/v3/episode", seriesId=series_id)
ret["episodeFile"] = self.get("api/v3/episodeFile", seriesId=series_id)
ret["queue"] = self.get("api/v3/queue/details", seriesId=series_id)
return ret
@timed_cache(seconds=60)
def queue(self, **kwargs):
data = []
page = 1
while True:
res = self.get("api/v3/queue", page=page, pageSize=100, **kwargs)
data = res.get("records", [])
page += 1
if len(data) >= res.get("totalRecords", 0):
break
return data
@timed_cache()
def details(self, episode_id):
return self.get("api/v3/queue/details", episodeIds=episode_id)
@timed_cache()
def episodes(self, series_id):
return self.get("api/v3/episode", seriesId=series_id)
def add(self, data):
data["qualityProfileId"] = self.quality_profile["id"]
data["languageProfileId"] = self.language_profile["id"]
data["rootFolderPath"] = self.root_folder
data["addOptions"] = {
"ignoreEpisodesWithoutFiles": False,
"ignoreEpisodesWithFiles": True,
"searchForMissingEpisodes": True,
}
data["seasonFolder"] = True
params = dict(apikey=self.api_key)
res = RQ.post(
urljoin(
self.url,
"api/v3/series"),
json=data,
params=params)
return res.json()
def log(self, limit=0):
return self.get(
"api/v3/log",
page=1,
pageSize=(limit or 1024),
sortDirection="descending",
sortKey="time",
)
def test(self):
self.status()
return {}

34
api/user.py Normal file
View file

@ -0,0 +1,34 @@
from flask_login import UserMixin
from api import Jellyfin
from utils import handle_config
class JellyfinUser(UserMixin):
def __init__(self, username, password):
api = Jellyfin(handle_config()["jellyfin_url"], username, password)
self.user = api.user
self.api_key = api.api_key
self.id = self.user["Id"]
api.logout()
def __getitem__(self, key):
return self.user[key]
@property
def is_anonymous(self):
return False
@property
def is_admin(self):
pol = self.user["Policy"]
return pol["IsAdministrator"]
@property
def is_authenticated(self):
return True
@property
def is_active(self):
pol = self.user["Policy"]
return not pol["IsDisabled"]