summaryrefslogtreecommitdiff
path: root/overlays/abs-podcast-autoplaylist/abs_podcast_autoplaylist
diff options
context:
space:
mode:
authorGregor Kleen <gkleen@yggdrasil.li>2025-05-10 12:53:58 +0200
committerGregor Kleen <gkleen@yggdrasil.li>2025-05-10 12:53:58 +0200
commit81cc664d4250189c9026edfb042e24c6806448ee (patch)
tree710579dbd9633a33179a8f373ad6fa1b7808c600 /overlays/abs-podcast-autoplaylist/abs_podcast_autoplaylist
parent83e0c7df55234bc5e7c2c918ceb587786af6d155 (diff)
downloadnixos-81cc664d4250189c9026edfb042e24c6806448ee.tar
nixos-81cc664d4250189c9026edfb042e24c6806448ee.tar.gz
nixos-81cc664d4250189c9026edfb042e24c6806448ee.tar.bz2
nixos-81cc664d4250189c9026edfb042e24c6806448ee.tar.xz
nixos-81cc664d4250189c9026edfb042e24c6806448ee.zip
abs-podcast-autoplaylist
Diffstat (limited to 'overlays/abs-podcast-autoplaylist/abs_podcast_autoplaylist')
-rw-r--r--overlays/abs-podcast-autoplaylist/abs_podcast_autoplaylist/__init__.py0
-rw-r--r--overlays/abs-podcast-autoplaylist/abs_podcast_autoplaylist/__main__.py107
2 files changed, 107 insertions, 0 deletions
diff --git a/overlays/abs-podcast-autoplaylist/abs_podcast_autoplaylist/__init__.py b/overlays/abs-podcast-autoplaylist/abs_podcast_autoplaylist/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/overlays/abs-podcast-autoplaylist/abs_podcast_autoplaylist/__init__.py
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 @@
1import click
2from pathlib import Path
3import tomllib
4import requests
5from urllib.parse import urljoin
6from operator import itemgetter
7import re
8from frozendict import frozendict
9
10class 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
17class 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))
29def 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()