quickstream

Find stream URIs for common streaming services.
git clone https://git.ce9e.org/quickstream.git

commit
7c3d8594bbf68f494385eba5b71fdeb69a360531
parent
0f1ee7b0f1aa0c8d4fba341b9f69ed12db584de8
Author
Tobias Bengfort <tobias.bengfort@posteo.de>
Date
2026-03-27 10:29
add mixcloud provider

Diffstat

M quickstream/__init__.py 8 ++++++++
A quickstream/providers/mixcloud.py 48 ++++++++++++++++++++++++++++++++++++++++++++++++

2 files changed, 56 insertions, 0 deletions


diff --git a/quickstream/__init__.py b/quickstream/__init__.py

@@ -7,6 +7,7 @@ from bs4 import BeautifulSoup
    7     7 
    8     8 from .base import registry
    9     9 from .providers import bandcamp  # noqa
   -1    10 from .providers import mixcloud  # noqa
   10    11 from .providers import soundcloud  # noqa
   11    12 
   12    13 
@@ -26,6 +27,13 @@ class Client:
   26    27         r = await self.session.get(url, **kwargs)
   27    28         return await r.json()
   28    29 
   -1    30     async def graphql(self, url, query, **kwargs):
   -1    31         r = await self.session.post(url, json={
   -1    32             'query': query,
   -1    33             'variables': kwargs,
   -1    34         })
   -1    35         return await r.json()
   -1    36 
   29    37 
   30    38 async def extract(url):
   31    39     for pattern, fn in registry:

diff --git a/quickstream/providers/mixcloud.py b/quickstream/providers/mixcloud.py

@@ -0,0 +1,48 @@
   -1     1 import base64
   -1     2 import itertools
   -1     3 import json
   -1     4 
   -1     5 from ..base import provider
   -1     6 
   -1     7 DECRYPTION_KEY = b'IFYOUWANTTHEARTISTSTOGETPAIDDONOTDOWNLOADFROMMIXCLOUD'
   -1     8 QUERY = """
   -1     9 query cloudcastQuery($lookup: CloudcastLookup!) {
   -1    10     cloudcast: cloudcastLookup(lookup: $lookup) {
   -1    11         name
   -1    12         audioLength
   -1    13         streamInfo { url }
   -1    14     }
   -1    15 }
   -1    16 """
   -1    17 
   -1    18 
   -1    19 def decrypt(text):
   -1    20     raw = base64.b64decode(text)
   -1    21     pairs = zip(raw, itertools.cycle(DECRYPTION_KEY), strict=False)
   -1    22     result = bytes([x ^ y for x, y in pairs])
   -1    23     return result.decode('utf-8')
   -1    24 
   -1    25 
   -1    26 @provider(
   -1    27     r'https?://www.mixcloud\.com/([^/]+)/(?!stream|uploads|favorites|listens|playlists)([^/]+)/',  # noqa
   -1    28     tests={
   -1    29         'http://www.mixcloud.com/dholbach/cryptkeeper/': {
   -1    30             'id': 'dholbach_cryptkeeper',
   -1    31             'title': 'Cryptkeeper',
   -1    32             'duration': 3723,
   -1    33         },
   -1    34     },
   -1    35 )
   -1    36 async def mixcloud_track(client, url, username, slug):
   -1    37     data = await client.graphql('https://app.mixcloud.com/graphql', QUERY, lookup={
   -1    38         'username': username,
   -1    39         'slug': slug,
   -1    40     })
   -1    41     trackinfo = data['data']['cloudcast']
   -1    42 
   -1    43     return {
   -1    44         'id': f'{username}_{slug}',
   -1    45         'title': trackinfo['name'],
   -1    46         'duration': trackinfo['audioLength'],
   -1    47         'stream': decrypt(trackinfo['streamInfo']['url']),
   -1    48     }