Switched to using nimrodel API

This commit is contained in:
Krateng 2019-05-23 13:13:42 +02:00
parent 452b84af5c
commit 410eee4a4d
5 changed files with 57 additions and 109 deletions

View File

@ -20,6 +20,7 @@ Also neat: You can use your **custom artist or track images**.
* [bottle.py](https://bottlepy.org/) - [GitHub](https://github.com/bottlepy/bottle)
* [waitress](https://docs.pylonsproject.org/projects/waitress/) - [GitHub](https://github.com/Pylons/waitress)
* [doreah](https://pypi.org/project/doreah/) - [GitHub](https://github.com/krateng/doreah) (at least Version 0.7.2)
* [nimrodel](https://pypi.org/project/nimrodel/) - [GitHub](https://github.com/krateng/nimrodel) (at least Version 0.4.4)
* If you'd like to display images, you will need API keys for [Last.fm](https://www.last.fm/api/account/create) and [Fanart.tv](https://fanart.tv/get-an-api-key/). These are free of charge!
## How to install
@ -58,13 +59,15 @@ If you didn't install Maloja from the package (and therefore don't have it in `/
3) Various folders have `.info` files with more information on how to use their associated features.
4) If you'd like to implement anything on top of Maloja, visit `/api_explorer`.
## How to scrobble
### Native API
If you use Plex Web or Youtube Music on Chromium, you can use the included extension (also available on the [Chrome Web Store](https://chrome.google.com/webstore/detail/maloja-scrobbler/cfnbifdmgbnaalphodcbandoopgbfeeh)). Make sure to enter the random key Maloja generates on first startup in the extension settings.
If you want to implement your own method of scrobbling, it's very simple: You only need one POST request to `/api/newscrobble` with the keys `artist`, `title` and `key`.
If you want to implement your own method of scrobbling, it's very simple: You only need one POST request to `/api/newscrobble` with the keys `artist`, `title` and `key` - either as from-data or json.
### Standard-compliant API

View File

@ -43,17 +43,12 @@ def handler(apiname,version):
return cls
return deco
def handle(path,keys,headers,auth):
def handle(path,keys):
print("API request: " + str(path))
print("Keys:")
for k in keys:
print("\t" + str(k) + ": " + str(keys.get(k)))
print("Headers:")
for h in headers:
print("\t" + str(h) + ": " + str(headers.get(h)))
print("Auth: " + str(auth))
print("\t",k,":",keys.get(k))
keys = {**keys,**headers}
if len(path)>1 and (path[0],path[1]) in handlers:
handler = handlers[(path[0],path[1])]

View File

@ -14,6 +14,9 @@ try:
from doreah.persistence import DiskDict
except: pass
import doreah
# nimrodel API
from nimrodel import EAPI as API
from nimrodel import Multi
# technical
import os
import datetime
@ -182,50 +185,10 @@ def getTrackID(artists,title):
########
# silly patch to get old syntax working without dbserver
# function to register all the functions to the real server
def register_subroutes(server,path):
for subpath in dbserver.handlers_get:
func = dbserver.handlers_get[subpath]
decorator = server.get(path + subpath)
decorator(func)
for subpath in dbserver.handlers_post:
func = dbserver.handlers_post[subpath]
decorator = server.post(path + subpath)
decorator(func)
dbserver = API(delay=True,path="api")
# fake server
class FakeBottle:
def __init__(self):
self.handlers_get = {}
self.handlers_post = {}
# these functions pretend that they're the bottle decorators, but only write
# down which functions asked for them so they can later report their names
# to the real bottle server
def get(self,path):
def register(func):
self.handlers_get[path] = func
return func
return register
def post(self,path):
def register(func):
self.handlers_post[path] = func
return func
return register
def route(self,path):
return self.get(path)
dbserver = FakeBottle()
@dbserver.route("/test")
@dbserver.get("test")
def test_server():
apikey = request.query.get("key")
response.set_header("Access-Control-Allow-Origin","*")
@ -247,9 +210,8 @@ def test_server():
## All database functions are separated - the external wrapper only reads the request keys, converts them into lists and renames them where necessary, and puts the end result in a dict if not already so it can be returned as json
@dbserver.route("/scrobbles")
def get_scrobbles_external():
keys = FormsDict.decode(request.query)
@dbserver.get("scrobbles")
def get_scrobbles_external(**keys):
k_filter, k_time, _, k_amount = uri_to_internal(keys)
ckeys = {**k_filter, **k_time, **k_amount}
@ -277,9 +239,8 @@ def get_scrobbles(**keys):
# return {"scrobbles":len(SCROBBLES),"tracks":len(TRACKS),"artists":len(ARTISTS)}
@dbserver.route("/numscrobbles")
def get_scrobbles_num_external():
keys = FormsDict.decode(request.query)
@dbserver.get("numscrobbles")
def get_scrobbles_num_external(**keys):
k_filter, k_time, _, k_amount = uri_to_internal(keys)
ckeys = {**k_filter, **k_time, **k_amount}
@ -342,9 +303,8 @@ def get_scrobbles_num(**keys):
@dbserver.route("/tracks")
def get_tracks_external():
keys = FormsDict.decode(request.query)
@dbserver.get("tracks")
def get_tracks_external(**keys):
k_filter, _, _, _ = uri_to_internal(keys,forceArtist=True)
ckeys = {**k_filter}
@ -366,7 +326,7 @@ def get_tracks(artist=None):
#ls = [t for t in tracklist if (artist in t["artists"]) or (artist==None)]
@dbserver.route("/artists")
@dbserver.get("artists")
def get_artists_external():
result = get_artists()
return {"list":result}
@ -380,9 +340,8 @@ def get_artists():
@dbserver.route("/charts/artists")
def get_charts_artists_external():
keys = FormsDict.decode(request.query)
@dbserver.get("charts/artists")
def get_charts_artists_external(**keys):
_, k_time, _, _ = uri_to_internal(keys)
ckeys = {**k_time}
@ -397,9 +356,8 @@ def get_charts_artists(**keys):
@dbserver.route("/charts/tracks")
def get_charts_tracks_external():
keys = FormsDict.decode(request.query)
@dbserver.get("charts/tracks")
def get_charts_tracks_external(**keys):
k_filter, k_time, _, _ = uri_to_internal(keys,forceArtist=True)
ckeys = {**k_filter, **k_time}
@ -417,9 +375,8 @@ def get_charts_tracks(**keys):
@dbserver.route("/pulse")
def get_pulse_external():
keys = FormsDict.decode(request.query)
@dbserver.get("pulse")
def get_pulse_external(**keys):
k_filter, k_time, k_internal, k_amount = uri_to_internal(keys)
ckeys = {**k_filter, **k_time, **k_internal, **k_amount}
@ -440,9 +397,8 @@ def get_pulse(**keys):
@dbserver.route("/performance")
def get_performance_external():
keys = FormsDict.decode(request.query)
@dbserver.get("performance")
def get_performance_external(**keys):
k_filter, k_time, k_internal, k_amount = uri_to_internal(keys)
ckeys = {**k_filter, **k_time, **k_internal, **k_amount}
@ -480,7 +436,7 @@ def get_performance(**keys):
@dbserver.route("/top/artists")
@dbserver.get("top/artists")
def get_top_artists_external():
keys = FormsDict.decode(request.query)
@ -513,9 +469,8 @@ def get_top_artists(**keys):
@dbserver.route("/top/tracks")
def get_top_tracks_external():
keys = FormsDict.decode(request.query)
@dbserver.get("top/tracks")
def get_top_tracks_external(**keys):
_, k_time, k_internal, _ = uri_to_internal(keys)
ckeys = {**k_time, **k_internal}
@ -548,9 +503,8 @@ def get_top_tracks(**keys):
@dbserver.route("/artistinfo")
def artistInfo_external():
keys = FormsDict.decode(request.query)
@dbserver.get("artistinfo")
def artistInfo_external(**keys):
k_filter, _, _, _ = uri_to_internal(keys,forceArtist=True)
ckeys = {**k_filter}
@ -577,9 +531,8 @@ def artistInfo(artist):
@dbserver.route("/trackinfo")
def trackInfo_external():
keys = FormsDict.decode(request.query)
@dbserver.get("trackinfo")
def trackInfo_external(**keys):
k_filter, _, _, _ = uri_to_internal(keys,forceTrack=True)
ckeys = {**k_filter}
@ -601,9 +554,8 @@ def trackInfo(artists,title):
@dbserver.get("/newscrobble")
def pseudo_post_scrobble():
keys = FormsDict.decode(request.query) # The Dal★Shabet handler
@dbserver.get("newscrobble")
def pseudo_post_scrobble(**keys):
artists = keys.get("artist")
title = keys.get("title")
apikey = keys.get("key")
@ -626,9 +578,8 @@ def pseudo_post_scrobble():
return {"status":"success","track":trackdict}
@dbserver.post("/newscrobble")
def post_scrobble():
keys = FormsDict.decode(request.forms) # The Dal★Shabet handler
@dbserver.post("newscrobble")
def post_scrobble(**keys):
artists = keys.get("artist")
title = keys.get("title")
apikey = keys.get("key")
@ -659,28 +610,26 @@ def post_scrobble():
# standard-compliant scrobbling methods
@dbserver.post("/s/<path:path>")
@dbserver.get("/s/<path:path>")
def sapi(path):
path = path.split("/")
@dbserver.post("s/{path}")
@dbserver.get("s/{path}")
def sapi(path:Multi,**keys):
"""Scrobbles according to a standardized protocol.
:param string path: Path according to the scrobble protocol
:param string keys: Query keys according to the scrobble protocol
"""
path = list(filter(None,path))
headers = request.headers
if request.get_header("Content-Type") is not None and "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)
return compliant_api.handle(path,keys)
@dbserver.route("/sync")
@dbserver.get("sync")
def abouttoshutdown():
sync()
#sys.exit()
@dbserver.post("/newrule")
@dbserver.post("newrule")
def newrule():
keys = FormsDict.decode(request.forms)
apikey = keys.pop("key",None)
@ -691,7 +640,7 @@ def newrule():
db_rulestate = False
@dbserver.route("/issues")
@dbserver.get("issues")
def issues_external(): #probably not even needed
return issues()
@ -787,7 +736,7 @@ def issues():
return {"duplicates":duplicates,"combined":combined,"newartists":newartists,"inconsistent":inconsistent}
@dbserver.post("/importrules")
@dbserver.post("importrules")
def import_rulemodule():
keys = FormsDict.decode(request.forms)
apikey = keys.pop("key",None)
@ -807,7 +756,7 @@ def import_rulemodule():
@dbserver.post("/rebuild")
@dbserver.post("rebuild")
def rebuild():
keys = FormsDict.decode(request.forms)
@ -827,9 +776,8 @@ def rebuild():
@dbserver.get("/search")
def search():
keys = FormsDict.decode(request.query)
@dbserver.get("search")
def search(**keys):
query = keys.get("query")
max_ = keys.get("max")
if max_ is not None: max_ = int(max_)

3
maloja
View File

@ -13,7 +13,8 @@ neededmodules = [
"bottle",
"waitress",
"setproctitle",
"doreah"
"doreah",
"nimrodel"
]
recommendedmodules = [

View File

@ -178,7 +178,8 @@ setproctitle.setproctitle("Maloja")
## start database
database.start_db()
database.register_subroutes(webserver,"/api")
#database.register_subroutes(webserver,"/api")
database.dbserver.mount(server=webserver)
log("Starting up Maloja server...")
run(webserver, host='::', port=MAIN_PORT, server='waitress')