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

Reorganized compliant APIs somewhat, possibly for the worse

This commit is contained in:
Krateng 2019-05-14 17:18:28 +02:00
parent 3c6cfc81c5
commit 53778ce644
2 changed files with 151 additions and 94 deletions

View File

@ -3,6 +3,8 @@ import hashlib
import random import random
import database import database
import datetime import datetime
import itertools
import sys
from cleanup import CleanerAgent from cleanup import CleanerAgent
from bottle import response from bottle import response
@ -33,6 +35,13 @@ def generate_key(ls):
# return False # return False
handlers = {}
def handler(apiname,version):
def deco(cls):
handlers[(apiname,version)] = cls()
return cls
return deco
def handle(path,keys,headers,auth): def handle(path,keys,headers,auth):
print("API request: " + str(path)) print("API request: " + str(path))
@ -44,56 +53,100 @@ def handle(path,keys,headers,auth):
print("\t" + str(h) + ": " + str(headers.get(h))) print("\t" + str(h) + ": " + str(headers.get(h)))
print("Auth: " + str(auth)) print("Auth: " + str(auth))
keys = {**keys,**headers}
if len(path)>1 and (path[0],path[1]) in handlers:
handler = handlers[(path[0],path[1])]
path = path[2:]
try: try:
if path[0] in ["audioscrobbler","gnukebox","gnufm"]: response.status,result = handler.handle(path,keys)
response = handle_audioscrobbler(path[1:],keys)
elif path[0] in ["listenbrainz","lbrnz"]:
response = handle_listenbrainz(path[1:],keys,headers)
else:
response = {"error_message":"Invalid scrobble protocol"}
except: except:
response = {"error_message":"Unknown API error"} type = sys.exc_info()[0]
raise response.status,result = handler.errors[type]
else:
result = {"error":"Invalid scrobble protocol"}
response.status = 500
print("Response: " + str(response))
return response
print("Response: " + str(result))
return result
class BadAuthException(Exception): pass
class InvalidAuthException(Exception): pass
class InvalidMethodException(Exception): pass
class InvalidSessionKey(Exception): pass
class MalformedJSONException(Exception): pass
class APIHandler:
_instance = None
def __new__(cls, *args, **kwargs):
if not isinstance(cls._instance, cls):
cls._instance = object.__new__(cls, *args, **kwargs)
return cls._instance
def handle(self,pathnodes,keys):
try:
methodname = self.get_method(pathnodes,keys)
method = self.methods[methodname]
except:
raise InvalidMethodException()
return method(pathnodes,keys)
@handler("audioscrobbler","2.0")
@handler("gnufm","2.0")
@handler("gnukebox","2.0")
class GNUFM2(APIHandler):
def __init__(self):
# no need to save these on disk, clients can always request a new session # no need to save these on disk, clients can always request a new session
mobile_sessions = [] self.mobile_sessions = []
self.methods = {
"auth.getMobileSession":self.authmobile,
"track.scrobble":self.scrobble
}
self.errors = {
BadAuthException:(400,{"error":6,"message":"Requires authentication"}),
InvalidAuthException:(401,{"error":4,"message":"Invalid credentials"}),
InvalidMethodException:(200,{"error":3,"message":"Invalid method"}),
InvalidSessionKey:(403,{"error":9,"message":"Invalid session key"})
}
def handle_audioscrobbler(path,keys): def get_method(self,pathnodes,keys):
return keys.get("method")
if path[0] == "2.0": def authmobile(self,pathnodes,keys):
if keys.get("method") == "auth.getMobileSession":
token = keys.get("authToken") token = keys.get("authToken")
user = keys.get("username") user = keys.get("username")
password = keys.get("password") password = keys.get("password")
# either username and password # either username and password
if user is not None and password is not None: if user is not None and password is not None:
if password in database.allAPIkeys(): if password in database.allAPIkeys():
sessionkey = generate_key(mobile_sessions) sessionkey = generate_key(self.mobile_sessions)
return {"session":{"key":sessionkey}} return 200,{"session":{"key":sessionkey}}
else:
raise InvalidAuthException()
# or username and token (deprecated by lastfm) # or username and token (deprecated by lastfm)
elif user is not None and token is not None: elif user is not None and token is not None:
for key in database.allAPIkeys(): for key in database.allAPIkeys():
if md5(user + md5(key)) == token: if md5(user + md5(key)) == token:
sessionkey = generate_key(mobile_sessions) sessionkey = generate_key(self.mobile_sessions)
return {"session":{"key":sessionkey}} return 200,{"session":{"key":sessionkey}}
return {"error":4} raise InvalidAuthException()
elif keys.get("method") == "track.scrobble":
if keys.get("sk") is None or keys.get("sk") not in mobile_sessions:
return {"error":9}
else: else:
raise BadAuthException()
def scrobble(self,pathnodes,keys):
if keys.get("sk") is None or keys.get("sk") not in self.mobile_sessions:
raise InvalidSessionKey()
else:
if "track" in keys and "artist" in keys: if "track" in keys and "artist" in keys:
artiststr,titlestr = keys["artist"], keys["track"] artiststr,titlestr = keys["artist"], keys["track"]
(artists,title) = cla.fullclean(artiststr,titlestr) (artists,title) = cla.fullclean(artiststr,titlestr)
timestamp = int(keys["timestamp"]) timestamp = int(keys["timestamp"])
database.createScrobble(artists,title,timestamp) database.createScrobble(artists,title,timestamp)
return {"scrobbles":{"@attr":{"ignored":0}}} return 200,{"scrobbles":{"@attr":{"ignored":0}}}
else: else:
for num in range(50): for num in range(50):
if "track[" + str(num) + "]" in keys: if "track[" + str(num) + "]" in keys:
@ -101,26 +154,41 @@ def handle_audioscrobbler(path,keys):
(artists,title) = cla.fullclean(artiststr,titlestr) (artists,title) = cla.fullclean(artiststr,titlestr)
timestamp = int(keys["timestamp[" + str(num) + "]"]) timestamp = int(keys["timestamp[" + str(num) + "]"])
database.createScrobble(artists,title,timestamp) database.createScrobble(artists,title,timestamp)
return {"scrobbles":{"@attr":{"ignored":0}}} return 200,{"scrobbles":{"@attr":{"ignored":0}}}
return {"error":3}
else:
return {"error_message":"API version not supported"}
def handle_listenbrainz(path,keys,headers):
if path[0] == "1": @handler("listenbrainz","1")
@handler("lbrnz","1")
class LBrnz1(APIHandler):
def __init__(self):
self.methods = {
"submit-listens":self.submit
}
self.errors = {
BadAuthException:(401,{"code":401,"error":"You need to provide an Authorization header."}),
InvalidAuthException:(401,{"code":401,"error":"Bad Auth"}),
InvalidMethodException:(200,{"code":200,"error":"Invalid Method"}),
MalformedJSONException:(400,{"code":400,"error":"Invalid JSON document submitted."})
}
if path[1] == "submit-listens": def get_method(self,pathnodes,keys):
print(pathnodes)
return pathnodes.pop(0)
if headers.get("Authorization") is not None: def submit(self,pathnodes,keys):
token = headers.get("Authorization").replace("token ","").strip() try:
if token in database.allAPIkeys(): token = keys.get("Authorization").replace("token ","").strip()
if "payload" in keys: except:
raise BadAuthException()
if token not in database.allAPIkeys():
raise InvalidAuthException()
try:
if keys["listen_type"] in ["single","import"]: if keys["listen_type"] in ["single","import"]:
for listen in keys["payload"]: payload = keys["payload"]
for listen in payload:
metadata = listen["track_metadata"] metadata = listen["track_metadata"]
artiststr, titlestr = metadata["artist_name"], metadata["track_name"] artiststr, titlestr = metadata["artist_name"], metadata["track_name"]
(artists,title) = cla.fullclean(artiststr,titlestr) (artists,title) = cla.fullclean(artiststr,titlestr)
@ -129,19 +197,8 @@ def handle_listenbrainz(path,keys,headers):
except: except:
timestamp = int(datetime.datetime.now(tz=datetime.timezone.utc).timestamp()) timestamp = int(datetime.datetime.now(tz=datetime.timezone.utc).timestamp())
database.createScrobble(artists,title,timestamp) database.createScrobble(artists,title,timestamp)
return {"code":200,"status":"ok"} return 200,{"code":200,"status":"ok"}
else: else:
response.status = 400 return 200,{"code":200,"status":"ok"}
return {"code":400,"error":"Invalid JSON document submitted."} except:
raise MalformedJSONException()
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"}

View File

@ -664,7 +664,7 @@ def sapi(path):
path = path.split("/") path = path.split("/")
path = list(filter(None,path)) path = list(filter(None,path))
headers = request.headers headers = request.headers
if "application/json" in request.get_header("Content-Type"): if request.get_header("Content-Type") is not None and "application/json" in request.get_header("Content-Type"):
keys = request.json keys = request.json
else: else:
keys = FormsDict.decode(request.params) keys = FormsDict.decode(request.params)