2026-02-06 23:24:41 +03:00
|
|
|
import atexit
|
|
|
|
|
import re
|
|
|
|
|
from dataclasses import dataclass
|
|
|
|
|
from hashlib import md5
|
|
|
|
|
from json import dumps
|
|
|
|
|
from time import time
|
|
|
|
|
from urllib.parse import parse_qs, urlparse
|
|
|
|
|
|
2026-01-22 23:55:58 +03:00
|
|
|
from apscheduler.schedulers.background import BackgroundScheduler
|
|
|
|
|
from apscheduler.triggers.interval import IntervalTrigger
|
|
|
|
|
import requests
|
2026-02-06 23:24:41 +03:00
|
|
|
from flask import Flask, jsonify
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
class Cache:
|
|
|
|
|
data = {}
|
|
|
|
|
last_updated = time()
|
|
|
|
|
status = None
|
2026-01-22 23:55:58 +03:00
|
|
|
|
2026-02-06 23:24:41 +03:00
|
|
|
data = {
|
|
|
|
|
"caches": {
|
|
|
|
|
"now": Cache(),
|
|
|
|
|
"listens": Cache()
|
|
|
|
|
},
|
|
|
|
|
"last_updated": time(),
|
|
|
|
|
"etag": ""
|
|
|
|
|
}
|
2026-01-22 23:55:58 +03:00
|
|
|
|
|
|
|
|
def yt_cover(youtube_url):
|
|
|
|
|
parsed_url = urlparse(youtube_url)
|
|
|
|
|
|
|
|
|
|
if parsed_url.netloc in ("youtube.com", "music.youtube.com"):
|
|
|
|
|
query_params = parse_qs(parsed_url.query)
|
|
|
|
|
video_id = query_params.get('v', [None])[0]
|
|
|
|
|
|
|
|
|
|
elif parsed_url.netloc == 'youtu.be':
|
|
|
|
|
video_id = parsed_url.path[1:]
|
|
|
|
|
|
|
|
|
|
if not video_id:
|
|
|
|
|
return
|
|
|
|
|
|
2026-02-06 23:24:41 +03:00
|
|
|
return f"https://img.youtube.com/vi/{video_id}/sddefault.jpg"
|
2026-01-22 23:55:58 +03:00
|
|
|
|
2026-02-06 23:24:41 +03:00
|
|
|
def parse_listens(json: dict) -> dict:
|
|
|
|
|
new_json = {
|
|
|
|
|
"count": json["count"],
|
2026-01-22 23:55:58 +03:00
|
|
|
"listens": []
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-06 23:24:41 +03:00
|
|
|
for track in json["listens"]:
|
2026-01-22 23:55:58 +03:00
|
|
|
track = track["track_metadata"]
|
|
|
|
|
|
|
|
|
|
new_track = {
|
|
|
|
|
"artist_name": track["artist_name"],
|
|
|
|
|
"track_name": track["track_name"]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if mb := track.get("mbid_mapping"):
|
|
|
|
|
new_track["id"] = mb.get("caa_release_mbid", mb["release_mbid"])
|
|
|
|
|
new_track["artist_name"] = mb["artists"][0]["artist_credit_name"]
|
|
|
|
|
new_track["track_name"] = mb["recording_name"]
|
|
|
|
|
elif info := track.get("additional_info"):
|
|
|
|
|
if info \
|
|
|
|
|
.get("music_service_name", "") \
|
|
|
|
|
.lower() in ("youtube", "youtube music"):
|
|
|
|
|
if cover := yt_cover(track["additional_info"]["origin_url"]):
|
|
|
|
|
new_track["cover_url"] = cover
|
|
|
|
|
|
|
|
|
|
if "cover_url" not in new_track.keys() and "id" in new_track.keys():
|
|
|
|
|
new_track["cover_url"] = "/asset/mb/" + new_track["id"]
|
|
|
|
|
|
2026-02-06 23:24:41 +03:00
|
|
|
new_json["listens"].append(new_track)
|
2026-01-22 23:55:58 +03:00
|
|
|
|
2026-02-06 23:24:41 +03:00
|
|
|
return new_json
|
2026-01-22 23:55:58 +03:00
|
|
|
|
2026-02-06 23:24:41 +03:00
|
|
|
def api_request(url: str, cache: Cache):
|
2026-01-22 23:55:58 +03:00
|
|
|
try:
|
|
|
|
|
response = requests.get(url, timeout=10)
|
|
|
|
|
if response.status_code == 200:
|
2026-02-06 23:24:41 +03:00
|
|
|
json = parse_listens(response.json().get("payload"))
|
|
|
|
|
cache.status = 'success'
|
|
|
|
|
|
|
|
|
|
if cache.data != json:
|
|
|
|
|
cache.data = json
|
|
|
|
|
cache.last_updated = time()
|
|
|
|
|
data['last_updated'] = time()
|
|
|
|
|
data['etag'] = md5(''.join(
|
|
|
|
|
( dumps(data['caches'][x].data) for x in data['caches'] )
|
|
|
|
|
).encode()).hexdigest()
|
2026-01-22 23:55:58 +03:00
|
|
|
else:
|
2026-02-06 23:24:41 +03:00
|
|
|
cache.status = f'error: {response.status_code}'
|
2026-01-22 23:55:58 +03:00
|
|
|
except Exception as e:
|
2026-02-06 23:24:41 +03:00
|
|
|
cache.status = f'error: {str(e)}'
|
2026-01-22 23:55:58 +03:00
|
|
|
|
|
|
|
|
scheduler = BackgroundScheduler()
|
|
|
|
|
scheduler.add_job(
|
2026-02-06 23:24:41 +03:00
|
|
|
func=lambda: api_request("https://api.listenbrainz.org/1/user/risdeveau/listens?count=5", data['caches']['listens']),
|
2026-01-22 23:55:58 +03:00
|
|
|
trigger=IntervalTrigger(minutes=1),
|
|
|
|
|
id='risdeveau.listenbrainz.listens',
|
|
|
|
|
replace_existing=True
|
|
|
|
|
)
|
|
|
|
|
scheduler.add_job(
|
2026-02-06 23:24:41 +03:00
|
|
|
func=lambda: api_request("https://api.listenbrainz.org/1/user/risdeveau/playing-now", data['caches']['now']),
|
2026-01-22 23:55:58 +03:00
|
|
|
trigger=IntervalTrigger(seconds=15),
|
|
|
|
|
id='risdeveau.listenbrainz.playing-now',
|
|
|
|
|
replace_existing=True
|
|
|
|
|
)
|
|
|
|
|
scheduler.start()
|
|
|
|
|
|
|
|
|
|
atexit.register(lambda: scheduler.shutdown())
|