From 56cc06d9052ddbd1da5a61dd6a6def3be7153f21 Mon Sep 17 00:00:00 2001 From: Krateng Date: Wed, 29 Jul 2020 15:52:01 +0200 Subject: [PATCH 01/14] Implemented proper authentication for backend --- maloja/__pkginfo__.py | 2 +- maloja/data_files/auth/dummy | 0 maloja/globalconf.py | 6 ++++++ maloja/server.py | 14 ++++++++++++++ 4 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 maloja/data_files/auth/dummy diff --git a/maloja/__pkginfo__.py b/maloja/__pkginfo__.py index a3fa867..317c320 100644 --- a/maloja/__pkginfo__.py +++ b/maloja/__pkginfo__.py @@ -15,7 +15,7 @@ links = { requires = [ "bottle>=0.12.16", "waitress>=1.3", - "doreah>=1.6.7", + "doreah>=1.6.8", "nimrodel>=0.6.3", "setproctitle>=1.1.10", "wand>=0.5.4", diff --git a/maloja/data_files/auth/dummy b/maloja/data_files/auth/dummy new file mode 100644 index 0000000..e69de29 diff --git a/maloja/globalconf.py b/maloja/globalconf.py index 5ed516f..bba0938 100644 --- a/maloja/globalconf.py +++ b/maloja/globalconf.py @@ -49,6 +49,12 @@ config( }, regular={ "autostart": False + }, + auth={ + "multiuser":False, + "cookieprefix":"maloja", + "stylesheets":["/style.css"], + "dbfile":datadir("auth/auth.ddb") } ) diff --git a/maloja/server.py b/maloja/server.py index 51eb690..bc322f4 100755 --- a/maloja/server.py +++ b/maloja/server.py @@ -25,6 +25,7 @@ from doreah import settings from doreah.logging import log from doreah.timing import Clock from doreah.pyhp import file as pyhpfile +from doreah.auth import get_login_page, authapi, authenticated # technical #from importlib.machinery import SourceFileLoader import importlib @@ -55,6 +56,7 @@ STATICFOLDER = pkg_resources.resource_filename(__name__,"static") DATAFOLDER = DATA_DIR webserver = Bottle() +authapi.mount(server=webserver) pthjoin = os.path.join @@ -158,6 +160,10 @@ def get_css(): return css +@webserver.route("/login") +def login(): + return get_login_page() + @webserver.route("/.") def static(name,ext): assert ext in ["txt","ico","jpeg","jpg","png","less","js"] @@ -216,7 +222,15 @@ jinjaenv = Environment( jinjaenv.globals.update(JINJA_CONTEXT) +@webserver.route("/") +@authenticated +def static_html_private(name): + return static_html(name) + @webserver.route("/") +def static_html_public(name): + return static_html(name) + def static_html(name): linkheaders = ["; rel=preload; as=style"] keys = remove_identical(FormsDict.decode(request.query)) From 2c754c75ce7db0490a4bb041d4913cbfd527364e Mon Sep 17 00:00:00 2001 From: Krateng Date: Wed, 29 Jul 2020 17:04:38 +0200 Subject: [PATCH 02/14] Updated admin panel and issues page --- maloja/web/jinja/issues.jinja | 89 +++++++++++++++++++++++++++++++++++ maloja/web/pyhp/admin.pyhp | 24 ++-------- 2 files changed, 92 insertions(+), 21 deletions(-) create mode 100644 maloja/web/jinja/issues.jinja diff --git a/maloja/web/jinja/issues.jinja b/maloja/web/jinja/issues.jinja new file mode 100644 index 0000000..b1672e4 --- /dev/null +++ b/maloja/web/jinja/issues.jinja @@ -0,0 +1,89 @@ +{% extends "base.jinja" %} +{% block title %}Maloja - Issues{% endblock %} + + +{% block scripts %} + + +{% endblock %} + +{% set issuedata = dbp.issues() %} + +{% block content %} + + + + + + +
+
+
+

Possible Issues


+ with your library + + +

Maloja can identify possible problems with consistency or redundancy in your library. After making any changes, you should rebuild your library.

+
+ + + +{% if issuedata.inconsistent %} + + + + +{% endif %} + +{% for issue in issuedata.duplicates %} + + + + + +{% endfor %} + +{% for issue in issuedata.combined %} + + + + +{% endfor %} + +{% for issue in issuedata.newartists %} + + + + +{% endfor %} + +
The current database wasn't built with all current rules in effect. Any problem below might be a false alarm and fixing it could create redundant rules.
Rebuild the database
{{ htmlgenerators.artistLink(issue[0]) }} is a possible duplicate of {{ htmlgenerators.artistLink(issue[1]) }}
{{ issue[1] }} is correct
{{ issue[0] }} is correct
{{ artistLink(issue[0]) }} sounds like the combination of {{ len(issue[1]) }} artists: + {{ issue[1]|join(", ") }} +
Fix it
Is '{{ issue[0] }}' in '{{ htmlgenerators.artistLink(issue[1]) }}' an artist?
Yes
+ +{% endblock %} diff --git a/maloja/web/pyhp/admin.pyhp b/maloja/web/pyhp/admin.pyhp index 61ff121..8b00718 100644 --- a/maloja/web/pyhp/admin.pyhp +++ b/maloja/web/pyhp/admin.pyhp @@ -53,22 +53,6 @@ window.location.reload(true); } - function buttonlock() { - button = document.getElementById("adminmodebutton") - if (apikeycorrect) { - button.classList.remove("locked"); - if (button.innerHTML == "Activate") { button.onclick = activate; } - else { button.onclick = deactivate; } - // ugh - } - else { - button.classList.add("locked"); - button.onclick = null; - } - } - - window.addEventListener("load",function(){checkAPIkey(buttonlock)}); - // we do this twice, but this one ensures that the button is correctly locked / unlocked after the api key has been checked @@ -83,9 +67,7 @@
-

Admin Panel


-

- API Key:

+

Admin Panel

@@ -101,8 +83,8 @@

Admin Mode

Admin Mode allows you to manually scrobble from various places on the web interface instead of just the dedicated page.

- Deactivate - Activate + Deactivate + Activate

Links

From a0a8ba4052082c60e96a5ce54df9c423ffd82faa Mon Sep 17 00:00:00 2001 From: Krateng Date: Wed, 29 Jul 2020 17:15:03 +0200 Subject: [PATCH 03/14] Updated manual scrobbling page --- maloja/web/issues.html | 68 ------------ maloja/web/issues.py | 44 -------- maloja/web/jinja/manual.jinja | 186 +++++++++++++++++++++++++++++++ maloja/web/manual.html | 199 ---------------------------------- 4 files changed, 186 insertions(+), 311 deletions(-) delete mode 100644 maloja/web/issues.html delete mode 100644 maloja/web/issues.py create mode 100644 maloja/web/jinja/manual.jinja delete mode 100644 maloja/web/manual.html diff --git a/maloja/web/issues.html b/maloja/web/issues.html deleted file mode 100644 index 1a8bb9e..0000000 --- a/maloja/web/issues.html +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - Maloja - Issues - - - - - - - - - -
-
-
-

Possible Issues


- with your library -

KEY_ISSUES Issues

- -

Maloja can identify possible problems with consistency or redundancy in your library. After making any changes, you should rebuild your library.
- Your API key is required to make any changes to the server:

-
- - KEY_ISSUESLIST - - - - - diff --git a/maloja/web/issues.py b/maloja/web/issues.py deleted file mode 100644 index eac9cdc..0000000 --- a/maloja/web/issues.py +++ /dev/null @@ -1,44 +0,0 @@ -import urllib -from .. import database -from ..htmlgenerators import artistLink - -def instructions(keys): - - db_data = database.issues() - i = 0 - - html = "" - if db_data["inconsistent"]: - html += "" - html += "" - html += """""" - html += "" - i += 1 - for d in db_data["duplicates"]: - html += "" - html += "" - html += """""" - html += """""" - html += "" - i += 1 - for c in db_data["combined"]: - html += "" - html += "" - html += """""" - html += "" - i += 1 - for n in db_data["newartists"]: - html += "" - html += "" - html += """""" - html += "" - i += 1 - - html += "
The current database wasn't built with all current rules in effect. Any problem below might be a false alarm and fixing it could create redundant rules.
Rebuild the database
'" + artistLink(d[0]) + "'" - html += " is a possible duplicate of " - html += "'" + artistLink(d[1]) + "'
""" + d[1] + """ is correct
""" + d[0] + """ is correct
'" + artistLink(c[0]) + "' sounds like the combination of " + str(len(c[1])) + " artists: " - for a in c[1]: - html += "'" + artistLink(a) + "' " - html += "
Fix it
Is '" + n[0] + "' in '" + artistLink(n[1]) + "' an artist?
Yes
" - - return ({"KEY_ISSUESLIST":html,"KEY_ISSUES":str(i)},[]) diff --git a/maloja/web/jinja/manual.jinja b/maloja/web/jinja/manual.jinja new file mode 100644 index 0000000..fbfbb51 --- /dev/null +++ b/maloja/web/jinja/manual.jinja @@ -0,0 +1,186 @@ +{% extends "base.jinja" %} +{% block title %}Maloja - Manual Scrobbling{% endblock %} + +{% block scripts %} + + + +{% endblock %} + + + +{% block content %} + + + + + + +
+
+
+

Manual Scrobbling


+

+ + +
+ +

Scrobble new discovery

+ + + + + + + + +
+ Artists: + + +
+ Title: + + +
+ +
+ + Scrobble! + +
+ + + +

Search

+ + +

+
+ + +{% endblock %} diff --git a/maloja/web/manual.html b/maloja/web/manual.html deleted file mode 100644 index e880d5c..0000000 --- a/maloja/web/manual.html +++ /dev/null @@ -1,199 +0,0 @@ - - - - - - Maloja - - - - - - - - - - - - - - - - -
-
-
-

Manual Scrobbling


-

- API Key:

- - -
- -

Scrobble new discovery

- - - - - - - - -
- Artists: - - -
- Title: - - -
- -
- - Scrobble! - -
- - - -

Search

- - -

-
- - - - - - - - - From 0ddb5a4dd9f970c3520053efc2ad961357b2f281 Mon Sep 17 00:00:00 2001 From: Krateng Date: Wed, 29 Jul 2020 17:49:55 +0200 Subject: [PATCH 04/14] Updated server setup page --- maloja/database.py | 47 ++++++++++++ maloja/web/jinja/setup.jinja | 140 +++++++++++++++++++++++++++++++++++ maloja/web/setup.html | 133 --------------------------------- maloja/web/setup.py | 57 -------------- 4 files changed, 187 insertions(+), 190 deletions(-) create mode 100644 maloja/web/jinja/setup.jinja delete mode 100644 maloja/web/setup.html delete mode 100644 maloja/web/setup.py diff --git a/maloja/database.py b/maloja/database.py index 608da89..c3dec71 100644 --- a/maloja/database.py +++ b/maloja/database.py @@ -829,6 +829,53 @@ def issues(): return {"duplicates":duplicates,"combined":combined,"newartists":newartists,"inconsistent":inconsistent} +def get_predefined_rulesets(): + validchars = "-_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + + rulesets = [] + + for f in os.listdir(datadir("rules/predefined")): + if f.endswith(".tsv"): + + rawf = f.replace(".tsv","") + valid = True + for char in rawf: + if char not in validchars: + valid = False + break # don't even show up invalid filenames + + if not valid: continue + if not "_" in rawf: continue + + try: + with open(datadir("rules/predefined",f)) as tsvfile: + line1 = tsvfile.readline() + line2 = tsvfile.readline() + + if "# NAME: " in line1: + name = line1.replace("# NAME: ","") + else: name = rawf.split("_")[1] + if "# DESC: " in line2: + desc = line2.replace("# DESC: ","") + else: desc = "" + + author = rawf.split("_")[0] + except: + continue + + ruleset = {"file":rawf} + rulesets.append(ruleset) + if os.path.exists(datadir("rules",f)): + ruleset["active"] = True + else: + ruleset["active"] = False + + ruleset["name"] = name + ruleset["author"] = author + ruleset["desc"] = desc + + return rulesets + @dbserver.post("importrules") def import_rulemodule(**keys): apikey = keys.pop("key",None) diff --git a/maloja/web/jinja/setup.jinja b/maloja/web/jinja/setup.jinja new file mode 100644 index 0000000..5fd88fe --- /dev/null +++ b/maloja/web/jinja/setup.jinja @@ -0,0 +1,140 @@ +{% extends "base.jinja" %} +{% block title %}Maloja - Setup{% endblock %} + +{% block scripts %} + + + +{% endblock %} + +{% set rulesets = dbp.get_predefined_rulesets() %} + +{% block content %} + + + + + + + +
+
+
+

Maloja


+ +

Welcome to your own Maloja server!

+
+ +

Start Scrobbling

+ + If you use Vivaldi, Brave, Iridium or any other Chromium-based browser and listen to music on Plex or YouTube Music, download the extension and simply enter the server URL as well as your API key in the relevant fields. They will turn green if the server is accessible. +

+ You can also use any standard-compliant scrobbler. For GNUFM (audioscrobbler) scrobblers, enter yourserver.tld/api/s/audioscrobbler as your Gnukebox server and your API key as the password. For Listenbrainz scrobblers, use yourserver.tld/api/s/listenbrainz as the API URL and your API key as token. +

+ If you use another browser or another music player, you could try to code your own extension. The API is super simple! Just send a POST HTTP request to + + yourserver.tld/api/newscrobble + + (make sure to use the public URL) with the key-value-pairs +
+
+ + + + + +
artist Artist String
title Title String
key API Key
seconds Duration of Scrobble - optional and currently not used
+

+ Finally, you could always manually scrobble! + +

+ +

Import your Last.FM data

+ + Switching from Last.fm? Download all your data and run the command maloja import (the file you just downloaded). +

+ +

Set up some rules

+ + After you've scrobbled for a bit, you might want to check the Issues page to see if you need to set up some rules. You can also manually add rules in your server's "rules" directory - just add your own .tsv file and read the instructions on how to declare a rule. +

+ + You can also set up some predefined rulesets right away! Enter your API key and click the buttons. +
+ +

+ + + + + + + + {% for rs in rulesets %} + + {% if rs.active %} + + {% else %} + + {% endif %} + + + + + {% endfor %} +
ModuleAuthorDescription
Remove:Add:{{ rs.name }}{{ rs.author }}{{ rs.desc }}
+ +

+ +

Say thanks

+ + Donations are never required, but always appreciated. If you really like Maloja, you can fund my next Buttergipfel via + PayPal, Bitcoin or Flattr. + +

+ +

View your stats

+ + Done! Visit yourserver.tld (or your public / proxy URL) to look at your overview page. Almost everything is clickable! + +{% endblock %} diff --git a/maloja/web/setup.html b/maloja/web/setup.html deleted file mode 100644 index fed262d..0000000 --- a/maloja/web/setup.html +++ /dev/null @@ -1,133 +0,0 @@ - - - - - - Maloja - Setup - - - - - - - - - - - - - - -
-
-
-

Maloja


- -

Welcome to your own Maloja server!

-
- -

Start Scrobbling

- - If you use Vivaldi, Brave, Iridium or any other Chromium-based browser and listen to music on Plex or YouTube Music, download the extension and simply enter the server URL as well as your API key in the relevant fields. They will turn green if the server is accessible. -

- You can also use any standard-compliant scrobbler. For GNUFM (audioscrobbler) scrobblers, enter yourserver.tld/api/s/audioscrobbler as your Gnukebox server and your API key as the password. For Listenbrainz scrobblers, use yourserver.tld/api/s/listenbrainz as the API URL and your API key as token. -

- If you use another browser or another music player, you could try to code your own extension. The API is super simple! Just send a POST HTTP request to - - yourserver.tld/api/newscrobble - - (make sure to use the public URL) with the key-value-pairs -
-
- - - - - -
artist Artist String
title Title String
key API Key
seconds Duration of Scrobble - optional and currently not used
-

- Finally, you could always manually scrobble! - -

- -

Import your Last.FM data

- - Switching from Last.fm? Download all your data and run the command maloja import (the file you just downloaded). -

- -

Set up some rules

- - After you've scrobbled for a bit, you might want to check the Issues page to see if you need to set up some rules. You can also manually add rules in your server's "rules" directory - just add your own .tsv file and read the instructions on how to declare a rule. -

- - You can also set up some predefined rulesets right away! Enter your API key and click the buttons. -
- API Key: - - -

- KEY_PREDEFINED_RULESETS - -

- -

Say thanks

- - Coding open source projects is fun, but not really monetizable. If you like Maloja, I would appreciate a small donation via - PayPal, Bitcoin or Flattr. - -

- -

View your stats

- - Done! Visit yourserver.tld (or your public / proxy URL) to look at your overview page. Almost everything is clickable! - - - - - diff --git a/maloja/web/setup.py b/maloja/web/setup.py deleted file mode 100644 index 596a3c1..0000000 --- a/maloja/web/setup.py +++ /dev/null @@ -1,57 +0,0 @@ -import os -from ..globalconf import datadir - -def instructions(keys): - - html = "" - - html += "" - - - validchars = "-_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" - for f in os.listdir(datadir("rules/predefined")): - if f.endswith(".tsv"): - - rawf = f.replace(".tsv","") - valid = True - for char in rawf: - if char not in validchars: - valid = False - break # don't even show up invalid filenames - - if not valid: continue - if not "_" in rawf: continue - - try: - with open(datadir("rules/predefined",f)) as tsvfile: - line1 = tsvfile.readline() - line2 = tsvfile.readline() - - if "# NAME: " in line1: - name = line1.replace("# NAME: ","") - else: name = rawf.split("_")[1] - if "# DESC: " in line2: - desc = line2.replace("# DESC: ","") - else: desc = "" - - author = rawf.split("_")[0] - except: - continue - - html += "" - - if os.path.exists(datadir("rules",f)): - html += "" - else: - html += "" - html += "" - html += "" - html += "" - - html += "" - html += "
ModuleAuthorDescription
Remove:Add:" + name + "" + author + "" + desc + "
" - - - pushresources = [] - replace = {"KEY_PREDEFINED_RULESETS":html} - return (replace,pushresources) From b5b09c405232dc20d1777cbe2deb0afba3dcd668 Mon Sep 17 00:00:00 2001 From: Krateng Date: Wed, 29 Jul 2020 20:11:51 +0200 Subject: [PATCH 05/14] Updated some methods to new authentication method --- maloja/database.py | 105 +++++++++++++++++++--------------- maloja/static/js/upload.js | 4 +- maloja/web/jinja/artist.jinja | 2 +- maloja/web/jinja/setup.jinja | 2 +- maloja/web/jinja/track.jinja | 2 +- 5 files changed, 63 insertions(+), 52 deletions(-) diff --git a/maloja/database.py b/maloja/database.py index c3dec71..e1e90d2 100644 --- a/maloja/database.py +++ b/maloja/database.py @@ -18,6 +18,7 @@ from doreah.logging import log from doreah import tsv from doreah import settings from doreah.caching import Cache, DeepCache +from doreah.auth import authenticated_api, authenticated_api_with_alternate try: from doreah.persistence import DiskDict except: pass @@ -240,6 +241,23 @@ def normalize_name(name): ######## ######## +# skip regular authentication if api key is present in request +# an api key now ONLY permits scrobbling tracks, no other admin tasks +def api_key_correct(request): + args = request.query + print(dict(args)) + if "key" in args: + apikey = args["key"] + print(args) + del args["key"] + print(args) + elif "apikey" in args: + apikey = args["apikey"] + del args["apikey"] + else: return False + + return checkAPIkey(apikey) + dbserver = API(delay=True,path="api") @@ -669,23 +687,19 @@ def trackInfo(track): @dbserver.get("newscrobble") @dbserver.post("newscrobble") +@authenticated_api_with_alternate(api_key_correct) def post_scrobble(artist:Multi,**keys): artists = "/".join(artist) title = keys.get("title") album = keys.get("album") duration = keys.get("seconds") - apikey = keys.get("key") - client = checkAPIkey(apikey) - if client == False: # empty string allowed! - response.status = 403 - return "" try: time = int(keys.get("time")) except: time = int(datetime.datetime.now(tz=datetime.timezone.utc).timestamp()) - log("Incoming scrobble (native API): Client " + client + ", ARTISTS: " + str(artists) + ", TRACK: " + title,module="debug") + log("Incoming scrobble (native API): ARTISTS: " + str(artists) + ", TRACK: " + title,module="debug") (artists,title) = cla.fullclean(artists,title) ## this is necessary for localhost testing @@ -719,18 +733,18 @@ def sapi(path:Multi,**keys): @dbserver.get("sync") +@authenticated_api def abouttoshutdown(): sync() #sys.exit() @dbserver.post("newrule") +@authenticated_api def newrule(**keys): - apikey = keys.pop("key",None) - if (checkAPIkey(apikey)): - tsv.add_entry(datadir("rules/webmade.tsv"),[k for k in keys]) - #addEntry("rules/webmade.tsv",[k for k in keys]) - global db_rulestate - db_rulestate = False + tsv.add_entry(datadir("rules/webmade.tsv"),[k for k in keys]) + #addEntry("rules/webmade.tsv",[k for k in keys]) + global db_rulestate + db_rulestate = False @dbserver.get("issues") @@ -877,39 +891,36 @@ def get_predefined_rulesets(): return rulesets @dbserver.post("importrules") +@authenticated_api def import_rulemodule(**keys): - apikey = keys.pop("key",None) + 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 (checkAPIkey(apikey)): - 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) - os.remove(datadir("rules/" + filename + ".tsv")) - else: - log("Importing predefined rulefile " + filename) - os.symlink(datadir("rules/predefined/" + filename + ".tsv"),datadir("rules/" + filename + ".tsv")) + if remove: + log("Deactivating predefined rulefile " + filename) + os.remove(datadir("rules/" + filename + ".tsv")) + else: + log("Importing predefined rulefile " + filename) + os.symlink(datadir("rules/predefined/" + filename + ".tsv"),datadir("rules/" + filename + ".tsv")) @dbserver.post("rebuild") +@authenticated_api def rebuild(**keys): - apikey = keys.pop("key",None) - if (checkAPIkey(apikey)): - log("Database rebuild initiated!") - global db_rulestate - db_rulestate = False - sync() - from .proccontrol.tasks.fixexisting import fix - fix() - global cla, coa - cla = CleanerAgent() - coa = CollectorAgent() - build_db() - invalidate_caches() + log("Database rebuild initiated!") + global db_rulestate + db_rulestate = False + sync() + from .proccontrol.tasks.fixexisting import fix + fix() + global cla, coa + cla = CleanerAgent() + coa = CollectorAgent() + build_db() + invalidate_caches() @@ -950,15 +961,15 @@ def search(**keys): @dbserver.post("addpicture") -def add_picture(b64,key,artist:Multi=[],title=None): - if (checkAPIkey(key)): - 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"] - utilities.set_image(b64,**k_filter) +@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) + k_filter, _, _, _ = uri_to_internal(keys) + if "track" in k_filter: k_filter = k_filter["track"] + utilities.set_image(b64,**k_filter) #### ## Server operation diff --git a/maloja/static/js/upload.js b/maloja/static/js/upload.js index 103ea12..797543a 100644 --- a/maloja/static/js/upload.js +++ b/maloja/static/js/upload.js @@ -1,3 +1,3 @@ -function upload(encodedentity,apikey,b64) { - neo.xhttprequest("/api/addpicture?key=" + apikey + "&" + encodedentity,{"b64":b64},"POST") +function upload(encodedentity,b64) { + neo.xhttprequest("/api/addpicture?" + encodedentity,{"b64":b64},"POST") } diff --git a/maloja/web/jinja/artist.jinja b/maloja/web/jinja/artist.jinja index acc6cad..99b030a 100644 --- a/maloja/web/jinja/artist.jinja +++ b/maloja/web/jinja/artist.jinja @@ -36,7 +36,7 @@ {% if adminmode %}
{% else %} diff --git a/maloja/web/jinja/setup.jinja b/maloja/web/jinja/setup.jinja index 5fd88fe..0b93561 100644 --- a/maloja/web/jinja/setup.jinja +++ b/maloja/web/jinja/setup.jinja @@ -99,7 +99,7 @@ After you've scrobbled for a bit, you might want to check the Issues page to see if you need to set up some rules. You can also manually add rules in your server's "rules" directory - just add your own .tsv file and read the instructions on how to declare a rule.

- You can also set up some predefined rulesets right away! Enter your API key and click the buttons. + You can also set up some predefined rulesets right away!


diff --git a/maloja/web/jinja/track.jinja b/maloja/web/jinja/track.jinja index 0dcefae..d1ff053 100644 --- a/maloja/web/jinja/track.jinja +++ b/maloja/web/jinja/track.jinja @@ -30,7 +30,7 @@ {% if adminmode %}
{% else %} From ba701a2317f587008f0ef2d8168925bebf4a92e5 Mon Sep 17 00:00:00 2001 From: Krateng Date: Wed, 29 Jul 2020 20:20:15 +0200 Subject: [PATCH 06/14] Removed unnecessary cross origin resource --- maloja/static/less/maloja.less | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/maloja/static/less/maloja.less b/maloja/static/less/maloja.less index 9a24ef2..6e75625 100644 --- a/maloja/static/less/maloja.less +++ b/maloja/static/less/maloja.less @@ -101,11 +101,11 @@ div.footer div:nth-child(3) { } div.footer span a { - padding-left:20px; + //padding-left:20px; background-repeat:no-repeat; background-size:contain; background-position:left; - background-image:url("https://github.com/favicon.ico"); + //background-image:url("https://github.com/favicon.ico"); } div.footer input { From c44e14d0a6c9cc5fef347395efb03e1dd7640e55 Mon Sep 17 00:00:00 2001 From: Krateng Date: Wed, 29 Jul 2020 21:19:29 +0200 Subject: [PATCH 07/14] Updated admin mode --- maloja/server.py | 10 ++-- maloja/web/jinja/admin.jinja | 98 +++++++++++++++++++++++++++++++++ maloja/web/jinja/track.jinja | 9 ++-- maloja/web/pyhp/admin.pyhp | 101 ----------------------------------- 4 files changed, 109 insertions(+), 109 deletions(-) create mode 100644 maloja/web/jinja/admin.jinja delete mode 100644 maloja/web/pyhp/admin.pyhp diff --git a/maloja/server.py b/maloja/server.py index bc322f4..e00ab47 100755 --- a/maloja/server.py +++ b/maloja/server.py @@ -25,7 +25,7 @@ from doreah import settings from doreah.logging import log from doreah.timing import Clock from doreah.pyhp import file as pyhpfile -from doreah.auth import get_login_page, authapi, authenticated +from doreah import auth # technical #from importlib.machinery import SourceFileLoader import importlib @@ -56,7 +56,7 @@ STATICFOLDER = pkg_resources.resource_filename(__name__,"static") DATAFOLDER = DATA_DIR webserver = Bottle() -authapi.mount(server=webserver) +auth.authapi.mount(server=webserver) pthjoin = os.path.join @@ -162,7 +162,7 @@ def get_css(): @webserver.route("/login") def login(): - return get_login_page() + return auth.get_login_page() @webserver.route("/.") def static(name,ext): @@ -223,7 +223,7 @@ jinjaenv.globals.update(JINJA_CONTEXT) @webserver.route("/") -@authenticated +@auth.authenticated def static_html_private(name): return static_html(name) @@ -242,7 +242,7 @@ def static_html(name): pyhp_pref = settings.get_settings("USE_PYHP") jinja_pref = settings.get_settings("USE_JINJA") - adminmode = request.cookies.get("adminmode") == "true" and database.checkAPIkey(request.cookies.get("apikey")) is not False + adminmode = request.cookies.get("adminmode") == "true" and auth.check(request) clock = Clock() clock.start() diff --git a/maloja/web/jinja/admin.jinja b/maloja/web/jinja/admin.jinja new file mode 100644 index 0000000..5499184 --- /dev/null +++ b/maloja/web/jinja/admin.jinja @@ -0,0 +1,98 @@ +{% extends "base.jinja" %} +{% block title %}Maloja - Admin Panel{% endblock %} + +{% block scripts %} + + +{% endblock %} + + +{% block content %} + + + + + + + + + +
+
+
+

Admin Panel

+ +
+ +

Update

+ + Currently installed Maloja version: Loading...
+ Latest recommended Maloja version: Loading...
+ + + +

Admin Mode

+ + Admin Mode allows you to manually scrobble from various places on the web interface instead of just the dedicated page.

+ {% if adminmode %} + Deactivate + {% else %} + Activate + {% endif %} + +

Links

+ + Server Setup
+ Manual Scrobbling
+ Database Maintenance + +

External

+ + Report Issue
+ + +{% endblock %} diff --git a/maloja/web/jinja/track.jinja b/maloja/web/jinja/track.jinja index d1ff053..69e1deb 100644 --- a/maloja/web/jinja/track.jinja +++ b/maloja/web/jinja/track.jinja @@ -4,8 +4,8 @@ {% block scripts %} {% endblock %} @@ -45,7 +45,10 @@ #{{ info.position }}
-

{{ info['scrobbles'] }} Scrobbles

+

+ {% if adminmode %}Scrobble now{% endif %} + {{ info['scrobbles'] }} Scrobbles +

diff --git a/maloja/web/pyhp/admin.pyhp b/maloja/web/pyhp/admin.pyhp deleted file mode 100644 index 8b00718..0000000 --- a/maloja/web/pyhp/admin.pyhp +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - Maloja - - - - - - - - - - - - - - - - - -
-
-
-

Admin Panel

- -
- -

Update

- - Currently installed Maloja version: Loading...
- Latest recommended Maloja version: Loading...
- - - -

Admin Mode

- - Admin Mode allows you to manually scrobble from various places on the web interface instead of just the dedicated page.

- Deactivate - Activate - -

Links

- - Server Setup
- Manual Scrobbling
- Database Maintenance - -

External

- - Report Issue
- - - - From 3e1331b0e3024abe6cdbe9d389571788465d44d7 Mon Sep 17 00:00:00 2001 From: Krateng Date: Thu, 30 Jul 2020 15:31:29 +0200 Subject: [PATCH 08/14] Reorganized backend --- maloja/static/js/manualscrobble.js | 123 ++++++++++++ maloja/web/jinja/admin.jinja | 105 +++------- .../{issues.jinja => admin_issues.jinja} | 20 +- maloja/web/jinja/admin_manual.jinja | 46 +++++ maloja/web/jinja/admin_overview.jinja | 77 ++++++++ .../jinja/{setup.jinja => admin_setup.jinja} | 25 +-- maloja/web/jinja/base.jinja | 2 +- maloja/web/jinja/manual.jinja | 186 ------------------ 8 files changed, 285 insertions(+), 299 deletions(-) create mode 100644 maloja/static/js/manualscrobble.js rename maloja/web/jinja/{issues.jinja => admin_issues.jinja} (78%) create mode 100644 maloja/web/jinja/admin_manual.jinja create mode 100644 maloja/web/jinja/admin_overview.jinja rename maloja/web/jinja/{setup.jinja => admin_setup.jinja} (85%) delete mode 100644 maloja/web/jinja/manual.jinja diff --git a/maloja/static/js/manualscrobble.js b/maloja/static/js/manualscrobble.js new file mode 100644 index 0000000..8b24c30 --- /dev/null +++ b/maloja/static/js/manualscrobble.js @@ -0,0 +1,123 @@ +function keyDetect(event) { + if (event.key === "Enter" || event.key === "Tab") { addArtist() } + if (event.key === "Backspace" && document.getElementById("artists").value == "") { removeArtist() } +} + +function addArtist() { + element = document.getElementById("artists"); + newartist = element.value.trim(); + element.value = ""; + if (newartist != "") { + artist = document.createElement("span"); + artist.innerHTML = newartist; + artist.style = "padding:5px;"; + document.getElementById("artists_td").insertBefore(artist,element); + + element.placeholder = "Backspace to remove last" + } +} +function removeArtist() { + artists = document.getElementById("artists_td").getElementsByTagName("span") + lastartist = artists[artists.length-1] + document.getElementById("artists_td").removeChild(lastartist); + if (artists.length < 1) { + document.getElementById("artists").placeholder = "Separate with Enter" + } +} + + +function scrobbleIfEnter(event) { + if (event.key === "Enter") { + scrobbleNew() + } +} + +function scrobbleNew() { + artistnodes = document.getElementById("artists_td").getElementsByTagName("span"); + artists = []; + for (let node of artistnodes) { + artists.push(node.innerHTML); + } + title = document.getElementById("title").value; + scrobble(artists,title); +} + +function scrobble(artists,title) { + + + artist = artists.join(";"); + + if (title != "" && artists.length > 0) { + xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = scrobbledone + xhttp.open("GET","/api/newscrobble?artist=" + encodeURIComponent(artist) + + "&title=" + encodeURIComponent(title), true); + xhttp.send(); + } + + document.getElementById("title").value = ""; + document.getElementById("artists").value = ""; + parent = document.getElementById("artists_td"); + artists = document.getElementById("artists_td").getElementsByTagName("span") + while (artists.length > 0) { + removeArtist(); + } +} + +function scrobbledone() { + if (this.readyState == 4 && this.status == 200) { + result = JSON.parse(this.responseText); + txt = result["track"]["title"] + " by " + result["track"]["artists"][0]; + if (result["track"]["artists"].length > 1) { + txt += " et al."; + } + document.getElementById("notification").innerHTML = "Scrobbled " + txt + "!"; + } + +} + + + + +/// +// SEARCH +/// + +function search_manualscrobbling(searchfield) { + txt = searchfield.value; + if (txt == "") { + + } + else { + xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = searchresult_manualscrobbling; + xhttp.open("GET","/api/search?max=5&query=" + encodeURIComponent(txt), true); + xhttp.send(); + } +} +function searchresult_manualscrobbling() { + if (this.readyState == 4 && this.status == 200) { + document.getElementById("searchresults").innerHTML = ""; + result = JSON.parse(this.responseText); + tracks = result["tracks"].slice(0,10); + console.log(tracks); + for (let t of tracks) { + track = document.createElement("span"); + trackstr = t["artists"].join(", ") + " - " + t["title"]; + tracklink = t["link"]; + track.innerHTML = "" + trackstr + ""; + row = document.createElement("tr") + col1 = document.createElement("td") + col1.className = "button" + col1.innerHTML = "Scrobble!" + col1.onclick = function(){ scrobble(t["artists"],t["title"])}; + col2 = document.createElement("td") + row.appendChild(col1) + row.appendChild(col2) + col2.appendChild(track) + document.getElementById("searchresults").appendChild(row); + } + + + } +} diff --git a/maloja/web/jinja/admin.jinja b/maloja/web/jinja/admin.jinja index 5499184..7e211fb 100644 --- a/maloja/web/jinja/admin.jinja +++ b/maloja/web/jinja/admin.jinja @@ -1,54 +1,5 @@ {% extends "base.jinja" %} -{% block title %}Maloja - Admin Panel{% endblock %} - -{% block scripts %} - - -{% endblock %} +{% block title %}Maloja - Backend{% endblock %} {% block content %} @@ -63,36 +14,36 @@

Admin Panel

+
+ + {% if page=='admin_overview' %} + Overview + {% else %} + Overview + {% endif %} | + {% if page=='admin_setup' %} + Server Setup + {% else %} + Server Setup + {% endif %} | + {% if page=='admin_manual' %} + Manual Scrobbling + {% else %} + Manual Scrobbling + {% endif %} | + {% if page=='admin_issues' %} + Database Maintenance + {% else %} + Database Maintenance + {% endif %} + +

+ -

Update

- - Currently installed Maloja version: Loading...
- Latest recommended Maloja version: Loading...
- - - -

Admin Mode

- - Admin Mode allows you to manually scrobble from various places on the web interface instead of just the dedicated page.

- {% if adminmode %} - Deactivate - {% else %} - Activate - {% endif %} - -

Links

- - Server Setup
- Manual Scrobbling
- Database Maintenance - -

External

- - Report Issue
- - + {% block maincontent %} + {% endblock %} {% endblock %} diff --git a/maloja/web/jinja/issues.jinja b/maloja/web/jinja/admin_issues.jinja similarity index 78% rename from maloja/web/jinja/issues.jinja rename to maloja/web/jinja/admin_issues.jinja index b1672e4..d078a31 100644 --- a/maloja/web/jinja/issues.jinja +++ b/maloja/web/jinja/admin_issues.jinja @@ -1,9 +1,9 @@ -{% extends "base.jinja" %} +{% set page ='admin_issues' %} +{% extends "admin.jinja" %} {% block title %}Maloja - Issues{% endblock %} {% block scripts %} - +{% endblock %} + + +{% block maincontent %} + +

Scrobble new discovery

+ + + + + + + + +
+ Artists: + + +
+ Title: + + +
+ +
+ + Scrobble! + +
+ + + +

Search

+ + +

+
+ + +{% endblock %} diff --git a/maloja/web/jinja/admin_overview.jinja b/maloja/web/jinja/admin_overview.jinja new file mode 100644 index 0000000..2b4ece2 --- /dev/null +++ b/maloja/web/jinja/admin_overview.jinja @@ -0,0 +1,77 @@ +{% set page ='admin_overview' %} +{% extends "admin.jinja" %} +{% block title %}Maloja - Admin Panel{% endblock %} + +{% block scripts %} + +{% endblock %} + + + {% block maincontent %} + +

Update

+ + Currently installed Maloja version: Loading...
+ Latest recommended Maloja version: Loading...
+ + + +

Admin Mode

+ + Admin Mode allows you to manually scrobble from various places on the web interface instead of just the dedicated page.

+ {% if adminmode %} + Deactivate + {% else %} + Activate + {% endif %} + +

Links

+ + Report Issue
+ Readme
+ PyPi
+ {% endblock %} diff --git a/maloja/web/jinja/setup.jinja b/maloja/web/jinja/admin_setup.jinja similarity index 85% rename from maloja/web/jinja/setup.jinja rename to maloja/web/jinja/admin_setup.jinja index 0b93561..f7ec84e 100644 --- a/maloja/web/jinja/setup.jinja +++ b/maloja/web/jinja/admin_setup.jinja @@ -1,8 +1,8 @@ -{% extends "base.jinja" %} +{% set page ='admin_setup' %} +{% extends "admin.jinja" %} {% block title %}Maloja - Setup{% endblock %} {% block scripts %} - - - -{% endblock %} - - - -{% block content %} - - - - - - -
-
-
-

Manual Scrobbling


-

- - -
- -

Scrobble new discovery

- - - - - - - - -
- Artists: - - -
- Title: - - -
- -
- - Scrobble! - -
- - - -

Search

- - -

-
- - -{% endblock %} From a88afe40ec5068b849141dee274543aed049a0bc Mon Sep 17 00:00:00 2001 From: Krateng Date: Sun, 16 Aug 2020 20:08:17 +0200 Subject: [PATCH 09/14] Added ability to quickly prefill manual scrobble form with last scrobble --- maloja/proccontrol/control.py | 2 +- maloja/static/js/manualscrobble.js | 66 ++++++++++++++++++++--------- maloja/web/jinja/admin_manual.jinja | 1 + 3 files changed, 48 insertions(+), 21 deletions(-) diff --git a/maloja/proccontrol/control.py b/maloja/proccontrol/control.py index d0cf53e..12c4342 100644 --- a/maloja/proccontrol/control.py +++ b/maloja/proccontrol/control.py @@ -43,7 +43,7 @@ def start(): print("Visit your server address (Port " + str(port) + ") to see your web interface. Visit /setup to get started.") print("If you're installing this on your local machine, these links should get you there:") print("\t" + col["blue"]("http://localhost:" + str(port))) - print("\t" + col["blue"]("http://localhost:" + str(port) + "/setup")) + print("\t" + col["blue"]("http://localhost:" + str(port) + "/admin_setup")) return True except: print("Error while starting Maloja.") diff --git a/maloja/static/js/manualscrobble.js b/maloja/static/js/manualscrobble.js index 8b24c30..03ce272 100644 --- a/maloja/static/js/manualscrobble.js +++ b/maloja/static/js/manualscrobble.js @@ -1,30 +1,47 @@ +var lastArtists = [] +var lastTrack = "" + + +function addArtist(artist) { + var newartistfield = document.getElementById("artists"); + var artistelement = document.createElement("span"); + artistelement.innerHTML = artist; + artistelement.style = "padding:5px;"; + document.getElementById("artists_td").insertBefore(artistelement,newartistfield); + newartistfield.placeholder = "Backspace to remove last" +} + function keyDetect(event) { - if (event.key === "Enter" || event.key === "Tab") { addArtist() } + if (event.key === "Enter" || event.key === "Tab") { addEnteredArtist() } if (event.key === "Backspace" && document.getElementById("artists").value == "") { removeArtist() } } -function addArtist() { - element = document.getElementById("artists"); - newartist = element.value.trim(); - element.value = ""; +function addEnteredArtist() { + var newartistfield = document.getElementById("artists"); + var newartist = newartistfield.value.trim(); + newartistfield.value = ""; if (newartist != "") { - artist = document.createElement("span"); - artist.innerHTML = newartist; - artist.style = "padding:5px;"; - document.getElementById("artists_td").insertBefore(artist,element); - - element.placeholder = "Backspace to remove last" + addArtist(newartist); } } function removeArtist() { - artists = document.getElementById("artists_td").getElementsByTagName("span") - lastartist = artists[artists.length-1] + var artists = document.getElementById("artists_td").getElementsByTagName("span") + var lastartist = artists[artists.length-1] document.getElementById("artists_td").removeChild(lastartist); if (artists.length < 1) { document.getElementById("artists").placeholder = "Separate with Enter" } } +function clear() { + document.getElementById("title").value = ""; + document.getElementById("artists").value = ""; + var artists = document.getElementById("artists_td").getElementsByTagName("span") + while (artists.length > 0) { + removeArtist(); + } +} + function scrobbleIfEnter(event) { if (event.key === "Enter") { @@ -33,19 +50,22 @@ function scrobbleIfEnter(event) { } function scrobbleNew() { - artistnodes = document.getElementById("artists_td").getElementsByTagName("span"); - artists = []; + var artistnodes = document.getElementById("artists_td").getElementsByTagName("span"); + var artists = []; for (let node of artistnodes) { artists.push(node.innerHTML); } - title = document.getElementById("title").value; + var title = document.getElementById("title").value; scrobble(artists,title); } function scrobble(artists,title) { + lastArtists = artists; + lastTrack = title; - artist = artists.join(";"); + + var artist = artists.join(";"); if (title != "" && artists.length > 0) { xhttp = new XMLHttpRequest(); @@ -57,8 +77,7 @@ function scrobble(artists,title) { document.getElementById("title").value = ""; document.getElementById("artists").value = ""; - parent = document.getElementById("artists_td"); - artists = document.getElementById("artists_td").getElementsByTagName("span") + var artists = document.getElementById("artists_td").getElementsByTagName("span"); while (artists.length > 0) { removeArtist(); } @@ -76,6 +95,13 @@ function scrobbledone() { } +function repeatLast() { + clear(); + for (let artist of lastArtists) { + addArtist(artist); + } + document.getElementById("title").value = lastTrack; +} @@ -84,7 +110,7 @@ function scrobbledone() { /// function search_manualscrobbling(searchfield) { - txt = searchfield.value; + var txt = searchfield.value; if (txt == "") { } diff --git a/maloja/web/jinja/admin_manual.jinja b/maloja/web/jinja/admin_manual.jinja index 6c9a6f5..5f55c60 100644 --- a/maloja/web/jinja/admin_manual.jinja +++ b/maloja/web/jinja/admin_manual.jinja @@ -31,6 +31,7 @@
Scrobble! + Last Manual Scrobble
From 9a1bc8be03489d5f7e1042998979b473b271bb7a Mon Sep 17 00:00:00 2001 From: Krateng Date: Mon, 17 Aug 2020 17:14:38 +0200 Subject: [PATCH 10/14] Fixed error page --- maloja/server.py | 83 +++++++++++++++-------------- maloja/web/jinja/error.jinja | 18 +++++++ maloja/web/pyhp/errors/generic.pyhp | 28 ---------- 3 files changed, 60 insertions(+), 69 deletions(-) create mode 100644 maloja/web/jinja/error.jinja delete mode 100644 maloja/web/pyhp/errors/generic.pyhp diff --git a/maloja/server.py b/maloja/server.py index e00ab47..d8707a8 100755 --- a/maloja/server.py +++ b/maloja/server.py @@ -4,7 +4,7 @@ from .globalconf import datadir, DATA_DIR # server stuff -from bottle import Bottle, route, get, post, error, run, template, static_file, request, response, FormsDict, redirect, template, HTTPResponse, BaseRequest +from bottle import Bottle, route, get, post, error, run, template, static_file, request, response, FormsDict, redirect, template, HTTPResponse, BaseRequest, abort import waitress # templating from jinja2 import Environment, PackageLoader, select_autoescape @@ -94,10 +94,9 @@ def mainpage(): def customerror(error): code = int(str(error).split(",")[0][1:]) - if os.path.exists(pthjoin(WEBFOLDER,"errors",str(code) + ".pyhp")): - return pyhpfile(pthjoin(WEBFOLDER,"errors",str(code) + ".pyhp"),{"errorcode":code}) - else: - return pyhpfile(pthjoin(WEBFOLDER,"errors","generic.pyhp"),{"errorcode":code}) + template = jinjaenv.get_template('error.jinja') + res = template.render(errorcode=code) + return res @@ -222,7 +221,7 @@ jinjaenv = Environment( jinjaenv.globals.update(JINJA_CONTEXT) -@webserver.route("/") +@webserver.route("/") @auth.authenticated def static_html_private(name): return static_html(name) @@ -291,48 +290,50 @@ def static_html(name): # if not, use the old way else: + try: + with open(pthjoin(WEBFOLDER,name + ".html")) as htmlfile: + html = htmlfile.read() - with open(pthjoin(WEBFOLDER,name + ".html")) as htmlfile: - html = htmlfile.read() - - # apply global substitutions - with open(pthjoin(WEBFOLDER,"common/footer.html")) as footerfile: - footerhtml = footerfile.read() - with open(pthjoin(WEBFOLDER,"common/header.html")) as headerfile: - headerhtml = headerfile.read() - html = html.replace("",footerhtml + "").replace("",headerhtml + "") + # apply global substitutions + with open(pthjoin(WEBFOLDER,"common/footer.html")) as footerfile: + footerhtml = footerfile.read() + with open(pthjoin(WEBFOLDER,"common/header.html")) as headerfile: + headerhtml = headerfile.read() + html = html.replace("",footerhtml + "").replace("",headerhtml + "") - # If a python file exists, it provides the replacement dict for the html file - if os.path.exists(pthjoin(WEBFOLDER,name + ".py")): - #txt_keys = SourceFileLoader(name,"web/" + name + ".py").load_module().replacedict(keys,DATABASE_PORT) - try: - module = importlib.import_module(".web." + name,package="maloja") - txt_keys,resources = module.instructions(keys) - except Exception as e: - log("Error in website generation: " + str(sys.exc_info()),module="error") - raise + # If a python file exists, it provides the replacement dict for the html file + if os.path.exists(pthjoin(WEBFOLDER,name + ".py")): + #txt_keys = SourceFileLoader(name,"web/" + name + ".py").load_module().replacedict(keys,DATABASE_PORT) + try: + module = importlib.import_module(".web." + name,package="maloja") + txt_keys,resources = module.instructions(keys) + except Exception as e: + log("Error in website generation: " + str(sys.exc_info()),module="error") + raise - # add headers for server push - for resource in resources: - if all(ord(c) < 128 for c in resource["file"]): - # we can only put ascii stuff in the http header - linkheaders.append("<" + resource["file"] + ">; rel=preload; as=" + resource["type"]) + # add headers for server push + for resource in resources: + if all(ord(c) < 128 for c in resource["file"]): + # we can only put ascii stuff in the http header + linkheaders.append("<" + resource["file"] + ">; rel=preload; as=" + resource["type"]) - # apply key substitutions - for k in txt_keys: - if isinstance(txt_keys[k],list): - # if list, we replace each occurence with the next item - for element in txt_keys[k]: - html = html.replace(k,element,1) - else: - html = html.replace(k,txt_keys[k]) + # apply key substitutions + for k in txt_keys: + if isinstance(txt_keys[k],list): + # if list, we replace each occurence with the next item + for element in txt_keys[k]: + html = html.replace(k,element,1) + else: + html = html.replace(k,txt_keys[k]) - response.set_header("Link",",".join(linkheaders)) - log("Generated page {name} in {time:.5f}s (Python+HTML)".format(name=name,time=clock.stop()),module="debug") - return html - #return static_file("web/" + name + ".html",root="") + response.set_header("Link",",".join(linkheaders)) + log("Generated page {name} in {time:.5f}s (Python+HTML)".format(name=name,time=clock.stop()),module="debug") + return html + + except: + abort(404, "Page does not exist") # Shortlinks diff --git a/maloja/web/jinja/error.jinja b/maloja/web/jinja/error.jinja new file mode 100644 index 0000000..5ce08a2 --- /dev/null +++ b/maloja/web/jinja/error.jinja @@ -0,0 +1,18 @@ +{% extends "admin.jinja" %} + +{% block content %} + + + + + +
+
+
+

Error {{ errorcode }}


+ + +

That did not work. Don't ask me why.

+
+ +{% endblock %} diff --git a/maloja/web/pyhp/errors/generic.pyhp b/maloja/web/pyhp/errors/generic.pyhp deleted file mode 100644 index aafa2e1..0000000 --- a/maloja/web/pyhp/errors/generic.pyhp +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - Maloja - Error <pyhp echo="errorcode" /> - - - - - - - - - -
-
-
-

Error


- - -

That did not work. Don't ask me why.

-
- - - - - From ae0da83a9cffba2115e0011cce962edcc97182d1 Mon Sep 17 00:00:00 2001 From: Krateng Date: Mon, 17 Aug 2020 17:23:17 +0200 Subject: [PATCH 11/14] Moved abstract templates to own folder --- maloja/web/common/footer.html | 2 +- maloja/web/jinja/{ => abstracts}/admin.jinja | 2 +- maloja/web/jinja/{ => abstracts}/base.jinja | 0 maloja/web/jinja/admin_issues.jinja | 2 +- maloja/web/jinja/admin_manual.jinja | 2 +- maloja/web/jinja/admin_overview.jinja | 2 +- maloja/web/jinja/admin_setup.jinja | 2 +- maloja/web/jinja/artist.jinja | 2 +- maloja/web/jinja/charts_artists.jinja | 2 +- maloja/web/jinja/charts_tracks.jinja | 2 +- maloja/web/jinja/error.jinja | 2 +- maloja/web/jinja/track.jinja | 2 +- 12 files changed, 11 insertions(+), 11 deletions(-) rename maloja/web/jinja/{ => abstracts}/admin.jinja (96%) rename maloja/web/jinja/{ => abstracts}/base.jinja (100%) diff --git a/maloja/web/common/footer.html b/maloja/web/common/footer.html index 7de3865..4f4a96f 100644 --- a/maloja/web/common/footer.html +++ b/maloja/web/common/footer.html @@ -22,6 +22,6 @@ -
+
diff --git a/maloja/web/jinja/admin.jinja b/maloja/web/jinja/abstracts/admin.jinja similarity index 96% rename from maloja/web/jinja/admin.jinja rename to maloja/web/jinja/abstracts/admin.jinja index 7e211fb..76c95ad 100644 --- a/maloja/web/jinja/admin.jinja +++ b/maloja/web/jinja/abstracts/admin.jinja @@ -1,4 +1,4 @@ -{% extends "base.jinja" %} +{% extends "abstracts/base.jinja" %} {% block title %}Maloja - Backend{% endblock %} diff --git a/maloja/web/jinja/base.jinja b/maloja/web/jinja/abstracts/base.jinja similarity index 100% rename from maloja/web/jinja/base.jinja rename to maloja/web/jinja/abstracts/base.jinja diff --git a/maloja/web/jinja/admin_issues.jinja b/maloja/web/jinja/admin_issues.jinja index d078a31..8dcabfb 100644 --- a/maloja/web/jinja/admin_issues.jinja +++ b/maloja/web/jinja/admin_issues.jinja @@ -1,5 +1,5 @@ {% set page ='admin_issues' %} -{% extends "admin.jinja" %} +{% extends "abstracts/admin.jinja" %} {% block title %}Maloja - Issues{% endblock %} diff --git a/maloja/web/jinja/admin_manual.jinja b/maloja/web/jinja/admin_manual.jinja index 5f55c60..6815bd7 100644 --- a/maloja/web/jinja/admin_manual.jinja +++ b/maloja/web/jinja/admin_manual.jinja @@ -1,5 +1,5 @@ {% set page ='admin_manual' %} -{% extends "admin.jinja" %} +{% extends "abstracts/admin.jinja" %} {% block title %}Maloja - Manual Scrobbling{% endblock %} {% block scripts %} diff --git a/maloja/web/jinja/admin_overview.jinja b/maloja/web/jinja/admin_overview.jinja index 2b4ece2..5c0d285 100644 --- a/maloja/web/jinja/admin_overview.jinja +++ b/maloja/web/jinja/admin_overview.jinja @@ -1,5 +1,5 @@ {% set page ='admin_overview' %} -{% extends "admin.jinja" %} +{% extends "abstracts/admin.jinja" %} {% block title %}Maloja - Admin Panel{% endblock %} {% block scripts %} diff --git a/maloja/web/jinja/admin_setup.jinja b/maloja/web/jinja/admin_setup.jinja index f7ec84e..b7c46c9 100644 --- a/maloja/web/jinja/admin_setup.jinja +++ b/maloja/web/jinja/admin_setup.jinja @@ -1,5 +1,5 @@ {% set page ='admin_setup' %} -{% extends "admin.jinja" %} +{% extends "abstracts/admin.jinja" %} {% block title %}Maloja - Setup{% endblock %} {% block scripts %} diff --git a/maloja/web/jinja/artist.jinja b/maloja/web/jinja/artist.jinja index 99b030a..a1c8701 100644 --- a/maloja/web/jinja/artist.jinja +++ b/maloja/web/jinja/artist.jinja @@ -1,4 +1,4 @@ -{% extends "base.jinja" %} +{% extends "abstracts/base.jinja" %} {% block title %}Maloja - {{ artist }}{% endblock %} {% block scripts %} diff --git a/maloja/web/jinja/charts_artists.jinja b/maloja/web/jinja/charts_artists.jinja index 4866901..5648ab7 100644 --- a/maloja/web/jinja/charts_artists.jinja +++ b/maloja/web/jinja/charts_artists.jinja @@ -1,4 +1,4 @@ -{% extends "base.jinja" %} +{% extends "abstracts/base.jinja" %} {% block title %}Maloja - {{ artist }}{% endblock %} {% block scripts %} diff --git a/maloja/web/jinja/charts_tracks.jinja b/maloja/web/jinja/charts_tracks.jinja index b25b11e..cb31f59 100644 --- a/maloja/web/jinja/charts_tracks.jinja +++ b/maloja/web/jinja/charts_tracks.jinja @@ -1,4 +1,4 @@ -{% extends "base.jinja" %} +{% extends "abstracts/base.jinja" %} {% block title %}Maloja - Track Charts{% endblock %} {% block scripts %} diff --git a/maloja/web/jinja/error.jinja b/maloja/web/jinja/error.jinja index 5ce08a2..3fe843b 100644 --- a/maloja/web/jinja/error.jinja +++ b/maloja/web/jinja/error.jinja @@ -1,4 +1,4 @@ -{% extends "admin.jinja" %} +{% extends "abstracts/base.jinja" %} {% block content %} diff --git a/maloja/web/jinja/track.jinja b/maloja/web/jinja/track.jinja index 69e1deb..d8edd89 100644 --- a/maloja/web/jinja/track.jinja +++ b/maloja/web/jinja/track.jinja @@ -1,4 +1,4 @@ -{% extends "base.jinja" %} +{% extends "abstracts/base.jinja" %} {% block title %}Maloja - {{ track.title }}{% endblock %} {% block scripts %} From 6050e26f7a1f733447717cf99861207007704620 Mon Sep 17 00:00:00 2001 From: Krateng Date: Mon, 17 Aug 2020 18:16:09 +0200 Subject: [PATCH 12/14] Added basic top info structure to base template --- maloja/server.py | 2 +- maloja/web/jinja/abstracts/base.jinja | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/maloja/server.py b/maloja/server.py index d8707a8..66d1b80 100755 --- a/maloja/server.py +++ b/maloja/server.py @@ -221,7 +221,7 @@ jinjaenv = Environment( jinjaenv.globals.update(JINJA_CONTEXT) -@webserver.route("/") +@webserver.route("/") @auth.authenticated def static_html_private(name): return static_html(name) diff --git a/maloja/web/jinja/abstracts/base.jinja b/maloja/web/jinja/abstracts/base.jinja index 9d0509b..924a989 100644 --- a/maloja/web/jinja/abstracts/base.jinja +++ b/maloja/web/jinja/abstracts/base.jinja @@ -20,6 +20,25 @@ {% block content %} +
+ + + + +
+
+
+

{% block heading %}{% endblock %}


+ + + {% block top_info %} + + {% endblock %} +
+ + {% block maincontent %} + + {% endblock %} {% endblock %} From 24840152619ad48bf17697d997ebb24c290aace5 Mon Sep 17 00:00:00 2001 From: Krateng Date: Mon, 17 Aug 2020 19:04:51 +0200 Subject: [PATCH 13/14] Added setup step for password --- maloja/data_files/settings/default.ini | 8 ++++++ maloja/proccontrol/setup.py | 39 ++++++++++++++++++++++---- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/maloja/data_files/settings/default.ini b/maloja/data_files/settings/default.ini index 86e9c2f..f5ff205 100644 --- a/maloja/data_files/settings/default.ini +++ b/maloja/data_files/settings/default.ini @@ -7,6 +7,14 @@ WEB_PORT = 42010 HOST = "::" # You most likely want either :: for IPv6 or 0.0.0.0 for IPv4 here +[Login] + +DEFAULT_PASSWORD = none +FORCE_PASSWORD = none +# these are only meant for Docker containers +# on first start, set the environment variable MALOJA_DEFAULT_PASSWORD +# if you forgot and already generated a random password, you can overwrite it with MALOJA_FORCE_PASSWORD + [Third Party Services] # order in which to use the metadata providers diff --git a/maloja/proccontrol/setup.py b/maloja/proccontrol/setup.py index ad6752a..c123a78 100644 --- a/maloja/proccontrol/setup.py +++ b/maloja/proccontrol/setup.py @@ -2,6 +2,7 @@ import pkg_resources from distutils import dir_util from doreah import settings from doreah.io import col, ask, prompt +from doreah import auth import os from ..globalconf import datadir @@ -22,7 +23,12 @@ def copy_initial_local_files(): #shutil.copy(folder,DATA_DIR) dir_util.copy_tree(folder,datadir(),update=False) - +def randomstring(length=32): + import random + key = "" + for i in range(length): + key += str(random.choice(list(range(10)) + list("abcdefghijklmnopqrstuvwxyz") + list("ABCDEFGHIJKLMNOPQRSTUVWXYZ"))) + return key def setup(): @@ -48,10 +54,7 @@ def setup(): else: answer = ask("Do you want to set up a key to enable scrobbling? Your scrobble extension needs that key so that only you can scrobble tracks to your database.",default=True,skip=SKIP) if answer: - import random - key = "" - for i in range(64): - key += str(random.choice(list(range(10)) + list("abcdefghijklmnopqrstuvwxyz") + list("ABCDEFGHIJKLMNOPQRSTUVWXYZ"))) + key = randomstring(64) print("Your API Key: " + col["yellow"](key)) with open(datadir("clients/authenticated_machines.tsv"),"w") as keyfile: keyfile.write(key + "\t" + "Default Generated Key") @@ -59,6 +62,32 @@ def setup(): pass + # PASSWORD + defaultpassword = settings.get_settings("DEFAULT_PASSWORD") + forcepassword = settings.get_settings("FORCE_PASSWORD") + # this is mainly meant for docker, supply password via environment variable + + if forcepassword is not None: + # user has specified to force the pw, nothing else matters + auth.defaultuser.setpw(forcepassword) + print("Password has been set.") + elif auth.defaultuser.checkpw("admin"): + # if the actual pw is admin, it means we've never set this up properly (eg first start after update) + if defaultpassword is None: + # non-docker installation or user didn't set environment variable + defaultpassword = randomstring(32) + newpw = prompt("Please set a password for web backend access. Leave this empty to generate a random password.",skip=SKIP,secret=True) + if newpw is None: + newpw = defaultpassword + print("Generated password:",newpw) + auth.defaultuser.setpw(newpw) + else: + # docker installation (or settings file, but don't do that) + # we still 'ask' the user to set one, but for docker this will be skipped + newpw = prompt("Please set a password for web backend access. Leave this empty to use the default password.",skip=SKIP,default=defaultpassword,secret=True) + auth.defaultuser.setpw(newpw) + + if settings.get_settings("NAME") is None: name = prompt("Please enter your name. This will be displayed e.g. when comparing your charts to another user. Leave this empty if you would not like to specify a name right now.",default="Generic Maloja User",skip=SKIP) settings.update_settings(datadir("settings/settings.ini"),{"NAME":name},create_new=True) From 7f3b7031ac57c7887d1ca5921b38a658144e841c Mon Sep 17 00:00:00 2001 From: Krateng Date: Tue, 18 Aug 2020 04:55:36 +0200 Subject: [PATCH 14/14] Updated Readme --- README.md | 6 ++++-- maloja/__pkginfo__.py | 2 +- maloja/proccontrol/setup.py | 3 ++- maloja/server.py | 5 +++++ 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 185e2df..0f75059 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ I can support you with issues best if you use **Alpine Linux**. In my experience 1) Make sure you have Python 3.5 or higher installed. You also need some basic packages that should be present on most systems, but I've provided simple shell scripts for Alpine and Ubuntu to get everything you need. -2) If you'd like to display images, you will need API keys for [Last.fm](https://www.last.fm/api/account/create) and [Fanart.tv](https://fanart.tv/get-an-api-key/) (you need a project key, not a personal one). These are free of charge! +2) If you'd like to display images, you will need some free API keys - check the settings for details! 3) Download Maloja with the command `pip install malojaserver`. Make sure to use the correct python version (Use `pip3` if necessary). @@ -74,6 +74,8 @@ I can support you with issues best if you use **Alpine Linux**. In my experience There is a Dockerfile in the repo that should work by itself. You can also use the unofficial [Dockerhub repository](https://hub.docker.com/r/foxxmd/maloja) kindly provided by FoxxMD. +You might want to set the environment variables `MALOJA_DEFAULT_PASSWORD`, `MALOJA_SKIP_SETUP` and `MALOJA_DATA_DIRECTORY`. + ## How to use @@ -141,7 +143,7 @@ It is recommended to define a different API key for every scrobbler you use in ` ### Manual -If you can't automatically scrobble your music, you can always do it manually on the `/manual` page of your Maloja server. +If you can't automatically scrobble your music, you can always do it manually on the `/admin_manual` page of your Maloja server. ## How to extend diff --git a/maloja/__pkginfo__.py b/maloja/__pkginfo__.py index 317c320..dae82f8 100644 --- a/maloja/__pkginfo__.py +++ b/maloja/__pkginfo__.py @@ -15,7 +15,7 @@ links = { requires = [ "bottle>=0.12.16", "waitress>=1.3", - "doreah>=1.6.8", + "doreah>=1.6.9", "nimrodel>=0.6.3", "setproctitle>=1.1.10", "wand>=0.5.4", diff --git a/maloja/proccontrol/setup.py b/maloja/proccontrol/setup.py index c123a78..7f1343c 100644 --- a/maloja/proccontrol/setup.py +++ b/maloja/proccontrol/setup.py @@ -23,11 +23,12 @@ def copy_initial_local_files(): #shutil.copy(folder,DATA_DIR) dir_util.copy_tree(folder,datadir(),update=False) +charset = list(range(10)) + list("abcdefghijklmnopqrstuvwxyz") + list("ABCDEFGHIJKLMNOPQRSTUVWXYZ") def randomstring(length=32): import random key = "" for i in range(length): - key += str(random.choice(list(range(10)) + list("abcdefghijklmnopqrstuvwxyz") + list("ABCDEFGHIJKLMNOPQRSTUVWXYZ"))) + key += str(random.choice(charset)) return key def setup(): diff --git a/maloja/server.py b/maloja/server.py index 66d1b80..7d28c97 100755 --- a/maloja/server.py +++ b/maloja/server.py @@ -178,6 +178,10 @@ def static(name,ext): return response +aliases = { + "admin": "admin_overview", + "manual": "admin_manual" +} @@ -231,6 +235,7 @@ def static_html_public(name): return static_html(name) def static_html(name): + if name in aliases: redirect(aliases[name]) linkheaders = ["; rel=preload; as=style"] keys = remove_identical(FormsDict.decode(request.query))