malojaserver 3.2.2__py3-none-any.whl → 3.2.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- maloja/__main__.py +1 -94
- maloja/__pkginfo__.py +1 -1
- maloja/apis/_base.py +26 -19
- maloja/apis/_exceptions.py +1 -1
- maloja/apis/audioscrobbler.py +35 -7
- maloja/apis/audioscrobbler_legacy.py +5 -5
- maloja/apis/listenbrainz.py +8 -5
- maloja/apis/native_v1.py +43 -26
- maloja/cleanup.py +9 -7
- maloja/data_files/config/rules/predefined/krateng_kpopgirlgroups.tsv +2 -2
- maloja/database/__init__.py +68 -23
- maloja/database/associated.py +10 -6
- maloja/database/exceptions.py +28 -3
- maloja/database/sqldb.py +216 -168
- maloja/dev/profiler.py +3 -4
- maloja/images.py +6 -0
- maloja/malojauri.py +2 -0
- maloja/pkg_global/conf.py +30 -28
- maloja/proccontrol/tasks/export.py +2 -1
- maloja/proccontrol/tasks/import_scrobbles.py +110 -47
- maloja/server.py +11 -10
- maloja/setup.py +29 -17
- maloja/web/jinja/abstracts/base.jinja +1 -1
- maloja/web/jinja/admin_albumless.jinja +2 -0
- maloja/web/jinja/admin_issues.jinja +2 -2
- maloja/web/jinja/admin_overview.jinja +3 -3
- maloja/web/jinja/admin_setup.jinja +3 -3
- maloja/web/jinja/partials/album_showcase.jinja +1 -1
- maloja/web/jinja/snippets/entityrow.jinja +2 -2
- maloja/web/jinja/snippets/links.jinja +3 -1
- maloja/web/static/css/maloja.css +8 -2
- maloja/web/static/css/startpage.css +2 -2
- maloja/web/static/js/manualscrobble.js +2 -2
- maloja/web/static/js/notifications.js +16 -8
- maloja/web/static/js/search.js +18 -12
- maloja/web/static/js/upload.js +1 -1
- {malojaserver-3.2.2.dist-info → malojaserver-3.2.4.dist-info}/METADATA +24 -72
- {malojaserver-3.2.2.dist-info → malojaserver-3.2.4.dist-info}/RECORD +42 -42
- {malojaserver-3.2.2.dist-info → malojaserver-3.2.4.dist-info}/WHEEL +1 -1
- /maloja/data_files/state/{scrobbles → import}/dummy +0 -0
- {malojaserver-3.2.2.dist-info → malojaserver-3.2.4.dist-info}/LICENSE +0 -0
- {malojaserver-3.2.2.dist-info → malojaserver-3.2.4.dist-info}/entry_points.txt +0 -0
maloja/__main__.py
CHANGED
@@ -26,77 +26,6 @@ def print_header_info():
|
|
26
26
|
#print("#####")
|
27
27
|
print()
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
def get_instance():
|
32
|
-
try:
|
33
|
-
return int(subprocess.check_output(["pgrep","-f","maloja$"]))
|
34
|
-
except Exception:
|
35
|
-
return None
|
36
|
-
|
37
|
-
def get_instance_supervisor():
|
38
|
-
try:
|
39
|
-
return int(subprocess.check_output(["pgrep","-f","maloja_supervisor"]))
|
40
|
-
except Exception:
|
41
|
-
return None
|
42
|
-
|
43
|
-
def restart():
|
44
|
-
if stop():
|
45
|
-
start()
|
46
|
-
else:
|
47
|
-
print(col["red"]("Could not stop Maloja!"))
|
48
|
-
|
49
|
-
def start():
|
50
|
-
if get_instance_supervisor() is not None:
|
51
|
-
print("Maloja is already running.")
|
52
|
-
else:
|
53
|
-
print_header_info()
|
54
|
-
setup()
|
55
|
-
try:
|
56
|
-
#p = subprocess.Popen(["python3","-m","maloja.server"],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL)
|
57
|
-
sp = subprocess.Popen(["python3","-m","maloja","supervisor"],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL)
|
58
|
-
print(col["green"]("Maloja started!"))
|
59
|
-
|
60
|
-
port = conf.malojaconfig["PORT"]
|
61
|
-
|
62
|
-
print("Visit your server address (Port " + str(port) + ") to see your web interface. Visit /admin_setup to get started.")
|
63
|
-
print("If you're installing this on your local machine, these links should get you there:")
|
64
|
-
print("\t" + col["blue"]("http://localhost:" + str(port)))
|
65
|
-
print("\t" + col["blue"]("http://localhost:" + str(port) + "/admin_setup"))
|
66
|
-
return True
|
67
|
-
except Exception:
|
68
|
-
print("Error while starting Maloja.")
|
69
|
-
return False
|
70
|
-
|
71
|
-
|
72
|
-
def stop():
|
73
|
-
|
74
|
-
for attempt in [(signal.SIGTERM,2),(signal.SIGTERM,5),(signal.SIGKILL,3),(signal.SIGKILL,5)]:
|
75
|
-
|
76
|
-
pid_sv = get_instance_supervisor()
|
77
|
-
pid = get_instance()
|
78
|
-
|
79
|
-
if pid is None and pid_sv is None:
|
80
|
-
print("Maloja stopped!")
|
81
|
-
return True
|
82
|
-
|
83
|
-
if pid_sv is not None:
|
84
|
-
os.kill(pid_sv,attempt[0])
|
85
|
-
if pid is not None:
|
86
|
-
os.kill(pid,attempt[0])
|
87
|
-
|
88
|
-
time.sleep(attempt[1])
|
89
|
-
|
90
|
-
return False
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
print("Maloja stopped!")
|
98
|
-
return True
|
99
|
-
|
100
29
|
def onlysetup():
|
101
30
|
print_header_info()
|
102
31
|
setup()
|
@@ -109,24 +38,6 @@ def run_server():
|
|
109
38
|
from . import server
|
110
39
|
server.run_server()
|
111
40
|
|
112
|
-
def run_supervisor():
|
113
|
-
setproctitle("maloja_supervisor")
|
114
|
-
while True:
|
115
|
-
log("Maloja is not running, starting...",module="supervisor")
|
116
|
-
try:
|
117
|
-
process = subprocess.Popen(
|
118
|
-
["python3", "-m", "maloja","run"],
|
119
|
-
stdout=subprocess.DEVNULL,
|
120
|
-
stderr=subprocess.DEVNULL,
|
121
|
-
)
|
122
|
-
except Exception as e:
|
123
|
-
log("Error starting Maloja: " + str(e),module="supervisor")
|
124
|
-
else:
|
125
|
-
try:
|
126
|
-
process.wait()
|
127
|
-
except Exception as e:
|
128
|
-
log("Maloja crashed: " + str(e),module="supervisor")
|
129
|
-
|
130
41
|
def debug():
|
131
42
|
os.environ["MALOJA_DEV_MODE"] = 'true'
|
132
43
|
conf.malojaconfig.load_environment()
|
@@ -135,7 +46,7 @@ def debug():
|
|
135
46
|
def print_info():
|
136
47
|
print_header_info()
|
137
48
|
print(col['lightblue']("Configuration Directory:"),conf.dir_settings['config'])
|
138
|
-
print(col['lightblue']("
|
49
|
+
print(col['lightblue']("State Directory: "),conf.dir_settings['state'])
|
139
50
|
print(col['lightblue']("Log Directory: "),conf.dir_settings['logs'])
|
140
51
|
print(col['lightblue']("Network: "),f"Dual Stack, Port {conf.malojaconfig['port']}" if conf.malojaconfig['host'] == "*" else f"IPv{ip_address(conf.malojaconfig['host']).version}, Port {conf.malojaconfig['port']}")
|
141
52
|
print(col['lightblue']("Timezone: "),f"UTC{conf.malojaconfig['timezone']:+d}")
|
@@ -173,11 +84,7 @@ def main(*args,**kwargs):
|
|
173
84
|
|
174
85
|
actions = {
|
175
86
|
# server
|
176
|
-
"start":start,
|
177
|
-
"restart":restart,
|
178
|
-
"stop":stop,
|
179
87
|
"run":run_server,
|
180
|
-
"supervisor":run_supervisor,
|
181
88
|
"debug":debug,
|
182
89
|
"setup":onlysetup,
|
183
90
|
# admin scripts
|
maloja/__pkginfo__.py
CHANGED
maloja/apis/_base.py
CHANGED
@@ -25,9 +25,20 @@ __logmodulename__ = "apis"
|
|
25
25
|
|
26
26
|
cla = CleanerAgent()
|
27
27
|
|
28
|
+
|
29
|
+
|
30
|
+
# wrapper method: calls handle. final net to catch exceptions and map them to the handlers proper json / xml response
|
31
|
+
# handle method: finds the method for this path / query. can only raise InvalidMethodException
|
32
|
+
# scrobble: NOT the exposed scrobble method - helper for all APIs to scrobble their results with self-identification
|
33
|
+
|
34
|
+
|
28
35
|
class APIHandler:
|
36
|
+
|
37
|
+
__apiname__: str
|
38
|
+
errors: dict
|
29
39
|
# make these classes singletons
|
30
40
|
_instance = None
|
41
|
+
|
31
42
|
def __new__(cls, *args, **kwargs):
|
32
43
|
if not isinstance(cls._instance, cls):
|
33
44
|
cls._instance = object.__new__(cls, *args, **kwargs)
|
@@ -62,37 +73,33 @@ class APIHandler:
|
|
62
73
|
|
63
74
|
try:
|
64
75
|
response.status,result = self.handle(path,keys)
|
65
|
-
except Exception:
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
76
|
+
except Exception as e:
|
77
|
+
for exc_type, exc_response in self.errors.items():
|
78
|
+
if isinstance(e, exc_type):
|
79
|
+
response.status, result = exc_response
|
80
|
+
log(f"Error with {self.__apiname__} API: {e} (Request: {path})")
|
81
|
+
break
|
70
82
|
else:
|
71
|
-
|
72
|
-
|
83
|
+
# THIS SHOULD NOT HAPPEN
|
84
|
+
response.status, result = 500, {"status": "Unknown error", "code": 500}
|
85
|
+
log(f"Unhandled Exception with {self.__apiname__} API: {e} (Request: {path})")
|
73
86
|
|
74
87
|
return result
|
75
|
-
#else:
|
76
|
-
# result = {"error":"Invalid scrobble protocol"}
|
77
|
-
# response.status = 500
|
78
88
|
|
79
89
|
|
80
90
|
def handle(self,path,keys):
|
81
91
|
|
82
92
|
try:
|
83
|
-
methodname = self.get_method(path,keys)
|
93
|
+
methodname = self.get_method(path, keys)
|
84
94
|
method = self.methods[methodname]
|
85
|
-
except
|
86
|
-
log("Could not find a handler for method
|
87
|
-
log("Keys: "
|
95
|
+
except KeyError:
|
96
|
+
log(f"Could not find a handler for method {methodname} in API {self.__apiname__}", module="debug")
|
97
|
+
log(f"Keys: {keys}", module="debug")
|
88
98
|
raise InvalidMethodException()
|
89
|
-
return method(path,keys)
|
99
|
+
return method(path, keys)
|
90
100
|
|
91
101
|
|
92
102
|
def scrobble(self,rawscrobble,client=None):
|
93
103
|
|
94
104
|
# fixing etc is handled by the main scrobble function
|
95
|
-
|
96
|
-
return database.incoming_scrobble(rawscrobble,api=self.__apiname__,client=client)
|
97
|
-
except Exception:
|
98
|
-
raise ScrobblingException()
|
105
|
+
return database.incoming_scrobble(rawscrobble,api=self.__apiname__,client=client)
|
maloja/apis/_exceptions.py
CHANGED
maloja/apis/audioscrobbler.py
CHANGED
@@ -21,13 +21,22 @@ class Audioscrobbler(APIHandler):
|
|
21
21
|
"track.scrobble":self.submit_scrobble
|
22
22
|
}
|
23
23
|
self.errors = {
|
24
|
-
BadAuthException:(400,{"error":6,"message":"Requires authentication"}),
|
25
|
-
InvalidAuthException:(401,{"error":4,"message":"Invalid credentials"}),
|
26
|
-
InvalidMethodException:(200,{"error":3,"message":"Invalid method"}),
|
27
|
-
InvalidSessionKey:(403,{"error":9,"message":"Invalid session key"}),
|
28
|
-
|
24
|
+
BadAuthException: (400, {"error": 6, "message": "Requires authentication"}),
|
25
|
+
InvalidAuthException: (401, {"error": 4, "message": "Invalid credentials"}),
|
26
|
+
InvalidMethodException: (200, {"error": 3, "message": "Invalid method"}),
|
27
|
+
InvalidSessionKey: (403, {"error": 9, "message": "Invalid session key"}),
|
28
|
+
Exception: (500, {"error": 8, "message": "Operation failed"})
|
29
29
|
}
|
30
30
|
|
31
|
+
# xml string escaping: https://stackoverflow.com/a/28703510
|
32
|
+
def xml_escape(self, str_xml: str):
|
33
|
+
str_xml = str_xml.replace("&", "&")
|
34
|
+
str_xml = str_xml.replace("<", "<")
|
35
|
+
str_xml = str_xml.replace("<", "<")
|
36
|
+
str_xml = str_xml.replace("\"", """)
|
37
|
+
str_xml = str_xml.replace("'", "'")
|
38
|
+
return str_xml
|
39
|
+
|
31
40
|
def get_method(self,pathnodes,keys):
|
32
41
|
return keys.get("method")
|
33
42
|
|
@@ -45,12 +54,22 @@ class Audioscrobbler(APIHandler):
|
|
45
54
|
token = keys.get("authToken")
|
46
55
|
user = keys.get("username")
|
47
56
|
password = keys.get("password")
|
57
|
+
format = keys.get("format") or "xml" # Audioscrobbler 2.0 uses XML by default
|
48
58
|
# either username and password
|
49
59
|
if user is not None and password is not None:
|
50
60
|
client = apikeystore.check_and_identify_key(password)
|
51
61
|
if client:
|
52
62
|
sessionkey = self.generate_key(client)
|
53
|
-
|
63
|
+
if format == "json":
|
64
|
+
return 200,{"session":{"key":sessionkey}}
|
65
|
+
else:
|
66
|
+
return 200,"""<lfm status="ok">
|
67
|
+
<session>
|
68
|
+
<name>%s</name>
|
69
|
+
<key>%s</key>
|
70
|
+
<subscriber>0</subscriber>
|
71
|
+
</session>
|
72
|
+
</lfm>""" % (self.xml_escape(user), self.xml_escape(sessionkey))
|
54
73
|
else:
|
55
74
|
raise InvalidAuthException()
|
56
75
|
# or username and token (deprecated by lastfm)
|
@@ -59,7 +78,16 @@ class Audioscrobbler(APIHandler):
|
|
59
78
|
key = apikeystore[client]
|
60
79
|
if md5(user + md5(key)) == token:
|
61
80
|
sessionkey = self.generate_key(client)
|
62
|
-
|
81
|
+
if format == "json":
|
82
|
+
return 200,{"session":{"key":sessionkey}}
|
83
|
+
else:
|
84
|
+
return 200,"""<lfm status="ok">
|
85
|
+
<session>
|
86
|
+
<name>%s</name>
|
87
|
+
<key>%s</key>
|
88
|
+
<subscriber>0</subscriber>
|
89
|
+
</session>
|
90
|
+
</lfm>""" % (self.xml_escape(user), self.xml_escape(sessionkey))
|
63
91
|
raise InvalidAuthException()
|
64
92
|
else:
|
65
93
|
raise BadAuthException()
|
@@ -23,11 +23,11 @@ class AudioscrobblerLegacy(APIHandler):
|
|
23
23
|
"scrobble":self.submit_scrobble
|
24
24
|
}
|
25
25
|
self.errors = {
|
26
|
-
BadAuthException:(403,"BADAUTH\n"),
|
27
|
-
InvalidAuthException:(403,"BADAUTH\n"),
|
28
|
-
InvalidMethodException:(400,"FAILED\n"),
|
29
|
-
InvalidSessionKey:(403,"BADSESSION\n"),
|
30
|
-
|
26
|
+
BadAuthException: (403, "BADAUTH\n"),
|
27
|
+
InvalidAuthException: (403, "BADAUTH\n"),
|
28
|
+
InvalidMethodException: (400, "FAILED\n"),
|
29
|
+
InvalidSessionKey: (403, "BADSESSION\n"),
|
30
|
+
Exception: (500, "FAILED\n")
|
31
31
|
}
|
32
32
|
|
33
33
|
def get_method(self,pathnodes,keys):
|
maloja/apis/listenbrainz.py
CHANGED
@@ -3,6 +3,7 @@ from ._exceptions import *
|
|
3
3
|
from .. import database
|
4
4
|
import datetime
|
5
5
|
from ._apikeys import apikeystore
|
6
|
+
from ..database.exceptions import DuplicateScrobble, DuplicateTimestamp
|
6
7
|
|
7
8
|
from ..pkg_global.conf import malojaconfig
|
8
9
|
|
@@ -21,11 +22,13 @@ class Listenbrainz(APIHandler):
|
|
21
22
|
"validate-token":self.validate_token
|
22
23
|
}
|
23
24
|
self.errors = {
|
24
|
-
BadAuthException:(401,{"code":401,"error":"You need to provide an Authorization header."}),
|
25
|
-
InvalidAuthException:(401,{"code":401,"error":"Incorrect Authorization"}),
|
26
|
-
InvalidMethodException:(200,{"code":200,"error":"Invalid Method"}),
|
27
|
-
MalformedJSONException:(400,{"code":400,"error":"Invalid JSON document submitted."}),
|
28
|
-
|
25
|
+
BadAuthException: (401, {"code": 401, "error": "You need to provide an Authorization header."}),
|
26
|
+
InvalidAuthException: (401, {"code": 401, "error": "Incorrect Authorization"}),
|
27
|
+
InvalidMethodException: (200, {"code": 200, "error": "Invalid Method"}),
|
28
|
+
MalformedJSONException: (400, {"code": 400, "error": "Invalid JSON document submitted."}),
|
29
|
+
DuplicateScrobble: (200, {"status": "ok"}),
|
30
|
+
DuplicateTimestamp: (409, {"error": "Scrobble with the same timestamp already exists."}),
|
31
|
+
Exception: (500, {"code": 500, "error": "Unspecified server error."})
|
29
32
|
}
|
30
33
|
|
31
34
|
def get_method(self,pathnodes,keys):
|
maloja/apis/native_v1.py
CHANGED
@@ -7,7 +7,6 @@ from bottle import response, static_file, FormsDict
|
|
7
7
|
from inspect import signature
|
8
8
|
|
9
9
|
from doreah.logging import log
|
10
|
-
from doreah.auth import authenticated_function
|
11
10
|
|
12
11
|
# nimrodel API
|
13
12
|
from nimrodel import EAPI as API
|
@@ -15,7 +14,7 @@ from nimrodel import Multi
|
|
15
14
|
|
16
15
|
|
17
16
|
from .. import database
|
18
|
-
from ..pkg_global.conf import malojaconfig, data_dir
|
17
|
+
from ..pkg_global.conf import malojaconfig, data_dir, auth
|
19
18
|
|
20
19
|
|
21
20
|
|
@@ -82,6 +81,24 @@ errors = {
|
|
82
81
|
'desc':"This entity does not exist in the database."
|
83
82
|
}
|
84
83
|
}),
|
84
|
+
database.exceptions.DuplicateTimestamp: lambda e: (409,{
|
85
|
+
"status":"error",
|
86
|
+
"error":{
|
87
|
+
'type':'duplicate_timestamp',
|
88
|
+
'value':e.rejected_scrobble,
|
89
|
+
'desc':"A scrobble is already registered with this timestamp."
|
90
|
+
}
|
91
|
+
}),
|
92
|
+
database.exceptions.DuplicateScrobble: lambda e: (200,{
|
93
|
+
"status": "success",
|
94
|
+
"desc": "The scrobble is present in the database.",
|
95
|
+
"track": {},
|
96
|
+
"warnings": [{
|
97
|
+
'type': 'scrobble_exists',
|
98
|
+
'value': None,
|
99
|
+
'desc': 'This scrobble exists in the database (same timestamp and track). The submitted scrobble was not added.'
|
100
|
+
}]
|
101
|
+
}),
|
85
102
|
images.MalformedB64: lambda e: (400,{
|
86
103
|
"status":"failure",
|
87
104
|
"error":{
|
@@ -474,7 +491,7 @@ def get_top_artists_external(k_filter, k_limit, k_delimit, k_amount):
|
|
474
491
|
:rtype: Dictionary"""
|
475
492
|
|
476
493
|
ckeys = {**k_limit, **k_delimit}
|
477
|
-
results = database.get_top_artists(**ckeys)
|
494
|
+
results = database.get_top_artists(**ckeys,compatibility=True)
|
478
495
|
|
479
496
|
return {
|
480
497
|
"status":"ok",
|
@@ -493,7 +510,7 @@ def get_top_tracks_external(k_filter, k_limit, k_delimit, k_amount):
|
|
493
510
|
:rtype: Dictionary"""
|
494
511
|
|
495
512
|
ckeys = {**k_limit, **k_delimit}
|
496
|
-
results = database.get_top_tracks(**ckeys)
|
513
|
+
results = database.get_top_tracks(**ckeys,compatibility=True)
|
497
514
|
# IMPLEMENT THIS FOR TOP TRACKS OF ARTIST/ALBUM AS WELL?
|
498
515
|
|
499
516
|
return {
|
@@ -513,7 +530,7 @@ def get_top_albums_external(k_filter, k_limit, k_delimit, k_amount):
|
|
513
530
|
:rtype: Dictionary"""
|
514
531
|
|
515
532
|
ckeys = {**k_limit, **k_delimit}
|
516
|
-
results = database.get_top_albums(**ckeys)
|
533
|
+
results = database.get_top_albums(**ckeys,compatibility=True)
|
517
534
|
# IMPLEMENT THIS FOR TOP ALBUMS OF ARTIST AS WELL?
|
518
535
|
|
519
536
|
return {
|
@@ -567,7 +584,7 @@ def album_info_external(k_filter, k_limit, k_delimit, k_amount):
|
|
567
584
|
|
568
585
|
|
569
586
|
@api.post("newscrobble")
|
570
|
-
@authenticated_function(alternate=api_key_correct,api=True,pass_auth_result_as='auth_result')
|
587
|
+
@auth.authenticated_function(alternate=api_key_correct,api=True,pass_auth_result_as='auth_result')
|
571
588
|
@catch_exceptions
|
572
589
|
def post_scrobble(
|
573
590
|
artist:Multi=None,
|
@@ -647,7 +664,7 @@ def post_scrobble(
|
|
647
664
|
|
648
665
|
|
649
666
|
@api.post("addpicture")
|
650
|
-
@authenticated_function(alternate=api_key_correct,api=True)
|
667
|
+
@auth.authenticated_function(alternate=api_key_correct,api=True)
|
651
668
|
@catch_exceptions
|
652
669
|
@convert_kwargs
|
653
670
|
def add_picture(k_filter, k_limit, k_delimit, k_amount, k_special):
|
@@ -670,7 +687,7 @@ def add_picture(k_filter, k_limit, k_delimit, k_amount, k_special):
|
|
670
687
|
|
671
688
|
|
672
689
|
@api.post("importrules")
|
673
|
-
@authenticated_function(api=True)
|
690
|
+
@auth.authenticated_function(api=True)
|
674
691
|
@catch_exceptions
|
675
692
|
def import_rulemodule(**keys):
|
676
693
|
"""Internal Use Only"""
|
@@ -689,7 +706,7 @@ def import_rulemodule(**keys):
|
|
689
706
|
|
690
707
|
|
691
708
|
@api.post("rebuild")
|
692
|
-
@authenticated_function(api=True)
|
709
|
+
@auth.authenticated_function(api=True)
|
693
710
|
@catch_exceptions
|
694
711
|
def rebuild(**keys):
|
695
712
|
"""Internal Use Only"""
|
@@ -765,7 +782,7 @@ def search(**keys):
|
|
765
782
|
|
766
783
|
|
767
784
|
@api.post("newrule")
|
768
|
-
@authenticated_function(api=True)
|
785
|
+
@auth.authenticated_function(api=True)
|
769
786
|
@catch_exceptions
|
770
787
|
def newrule(**keys):
|
771
788
|
"""Internal Use Only"""
|
@@ -776,21 +793,21 @@ def newrule(**keys):
|
|
776
793
|
|
777
794
|
|
778
795
|
@api.post("settings")
|
779
|
-
@authenticated_function(api=True)
|
796
|
+
@auth.authenticated_function(api=True)
|
780
797
|
@catch_exceptions
|
781
798
|
def set_settings(**keys):
|
782
799
|
"""Internal Use Only"""
|
783
800
|
malojaconfig.update(keys)
|
784
801
|
|
785
802
|
@api.post("apikeys")
|
786
|
-
@authenticated_function(api=True)
|
803
|
+
@auth.authenticated_function(api=True)
|
787
804
|
@catch_exceptions
|
788
805
|
def set_apikeys(**keys):
|
789
806
|
"""Internal Use Only"""
|
790
807
|
apikeystore.update(keys)
|
791
808
|
|
792
809
|
@api.post("import")
|
793
|
-
@authenticated_function(api=True)
|
810
|
+
@auth.authenticated_function(api=True)
|
794
811
|
@catch_exceptions
|
795
812
|
def import_scrobbles(identifier):
|
796
813
|
"""Internal Use Only"""
|
@@ -798,7 +815,7 @@ def import_scrobbles(identifier):
|
|
798
815
|
import_scrobbles(identifier)
|
799
816
|
|
800
817
|
@api.get("backup")
|
801
|
-
@authenticated_function(api=True)
|
818
|
+
@auth.authenticated_function(api=True)
|
802
819
|
@catch_exceptions
|
803
820
|
def get_backup(**keys):
|
804
821
|
"""Internal Use Only"""
|
@@ -811,7 +828,7 @@ def get_backup(**keys):
|
|
811
828
|
return static_file(os.path.basename(archivefile),root=tmpfolder)
|
812
829
|
|
813
830
|
@api.get("export")
|
814
|
-
@authenticated_function(api=True)
|
831
|
+
@auth.authenticated_function(api=True)
|
815
832
|
@catch_exceptions
|
816
833
|
def get_export(**keys):
|
817
834
|
"""Internal Use Only"""
|
@@ -825,7 +842,7 @@ def get_export(**keys):
|
|
825
842
|
|
826
843
|
|
827
844
|
@api.post("delete_scrobble")
|
828
|
-
@authenticated_function(api=True)
|
845
|
+
@auth.authenticated_function(api=True)
|
829
846
|
@catch_exceptions
|
830
847
|
def delete_scrobble(timestamp):
|
831
848
|
"""Internal Use Only"""
|
@@ -837,7 +854,7 @@ def delete_scrobble(timestamp):
|
|
837
854
|
|
838
855
|
|
839
856
|
@api.post("edit_artist")
|
840
|
-
@authenticated_function(api=True)
|
857
|
+
@auth.authenticated_function(api=True)
|
841
858
|
@catch_exceptions
|
842
859
|
def edit_artist(id,name):
|
843
860
|
"""Internal Use Only"""
|
@@ -847,7 +864,7 @@ def edit_artist(id,name):
|
|
847
864
|
}
|
848
865
|
|
849
866
|
@api.post("edit_track")
|
850
|
-
@authenticated_function(api=True)
|
867
|
+
@auth.authenticated_function(api=True)
|
851
868
|
@catch_exceptions
|
852
869
|
def edit_track(id,title):
|
853
870
|
"""Internal Use Only"""
|
@@ -857,7 +874,7 @@ def edit_track(id,title):
|
|
857
874
|
}
|
858
875
|
|
859
876
|
@api.post("edit_album")
|
860
|
-
@authenticated_function(api=True)
|
877
|
+
@auth.authenticated_function(api=True)
|
861
878
|
@catch_exceptions
|
862
879
|
def edit_album(id,albumtitle):
|
863
880
|
"""Internal Use Only"""
|
@@ -868,7 +885,7 @@ def edit_album(id,albumtitle):
|
|
868
885
|
|
869
886
|
|
870
887
|
@api.post("merge_tracks")
|
871
|
-
@authenticated_function(api=True)
|
888
|
+
@auth.authenticated_function(api=True)
|
872
889
|
@catch_exceptions
|
873
890
|
def merge_tracks(target_id,source_ids):
|
874
891
|
"""Internal Use Only"""
|
@@ -879,7 +896,7 @@ def merge_tracks(target_id,source_ids):
|
|
879
896
|
}
|
880
897
|
|
881
898
|
@api.post("merge_artists")
|
882
|
-
@authenticated_function(api=True)
|
899
|
+
@auth.authenticated_function(api=True)
|
883
900
|
@catch_exceptions
|
884
901
|
def merge_artists(target_id,source_ids):
|
885
902
|
"""Internal Use Only"""
|
@@ -890,7 +907,7 @@ def merge_artists(target_id,source_ids):
|
|
890
907
|
}
|
891
908
|
|
892
909
|
@api.post("merge_albums")
|
893
|
-
@authenticated_function(api=True)
|
910
|
+
@auth.authenticated_function(api=True)
|
894
911
|
@catch_exceptions
|
895
912
|
def merge_artists(target_id,source_ids):
|
896
913
|
"""Internal Use Only"""
|
@@ -901,7 +918,7 @@ def merge_artists(target_id,source_ids):
|
|
901
918
|
}
|
902
919
|
|
903
920
|
@api.post("associate_albums_to_artist")
|
904
|
-
@authenticated_function(api=True)
|
921
|
+
@auth.authenticated_function(api=True)
|
905
922
|
@catch_exceptions
|
906
923
|
def associate_albums_to_artist(target_id,source_ids,remove=False):
|
907
924
|
result = database.associate_albums_to_artist(target_id,source_ids,remove=remove)
|
@@ -913,7 +930,7 @@ def associate_albums_to_artist(target_id,source_ids,remove=False):
|
|
913
930
|
}
|
914
931
|
|
915
932
|
@api.post("associate_tracks_to_artist")
|
916
|
-
@authenticated_function(api=True)
|
933
|
+
@auth.authenticated_function(api=True)
|
917
934
|
@catch_exceptions
|
918
935
|
def associate_tracks_to_artist(target_id,source_ids,remove=False):
|
919
936
|
result = database.associate_tracks_to_artist(target_id,source_ids,remove=remove)
|
@@ -925,7 +942,7 @@ def associate_tracks_to_artist(target_id,source_ids,remove=False):
|
|
925
942
|
}
|
926
943
|
|
927
944
|
@api.post("associate_tracks_to_album")
|
928
|
-
@authenticated_function(api=True)
|
945
|
+
@auth.authenticated_function(api=True)
|
929
946
|
@catch_exceptions
|
930
947
|
def associate_tracks_to_album(target_id,source_ids):
|
931
948
|
result = database.associate_tracks_to_album(target_id,source_ids)
|
@@ -937,7 +954,7 @@ def associate_tracks_to_album(target_id,source_ids):
|
|
937
954
|
|
938
955
|
|
939
956
|
@api.post("reparse_scrobble")
|
940
|
-
@authenticated_function(api=True)
|
957
|
+
@auth.authenticated_function(api=True)
|
941
958
|
@catch_exceptions
|
942
959
|
def reparse_scrobble(timestamp):
|
943
960
|
"""Internal Use Only"""
|
maloja/cleanup.py
CHANGED
@@ -15,13 +15,15 @@ class CleanerAgent:
|
|
15
15
|
def updateRules(self):
|
16
16
|
|
17
17
|
rawrules = []
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
18
|
+
try:
|
19
|
+
for f in os.listdir(data_dir["rules"]()):
|
20
|
+
if f.split('.')[-1].lower() != 'tsv': continue
|
21
|
+
filepath = data_dir["rules"](f)
|
22
|
+
with open(filepath,'r') as filed:
|
23
|
+
reader = csv.reader(filed,delimiter="\t")
|
24
|
+
rawrules += [[col for col in entry if col] for entry in reader if len(entry)>0 and not entry[0].startswith('#')]
|
25
|
+
except FileNotFoundError:
|
26
|
+
pass
|
25
27
|
|
26
28
|
self.rules_belongtogether = [r[1] for r in rawrules if r[0]=="belongtogether"]
|
27
29
|
self.rules_notanartist = [r[1] for r in rawrules if r[0]=="notanartist"]
|
@@ -160,8 +160,8 @@ replaceartist 여자친구 GFriend GFriend
|
|
160
160
|
# Girl's Generation
|
161
161
|
replaceartist 소녀시대 Girls' Generation
|
162
162
|
replaceartist SNSD Girls' Generation
|
163
|
-
replaceartist
|
164
|
-
countas
|
163
|
+
replaceartist Girls' Generation-TTS TaeTiSeo
|
164
|
+
countas TaeTiSeo Girls' Generation
|
165
165
|
|
166
166
|
# Apink
|
167
167
|
replaceartist A Pink Apink
|