maloja/maloja/apis/native_v1.py

749 lines
18 KiB
Python
Raw Normal View History

2022-03-25 21:50:16 +03:00
import os
2022-04-13 16:42:45 +03:00
import math
2022-04-22 18:14:57 +03:00
import traceback
2022-03-25 21:58:34 +03:00
from bottle import response, static_file, request, FormsDict
2022-03-25 21:55:58 +03:00
from doreah.logging import log
from doreah.auth import authenticated_api, authenticated_api_with_alternate, authenticated_function
2022-03-25 21:58:34 +03:00
# nimrodel API
from nimrodel import EAPI as API
from nimrodel import Multi
from .. import database
from ..pkg_global.conf import malojaconfig, data_dir
2021-12-26 23:36:36 +03:00
from ..__pkginfo__ import VERSION
2022-03-06 06:52:10 +03:00
from ..malojauri import uri_to_internal, compose_querystring, internal_to_uri
from .. import images
2022-04-06 23:46:43 +03:00
from ._apikeys import apikeystore, api_key_correct
2020-09-02 16:22:30 +03:00
2022-03-25 21:58:34 +03:00
2020-09-02 16:22:30 +03:00
api = API(delay=True)
2020-09-04 01:08:24 +03:00
api.__apipath__ = "mlj_1"
2020-09-02 16:22:30 +03:00
2022-04-16 16:59:42 +03:00
errors = {
database.exceptions.MissingScrobbleParameters: lambda e: (400,{
2022-04-16 16:59:42 +03:00
"status":"failure",
"error":{
'type':'missing_scrobble_data',
'value':e.params,
2022-04-21 18:02:10 +03:00
'desc':"The scrobble is missing needed parameters."
2022-04-16 16:59:42 +03:00
}
}),
database.exceptions.MissingEntityParameter: lambda e: (400,{
"status":"error",
"error":{
'type':'missing_entity_parameter',
'value':None,
'desc':"This API call is not valid without an entity (track or artist)."
}
}),
2022-04-21 16:12:48 +03:00
database.exceptions.EntityExists: lambda e: (409,{
"status":"failure",
"error":{
'type':'entity_exists',
'value':e.entitydict,
2022-04-22 21:34:46 +03:00
'desc':"This entity already exists in the database. Consider merging instead."
2022-04-21 16:12:48 +03:00
}
}),
database.exceptions.DatabaseNotBuilt: lambda e: (503,{
"status":"error",
"error":{
'type':'server_not_ready',
'value':'db_upgrade',
'desc':"The database is being upgraded. Please try again later."
}
}),
2022-10-19 20:53:13 +03:00
images.MalformedB64: lambda e: (400,{
"status":"failure",
"error":{
'type':'malformed_b64',
'value':None,
'desc':"The provided base 64 string is not valid."
}
}),
# for http errors, use their status code
Exception: lambda e: ((e.status_code if hasattr(e,'statuscode') else 500),{
2022-04-16 16:59:42 +03:00
"status":"failure",
"error":{
'type':'unknown_error',
'value':e.__repr__(),
'desc':"The server has encountered an exception."
}
})
}
def catch_exceptions(func):
def protector(*args,**kwargs):
try:
return func(*args,**kwargs)
except Exception as e:
2022-04-22 18:14:57 +03:00
print(traceback.format_exc())
for etype in errors:
if isinstance(e,etype):
errorhandling = errors[etype](e)
response.status = errorhandling[0]
return errorhandling[1]
protector.__doc__ = func.__doc__
protector.__annotations__ = func.__annotations__
return protector
2022-04-16 16:59:42 +03:00
def add_common_args_to_docstring(filterkeys=False,limitkeys=False,delimitkeys=False,amountkeys=False):
def decorator(func):
2022-04-14 18:00:45 +03:00
timeformats = "Possible formats include '2022', '2022/08', '2022/08/01', '2022/W42', 'today', 'thismonth', 'monday', 'august'"
if filterkeys:
2022-04-14 18:00:45 +03:00
func.__doc__ += f"""
:param string title: Track title
2022-04-14 20:34:42 +03:00
:param string artist: Track artist. Can be specified multiple times.
:param bool associated: Whether to include associated artists.
"""
if limitkeys:
2022-04-14 18:00:45 +03:00
func.__doc__ += f"""
:param string from: Start of the desired time range. Can also be called since or start. {timeformats}
:param string until: End of the desired range. Can also be called to or end. {timeformats}
:param string in: Desired range. Can also be called within or during. {timeformats}
"""
if delimitkeys:
func.__doc__ += """
:param string step: Step, e.g. month or week.
:param int stepn: Number of base type units per step
:param int trail: How many preceding steps should be factored in.
:param bool cumulative: Instead of a fixed trail length, use all history up to this point.
"""
if amountkeys:
func.__doc__ += """
:param int page: Page to show
:param int perpage: Entries per page.
:param int max: Legacy. Show first page with this many entries.
"""
return func
return decorator
2020-09-02 16:22:30 +03:00
@api.get("test")
@catch_exceptions
2020-09-02 16:22:30 +03:00
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.
:return: status (String), error (String)
:rtype: Dictionary
2020-10-09 18:40:12 +03:00
"""
2020-09-02 16:22:30 +03:00
response.set_header("Access-Control-Allow-Origin","*")
2022-04-06 23:46:43 +03:00
if key is not None and not apikeystore.check_key(key):
2020-09-02 16:22:30 +03:00
response.status = 403
2022-04-16 16:59:42 +03:00
return {
"status":"error",
"error":"Wrong API key"
}
2020-09-02 16:22:30 +03:00
else:
response.status = 200
2022-04-16 16:59:42 +03:00
return {
"status":"ok"
}
2020-09-02 16:22:30 +03:00
@api.get("serverinfo")
@catch_exceptions
2020-09-02 16:22:30 +03:00
def server_info():
"""Returns basic information about the server.
2022-04-16 17:21:24 +03:00
:return: name (String), version (Tuple), versionstring (String), db_status (Mapping). Additional keys can be added at any point, but will not be removed within API version.
:rtype: Dictionary
"""
2020-09-02 16:22:30 +03:00
response.set_header("Access-Control-Allow-Origin","*")
return {
2021-12-19 23:10:55 +03:00
"name":malojaconfig["NAME"],
"version":VERSION.split("."),
"versionstring":VERSION,
"db_status":database.dbstatus
2020-09-02 16:22:30 +03:00
}
## API ENDPOINTS THAT CLOSELY MATCH ONE DATABASE FUNCTION
@api.get("scrobbles")
@catch_exceptions
@add_common_args_to_docstring(filterkeys=True,limitkeys=True,amountkeys=True)
2020-09-02 16:22:30 +03:00
def get_scrobbles_external(**keys):
"""Returns a list of scrobbles.
:return: list (List)
:rtype: Dictionary
"""
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 = database.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
2022-04-16 16:59:42 +03:00
return {
2022-04-25 23:54:53 +03:00
"status":"ok",
2022-04-16 16:59:42 +03:00
"list":result
}
2020-09-02 16:22:30 +03:00
@api.get("numscrobbles")
@catch_exceptions
@add_common_args_to_docstring(filterkeys=True,limitkeys=True,amountkeys=True)
2020-09-02 16:22:30 +03:00
def get_scrobbles_num_external(**keys):
"""Returns amount of scrobbles.
:return: amount (Integer)
:rtype: Dictionary
"""
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 = database.get_scrobbles_num(**ckeys)
2022-04-16 16:59:42 +03:00
return {
2022-04-25 23:54:53 +03:00
"status":"ok",
2022-04-16 16:59:42 +03:00
"amount":result
}
2020-09-02 16:22:30 +03:00
@api.get("tracks")
@catch_exceptions
@add_common_args_to_docstring(filterkeys=True)
2020-09-02 16:22:30 +03:00
def get_tracks_external(**keys):
2022-04-14 20:34:42 +03:00
"""Returns all tracks (optionally of an artist).
:return: list (List)
:rtype: Dictionary
"""
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 = database.get_tracks(**ckeys)
2022-04-16 16:59:42 +03:00
return {
2022-04-25 23:54:53 +03:00
"status":"ok",
2022-04-16 16:59:42 +03:00
"list":result
}
2020-09-02 16:22:30 +03:00
@api.get("artists")
@catch_exceptions
@add_common_args_to_docstring()
2020-09-02 16:22:30 +03:00
def get_artists_external():
"""Returns all artists.
:return: list (List)
:rtype: Dictionary"""
result = database.get_artists()
2022-04-16 16:59:42 +03:00
return {
2022-04-25 23:54:53 +03:00
"status":"ok",
2022-04-16 16:59:42 +03:00
"list":result
}
2020-09-02 16:22:30 +03:00
@api.get("charts/artists")
@catch_exceptions
@add_common_args_to_docstring(limitkeys=True)
2020-09-02 16:22:30 +03:00
def get_charts_artists_external(**keys):
"""Returns artist charts
:return: list (List)
:rtype: Dictionary"""
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 = database.get_charts_artists(**ckeys)
2022-04-16 16:59:42 +03:00
return {
2022-04-25 23:54:53 +03:00
"status":"ok",
2022-04-16 16:59:42 +03:00
"list":result
}
2020-09-02 16:22:30 +03:00
@api.get("charts/tracks")
@catch_exceptions
@add_common_args_to_docstring(filterkeys=True,limitkeys=True)
2020-09-02 16:22:30 +03:00
def get_charts_tracks_external(**keys):
"""Returns track charts
:return: list (List)
:rtype: Dictionary"""
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 = database.get_charts_tracks(**ckeys)
2022-04-16 16:59:42 +03:00
return {
2022-04-25 23:54:53 +03:00
"status":"ok",
2022-04-16 16:59:42 +03:00
"list":result
}
2020-09-02 16:22:30 +03:00
@api.get("pulse")
@catch_exceptions
@add_common_args_to_docstring(filterkeys=True,limitkeys=True,delimitkeys=True,amountkeys=True)
2020-09-02 16:22:30 +03:00
def get_pulse_external(**keys):
"""Returns amounts of scrobbles in specified time frames
:return: list (List)
:rtype: Dictionary"""
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 = database.get_pulse(**ckeys)
2022-04-16 16:59:42 +03:00
return {
2022-04-25 23:54:53 +03:00
"status":"ok",
2022-04-16 16:59:42 +03:00
"list":results
}
2020-09-02 16:22:30 +03:00
@api.get("performance")
@catch_exceptions
@add_common_args_to_docstring(filterkeys=True,limitkeys=True,delimitkeys=True,amountkeys=True)
2020-09-02 16:22:30 +03:00
def get_performance_external(**keys):
"""Returns artist's or track's rank in specified time frames
:return: list (List)
:rtype: Dictionary"""
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 = database.get_performance(**ckeys)
2022-04-16 16:59:42 +03:00
return {
2022-04-25 23:54:53 +03:00
"status":"ok",
2022-04-16 16:59:42 +03:00
"list":results
}
2020-09-02 16:22:30 +03:00
@api.get("top/artists")
@catch_exceptions
@add_common_args_to_docstring(limitkeys=True,delimitkeys=True)
2020-09-02 16:22:30 +03:00
def get_top_artists_external(**keys):
"""Returns respective number 1 artists in specified time frames
:return: list (List)
:rtype: Dictionary"""
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 = database.get_top_artists(**ckeys)
2022-04-16 16:59:42 +03:00
return {
2022-04-25 23:54:53 +03:00
"status":"ok",
2022-04-16 16:59:42 +03:00
"list":results
}
2020-09-02 16:22:30 +03:00
@api.get("top/tracks")
@catch_exceptions
@add_common_args_to_docstring(limitkeys=True,delimitkeys=True)
2020-09-02 16:22:30 +03:00
def get_top_tracks_external(**keys):
"""Returns respective number 1 tracks in specified time frames
:return: list (List)
:rtype: Dictionary"""
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 = database.get_top_tracks(**ckeys)
2022-04-16 16:59:42 +03:00
return {
2022-04-25 23:54:53 +03:00
"status":"ok",
2022-04-16 16:59:42 +03:00
"list":results
}
2020-09-02 16:22:30 +03:00
@api.get("artistinfo")
@catch_exceptions
@add_common_args_to_docstring(filterkeys=True)
2022-01-07 06:57:13 +03:00
def artist_info_external(**keys):
"""Returns information about an artist
:return: artist (String), scrobbles (Integer), position (Integer), associated (List), medals (Mapping), topweeks (Integer)
:rtype: Dictionary"""
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}
return database.artist_info(**ckeys)
2020-09-02 16:22:30 +03:00
@api.get("trackinfo")
@catch_exceptions
@add_common_args_to_docstring(filterkeys=True)
def track_info_external(artist:Multi[str]=[],**keys):
"""Returns information about a track
:return: track (Mapping), scrobbles (Integer), position (Integer), medals (Mapping), certification (String), topweeks (Integer)
:rtype: Dictionary"""
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}
return database.track_info(**ckeys)
2020-09-02 16:22:30 +03:00
@api.post("newscrobble")
@authenticated_function(alternate=api_key_correct,api=True,pass_auth_result_as='auth_result')
@catch_exceptions
def post_scrobble(
artist:Multi=None,
artists:list=[],
title:str="",
album:str=None,
albumartists:list=[],
duration:int=None,
length:int=None,
time:int=None,
2022-04-14 18:44:52 +03:00
nofix=None,
auth_result=None,
**extra_kwargs):
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.
2022-04-16 16:59:42 +03:00
:param list artists: List of artists.
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.
:param list 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-04-14 18:44:52 +03:00
:param flag nofix: Skip server-side metadata parsing. Optional.
:return: status (String), track (Mapping)
:rtype: Dictionary
2020-09-22 18:02:50 +03:00
"""
2020-09-02 16:22:30 +03:00
2022-04-06 18:42:48 +03:00
rawscrobble = {
2022-04-16 16:59:42 +03:00
'track_artists':(artist or []) + artists,
'track_title':title,
'album_name':album,
'album_artists':albumartists,
'scrobble_duration':duration,
'track_length':length,
'scrobble_time':time
2022-04-06 18:42:48 +03:00
}
# for logging purposes, don't pass values that we didn't actually supply
rawscrobble = {k:rawscrobble[k] for k in rawscrobble if rawscrobble[k]}
result = database.incoming_scrobble(
rawscrobble,
client='browser' if auth_result.get('doreah_native_auth_check') else auth_result.get('client'),
api='native/v1',
fix=(nofix is None)
)
responsedict = {
'status': 'success',
'track': {
'artists':result['track']['artists'],
'title':result['track']['title']
2022-04-22 19:36:06 +03:00
},
2022-04-22 21:04:24 +03:00
'desc':f"Scrobbled {result['track']['title']} by {', '.join(result['track']['artists'])}"
}
if extra_kwargs:
responsedict['warnings'] = [
{'type':'invalid_keyword_ignored','value':k,
'desc':"This key was not recognized by the server and has been discarded."}
for k in extra_kwargs
]
if artist and artists:
responsedict['warnings'] = [
{'type':'mixed_schema','value':['artist','artists'],
'desc':"These two fields are meant as alternative methods to submit information. Use of both is discouraged, but works at the moment."}
]
return responsedict
2020-09-02 16:22:30 +03:00
@api.post("addpicture")
@authenticated_function(alternate=api_key_correct,api=True)
@catch_exceptions
def add_picture(b64,artist:Multi=[],title=None):
"""Uploads a new image for an artist or track.
param string b64: Base 64 representation of the image
param string artist: Artist name. Can be supplied multiple times for tracks with multiple artists.
param string title: Title of the track. Optional.
"""
keys = FormsDict()
for a in artist:
keys.append("artist",a)
if title is not None: keys.append("title",title)
k_filter, _, _, _, _ = uri_to_internal(keys)
if "track" in k_filter: k_filter = k_filter["track"]
url = images.set_image(b64,**k_filter)
return {
'status': 'success',
'url': url
}
2020-09-02 16:22:30 +03:00
@api.post("importrules")
@authenticated_function(api=True)
@catch_exceptions
2020-09-02 16:22:30 +03:00
def import_rulemodule(**keys):
2022-04-14 20:34:42 +03:00
"""Internal Use Only"""
2020-09-02 16:22:30 +03:00
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_function(api=True)
@catch_exceptions
2020-09-02 16:22:30 +03:00
def rebuild(**keys):
2022-04-14 20:34:42 +03:00
"""Internal Use Only"""
2020-09-02 16:22:30 +03:00
log("Database rebuild initiated!")
database.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
2020-09-02 16:22:30 +03:00
cla = CleanerAgent()
database.build_db()
database.invalidate_caches()
2020-09-02 16:22:30 +03:00
@api.get("search")
@catch_exceptions
2020-09-02 16:22:30 +03:00
def search(**keys):
2022-04-14 20:34:42 +03:00
"""Internal Use Only"""
2020-09-02 16:22:30 +03:00
query = keys.get("query")
max_ = keys.get("max")
if max_ is not None: max_ = int(max_)
query = query.lower()
2022-03-06 06:52:10 +03:00
artists = database.db_search(query,type="ARTIST")
tracks = database.db_search(query,type="TRACK")
2020-09-02 16:22:30 +03:00
# 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 = {
2022-04-23 20:22:32 +03:00
'artist': a,
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
'link': "/artist?" + compose_querystring(internal_to_uri({"artist": a})),
2022-04-23 20:22:32 +03:00
'image': images.get_artist_image(a)
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
}
2020-09-02 16:22:30 +03:00
artists_result.append(result)
tracks_result = []
for t in tracks:
2022-04-23 20:22:32 +03:00
result = {
'track': t,
'link': "/track?" + compose_querystring(internal_to_uri({"track":t})),
'image': images.get_track_image(t)
}
2020-09-02 16:22:30 +03:00
tracks_result.append(result)
return {"artists":artists_result[:max_],"tracks":tracks_result[:max_]}
@api.post("newrule")
@authenticated_function(api=True)
@catch_exceptions
2020-09-02 16:22:30 +03:00
def newrule(**keys):
2022-04-14 20:34:42 +03:00
"""Internal Use Only"""
pass
# TODO after implementing new rule system
#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_function(api=True)
@catch_exceptions
2021-12-19 23:04:43 +03:00
def set_settings(**keys):
2022-04-14 20:34:42 +03:00
"""Internal Use Only"""
malojaconfig.update(keys)
@api.post("apikeys")
@authenticated_function(api=True)
@catch_exceptions
2021-12-26 22:53:38 +03:00
def set_apikeys(**keys):
2022-04-14 20:34:42 +03:00
"""Internal Use Only"""
apikeystore.update(keys)
2021-12-26 22:53:38 +03:00
@api.post("import")
@authenticated_function(api=True)
@catch_exceptions
def import_scrobbles(identifier):
2022-04-14 20:34:42 +03:00
"""Internal Use Only"""
from ..thirdparty import import_scrobbles
import_scrobbles(identifier)
2021-12-26 22:53:38 +03:00
@api.get("backup")
@authenticated_function(api=True)
@catch_exceptions
2021-12-26 22:53:38 +03:00
def get_backup(**keys):
2022-04-14 20:34:42 +03:00
"""Internal Use Only"""
2021-12-26 22:53:38 +03:00
from ..proccontrol.tasks.backup import backup
import tempfile
tmpfolder = tempfile.gettempdir()
archivefile = backup(tmpfolder)
return static_file(os.path.basename(archivefile),root=tmpfolder)
2022-04-04 19:31:04 +03:00
@api.get("export")
@authenticated_function(api=True)
@catch_exceptions
2022-04-04 19:31:04 +03:00
def get_export(**keys):
2022-04-14 20:34:42 +03:00
"""Internal Use Only"""
2022-04-04 19:31:04 +03:00
from ..proccontrol.tasks.export import export
import tempfile
tmpfolder = tempfile.gettempdir()
resultfile = export(tmpfolder)
return static_file(os.path.basename(resultfile),root=tmpfolder)
@api.post("delete_scrobble")
@authenticated_function(api=True)
@catch_exceptions
def delete_scrobble(timestamp):
2022-04-14 20:34:42 +03:00
"""Internal Use Only"""
2022-04-17 21:18:26 +03:00
result = database.remove_scrobble(timestamp)
return {
"status":"success",
"desc":f"Scrobble was deleted!"
2022-04-17 21:18:26 +03:00
}
2022-04-15 19:48:03 +03:00
@api.post("edit_artist")
@authenticated_function(api=True)
@catch_exceptions
def edit_artist(id,name):
2022-04-15 19:48:03 +03:00
"""Internal Use Only"""
2022-04-17 21:18:26 +03:00
result = database.edit_artist(id,name)
return {
"status":"success"
}
@api.post("edit_track")
@authenticated_function(api=True)
2022-04-21 16:13:14 +03:00
@catch_exceptions
def edit_track(id,title):
"""Internal Use Only"""
2022-04-17 21:18:26 +03:00
result = database.edit_track(id,{'title':title})
return {
"status":"success"
}
@api.post("merge_tracks")
@authenticated_function(api=True)
@catch_exceptions
def merge_tracks(target_id,source_ids):
"""Internal Use Only"""
2022-04-17 21:18:26 +03:00
result = database.merge_tracks(target_id,source_ids)
return {
"status":"success"
}
@api.post("merge_artists")
@authenticated_function(api=True)
@catch_exceptions
def merge_artists(target_id,source_ids):
"""Internal Use Only"""
2022-04-17 21:18:26 +03:00
result = database.merge_artists(target_id,source_ids)
return {
"status":"success"
}
2022-04-20 16:59:33 +03:00
@api.post("reparse_scrobble")
@authenticated_function(api=True)
@catch_exceptions
2022-04-20 16:59:33 +03:00
def reparse_scrobble(timestamp):
"""Internal Use Only"""
result = database.reparse_scrobble(timestamp)
2022-04-22 19:36:06 +03:00
if result:
return {
"status":"success",
2022-04-25 21:48:22 +03:00
"desc":f"Scrobble was reparsed!",
"scrobble":result
2022-04-22 19:36:06 +03:00
}
else:
return {
2022-04-22 22:37:48 +03:00
"status":"no_operation",
"desc":"The scrobble was not changed."
2022-04-22 19:36:06 +03:00
}