Skip to content

Commit 8136e35

Browse files
committed
lastfm integration
1 parent 78224d1 commit 8136e35

4 files changed

Lines changed: 125 additions & 0 deletions

File tree

app/api/plugins/__init__.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
from flask_openapi3 import APIBlueprint
33
from pydantic import BaseModel, Field
44
from app.api.auth import admin_required
5+
from app.config import UserConfig
56
from app.db.userdata import PluginTable
7+
from app.plugins.lastfm import LastFmPlugin
8+
from app.utils.auth import get_current_userid
69

710
bp_tag = Tag(name="Plugins", description="Manage plugins")
811
api = APIBlueprint("plugins", __name__, url_prefix="/plugins", abp_tags=[bp_tag])
@@ -61,3 +64,40 @@ def update_plugin_settings(body: PluginSettingsBody):
6164
plugin = PluginTable.get_by_name(plugin)
6265

6366
return {"status": "success", "settings": plugin.settings}
67+
68+
69+
class LastFmSessionBody(BaseModel):
70+
token: str = Field(description="The token to use to create the session")
71+
72+
73+
@api.post("/lastfm/session/create")
74+
def create_lastfm_session(body: LastFmSessionBody):
75+
"""
76+
Create a Last.fm session
77+
"""
78+
if not body.token:
79+
return {"error": "Missing token"}, 400
80+
81+
lastfm = LastFmPlugin()
82+
session_key = lastfm.get_session_key(body.token)
83+
84+
if session_key:
85+
config = UserConfig()
86+
current_user = get_current_userid()
87+
config.lastfmSessionKeys[str(current_user)] = session_key
88+
config.lastfmSessionKeys = config.lastfmSessionKeys
89+
90+
return {"status": "success", "session_key": session_key}
91+
92+
93+
@api.post("/lastfm/session/delete")
94+
def delete_lastfm_session():
95+
"""
96+
Delete the Last.fm session
97+
"""
98+
config = UserConfig()
99+
current_user = get_current_userid()
100+
config.lastfmSessionKeys[str(current_user)] = ""
101+
config.lastfmSessionKeys = config.lastfmSessionKeys
102+
103+
return {"status": "success"}

app/api/scrobble/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from app.models.album import Album
1414
from app.models.stats import StatItem
1515
from app.models.track import Track
16+
from app.plugins.lastfm import LastFmPlugin
1617
from app.serializers.artist import serialize_for_card
1718
from app.serializers.album import serialize_for_card as serialize_for_album_card
1819
from app.serializers.track import serialize_track, serialize_tracks
@@ -97,6 +98,11 @@ def log_track(body: LogTrackBody):
9798
if track:
9899
track.increment_playcount(duration, timestamp)
99100

101+
lastfm = LastFmPlugin()
102+
103+
if lastfm.enabled:
104+
lastfm.scrobble(trackentry.tracks[0], timestamp)
105+
100106
return {"msg": "recorded"}, 201
101107

102108

app/config.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ class UserConfig:
4848

4949
# plugins
5050
enablePlugins: bool = True
51+
lastfmApiKey: str = "5e5306fbf3e8e3bc92f039b6c6c4bd4e"
52+
lastfmApiSecret: str = "0553005e93f9a4b4819d835182181806"
53+
lastfmSessionKeys: dict[str, str] = field(default_factory=dict)
5154

5255
def __post_init__(self):
5356
"""

app/plugins/lastfm.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import requests
2+
from typing import Any
3+
from hashlib import md5
4+
from urllib.parse import quote_plus
5+
6+
from app.config import UserConfig
7+
from app.models.track import Track
8+
from app.utils.auth import get_current_userid
9+
from app.utils.threading import background
10+
from app.plugins import Plugin, plugin_method
11+
12+
13+
class LastFmPlugin(Plugin):
14+
def __init__(self):
15+
self.config = UserConfig()
16+
super().__init__("lastfm", "Last.fm scrobbler")
17+
self.set_active(
18+
bool(
19+
self.config.lastfmApiKey
20+
and self.config.lastfmApiSecret
21+
and self.config.lastfmSessionKeys.get(str(get_current_userid()))
22+
)
23+
)
24+
25+
def get_api_signature(self, data: dict[str, Any]) -> str:
26+
params = {k: v for k, v in data.items()}
27+
28+
signature = "".join(f"{k}{v}" for k, v in sorted(params.items()))
29+
signature += self.config.lastfmApiSecret
30+
31+
return md5(signature.encode("utf-8")).hexdigest()
32+
33+
def post(self, data: dict[str, Any], useSessionKey: bool = True):
34+
url = "http://ws.audioscrobbler.com/2.0/?format=json"
35+
data["api_key"] = self.config.lastfmApiKey
36+
if useSessionKey:
37+
data["sk"] = self.config.lastfmSessionKeys.get(str(get_current_userid()))
38+
39+
data["api_sig"] = self.get_api_signature(data)
40+
41+
final_url = (
42+
url + "&" + "&".join(f"{k}={quote_plus(str(v))}" for k, v in data.items())
43+
)
44+
45+
return requests.post(final_url)
46+
47+
def get_session_key(self, token: str):
48+
data = {
49+
"method": "auth.getSession",
50+
"token": token,
51+
}
52+
53+
try:
54+
res = self.post(data, useSessionKey=False)
55+
return res.json()["session"]["key"]
56+
except Exception as e:
57+
print("get_session_key error", e)
58+
return None
59+
60+
@plugin_method
61+
@background
62+
def scrobble(self, track: Track, timestamp: int):
63+
print("Last.fm: logging track: ", track.title, "-", track.artists[0]["name"])
64+
data = {
65+
"method": "track.scrobble",
66+
"artist": track.artists[0]["name"],
67+
"track": track.title,
68+
"timestamp": timestamp,
69+
"album": track.album,
70+
"albumArtist": track.albumartists[0]["name"],
71+
}
72+
73+
try:
74+
self.post(data)
75+
except Exception as e:
76+
print("scrobble error", e)

0 commit comments

Comments
 (0)