diff options
Diffstat (limited to 'overlays/abs-podcast-autoplaylist/abs_podcast_autoplaylist/__main__.py')
-rw-r--r-- | overlays/abs-podcast-autoplaylist/abs_podcast_autoplaylist/__main__.py | 107 |
1 files changed, 107 insertions, 0 deletions
diff --git a/overlays/abs-podcast-autoplaylist/abs_podcast_autoplaylist/__main__.py b/overlays/abs-podcast-autoplaylist/abs_podcast_autoplaylist/__main__.py new file mode 100644 index 00000000..fd739805 --- /dev/null +++ b/overlays/abs-podcast-autoplaylist/abs_podcast_autoplaylist/__main__.py | |||
@@ -0,0 +1,107 @@ | |||
1 | import click | ||
2 | from pathlib import Path | ||
3 | import tomllib | ||
4 | import requests | ||
5 | from urllib.parse import urljoin | ||
6 | from operator import itemgetter | ||
7 | import re | ||
8 | from frozendict import frozendict | ||
9 | |||
10 | class BearerAuth(requests.auth.AuthBase): | ||
11 | def __init__(self, token): | ||
12 | self.token = token | ||
13 | def __call__(self, r): | ||
14 | r.headers["authorization"] = "Bearer " + self.token | ||
15 | return r | ||
16 | |||
17 | class ABSSession(requests.Session): | ||
18 | def __init__(self, config): | ||
19 | super().__init__() | ||
20 | self.base_url = config['instance'] | ||
21 | self.auth = BearerAuth(config['api_token']) | ||
22 | |||
23 | def request(self, method, url, *args, **kwargs): | ||
24 | joined_url = urljoin(self.base_url, url) | ||
25 | return super().request(method, joined_url, *args, **kwargs) | ||
26 | |||
27 | @click.command() | ||
28 | @click.argument('config_file', type=click.Path(dir_okay=False, path_type=Path)) | ||
29 | def main(config_file: Path): | ||
30 | with config_file.open('rb') as fh: | ||
31 | config = tomllib.load(fh) | ||
32 | |||
33 | with ABSSession(config) as s: | ||
34 | libraries = s.get('/api/libraries').json()['libraries'] | ||
35 | playlists = s.get('/api/playlists').json()['playlists'] | ||
36 | |||
37 | for library_config in config['libraries']: | ||
38 | [library] = filter(lambda l: l['name'] == library_config['name'], libraries) | ||
39 | filtered_playlists = list(filter(lambda p: p['name'] == library_config['playlist'] and p['libraryId'] == library['id'], playlists)) | ||
40 | def get_playlist(): | ||
41 | playlist = None | ||
42 | if filtered_playlists: | ||
43 | [playlist] = filtered_playlists | ||
44 | if not playlist: | ||
45 | playlist = s.post('/api/playlists', json={ | ||
46 | 'libraryId': library['id'], | ||
47 | 'name': library_config['playlist'], | ||
48 | }).json() | ||
49 | return playlist | ||
50 | |||
51 | podcasts = dict() | ||
52 | items = s.get('/api/libraries/{}/items'.format(library['id'])).json()['results'] | ||
53 | for item in items: | ||
54 | item = s.get('/api/items/{}'.format(item['id']), json={'expanded': True}).json() | ||
55 | episodes = list() | ||
56 | for episode in sorted(item['media']['episodes'], key = itemgetter('publishedAt')): | ||
57 | progress = s.get('/api/me/progress/{}/{}'.format(episode['libraryItemId'], episode['id'])) | ||
58 | if progress.ok and progress.json()["isFinished"]: | ||
59 | continue | ||
60 | episodes.append(episode) | ||
61 | podcasts[item['media']['metadata']['title']] = list(map(lambda x: frozendict({ 'libraryItemId': x['libraryItemId'], 'episodeId': x['id']}), episodes)) | ||
62 | def lookup_podcast(expr): | ||
63 | expr = re.compile(expr, flags=re.I) | ||
64 | matches = filter(lambda t: expr.search(t), podcasts.keys()) | ||
65 | match list(matches): | ||
66 | case [x]: | ||
67 | return (x,) | ||
68 | case _: | ||
69 | raise RuntimeError("No unique match for ‘{}’".format(expr)) | ||
70 | |||
71 | priorities = [ | ||
72 | [ | ||
73 | k | ||
74 | for item in (section if type(section) is list else [section]) | ||
75 | for k in lookup_podcast(item) | ||
76 | ] | ||
77 | for section in library_config['priorities'] | ||
78 | ] | ||
79 | |||
80 | playlist_items = list() | ||
81 | for section in priorities: | ||
82 | while any(map(lambda item: item in podcasts, section)): | ||
83 | for item in section: | ||
84 | if not item in podcasts: | ||
85 | continue | ||
86 | |||
87 | if not podcasts[item]: | ||
88 | del podcasts[item] | ||
89 | continue | ||
90 | |||
91 | playlist_items.append(podcasts[item].pop(0)) | ||
92 | |||
93 | playlist = get_playlist() | ||
94 | current_playlist_items = map(lambda item: frozendict({ k: v for k, v in item.items() if k in {'libraryItemId', 'episodeId'}}), playlist['items']) | ||
95 | |||
96 | if current_playlist_items == playlist_items: | ||
97 | continue | ||
98 | |||
99 | to_remove = set(current_playlist_items) - set(playlist_items) | ||
100 | if to_remove: | ||
101 | s.post('/api/playlists/{}/batch/remove'.format(playlist['id']), json={'items': list(to_remove)}).raise_for_status() | ||
102 | playlist = get_playlist() | ||
103 | to_add = set(playlist_items) - set(current_playlist_items) | ||
104 | if to_add: | ||
105 | s.post('/api/playlists/{}/batch/add'.format(playlist['id']), json={'items': list(to_add)}).raise_for_status() | ||
106 | |||
107 | r = s.patch('/api/playlists/{}'.format(playlist['id']), json={'items': playlist_items}).raise_for_status() | ||