1
0
mirror of https://github.com/krateng/maloja.git synced 2023-08-10 21:12:55 +03:00

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) * [bottle.py](https://bottlepy.org/) - [GitHub](https://github.com/bottlepy/bottle)
* [waitress](https://docs.pylonsproject.org/projects/waitress/) - [GitHub](https://github.com/Pylons/waitress) * [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) * [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! * 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 ## 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. 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 ## How to scrobble
### Native API ### 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 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 ### Standard-compliant API

View File

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

View File

@ -14,6 +14,9 @@ try:
from doreah.persistence import DiskDict from doreah.persistence import DiskDict
except: pass except: pass
import doreah import doreah
# nimrodel API
from nimrodel import EAPI as API
from nimrodel import Multi
# technical # technical
import os import os
import datetime import datetime
@ -182,50 +185,10 @@ def getTrackID(artists,title):
######## ########
# silly patch to get old syntax working without dbserver dbserver = API(delay=True,path="api")
# 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)
# fake server @dbserver.get("test")
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")
def test_server(): def test_server():
apikey = request.query.get("key") apikey = request.query.get("key")
response.set_header("Access-Control-Allow-Origin","*") 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 ## 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") @dbserver.get("scrobbles")
def get_scrobbles_external(): def get_scrobbles_external(**keys):
keys = FormsDict.decode(request.query)
k_filter, k_time, _, k_amount = uri_to_internal(keys) k_filter, k_time, _, k_amount = uri_to_internal(keys)
ckeys = {**k_filter, **k_time, **k_amount} 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)} # return {"scrobbles":len(SCROBBLES),"tracks":len(TRACKS),"artists":len(ARTISTS)}
@dbserver.route("/numscrobbles") @dbserver.get("numscrobbles")
def get_scrobbles_num_external(): def get_scrobbles_num_external(**keys):
keys = FormsDict.decode(request.query)
k_filter, k_time, _, k_amount = uri_to_internal(keys) k_filter, k_time, _, k_amount = uri_to_internal(keys)
ckeys = {**k_filter, **k_time, **k_amount} ckeys = {**k_filter, **k_time, **k_amount}
@ -342,9 +303,8 @@ def get_scrobbles_num(**keys):
@dbserver.route("/tracks") @dbserver.get("tracks")
def get_tracks_external(): def get_tracks_external(**keys):
keys = FormsDict.decode(request.query)
k_filter, _, _, _ = uri_to_internal(keys,forceArtist=True) k_filter, _, _, _ = uri_to_internal(keys,forceArtist=True)
ckeys = {**k_filter} 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)] #ls = [t for t in tracklist if (artist in t["artists"]) or (artist==None)]
@dbserver.route("/artists") @dbserver.get("artists")
def get_artists_external(): def get_artists_external():
result = get_artists() result = get_artists()
return {"list":result} return {"list":result}
@ -380,9 +340,8 @@ def get_artists():
@dbserver.route("/charts/artists") @dbserver.get("charts/artists")
def get_charts_artists_external(): def get_charts_artists_external(**keys):
keys = FormsDict.decode(request.query)
_, k_time, _, _ = uri_to_internal(keys) _, k_time, _, _ = uri_to_internal(keys)
ckeys = {**k_time} ckeys = {**k_time}
@ -397,9 +356,8 @@ def get_charts_artists(**keys):
@dbserver.route("/charts/tracks") @dbserver.get("charts/tracks")
def get_charts_tracks_external(): def get_charts_tracks_external(**keys):
keys = FormsDict.decode(request.query)
k_filter, k_time, _, _ = uri_to_internal(keys,forceArtist=True) k_filter, k_time, _, _ = uri_to_internal(keys,forceArtist=True)
ckeys = {**k_filter, **k_time} ckeys = {**k_filter, **k_time}
@ -417,9 +375,8 @@ def get_charts_tracks(**keys):
@dbserver.route("/pulse") @dbserver.get("pulse")
def get_pulse_external(): def get_pulse_external(**keys):
keys = FormsDict.decode(request.query)
k_filter, k_time, k_internal, k_amount = uri_to_internal(keys) k_filter, k_time, k_internal, k_amount = uri_to_internal(keys)
ckeys = {**k_filter, **k_time, **k_internal, **k_amount} ckeys = {**k_filter, **k_time, **k_internal, **k_amount}
@ -440,9 +397,8 @@ def get_pulse(**keys):
@dbserver.route("/performance") @dbserver.get("performance")
def get_performance_external(): def get_performance_external(**keys):
keys = FormsDict.decode(request.query)
k_filter, k_time, k_internal, k_amount = uri_to_internal(keys) k_filter, k_time, k_internal, k_amount = uri_to_internal(keys)
ckeys = {**k_filter, **k_time, **k_internal, **k_amount} 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(): def get_top_artists_external():
keys = FormsDict.decode(request.query) keys = FormsDict.decode(request.query)
@ -513,9 +469,8 @@ def get_top_artists(**keys):
@dbserver.route("/top/tracks") @dbserver.get("top/tracks")
def get_top_tracks_external(): def get_top_tracks_external(**keys):
keys = FormsDict.decode(request.query)
_, k_time, k_internal, _ = uri_to_internal(keys) _, k_time, k_internal, _ = uri_to_internal(keys)
ckeys = {**k_time, **k_internal} ckeys = {**k_time, **k_internal}
@ -548,9 +503,8 @@ def get_top_tracks(**keys):
@dbserver.route("/artistinfo") @dbserver.get("artistinfo")
def artistInfo_external(): def artistInfo_external(**keys):
keys = FormsDict.decode(request.query)
k_filter, _, _, _ = uri_to_internal(keys,forceArtist=True) k_filter, _, _, _ = uri_to_internal(keys,forceArtist=True)
ckeys = {**k_filter} ckeys = {**k_filter}
@ -577,9 +531,8 @@ def artistInfo(artist):
@dbserver.route("/trackinfo") @dbserver.get("trackinfo")
def trackInfo_external(): def trackInfo_external(**keys):
keys = FormsDict.decode(request.query)
k_filter, _, _, _ = uri_to_internal(keys,forceTrack=True) k_filter, _, _, _ = uri_to_internal(keys,forceTrack=True)
ckeys = {**k_filter} ckeys = {**k_filter}
@ -601,9 +554,8 @@ def trackInfo(artists,title):
@dbserver.get("/newscrobble") @dbserver.get("newscrobble")
def pseudo_post_scrobble(): def pseudo_post_scrobble(**keys):
keys = FormsDict.decode(request.query) # The Dal★Shabet handler
artists = keys.get("artist") artists = keys.get("artist")
title = keys.get("title") title = keys.get("title")
apikey = keys.get("key") apikey = keys.get("key")
@ -626,9 +578,8 @@ def pseudo_post_scrobble():
return {"status":"success","track":trackdict} return {"status":"success","track":trackdict}
@dbserver.post("/newscrobble") @dbserver.post("newscrobble")
def post_scrobble(): def post_scrobble(**keys):
keys = FormsDict.decode(request.forms) # The Dal★Shabet handler
artists = keys.get("artist") artists = keys.get("artist")
title = keys.get("title") title = keys.get("title")
apikey = keys.get("key") apikey = keys.get("key")
@ -659,28 +610,26 @@ def post_scrobble():
# standard-compliant scrobbling methods # standard-compliant scrobbling methods
@dbserver.post("/s/<path:path>") @dbserver.post("s/{path}")
@dbserver.get("/s/<path:path>") @dbserver.get("s/{path}")
def sapi(path): def sapi(path:Multi,**keys):
path = path.split("/") """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)) path = list(filter(None,path))
headers = request.headers return compliant_api.handle(path,keys)
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)
@dbserver.route("/sync") @dbserver.get("sync")
def abouttoshutdown(): def abouttoshutdown():
sync() sync()
#sys.exit() #sys.exit()
@dbserver.post("/newrule") @dbserver.post("newrule")
def newrule(): def newrule():
keys = FormsDict.decode(request.forms) keys = FormsDict.decode(request.forms)
apikey = keys.pop("key",None) apikey = keys.pop("key",None)
@ -691,7 +640,7 @@ def newrule():
db_rulestate = False db_rulestate = False
@dbserver.route("/issues") @dbserver.get("issues")
def issues_external(): #probably not even needed def issues_external(): #probably not even needed
return issues() return issues()
@ -787,7 +736,7 @@ def issues():
return {"duplicates":duplicates,"combined":combined,"newartists":newartists,"inconsistent":inconsistent} return {"duplicates":duplicates,"combined":combined,"newartists":newartists,"inconsistent":inconsistent}
@dbserver.post("/importrules") @dbserver.post("importrules")
def import_rulemodule(): def import_rulemodule():
keys = FormsDict.decode(request.forms) keys = FormsDict.decode(request.forms)
apikey = keys.pop("key",None) apikey = keys.pop("key",None)
@ -807,7 +756,7 @@ def import_rulemodule():
@dbserver.post("/rebuild") @dbserver.post("rebuild")
def rebuild(): def rebuild():
keys = FormsDict.decode(request.forms) keys = FormsDict.decode(request.forms)
@ -827,9 +776,8 @@ def rebuild():
@dbserver.get("/search") @dbserver.get("search")
def search(): def search(**keys):
keys = FormsDict.decode(request.query)
query = keys.get("query") query = keys.get("query")
max_ = keys.get("max") max_ = keys.get("max")
if max_ is not None: max_ = int(max_) if max_ is not None: max_ = int(max_)

3
maloja
View File

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

View File

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