diff --git a/README.md b/README.md index 2a90549..7acec3f 100644 --- a/README.md +++ b/README.md @@ -61,12 +61,24 @@ If you didn't install Maloja from the package (and therefore don't have it in `/ ## How to scrobble +### Native API + If you use Plex Web or Youtube Music on Chromium, you can use the included extension. Make sure to enter the random key Maloja generates on first startup in the extension settings. -You can use any third-party scrobbler that supports the audioscrobbler protocol (GNUFM). This is still very experimental, but give it a try with these settings: - - Gnukebox URL: Your Maloja URL followed by `/api/s/audioscrobbler` - Username: Any name, doesn't matter - Password: Any of your API keys (you can define new ones in `clients/authenticated_machines` in your Maloja folder) - If you want to implement your own method of scrobbling, it's very simple: You only need one POST request with the keys `artist`, `title` and `key`. + +### Standard-compliant API + +You can use any third-party scrobbler that supports the audioscrobbler (GNUFM) or the ListenBrainz protocol. This is still very experimental, but give it a try with these settings: + +GNU FM |   +------ | --------- +Gnukebox URL | Your Maloja URL followed by `/api/s/audioscrobbler` +Username | Any name, doesn't matter +Password | Any of your API keys (you can define new ones in `clients/authenticated_machines` in your Maloja folder) + +ListenBrainz |   +------ | --------- +API URL | Your Maloja URL followed by `api/s/listenbrainz` +Username | Any name, doesn't matter +Auth Token | Any of your API keys (you can define new ones in `clients/authenticated_machines` in your Maloja folder) diff --git a/compliant_api.py b/compliant_api.py index 88e826a..576c434 100644 --- a/compliant_api.py +++ b/compliant_api.py @@ -2,7 +2,9 @@ from doreah.logging import log import hashlib import random import database +import datetime from cleanup import CleanerAgent +from bottle import response ## GNU-FM-compliant scrobbling @@ -51,6 +53,7 @@ def handle(path,keys,headers,auth): response = {"error_message":"Invalid scrobble protocol"} except: response = {"error_message":"Unknown API error"} + raise print("Response: " + str(response)) return response @@ -113,8 +116,32 @@ def handle_listenbrainz(path,keys,headers): if path[1] == "submit-listens": if headers.get("Authorization") is not None: - print(headers.get("Authorization")) - return {"wat":"wut"} + token = headers.get("Authorization").replace("token ","").strip() + if token in database.allAPIkeys(): + if "payload" in keys: + if keys["listen_type"] in ["single","import"]: + for listen in keys["payload"]: + metadata = listen["track_metadata"] + artiststr, titlestr = metadata["artist_name"], metadata["track_name"] + (artists,title) = cla.fullclean(artiststr,titlestr) + try: + timestamp = int(listen["listened_at"]) + except: + timestamp = int(datetime.datetime.now(tz=datetime.timezone.utc).timestamp()) + database.createScrobble(artists,title,timestamp) + return {"code":200,"status":"ok"} + else: + response.status = 400 + return {"code":400,"error":"Invalid JSON document submitted."} + + else: + return {"error":"Bad Auth"} + + else: + return {"code":401,"error":"You need to provide an Authorization header."} + + else: + return {"error_message":"Invalid API method"} else: return {"error_message":"API version not supported"} diff --git a/database.py b/database.py index e324aa3..cb21a89 100644 --- a/database.py +++ b/database.py @@ -660,8 +660,11 @@ def post_scrobble(): def sapi(path): path = path.split("/") path = list(filter(None,path)) - keys = FormsDict.decode(request.params) headers = request.headers + if "application/json" in request.get_header("Content-Type"): + keys = request.json + else: + keys = FormsDict.decode(request.params) auth = request.auth return compliant_api.handle(path,keys,headers,auth) diff --git a/server.py b/server.py index c5e771e..c0e8ee4 100755 --- a/server.py +++ b/server.py @@ -69,59 +69,6 @@ def customerror(error): -#@webserver.get("/api/") -#def api(pth): -# return database.handle_get(pth,request) - -#@webserver.post("/api/") -#def api_post(pth): -# return database.handle_post(pth,request) - -# this is the fallback option. If you run this service behind a reverse proxy, it is recommended to rewrite /db/ requests to the port of the db server -# e.g. location /db { rewrite ^/db(.*)$ $1 break; proxy_pass http://yoururl:12349; } - -#@webserver.get("/api/") -def database_get(pth): - keys = FormsDict.decode(request.query) # The Dal★Shabet handler - keystring = "?" - for k in keys: - keystring += urllib.parse.quote(k) + "=" + urllib.parse.quote(keys[k]) + "&" - response.set_header("Access-Control-Allow-Origin","*") - try: - proxyresponse = urllib.request.urlopen("http://[::1]:" + str(DATABASE_PORT) + "/" + pth + keystring) - contents = proxyresponse.read() - response.status = proxyresponse.getcode() - response.content_type = "application/json" - return contents - except HTTPError as e: - response.status = e.code - return - -#@webserver.post("/api/") -def database_post(pth): - #print(request.headers) - #response.set_header("Access-Control-Allow-Origin","*") - try: - proxyrequest = urllib.request.Request( - url="http://[::1]:" + str(DATABASE_PORT) + "/" + pth, - data=request.body, - headers=request.headers, - method="POST" - ) - proxyresponse = urllib.request.urlopen(proxyrequest) - - contents = proxyresponse.read() - response.status = proxyresponse.getcode() - response.headers = proxyresponse.headers - response.content_type = "application/json" - return contents - except HTTPError as e: - response.status = e.code - return - - return - - def graceful_exit(sig=None,frame=None): #urllib.request.urlopen("http://[::1]:" + str(DATABASE_PORT) + "/sync") database.sync() @@ -231,9 +178,7 @@ signal.signal(signal.SIGTERM, graceful_exit) #rename process, this is now required for the daemon manager to work setproctitle.setproctitle("Maloja") -## start database server -#_thread.start_new_thread(SourceFileLoader("database","database.py").load_module().runserver,(DATABASE_PORT,)) -#_thread.start_new_thread(database.runserver,(DATABASE_PORT,)) +## start database database.start_db() database.register_subroutes(webserver,"/api") diff --git a/website/setup.html b/website/setup.html index d33240e..ede03c9 100644 --- a/website/setup.html +++ b/website/setup.html @@ -76,7 +76,7 @@ If you use Vivaldi, Brave, Iridium or any other Chromium-based browser and listen to music on Plex or YouTube Music, download the extension and simply enter the server URL as well as your API key in the relevant fields. They will turn green if the server is accessible.

- You can also use any GNUFM-compliant scrobbler. Enter yourserver.tld/api/s/audioscrobbler as your Gnukebox server and your API key as the password. + You can also use any standard-compliant scrobbler. For GNUFM (audioscrobbler) scrobblers, enter yourserver.tld/api/s/audioscrobbler as your Gnukebox server and your API key as the password. For Listenbrainz scrobblers, use yourserver.tld/api/s/listenbrainz as the API URL and your API key as token.

If you use another browser or another music player, you could try to code your own extension. The API is super simple! Just send a POST HTTP request to