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:
parent
452b84af5c
commit
410eee4a4d
@ -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
|
||||
|
||||
|
@ -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])]
|
||||
|
146
database.py
146
database.py
@ -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
3
maloja
@ -13,7 +13,8 @@ neededmodules = [
|
||||
"bottle",
|
||||
"waitress",
|
||||
"setproctitle",
|
||||
"doreah"
|
||||
"doreah",
|
||||
"nimrodel"
|
||||
]
|
||||
|
||||
recommendedmodules = [
|
||||
|
@ -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')
|
||||
|
Loading…
Reference in New Issue
Block a user