audiothek

alternative frontend for ardaudiothek.de  https://audiothek.ce9e.org/
git clone https://git.ce9e.org/audiothek.git

commit
c8a04d20618ef09522df7743c696cee216975a0d
parent
476189072ab3657f035c71d3c501488ba3c06112
Author
Tobias Bengfort <tobias.bengfort@posteo.de>
Date
2024-02-13 18:15
refactor xml rendering

Diffstat

M audiothek.py 137 +++++++++++++++++++++++++++++++++----------------------------

1 files changed, 75 insertions, 62 deletions


diff --git a/audiothek.py b/audiothek.py

@@ -1,6 +1,7 @@
    1     1 import argparse
    2     2 import json
    3     3 from xml.sax.saxutils import escape
   -1     4 from xml.sax.saxutils import quoteattr
    4     5 
    5     6 import aiohttp
    6     7 from aiohttp import web
@@ -32,43 +33,77 @@ query ProgramSetEpisodesQuery($id:ID!, $offset:Int!, $count:Int!) {
   32    33 }
   33    34 """.replace('    ', '').replace('\n', ' ').strip()
   34    35 
   35    -1 FEED = """<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
   36    -1     <channel>
   37    -1         <title>{title}</title>
   38    -1         <link>{url}</link>
   39    -1         <image>
   40    -1             <url>{img_url}</url>
   41    -1             <width>{img_size}</width>
   42    -1             <height>{img_size}</height>
   43    -1         </image>
   44    -1 
   45    -1 {entries}
   46    -1     </channel>
   47    -1 </rss>"""
   48    -1 
   49    -1 LINK = '<atom:link href="{href}" rel="{rel}" type="application/rss+xml"/>'
   50    -1 
   51    -1 ENTRY = """        <item>
   52    -1             <title>{title}</title>
   53    -1             <link>{url}</link>
   54    -1             <guid>{url}</guid>
   55    -1             <enclosure url="{downloadUrl}" type="audio/mpeg"/>
   56    -1             <pubDate>{updated}</pubDate>
   57    -1             <description>{summary}</description>
   58    -1             <itunes:duration>{duration}</itunes:duration>
   59    -1         </item>"""
   60    -1 
   61    -1 
   62    -1 def escape_data(data):
   63    -1     return {
   64    -1         key: escape(value) for key, value in data.items()
   65    -1     }
   66    -1 
   67    36 
   68    -1 def render_feed(show, episodes, links):
   69    -1     entries = [ENTRY.format(**escape_data(ep)) for ep in reversed(episodes)]
   70    -1     entries += [LINK.format(**escape_data(link)) for link in links]
   71    -1     return FEED.format(entries='\n'.join(entries), **escape_data(show))
   -1    37 def el(tag, content=None, attrs={}):
   -1    38     a =  ''.join(f' {k}={quoteattr(v)}' for k, v in attrs.items())
   -1    39     if content is None:
   -1    40         return f'<{tag}{a} />'
   -1    41     else:
   -1    42         return f'<{tag}{a}>{escape(content)}</{tag}>'
   -1    43 
   -1    44 
   -1    45 def indent(lines):
   -1    46     return [f'\t{line}' for line in lines]
   -1    47 
   -1    48 
   -1    49 def render_items(items):
   -1    50     lines = []
   -1    51     for item in reversed(items):
   -1    52         lines += [
   -1    53             '<item>',
   -1    54             *indent([
   -1    55                 el('title', item['title']),
   -1    56                 el('link', 'https://www.ardaudiothek.de' + item['path']),
   -1    57                 el('guid', 'https://www.ardaudiothek.de' + item['path']),
   -1    58                 el('enclosure', attrs={
   -1    59                     'url': item['audios'][0]['url'],
   -1    60                     'type': 'audio/mpeg',
   -1    61                 }),
   -1    62                 el('pubDate', item['publishDate']),
   -1    63                 el('description', item['summary']),
   -1    64                 el('itunes:duration', str(item['duration'])),
   -1    65             ]),
   -1    66             '</item>',
   -1    67         ]
   -1    68     return lines
   -1    69 
   -1    70 
   -1    71 def render_feed(show, links):
   -1    72     feed = [
   -1    73         '<?xml version="1.0" encoding="UTF-8"?>',
   -1    74         (
   -1    75             '<rss version="2.0" '
   -1    76             'xmlns:atom="http://www.w3.org/2005/Atom" '
   -1    77             'xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">'
   -1    78         ),
   -1    79         *indent([
   -1    80             '<channel>',
   -1    81             *indent([
   -1    82                 el('title', show['title']),
   -1    83                 el('link', 'https://www.ardaudiothek.de' + show['path']),
   -1    84                 '<image>',
   -1    85                 *indent([
   -1    86                     el('url', show['image']['url1X1'].replace('{width}', '64')),
   -1    87                     el('width', '64'),
   -1    88                     el('height', '64'),
   -1    89                 ]),
   -1    90                 '</image>',
   -1    91                 *render_items(show['items']['nodes']),
   -1    92                 *[
   -1    93                     el('atom:link', attrs={
   -1    94                         'href': href,
   -1    95                         'rel': rel,
   -1    96                         'type': 'application/rss+xml',
   -1    97                     })
   -1    98                     for rel, href in links.items()
   -1    99                 ],
   -1   100             ]),
   -1   101             '</channel>',
   -1   102         ]),
   -1   103         '</rss>',
   -1   104     ]
   -1   105 
   -1   106     return ''.join(f'{line}\n' for line in feed)
   72   107 
   73   108 
   74   109 async def feed(request):
@@ -96,35 +131,13 @@ async def feed(request):
   96   131     if show is None:
   97   132         raise web.HTTPNotFound
   98   133 
   99    -1     show_data = {
  100    -1         'title': show['title'],
  101    -1         'url': 'https://www.ardaudiothek.de' + show['path'],
  102    -1         'img_url': show['image']['url1X1'].replace('{width}', '64'),
  103    -1         'img_size': '64',
  104    -1     }
  105    -1 
  106    -1     episode_data = [{
  107    -1         'title': node['title'],
  108    -1         'url': 'https://www.ardaudiothek.de' + node['path'],
  109    -1         'summary': node['summary'],
  110    -1         'updated': node['publishDate'],
  111    -1         'downloadUrl': node['audios'][0]['url'],  # FIXME: more than one
  112    -1         'duration': str(node['duration']),
  113    -1     } for node in show['items']['nodes']]
  114    -1 
  115    -1     links = []
   -1   134     links = {}
  116   135     if ORIGIN:
  117    -1         links.append({
  118    -1             'href': f'{ORIGIN}{request.url.path_qs}',
  119    -1             'rel': 'self',
  120    -1         })
   -1   136         links['self'] = f'{ORIGIN}{request.url.path_qs}'
  121   137         if show['items']['pageInfo']['hasNextPage']:
  122    -1             links.append({
  123    -1                 'href': f'{ORIGIN}{request.url.path}?offset={offset + COUNT}',
  124    -1                 'rel': 'next',
  125    -1             })
   -1   138             links['next'] = f'{ORIGIN}{request.url.path}?offset={offset + COUNT}'
  126   139 
  127    -1     body = render_feed(show_data, episode_data, links)
   -1   140     body = render_feed(show, links)
  128   141 
  129   142     return web.Response(
  130   143         body=body.encode('utf8'),