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

Integrated API server into main server

This commit is contained in:
Krateng 2019-05-12 18:39:46 +02:00
parent 3ba7e4cfef
commit e9cde843e1
4 changed files with 131 additions and 37 deletions

View File

@ -14,6 +14,12 @@ def md5(input):
m.update(bytes(input,encoding="utf-8")) m.update(bytes(input,encoding="utf-8"))
return m.hexdigest() return m.hexdigest()
def generate_key(ls):
key = ""
for i in range(64):
key += str(random.choice(list(range(10)) + list("abcdefghijklmnopqrstuvwxyz") + list("ABCDEFGHIJKLMNOPQRSTUVWXYZ")))
ls.append(key)
return key
#def check_sig(keys): #def check_sig(keys):
# try: # try:
@ -26,15 +32,28 @@ def md5(input):
def handle(path,keys): def handle(path,keys,headers,auth):
# log("API REQUEST") print("API request: " + str(path))
# log(str(path)) print("Keys:")
# for k in keys: for k in keys:
# log(str(k) + ": " + str(keys.getall(k))) 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))
if path[0] == "audioscrobbler": try:
return handle_audioscrobbler(path[1:],keys) if path[0] in ["audioscrobbler","gnukebox","gnufm"]:
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:
response = {"error_message":"Unknown API error"}
print("Response: " + str(response))
return response
# 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 = [] mobile_sessions = []
@ -46,13 +65,18 @@ def handle_audioscrobbler(path,keys):
if keys.get("method") == "auth.getMobileSession": if keys.get("method") == "auth.getMobileSession":
token = keys.get("authToken") token = keys.get("authToken")
user = keys.get("username") user = keys.get("username")
for key in database.allAPIkeys(): password = keys.get("password")
if md5(user + md5(key)) == token: # either username and password
sessionkey = "" if user is not None and password is not None:
for i in range(64): if password in database.allAPIkeys():
sessionkey += str(random.choice(list(range(10)) + list("abcdefghijklmnopqrstuvwxyz") + list("ABCDEFGHIJKLMNOPQRSTUVWXYZ"))) sessionkey = generate_key(mobile_sessions)
mobile_sessions.append(sessionkey)
return {"session":{"key":sessionkey}} return {"session":{"key":sessionkey}}
# or username and token (deprecated by lastfm)
elif user is not None and token is not None:
for key in database.allAPIkeys():
if md5(user + md5(key)) == token:
sessionkey = generate_key(mobile_sessions)
return {"session":{"key":sessionkey}}
return {"error":4} return {"error":4}
@ -77,3 +101,20 @@ def handle_audioscrobbler(path,keys):
return {"scrobbles":{"@attr":{"ignored":0}}} return {"scrobbles":{"@attr":{"ignored":0}}}
return {"error":3} return {"error":3}
else:
return {"error_message":"API version not supported"}
def handle_listenbrainz(path,keys,headers):
if path[0] == "1":
if path[1] == "submit-listens":
if headers.get("Authorization") is not None:
print(headers.get("Authorization"))
return {"wat":"wut"}
else:
return {"error_message":"API version not supported"}

View File

@ -1,6 +1,5 @@
# server # server
from bottle import Bottle, route, get, post, run, template, static_file, request, response, FormsDict from bottle import request, response, FormsDict
import waitress
# rest of the project # rest of the project
from cleanup import * from cleanup import *
from utilities import * from utilities import *
@ -29,8 +28,6 @@ import urllib
dbserver = Bottle()
dblock = Lock() #global database lock dblock = Lock() #global database lock
SCROBBLES = [] # Format: tuple(track_ref,timestamp,saved) SCROBBLES = [] # Format: tuple(track_ref,timestamp,saved)
@ -183,6 +180,47 @@ 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)
# 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.route("/test")
@ -618,15 +656,14 @@ def post_scrobble():
# standard-compliant scrobbling methods # standard-compliant scrobbling methods
@dbserver.post("/s/<path:path>") @dbserver.post("/s/<path:path>")
def sapi(path):
path = path.split("/")
keys = FormsDict.decode(request.forms)
return compliant_api.handle(path,keys)
@dbserver.get("/s/<path:path>") @dbserver.get("/s/<path:path>")
def sapi(path): def sapi(path):
path = path.split("/") path = path.split("/")
keys = FormsDict.decode(request.query) path = list(filter(None,path))
return compliant_api.handle(path,keys) keys = FormsDict.decode(request.params)
headers = request.headers
auth = request.auth
return compliant_api.handle(path,keys,headers,auth)
@ -806,17 +843,14 @@ def search():
# Starts the server # Starts the server
def runserver(PORT): def start_db():
log("Starting database server...") log("Starting database...")
global lastsync global lastsync
lastsync = int(datetime.datetime.now(tz=datetime.timezone.utc).timestamp()) lastsync = int(datetime.datetime.now(tz=datetime.timezone.utc).timestamp())
build_db() build_db()
loadAPIkeys() loadAPIkeys()
#run(dbserver, host='::', port=PORT, server='waitress')
run(dbserver, host='::', port=PORT, server='waitress') log("Database reachable!")
log("Database server reachable!")
def build_db(): def build_db():

View File

@ -28,7 +28,7 @@ from urllib.error import *
#settings.config(files=["settings/default.ini","settings/settings.ini"]) #settings.config(files=["settings/default.ini","settings/settings.ini"])
#settings.update("settings/default.ini","settings/settings.ini") #settings.update("settings/default.ini","settings/settings.ini")
MAIN_PORT, DATABASE_PORT = settings.get_settings("WEB_PORT","API_PORT") MAIN_PORT = settings.get_settings("WEB_PORT")
webserver = Bottle() webserver = Bottle()
@ -68,10 +68,19 @@ def customerror(error):
return html return html
#@webserver.get("/api/<pth:path>")
#def api(pth):
# return database.handle_get(pth,request)
#@webserver.post("/api/<pth:path>")
#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 # 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; } # e.g. location /db { rewrite ^/db(.*)$ $1 break; proxy_pass http://yoururl:12349; }
@webserver.get("/api/<pth:path>") #@webserver.get("/api/<pth:path>")
def database_get(pth): def database_get(pth):
keys = FormsDict.decode(request.query) # The Dal★Shabet handler keys = FormsDict.decode(request.query) # The Dal★Shabet handler
keystring = "?" keystring = "?"
@ -88,13 +97,22 @@ def database_get(pth):
response.status = e.code response.status = e.code
return return
@webserver.post("/api/<pth:path>") #@webserver.post("/api/<pth:path>")
def database_post(pth): def database_post(pth):
response.set_header("Access-Control-Allow-Origin","*") #print(request.headers)
#response.set_header("Access-Control-Allow-Origin","*")
try: try:
proxyresponse = urllib.request.urlopen("http://[::1]:" + str(DATABASE_PORT) + "/" + pth,request.body) 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() contents = proxyresponse.read()
response.status = proxyresponse.getcode() response.status = proxyresponse.getcode()
response.headers = proxyresponse.headers
response.content_type = "application/json" response.content_type = "application/json"
return contents return contents
except HTTPError as e: except HTTPError as e:
@ -215,7 +233,9 @@ setproctitle.setproctitle("Maloja")
## start database server ## start database server
#_thread.start_new_thread(SourceFileLoader("database","database.py").load_module().runserver,(DATABASE_PORT,)) #_thread.start_new_thread(SourceFileLoader("database","database.py").load_module().runserver,(DATABASE_PORT,))
_thread.start_new_thread(database.runserver,(DATABASE_PORT,)) #_thread.start_new_thread(database.runserver,(DATABASE_PORT,))
database.start_db()
database.register_subroutes(webserver,"/api")
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')

View File

@ -1,7 +1,6 @@
[HTTP] [HTTP]
WEB_PORT = 42010 WEB_PORT = 42010
API_PORT = 42011
[Third Party Services] [Third Party Services]