maloja/maloja/apis/native_v1.py

362 lines
9.1 KiB
Python
Raw Normal View History

2020-09-02 16:22:30 +03:00
from ..database import *
from ..globalconf import malojaconfig, apikeystore
2021-12-26 23:36:36 +03:00
from ..__pkginfo__ import VERSION
2020-09-02 16:22:30 +03:00
from ..malojauri import uri_to_internal
from .. import utilities
2022-02-14 08:39:18 +03:00
from ._apikeys import api_key_correct, checkAPIkey
2020-09-02 16:22:30 +03:00
2021-12-26 22:53:38 +03:00
from bottle import response, static_file
2020-09-02 16:22:30 +03:00
# nimrodel API
from nimrodel import EAPI as API
from nimrodel import Multi
api = API(delay=True)
2020-09-04 01:08:24 +03:00
api.__apipath__ = "mlj_1"
2020-09-02 16:22:30 +03:00
2020-09-02 16:22:30 +03:00
@api.get("test")
def test_server(key=None):
2020-10-09 18:40:12 +03:00
"""Pings the server. If an API key is supplied, the server will respond with 200
if the key is correct and 403 if it isn't. If no key is supplied, the server will
always respond with 200.
:param string key: An API key to be tested. Optional.
"""
2020-09-02 16:22:30 +03:00
response.set_header("Access-Control-Allow-Origin","*")
if key is not None and not (checkAPIkey(key)):
response.status = 403
return {"error":"Wrong API key"}
else:
response.status = 200
return {"status":"ok"}
@api.get("serverinfo")
def server_info():
response.set_header("Access-Control-Allow-Origin","*")
response.set_header("Content-Type","application/json")
return {
2021-12-19 23:10:55 +03:00
"name":malojaconfig["NAME"],
"version":VERSION.split("."),
"versionstring":VERSION,
"db_status":dbstatus
2020-09-02 16:22:30 +03:00
}
## API ENDPOINTS THAT CLOSELY MATCH ONE DATABASE FUNCTION
@api.get("scrobbles")
def get_scrobbles_external(**keys):
k_filter, k_time, _, k_amount, _ = uri_to_internal(keys,api=True)
2020-09-02 16:22:30 +03:00
ckeys = {**k_filter, **k_time, **k_amount}
result = get_scrobbles(**ckeys)
2021-01-12 20:47:18 +03:00
offset = (k_amount.get('page') * k_amount.get('perpage')) if k_amount.get('perpage') is not math.inf else 0
result = result[offset:]
if k_amount.get('perpage') is not math.inf: result = result[:k_amount.get('perpage')]
2021-12-14 23:19:15 +03:00
2020-09-02 16:22:30 +03:00
return {"list":result}
# info for comparison
@api.get("info")
def info_external(**keys):
response.set_header("Access-Control-Allow-Origin","*")
response.set_header("Content-Type","application/json")
Refactoring (#83) * Merge isinstance calls * Inline variable that is immediately returned * Replace set() with comprehension * Replace assignment with augmented assignment * Remove unnecessary else after guard condition * Convert for loop into list comprehension * Replace unused for index with underscore * Merge nested if conditions * Convert for loop into list comprehension * Convert for loop into set comprehension * Remove unnecessary else after guard condition * Replace if statements with if expressions * Simplify sequence comparison * Replace multiple comparisons with in operator * Merge isinstance calls * Merge nested if conditions * Add guard clause * Merge duplicate blocks in conditional * Replace unneeded comprehension with generator * Inline variable that is immediately returned * Remove unused imports * Replace unneeded comprehension with generator * Remove unused imports * Remove unused import * Inline variable that is immediately returned * Swap if/else branches and remove unnecessary else * Use str.join() instead of for loop * Multiple refactors - Remove redundant pass statement - Hoist repeated code outside conditional statement - Swap if/else to remove empty if body * Inline variable that is immediately returned * Simplify generator expression * Replace if statement with if expression * Multiple refactoring - Replace range(0, x) with range(x) - Swap if/else branches - Remove unnecessary else after guard condition * Use str.join() instead of for loop * Hoist repeated code outside conditional statement * Use str.join() instead of for loop * Inline variables that are immediately returned * Merge dictionary assignment with declaration * Use items() to directly unpack dictionary values * Extract dup code from methods into a new one
2021-10-19 15:58:24 +03:00
return info()
2020-09-02 16:22:30 +03:00
@api.get("numscrobbles")
def get_scrobbles_num_external(**keys):
2020-09-17 06:09:28 +03:00
k_filter, k_time, _, k_amount, _ = uri_to_internal(keys)
2020-09-02 16:22:30 +03:00
ckeys = {**k_filter, **k_time, **k_amount}
result = get_scrobbles_num(**ckeys)
return {"amount":result}
@api.get("tracks")
def get_tracks_external(**keys):
2020-09-27 03:03:35 +03:00
k_filter, _, _, _, _ = uri_to_internal(keys,forceArtist=True)
2020-09-02 16:22:30 +03:00
ckeys = {**k_filter}
result = get_tracks(**ckeys)
return {"list":result}
@api.get("artists")
def get_artists_external():
result = get_artists()
return {"list":result}
@api.get("charts/artists")
def get_charts_artists_external(**keys):
2020-09-17 06:09:28 +03:00
_, k_time, _, _, _ = uri_to_internal(keys)
2020-09-02 16:22:30 +03:00
ckeys = {**k_time}
result = get_charts_artists(**ckeys)
return {"list":result}
@api.get("charts/tracks")
def get_charts_tracks_external(**keys):
2020-09-27 03:03:35 +03:00
k_filter, k_time, _, _, _ = uri_to_internal(keys,forceArtist=True)
2020-09-02 16:22:30 +03:00
ckeys = {**k_filter, **k_time}
result = get_charts_tracks(**ckeys)
return {"list":result}
@api.get("pulse")
def get_pulse_external(**keys):
2020-09-17 06:09:28 +03:00
k_filter, k_time, k_internal, k_amount, _ = uri_to_internal(keys)
2020-09-02 16:22:30 +03:00
ckeys = {**k_filter, **k_time, **k_internal, **k_amount}
results = get_pulse(**ckeys)
return {"list":results}
@api.get("performance")
def get_performance_external(**keys):
2020-09-17 06:09:28 +03:00
k_filter, k_time, k_internal, k_amount, _ = uri_to_internal(keys)
2020-09-02 16:22:30 +03:00
ckeys = {**k_filter, **k_time, **k_internal, **k_amount}
results = get_performance(**ckeys)
return {"list":results}
@api.get("top/artists")
def get_top_artists_external(**keys):
2020-09-17 06:09:28 +03:00
_, k_time, k_internal, _, _ = uri_to_internal(keys)
2020-09-02 16:22:30 +03:00
ckeys = {**k_time, **k_internal}
results = get_top_artists(**ckeys)
return {"list":results}
@api.get("top/tracks")
def get_top_tracks_external(**keys):
2020-09-17 06:09:28 +03:00
_, k_time, k_internal, _, _ = uri_to_internal(keys)
2020-09-02 16:22:30 +03:00
ckeys = {**k_time, **k_internal}
# IMPLEMENT THIS FOR TOP TRACKS OF ARTIST AS WELL?
results = get_top_tracks(**ckeys)
return {"list":results}
@api.get("artistinfo")
2022-01-07 06:57:13 +03:00
def artist_info_external(**keys):
2020-09-27 03:03:35 +03:00
k_filter, _, _, _, _ = uri_to_internal(keys,forceArtist=True)
2020-09-02 16:22:30 +03:00
ckeys = {**k_filter}
2022-01-07 06:57:13 +03:00
return artist_info(**ckeys)
2020-09-02 16:22:30 +03:00
@api.get("trackinfo")
2022-01-07 06:57:13 +03:00
def track_info_external(artist:Multi[str],**keys):
2020-09-02 16:22:30 +03:00
# transform into a multidict so we can use our nomral uri_to_internal function
keys = FormsDict(keys)
for a in artist:
keys.append("artist",a)
2020-09-27 03:03:35 +03:00
k_filter, _, _, _, _ = uri_to_internal(keys,forceTrack=True)
2020-09-02 16:22:30 +03:00
ckeys = {**k_filter}
2022-01-07 06:57:13 +03:00
return track_info(**ckeys)
2020-09-02 16:22:30 +03:00
@api.get("compare")
def compare_external(**keys):
Refactoring (#83) * Merge isinstance calls * Inline variable that is immediately returned * Replace set() with comprehension * Replace assignment with augmented assignment * Remove unnecessary else after guard condition * Convert for loop into list comprehension * Replace unused for index with underscore * Merge nested if conditions * Convert for loop into list comprehension * Convert for loop into set comprehension * Remove unnecessary else after guard condition * Replace if statements with if expressions * Simplify sequence comparison * Replace multiple comparisons with in operator * Merge isinstance calls * Merge nested if conditions * Add guard clause * Merge duplicate blocks in conditional * Replace unneeded comprehension with generator * Inline variable that is immediately returned * Remove unused imports * Replace unneeded comprehension with generator * Remove unused imports * Remove unused import * Inline variable that is immediately returned * Swap if/else branches and remove unnecessary else * Use str.join() instead of for loop * Multiple refactors - Remove redundant pass statement - Hoist repeated code outside conditional statement - Swap if/else to remove empty if body * Inline variable that is immediately returned * Simplify generator expression * Replace if statement with if expression * Multiple refactoring - Replace range(0, x) with range(x) - Swap if/else branches - Remove unnecessary else after guard condition * Use str.join() instead of for loop * Hoist repeated code outside conditional statement * Use str.join() instead of for loop * Inline variables that are immediately returned * Merge dictionary assignment with declaration * Use items() to directly unpack dictionary values * Extract dup code from methods into a new one
2021-10-19 15:58:24 +03:00
return compare(keys["remote"])
2020-09-02 16:22:30 +03:00
@api.get("newscrobble")
2021-01-06 23:53:43 +03:00
@authenticated_api_with_alternate(api_key_correct)
2020-09-25 18:06:45 +03:00
def get_post_scrobble(artist:Multi,**keys):
2020-09-22 18:02:50 +03:00
"""DEPRECATED. Use the equivalent POST method instead."""
2020-09-25 18:06:45 +03:00
artists = artist
title = keys.get("title")
album = keys.get("album")
duration = keys.get("seconds")
time = keys.get("time")
if time is not None: time = int(time)
return incoming_scrobble(artists,title,album=album,duration=duration,time=time)
2020-09-22 18:02:50 +03:00
2020-09-02 16:22:30 +03:00
@api.post("newscrobble")
@authenticated_api_with_alternate(api_key_correct)
def post_scrobble(artist:Multi=None,**keys):
2020-09-22 18:02:50 +03:00
"""Submit a new scrobble.
:param string artist: Artist. Can be submitted multiple times as query argument for multiple artists.
:param string artists: List of artists. Overwritten by artist parameter.
2020-09-22 18:02:50 +03:00
:param string title: Title of the track.
2020-10-09 18:40:12 +03:00
:param string album: Name of the album. Optional.
2022-02-13 09:45:22 +03:00
:param string albumartists: Album artists. Optional.
2020-09-22 18:02:50 +03:00
:param int duration: Actual listened duration of the scrobble in seconds. Optional.
2022-02-13 09:45:22 +03:00
:param int length: Total length of the track in seconds. Optional.
2020-09-22 18:02:50 +03:00
:param int time: UNIX timestamp of the scrobble. Optional, not needed if scrobble is at time of request.
2022-02-13 09:45:22 +03:00
:param boolean nofix: Skip server-side metadata parsing. Optional.
2020-09-22 18:02:50 +03:00
"""
2020-09-04 15:32:11 +03:00
#artists = "/".join(artist)
2022-02-14 08:45:53 +03:00
# url multi args automatically become list
keys['artists'] = artist if artist is not None else keys.get("artists")
2022-02-13 09:45:22 +03:00
keys['fix'] = keys.get("nofix") is None
2022-02-14 08:42:27 +03:00
if keys.get('time') is not None: keys['time'] = int(keys.get('time'))
2020-09-02 16:22:30 +03:00
2022-02-14 08:07:54 +03:00
return incoming_scrobble(**keys,client=request.malojaclient)
# TODO: malojaclient needs to be converted to proper argument in doreah
2020-09-02 16:22:30 +03:00
@api.post("importrules")
@authenticated_api
def import_rulemodule(**keys):
filename = keys.get("filename")
remove = keys.get("remove") is not None
validchars = "-_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
filename = "".join(c for c in filename if c in validchars)
if remove:
log("Deactivating predefined rulefile " + filename)
2020-12-25 06:52:05 +03:00
os.remove(data_dir['rules'](filename + ".tsv"))
2020-09-02 16:22:30 +03:00
else:
log("Importing predefined rulefile " + filename)
2020-12-25 06:52:05 +03:00
os.symlink(data_dir['rules']("predefined/" + filename + ".tsv"),data_dir['rules'](filename + ".tsv"))
2020-09-02 16:22:30 +03:00
@api.post("rebuild")
@authenticated_api
def rebuild(**keys):
log("Database rebuild initiated!")
sync()
dbstatus['rebuildinprogress'] = True
2020-12-02 21:18:12 +03:00
from ..proccontrol.tasks.fixexisting import fix
2020-09-02 16:22:30 +03:00
fix()
global cla, coa
cla = CleanerAgent()
coa = CollectorAgent()
build_db()
invalidate_caches()
@api.get("search")
def search(**keys):
query = keys.get("query")
max_ = keys.get("max")
if max_ is not None: max_ = int(max_)
query = query.lower()
artists = db_search(query,type="ARTIST")
tracks = db_search(query,type="TRACK")
# if the string begins with the query it's a better match, if a word in it begins with it, still good
# also, shorter is better (because longer titles would be easier to further specify)
artists.sort(key=lambda x: ((0 if x.lower().startswith(query) else 1 if " " + query in x.lower() else 2),len(x)))
tracks.sort(key=lambda x: ((0 if x["title"].lower().startswith(query) else 1 if " " + query in x["title"].lower() else 2),len(x["title"])))
# add links
artists_result = []
for a in artists:
Refactoring (#83) * Merge isinstance calls * Inline variable that is immediately returned * Replace set() with comprehension * Replace assignment with augmented assignment * Remove unnecessary else after guard condition * Convert for loop into list comprehension * Replace unused for index with underscore * Merge nested if conditions * Convert for loop into list comprehension * Convert for loop into set comprehension * Remove unnecessary else after guard condition * Replace if statements with if expressions * Simplify sequence comparison * Replace multiple comparisons with in operator * Merge isinstance calls * Merge nested if conditions * Add guard clause * Merge duplicate blocks in conditional * Replace unneeded comprehension with generator * Inline variable that is immediately returned * Remove unused imports * Replace unneeded comprehension with generator * Remove unused imports * Remove unused import * Inline variable that is immediately returned * Swap if/else branches and remove unnecessary else * Use str.join() instead of for loop * Multiple refactors - Remove redundant pass statement - Hoist repeated code outside conditional statement - Swap if/else to remove empty if body * Inline variable that is immediately returned * Simplify generator expression * Replace if statement with if expression * Multiple refactoring - Replace range(0, x) with range(x) - Swap if/else branches - Remove unnecessary else after guard condition * Use str.join() instead of for loop * Hoist repeated code outside conditional statement * Use str.join() instead of for loop * Inline variables that are immediately returned * Merge dictionary assignment with declaration * Use items() to directly unpack dictionary values * Extract dup code from methods into a new one
2021-10-19 15:58:24 +03:00
result = {
'name': a,
'link': "/artist?" + compose_querystring(internal_to_uri({"artist": a})),
}
2020-09-02 16:22:30 +03:00
result["image"] = "/image?" + compose_querystring(internal_to_uri({"artist":a}))
artists_result.append(result)
tracks_result = []
for t in tracks:
result = t
result["link"] = "/track?" + compose_querystring(internal_to_uri({"track":t}))
result["image"] = "/image?" + compose_querystring(internal_to_uri({"track":t}))
tracks_result.append(result)
return {"artists":artists_result[:max_],"tracks":tracks_result[:max_]}
@api.post("addpicture")
@authenticated_api
def add_picture(b64,artist:Multi=[],title=None):
keys = FormsDict()
for a in artist:
keys.append("artist",a)
if title is not None: keys.append("title",title)
2020-09-04 15:07:21 +03:00
k_filter, _, _, _, _ = uri_to_internal(keys)
2020-09-02 16:22:30 +03:00
if "track" in k_filter: k_filter = k_filter["track"]
utilities.set_image(b64,**k_filter)
@api.post("newrule")
@authenticated_api
def newrule(**keys):
2020-12-25 06:52:05 +03:00
tsv.add_entry(data_dir['rules']("webmade.tsv"),[k for k in keys])
2020-09-02 16:22:30 +03:00
#addEntry("rules/webmade.tsv",[k for k in keys])
2021-12-14 23:19:15 +03:00
@api.post("settings")
@authenticated_api
2021-12-19 23:04:43 +03:00
def set_settings(**keys):
malojaconfig.update(keys)
@api.post("apikeys")
@authenticated_api
2021-12-26 22:53:38 +03:00
def set_apikeys(**keys):
apikeystore.update(keys)
2021-12-26 22:53:38 +03:00
@api.post("import")
@authenticated_api
def import_scrobbles(identifier):
from ..thirdparty import import_scrobbles
import_scrobbles(identifier)
2021-12-26 22:53:38 +03:00
@api.get("backup")
@authenticated_api
def get_backup(**keys):
from ..proccontrol.tasks.backup import backup
import tempfile
tmpfolder = tempfile.gettempdir()
archivefile = backup(tmpfolder)
return static_file(os.path.basename(archivefile),root=tmpfolder)