From 55621ef4efdf61c3092d42565e897dfbaa0244c8 Mon Sep 17 00:00:00 2001 From: Krateng Date: Sun, 24 Nov 2019 21:47:03 +0100 Subject: [PATCH] Refactored into Python Package --- logs/dummy => .dev | 0 .gitignore | 4 +- info.py | 10 - maloja | 350 --------- maloja/__init__.py | 19 + cleanup.py => maloja/cleanup.py | 2 +- compliant_api.py => maloja/compliant_api.py | 6 +- maloja/controller.py | 165 +++++ .doreah => maloja/data_files/.doreah | 0 {cache => maloja/data_files/cache}/.gitignore | 0 .../data_files/clients}/.gitignore | 0 .../data_files/clients}/example_file.tsv | 0 .../data_files/images}/.gitignore | 0 .../data_files/images}/images.info | 0 {scrobbles => maloja/data_files/logs}/dummy | 0 .../data_files/rules}/predefined/.gitignore | 0 .../predefined/krateng_artistsingroups.tsv | 0 .../predefined/krateng_firefly-soundtrack.tsv | 0 .../rules}/predefined/krateng_jeremysoule.tsv | 0 .../predefined/krateng_kpopgirlgroups.tsv | 0 .../predefined/krateng_lotr-soundtrack.tsv | 0 .../rules}/predefined/krateng_monstercat.tsv | 0 .../rules}/predefined/predefined.info | 0 {rules => maloja/data_files/rules}/rules.info | 0 maloja/data_files/scrobbles/dummy | 0 .../data_files/settings}/.gitignore | 0 .../data_files/settings}/default.ini | 0 database.py => maloja/database.py | 15 +- maloja/doreah | 1 + external.py => maloja/external.py | 0 fixexisting.py => maloja/fixexisting.py | 2 +- htmlgenerators.py => maloja/htmlgenerators.py | 2 +- htmlmodules.py => maloja/htmlmodules.py | 10 +- maloja/info.py | 19 + .../lastfmconverter.py | 4 +- malojatime.py => maloja/malojatime.py | 0 monkey.py => maloja/monkey.py | 0 server.py => maloja/server.py | 77 +- supervisor.py => maloja/supervisor.py | 2 +- urihandler.py => maloja/urihandler.py | 2 +- utilities.py => maloja/utilities.py | 8 +- {website => maloja/website}/admin.pyhp | 0 {website => maloja/website}/artist.html | 0 {website => maloja/website}/artist.py | 12 +- {website => maloja/website}/artist.pyhp | 0 .../website}/charts_artists.html | 0 {website => maloja/website}/charts_artists.py | 8 +- .../website}/charts_tracks.html | 0 {website => maloja/website}/charts_tracks.py | 10 +- .../website}/common/footer.html | 0 .../website}/common/header.html | 0 {website => maloja/website}/compare.html | 0 {website => maloja/website}/compare.py | 6 +- maloja/website/css/style.css | 671 ++++++++++++++++++ .../website}/errors/generic.html | 0 {website => maloja/website}/favicon.ico | Bin {website => maloja/website}/favicon.png | Bin {website => maloja/website}/issues.html | 0 {website => maloja/website}/issues.py | 4 +- .../website}/javascript/cookies.js | 0 .../website}/javascript/datechange.js | 0 .../website}/javascript/neopolitan.js | 3 +- .../website}/javascript/rangeselect.js | 0 .../website}/javascript/search.js | 0 .../website}/javascript/upload.js | 0 {website => maloja/website}/less/grisons.less | 0 .../website}/less/grisonsfont.less | 0 {website => maloja/website}/less/maloja.less | 0 {website => maloja/website}/manual.html | 0 .../website}/media/chartpos_bronze.png | Bin .../website}/media/chartpos_gold.png | Bin .../website}/media/chartpos_normal.png | Bin .../website}/media/chartpos_silver.png | Bin .../website}/media/record_diamond.png | Bin .../website}/media/record_gold.png | Bin .../website}/media/record_gold_original.png | Bin .../website}/media/record_platinum.png | Bin {website => maloja/website}/media/star.png | Bin .../website}/media/star_alt.png | Bin {website => maloja/website}/performance.html | 0 {website => maloja/website}/performance.py | 12 +- {website => maloja/website}/proxy.html | 0 {website => maloja/website}/proxy.py | 4 +- {website => maloja/website}/pulse.html | 0 {website => maloja/website}/pulse.py | 12 +- {website => maloja/website}/robots.txt | 0 {website => maloja/website}/scrobbles.html | 0 {website => maloja/website}/scrobbles.py | 12 +- {website => maloja/website}/setup.html | 0 {website => maloja/website}/setup.py | 0 {website => maloja/website}/start.html | 0 {website => maloja/website}/start.py | 6 +- {website => maloja/website}/top_artists.html | 0 {website => maloja/website}/top_artists.py | 10 +- {website => maloja/website}/top_tracks.html | 0 {website => maloja/website}/top_tracks.py | 10 +- {website => maloja/website}/track.html | 0 {website => maloja/website}/track.py | 12 +- {website => maloja/website}/track.pyhp | 0 {website => maloja/website}/wait.html | 0 setup.py | 34 + update_requirements.sh | 2 - 102 files changed, 1043 insertions(+), 483 deletions(-) rename logs/dummy => .dev (100%) delete mode 100644 info.py delete mode 100755 maloja create mode 100644 maloja/__init__.py rename cleanup.py => maloja/cleanup.py (99%) rename compliant_api.py => maloja/compliant_api.py (98%) create mode 100755 maloja/controller.py rename .doreah => maloja/data_files/.doreah (100%) rename {cache => maloja/data_files/cache}/.gitignore (100%) rename {clients => maloja/data_files/clients}/.gitignore (100%) rename {clients => maloja/data_files/clients}/example_file.tsv (100%) rename {images => maloja/data_files/images}/.gitignore (100%) rename {images => maloja/data_files/images}/images.info (100%) rename {scrobbles => maloja/data_files/logs}/dummy (100%) rename {rules => maloja/data_files/rules}/predefined/.gitignore (100%) rename {rules => maloja/data_files/rules}/predefined/krateng_artistsingroups.tsv (100%) rename {rules => maloja/data_files/rules}/predefined/krateng_firefly-soundtrack.tsv (100%) rename {rules => maloja/data_files/rules}/predefined/krateng_jeremysoule.tsv (100%) rename {rules => maloja/data_files/rules}/predefined/krateng_kpopgirlgroups.tsv (100%) rename {rules => maloja/data_files/rules}/predefined/krateng_lotr-soundtrack.tsv (100%) rename {rules => maloja/data_files/rules}/predefined/krateng_monstercat.tsv (100%) rename {rules => maloja/data_files/rules}/predefined/predefined.info (100%) rename {rules => maloja/data_files/rules}/rules.info (100%) create mode 100644 maloja/data_files/scrobbles/dummy rename {settings => maloja/data_files/settings}/.gitignore (100%) rename {settings => maloja/data_files/settings}/default.ini (100%) rename database.py => maloja/database.py (99%) create mode 120000 maloja/doreah rename external.py => maloja/external.py (100%) rename fixexisting.py => maloja/fixexisting.py (97%) rename htmlgenerators.py => maloja/htmlgenerators.py (99%) rename htmlmodules.py => maloja/htmlmodules.py (98%) create mode 100644 maloja/info.py rename lastfmconverter.py => maloja/lastfmconverter.py (96%) rename malojatime.py => maloja/malojatime.py (100%) rename monkey.py => maloja/monkey.py (100%) rename server.py => maloja/server.py (79%) rename supervisor.py => maloja/supervisor.py (81%) rename urihandler.py => maloja/urihandler.py (98%) rename utilities.py => maloja/utilities.py (97%) rename {website => maloja/website}/admin.pyhp (100%) rename {website => maloja/website}/artist.html (100%) rename {website => maloja/website}/artist.py (92%) rename {website => maloja/website}/artist.pyhp (100%) rename {website => maloja/website}/charts_artists.html (100%) rename {website => maloja/website}/charts_artists.py (79%) rename {website => maloja/website}/charts_tracks.html (100%) rename {website => maloja/website}/charts_tracks.py (81%) rename {website => maloja/website}/common/footer.html (100%) rename {website => maloja/website}/common/header.html (100%) rename {website => maloja/website}/compare.html (100%) rename {website => maloja/website}/compare.py (96%) create mode 100644 maloja/website/css/style.css rename {website => maloja/website}/errors/generic.html (100%) rename {website => maloja/website}/favicon.ico (100%) rename {website => maloja/website}/favicon.png (100%) rename {website => maloja/website}/issues.html (100%) rename {website => maloja/website}/issues.py (96%) rename {website => maloja/website}/javascript/cookies.js (100%) rename {website => maloja/website}/javascript/datechange.js (100%) rename {website => maloja/website}/javascript/neopolitan.js (83%) rename {website => maloja/website}/javascript/rangeselect.js (100%) rename {website => maloja/website}/javascript/search.js (100%) rename {website => maloja/website}/javascript/upload.js (100%) rename {website => maloja/website}/less/grisons.less (100%) rename {website => maloja/website}/less/grisonsfont.less (100%) rename {website => maloja/website}/less/maloja.less (100%) rename {website => maloja/website}/manual.html (100%) rename {website => maloja/website}/media/chartpos_bronze.png (100%) rename {website => maloja/website}/media/chartpos_gold.png (100%) rename {website => maloja/website}/media/chartpos_normal.png (100%) rename {website => maloja/website}/media/chartpos_silver.png (100%) rename {website => maloja/website}/media/record_diamond.png (100%) rename {website => maloja/website}/media/record_gold.png (100%) rename {website => maloja/website}/media/record_gold_original.png (100%) rename {website => maloja/website}/media/record_platinum.png (100%) rename {website => maloja/website}/media/star.png (100%) rename {website => maloja/website}/media/star_alt.png (100%) rename {website => maloja/website}/performance.html (100%) rename {website => maloja/website}/performance.py (85%) rename {website => maloja/website}/proxy.html (100%) rename {website => maloja/website}/proxy.py (96%) rename {website => maloja/website}/pulse.html (100%) rename {website => maloja/website}/pulse.py (86%) rename {website => maloja/website}/robots.txt (100%) rename {website => maloja/website}/scrobbles.html (100%) rename {website => maloja/website}/scrobbles.py (82%) rename {website => maloja/website}/setup.html (100%) rename {website => maloja/website}/setup.py (100%) rename {website => maloja/website}/start.html (100%) rename {website => maloja/website}/start.py (94%) rename {website => maloja/website}/top_artists.html (100%) rename {website => maloja/website}/top_artists.py (75%) rename {website => maloja/website}/top_tracks.html (100%) rename {website => maloja/website}/top_tracks.py (76%) rename {website => maloja/website}/track.html (100%) rename {website => maloja/website}/track.py (91%) rename {website => maloja/website}/track.pyhp (100%) rename {website => maloja/website}/wait.html (100%) create mode 100644 setup.py delete mode 100644 update_requirements.sh diff --git a/logs/dummy b/.dev similarity index 100% rename from logs/dummy rename to .dev diff --git a/.gitignore b/.gitignore index ff3a5ed..fe27dbb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,15 @@ # generic temporary / dev files *.pyc *.sh -!/update_requirements.sh *.note *.xcf nohup.out -/.dev +/maloja.egg-info # user files *.tsv *.rulestate *.log -*.css # currently not using /screenshot*.png diff --git a/info.py b/info.py deleted file mode 100644 index 941fd4b..0000000 --- a/info.py +++ /dev/null @@ -1,10 +0,0 @@ -import os - -author = { - "name":"Johannes Krattenmacher", - "email":"maloja@krateng.dev", - "github": "krateng" -} -version = 1,5,15 -versionstr = ".".join(str(n) for n in version) -dev = os.path.exists("./.dev") diff --git a/maloja b/maloja deleted file mode 100755 index 8019e20..0000000 --- a/maloja +++ /dev/null @@ -1,350 +0,0 @@ -#!/usr/bin/env python3 - -import subprocess -import sys -import signal -import os -import stat -import pathlib - - - -neededmodules = [ - "bottle", - "waitress", - "setproctitle", - "doreah", - "nimrodel" -] - -recommendedmodules = [ - "wand" -] - -SOURCE_URL = "https://github.com/krateng/maloja/archive/master.zip" - - - -def blue(txt): return "\033[94m" + txt + "\033[0m" -def green(txt): return "\033[92m" + txt + "\033[0m" -def yellow(txt): return "\033[93m" + txt + "\033[0m" - -## GOTODIR goes to directory that seems to have a maloja install -## SETUP assumes correct directory. sets settings and key -## INSTALL ignores local files, just installs prerequisites -## START INSTALL - GOTODIR - SETUP - starts process -## RESTART STOP - START -## STOP Stops process -## UPDATE GOTODIR - updates from repo -## LOADLASTFM GOTODIR - imports csv data -## INSTALLHERE makes this directory valid - UPDATE - INSTALL - SETUP - -def gotodir(): - if os.path.exists("./server.py"): - return True - elif os.path.exists("/opt/maloja/server.py"): - os.chdir("/opt/maloja/") - return True - - print("Maloja installation could not be found.") - return False - -def setup(): - - from doreah import settings - - # EXTERNAL API KEYS - apikeys = { - "LASTFM_API_KEY":"Last.fm API Key", - "FANARTTV_API_KEY":"Fanart.tv API Key", - "SPOTIFY_API_ID":"Spotify Client ID", - "SPOTIFY_API_SECRET":"Spotify Client Secret" - } - - print("Various external services can be used to display images. If not enough of them are set up, only local images will be used.") - for k in apikeys: - key = settings.get_settings(k) - if key is None: - print("\t" + "Currently not using a " + apikeys[k] + " for image display.") - elif key == "ASK": - print("\t" + "Please enter your " + apikeys[k] + ". If you do not want to use one at this moment, simply leave this empty and press Enter.") - key = input() - if key == "": key = None - settings.update_settings("settings/settings.ini",{k:key},create_new=True) - else: - print("\t" + apikeys[k] + " found.") - - - # OWN API KEY - if os.path.exists("./clients/authenticated_machines.tsv"): - pass - else: - print("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. [Y/n]") - answer = input() - if answer.lower() in ["y","yes","yea","1","positive","true",""]: - import random - key = "" - for i in range(64): - key += str(random.choice(list(range(10)) + list("abcdefghijklmnopqrstuvwxyz") + list("ABCDEFGHIJKLMNOPQRSTUVWXYZ"))) - print("Your API Key: " + yellow(key)) - with open("./clients/authenticated_machines.tsv","w") as keyfile: - keyfile.write(key + "\t" + "Default Generated Key") - elif answer.lower() in ["n","no","nay","0","negative","false"]: - pass - -def install(): - toinstall = [] - toinstallr = [] - for m in neededmodules: - try: - exec("import " + m) #I'm sorry - except: - toinstall.append(m) - - for m in recommendedmodules: - try: - exec("import " + m) - except: - toinstallr.append(m) - - if toinstall != []: - print("The following python modules need to be installed:") - for m in toinstall: - print("\t" + yellow(m)) - if toinstallr != []: - print("The following python modules are highly recommended, some features will not work without them:") - for m in toinstallr: - print("\t" + yellow(m)) - - if toinstall != [] or toinstallr != []: - if os.geteuid() != 0: - print("You can install them with",yellow("pip install -r requirements.txt"),"or Maloja can try to install them automatically. For this, you need to run this script as a root user.") - return False - else: - print("You can install them with",yellow("pip install -r requirements.txt"),"or Maloja can try to install them automatically, This might or might not work / bloat your system / cause a nuclear war.") - fail = False - if toinstall != []: - print("Attempt to install required modules? [Y/n]") - answer = input() - - if answer.lower() in ["y","yes","yea","1","positive","true",""]: - for m in toinstall: - try: - print("Installing " + m + " with pip...") - from pip._internal import main as pipmain - #os.system("pip3 install " + m) - pipmain(["install",m]) - print("Success!") - except: - print("Failure!") - fail = True - - elif answer.lower() in ["n","no","nay","0","negative","false"]: - return False #if you dont want to auto install required, you probably dont want to install recommended - else: - print("What?") - return False - if toinstallr != []: - print("Attempt to install recommended modules? [Y/n]") - answer = input() - - if answer.lower() in ["y","yes","yea","1","positive","true",""]: - for m in toinstallr: - try: - print("Installing " + m + " with pip...") - from pip._internal import main as pipmain - #os.system("pip3 install " + m) - pipmain(["install",m]) - print("Success!") - except: - print("Failure!") - fail = True - - elif answer.lower() in ["n","no","nay","0","negative","false"]: - return False - else: - print("What?") - return False - - if fail: return False - print("All modules successfully installed!") - print("Run the script again (without root) to start Maloja.") - return False - - else: - print("All necessary modules seem to be installed.") - return True - -def getInstance(): - try: - output = subprocess.check_output(["pidof","Maloja"]) - pid = int(output) - return pid - except: - return None - -def getInstanceSupervisor(): - try: - output = subprocess.check_output(["pidof","maloja_supervisor"]) - pid = int(output) - return pid - except: - return None - -def start(): - if install(): - - if gotodir(): - setup() - p = subprocess.Popen(["python3","server.py"],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL) - p = subprocess.Popen(["python3","supervisor.py"],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL) - print(green("Maloja started!") + " PID: " + str(p.pid)) - - from doreah import settings - port = settings.get_settings("WEB_PORT") - - 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" + blue("http://localhost:" + str(port))) - print("\t" + blue("http://localhost:" + str(port) + "/setup")) - return True - #else: - # os.chdir("/opt/maloja/") - # p = subprocess.Popen(["python3","server.py"],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL) - # print("Maloja started! PID: " + str(p.pid)) - # return True - - print("Error while starting Maloja.") - return False - -def restart(): - #pid = getInstance() - #if pid == None: - # print("Server is not running.") - #else: - # stop() - #start() - - wasrunning = stop() - start() - return wasrunning - -def stop(): - pid_sv = getInstanceSupervisor() - if pid_sv is not None: - os.kill(pid_sv,signal.SIGTERM) - - pid = getInstance() - if pid is None: - print("Server is not running") - return False - else: - os.kill(pid,signal.SIGTERM) - print("Maloja stopped! PID: " + str(pid)) - return True - -def update(): - - import urllib.request - import shutil - #import tempfile - import zipfile - import distutils.dir_util - - if not gotodir(): return False - - if os.path.exists("./.dev"): - print("Better not overwrite the development server!") - return - - print("Updating Maloja...") - #with urllib.request.urlopen(SOURCE_URL) as response: - # with tempfile.NamedTemporaryFile(delete=True) as tmpfile: - # shutil.copyfileobj(response,tmpfile) - # - # with zipfile.ZipFile(tmpfile.name,"r") as z: - # - # for f in z.namelist(): - # #print("extracting " + f) - # z.extract(f) - - - os.system("wget " + SOURCE_URL) - with zipfile.ZipFile("./master.zip","r") as z: - - # if we ever have a separate directory for the code - # (the calling update script is not the same version as the current - # remote repository, so we better add this check just in case) - if "source/" in z.namelist(): - for f in z.namelist(): - if f.startswith("source/"): - z.extract(f) - for dir,_,files in os.walk("source"): - for f in files: - origfile = os.path.join(dir,f) - newfile = ps.path.join(dir[7:],f) - os.renames(origfile,newfile) #also prunes empty directory - else: - for f in z.namelist(): - z.extract(f) - - os.remove("./master.zip") - - - distutils.dir_util.copy_tree("./maloja-master/","./",verbose=2) - shutil.rmtree("./maloja-master") - print("Done!") - - os.chmod("./maloja",os.stat("./maloja").st_mode | stat.S_IXUSR) - os.chmod("./update_requirements.sh",os.stat("./update_requirements.sh").st_mode | stat.S_IXUSR) - - try: - returnval = os.system("./update_requirements.sh") - assert returnval == 0 - except: - print("Make sure to update required modules! (" + yellow("./update_requirements.sh") + ")") - - if stop(): start() #stop returns whether it was running before, in which case we restart it - -def loadlastfm(): - - try: - filename = sys.argv[2] - filename = os.path.abspath(filename) - except: - print("Please specify a file!") - return - - if gotodir(): - if os.path.exists("./scrobbles/lastfmimport.tsv"): - print("Already imported Last.FM data. Overwrite? [y/N]") - if input().lower() in ["y","yes","yea","1","positive","true"]: - pass - else: - return - print("Please wait...") - os.system("python3 ./lastfmconverter.py " + filename + " ./scrobbles/lastfmimport.tsv") - print("Successfully imported your Last.FM scrobbles!") - -def installhere(): - if len(os.listdir()) > 1: - print("You should install Maloja in an empty directory.") - return False - else: - open("server.py","w").close() - # if it's cheese, but it works, it ain't cheese - update() - install() - setup() - - print("Maloja installed! Start with " + yellow("./maloja start")) - - -if __name__ == "__main__": - if sys.argv[1] == "start": restart() - elif sys.argv[1] == "restart": restart() - elif sys.argv[1] == "stop": stop() - elif sys.argv[1] == "update": update() - elif sys.argv[1] == "import": loadlastfm() - elif sys.argv[1] == "install": installhere() - else: print("Valid commands: start restart stop update import install") diff --git a/maloja/__init__.py b/maloja/__init__.py new file mode 100644 index 0000000..69868b0 --- /dev/null +++ b/maloja/__init__.py @@ -0,0 +1,19 @@ +name = "maloja" + +from .info import author,version,versionstr + +requires = [ + "bottle>=0.12.16", + "waitress>=1.3", + "doreah>=1.2.7", + "nimrodel>=0.4.9", + "setproctitle>=1.1.10", + "wand>=0.5.4", + "lesscpy>=0.13" +] +resources = [ + "website/*/*", + "website/*", + "data_files/*/*", + "data_files/.doreah" +] diff --git a/cleanup.py b/maloja/cleanup.py similarity index 99% rename from cleanup.py rename to maloja/cleanup.py index 7961940..600170a 100644 --- a/cleanup.py +++ b/maloja/cleanup.py @@ -1,5 +1,5 @@ import re -import utilities +from . import utilities from doreah import tsv, settings # need to do this as a class so it can retain loaded settings from file diff --git a/compliant_api.py b/maloja/compliant_api.py similarity index 98% rename from compliant_api.py rename to maloja/compliant_api.py index 5960efa..ae20c26 100644 --- a/compliant_api.py +++ b/maloja/compliant_api.py @@ -1,11 +1,11 @@ from doreah.logging import log import hashlib import random -import database +from . import database import datetime import itertools import sys -from cleanup import CleanerAgent +from .cleanup import CleanerAgent from bottle import response ## GNU-FM-compliant scrobbling @@ -68,7 +68,7 @@ def handle(path,keys): def scrobbletrack(artiststr,titlestr,timestamp): try: - log("Incoming scrobble (compliant API): ARTISTS: " + artiststr + ", TRACK: " + titlestr,module="debug") + log("Incoming scrobble (compliant API): ARTISTS: " + artiststr + ", TRACK: " + titlestr,module="debug") (artists,title) = cla.fullclean(artiststr,titlestr) database.createScrobble(artists,title,timestamp) database.sync() diff --git a/maloja/controller.py b/maloja/controller.py new file mode 100755 index 0000000..ba03c78 --- /dev/null +++ b/maloja/controller.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python3 + +import subprocess +import sys +import signal +import os +import shutil +from distutils import dir_util +import stat +import pathlib +import pkg_resources + +from .info import DATA_DIR + + + +def blue(txt): return "\033[94m" + txt + "\033[0m" +def green(txt): return "\033[92m" + txt + "\033[0m" +def yellow(txt): return "\033[93m" + txt + "\033[0m" + + + + + +os.chdir(DATA_DIR) + +def copy_initial_local_files(): + folder = pkg_resources.resource_filename(__name__,"data_files") + #shutil.copy(folder,DATA_DIR) + dir_util.copy_tree(folder,DATA_DIR) + + +def setup(): + + copy_initial_local_files() + + from doreah import settings + + # EXTERNAL API KEYS + apikeys = { + "LASTFM_API_KEY":"Last.fm API Key", + "FANARTTV_API_KEY":"Fanart.tv API Key", + "SPOTIFY_API_ID":"Spotify Client ID", + "SPOTIFY_API_SECRET":"Spotify Client Secret" + } + + print("Various external services can be used to display images. If not enough of them are set up, only local images will be used.") + for k in apikeys: + key = settings.get_settings(k) + if key is None: + print("\t" + "Currently not using a " + apikeys[k] + " for image display.") + elif key == "ASK": + print("\t" + "Please enter your " + apikeys[k] + ". If you do not want to use one at this moment, simply leave this empty and press Enter.") + key = input() + if key == "": key = None + settings.update_settings("settings/settings.ini",{k:key},create_new=True) + else: + print("\t" + apikeys[k] + " found.") + + + # OWN API KEY + if os.path.exists("./clients/authenticated_machines.tsv"): + pass + else: + print("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. [Y/n]") + answer = input() + if answer.lower() in ["y","yes","yea","1","positive","true",""]: + import random + key = "" + for i in range(64): + key += str(random.choice(list(range(10)) + list("abcdefghijklmnopqrstuvwxyz") + list("ABCDEFGHIJKLMNOPQRSTUVWXYZ"))) + print("Your API Key: " + yellow(key)) + with open("./clients/authenticated_machines.tsv","w") as keyfile: + keyfile.write(key + "\t" + "Default Generated Key") + elif answer.lower() in ["n","no","nay","0","negative","false"]: + pass + + +def getInstance(): + try: + output = subprocess.check_output(["pidof","Maloja"]) + pid = int(output) + return pid + except: + return None + +def getInstanceSupervisor(): + try: + output = subprocess.check_output(["pidof","maloja_supervisor"]) + pid = int(output) + return pid + except: + return None + +def start(): + setup() + try: + p = subprocess.Popen(["python3","-m","maloja.server"],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL,cwd=DATA_DIR) + sp = subprocess.Popen(["python3","-m","maloja.supervisor"],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL,cwd=DATA_DIR) + print(green("Maloja started!") + " PID: " + str(p.pid)) + + from doreah import settings + port = settings.get_settings("WEB_PORT") + + 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" + blue("http://localhost:" + str(port))) + print("\t" + blue("http://localhost:" + str(port) + "/setup")) + return True + except: + print("Error while starting Maloja.") + return False + +def restart(): + wasrunning = stop() + start() + return wasrunning + +def stop(): + pid_sv = getInstanceSupervisor() + if pid_sv is not None: + os.kill(pid_sv,signal.SIGTERM) + + pid = getInstance() + if pid is None: + print("Server is not running") + return False + else: + os.kill(pid,signal.SIGTERM) + print("Maloja stopped! PID: " + str(pid)) + return True + + +def loadlastfm(): + + try: + filename = sys.argv[2] + filename = os.path.abspath(filename) + except: + print("Please specify a file!") + return + + if os.path.exists("./scrobbles/lastfmimport.tsv"): + print("Already imported Last.FM data. Overwrite? [y/N]") + if input().lower() in ["y","yes","yea","1","positive","true"]: + pass + else: + return + print("Please wait...") + os.system("python3 ./lastfmconverter.py " + filename + " ./scrobbles/lastfmimport.tsv") + print("Successfully imported your Last.FM scrobbles!") + + + +def main(): + if sys.argv[1] == "start": restart() + elif sys.argv[1] == "restart": restart() + elif sys.argv[1] == "stop": stop() + #elif sys.argv[1] == "update": update() + elif sys.argv[1] == "import": loadlastfm() + #elif sys.argv[1] == "install": installhere() + else: print("Valid commands: start restart stop import") + +if __name__ == "__main__": + main() diff --git a/.doreah b/maloja/data_files/.doreah similarity index 100% rename from .doreah rename to maloja/data_files/.doreah diff --git a/cache/.gitignore b/maloja/data_files/cache/.gitignore similarity index 100% rename from cache/.gitignore rename to maloja/data_files/cache/.gitignore diff --git a/clients/.gitignore b/maloja/data_files/clients/.gitignore similarity index 100% rename from clients/.gitignore rename to maloja/data_files/clients/.gitignore diff --git a/clients/example_file.tsv b/maloja/data_files/clients/example_file.tsv similarity index 100% rename from clients/example_file.tsv rename to maloja/data_files/clients/example_file.tsv diff --git a/images/.gitignore b/maloja/data_files/images/.gitignore similarity index 100% rename from images/.gitignore rename to maloja/data_files/images/.gitignore diff --git a/images/images.info b/maloja/data_files/images/images.info similarity index 100% rename from images/images.info rename to maloja/data_files/images/images.info diff --git a/scrobbles/dummy b/maloja/data_files/logs/dummy similarity index 100% rename from scrobbles/dummy rename to maloja/data_files/logs/dummy diff --git a/rules/predefined/.gitignore b/maloja/data_files/rules/predefined/.gitignore similarity index 100% rename from rules/predefined/.gitignore rename to maloja/data_files/rules/predefined/.gitignore diff --git a/rules/predefined/krateng_artistsingroups.tsv b/maloja/data_files/rules/predefined/krateng_artistsingroups.tsv similarity index 100% rename from rules/predefined/krateng_artistsingroups.tsv rename to maloja/data_files/rules/predefined/krateng_artistsingroups.tsv diff --git a/rules/predefined/krateng_firefly-soundtrack.tsv b/maloja/data_files/rules/predefined/krateng_firefly-soundtrack.tsv similarity index 100% rename from rules/predefined/krateng_firefly-soundtrack.tsv rename to maloja/data_files/rules/predefined/krateng_firefly-soundtrack.tsv diff --git a/rules/predefined/krateng_jeremysoule.tsv b/maloja/data_files/rules/predefined/krateng_jeremysoule.tsv similarity index 100% rename from rules/predefined/krateng_jeremysoule.tsv rename to maloja/data_files/rules/predefined/krateng_jeremysoule.tsv diff --git a/rules/predefined/krateng_kpopgirlgroups.tsv b/maloja/data_files/rules/predefined/krateng_kpopgirlgroups.tsv similarity index 100% rename from rules/predefined/krateng_kpopgirlgroups.tsv rename to maloja/data_files/rules/predefined/krateng_kpopgirlgroups.tsv diff --git a/rules/predefined/krateng_lotr-soundtrack.tsv b/maloja/data_files/rules/predefined/krateng_lotr-soundtrack.tsv similarity index 100% rename from rules/predefined/krateng_lotr-soundtrack.tsv rename to maloja/data_files/rules/predefined/krateng_lotr-soundtrack.tsv diff --git a/rules/predefined/krateng_monstercat.tsv b/maloja/data_files/rules/predefined/krateng_monstercat.tsv similarity index 100% rename from rules/predefined/krateng_monstercat.tsv rename to maloja/data_files/rules/predefined/krateng_monstercat.tsv diff --git a/rules/predefined/predefined.info b/maloja/data_files/rules/predefined/predefined.info similarity index 100% rename from rules/predefined/predefined.info rename to maloja/data_files/rules/predefined/predefined.info diff --git a/rules/rules.info b/maloja/data_files/rules/rules.info similarity index 100% rename from rules/rules.info rename to maloja/data_files/rules/rules.info diff --git a/maloja/data_files/scrobbles/dummy b/maloja/data_files/scrobbles/dummy new file mode 100644 index 0000000..e69de29 diff --git a/settings/.gitignore b/maloja/data_files/settings/.gitignore similarity index 100% rename from settings/.gitignore rename to maloja/data_files/settings/.gitignore diff --git a/settings/default.ini b/maloja/data_files/settings/default.ini similarity index 100% rename from settings/default.ini rename to maloja/data_files/settings/default.ini diff --git a/database.py b/maloja/database.py similarity index 99% rename from database.py rename to maloja/database.py index 1ae61db..f58f26a 100644 --- a/database.py +++ b/maloja/database.py @@ -1,12 +1,13 @@ # server from bottle import request, response, FormsDict # rest of the project -from cleanup import CleanerAgent, CollectorAgent -import utilities -from malojatime import register_scrobbletime, time_stamps, ranges -from urihandler import uri_to_internal, internal_to_uri, compose_querystring -import compliant_api -from external import proxy_scrobble +from .cleanup import CleanerAgent, CollectorAgent +from . import utilities +from .malojatime import register_scrobbletime, time_stamps, ranges +from .urihandler import uri_to_internal, internal_to_uri, compose_querystring +from . import compliant_api +from .external import proxy_scrobble +from . import info # doreah toolkit from doreah.logging import log from doreah import tsv @@ -232,7 +233,7 @@ def test_server(key=None): @dbserver.get("serverinfo") def server_info(): - import info + response.set_header("Access-Control-Allow-Origin","*") response.set_header("Content-Type","application/json") diff --git a/maloja/doreah b/maloja/doreah new file mode 120000 index 0000000..2724e86 --- /dev/null +++ b/maloja/doreah @@ -0,0 +1 @@ +../../tools/doreah/doreah \ No newline at end of file diff --git a/external.py b/maloja/external.py similarity index 100% rename from external.py rename to maloja/external.py diff --git a/fixexisting.py b/maloja/fixexisting.py similarity index 97% rename from fixexisting.py rename to maloja/fixexisting.py index 5c4538e..45cb36c 100644 --- a/fixexisting.py +++ b/maloja/fixexisting.py @@ -1,6 +1,6 @@ import os import re -from cleanup import CleanerAgent +from .cleanup import CleanerAgent from doreah.logging import log import difflib diff --git a/htmlgenerators.py b/maloja/htmlgenerators.py similarity index 99% rename from htmlgenerators.py rename to maloja/htmlgenerators.py index e443fd7..6c0c16b 100644 --- a/htmlgenerators.py +++ b/maloja/htmlgenerators.py @@ -1,7 +1,7 @@ import urllib from bottle import FormsDict import datetime -from urihandler import compose_querystring +from .urihandler import compose_querystring import urllib.parse from doreah.settings import get_settings diff --git a/htmlmodules.py b/maloja/htmlmodules.py similarity index 98% rename from htmlmodules.py rename to maloja/htmlmodules.py index 6182af2..be173e8 100644 --- a/htmlmodules.py +++ b/maloja/htmlmodules.py @@ -1,8 +1,8 @@ -from htmlgenerators import * -import database -from utilities import getArtistImage, getTrackImage -from malojatime import * -from urihandler import compose_querystring, internal_to_uri, uri_to_internal +from .htmlgenerators import * +from . import database +from .utilities import getArtistImage, getTrackImage +from .malojatime import * +from .urihandler import compose_querystring, internal_to_uri, uri_to_internal import urllib import datetime import math diff --git a/maloja/info.py b/maloja/info.py new file mode 100644 index 0000000..55c2894 --- /dev/null +++ b/maloja/info.py @@ -0,0 +1,19 @@ +import os + +author = { + "name":"Johannes Krattenmacher", + "email":"maloja@krateng.dev", + "github": "krateng" +} +version = 2,0,0 +versionstr = ".".join(str(n) for n in version) + + +try: + DATA_DIR = os.environ["XDG_DATA_HOME"].split(":")[0] + assert os.path.exists(DATA_DIR) +except: + DATA_DIR = os.path.join(os.environ["HOME"],".local/share/") + +DATA_DIR = os.path.join(DATA_DIR,"maloja") +os.makedirs(DATA_DIR,exist_ok=True) diff --git a/lastfmconverter.py b/maloja/lastfmconverter.py similarity index 96% rename from lastfmconverter.py rename to maloja/lastfmconverter.py index 162234b..8f2959a 100644 --- a/lastfmconverter.py +++ b/maloja/lastfmconverter.py @@ -1,6 +1,6 @@ import sys, os, datetime, re, cleanup -from cleanup import * -from utilities import * +from .cleanup import * +from .utilities import * log = open(sys.argv[1]) diff --git a/malojatime.py b/maloja/malojatime.py similarity index 100% rename from malojatime.py rename to maloja/malojatime.py diff --git a/monkey.py b/maloja/monkey.py similarity index 100% rename from monkey.py rename to maloja/monkey.py diff --git a/server.py b/maloja/server.py similarity index 79% rename from server.py rename to maloja/server.py index 0c558c8..6e014d5 100755 --- a/server.py +++ b/maloja/server.py @@ -1,55 +1,66 @@ #!/usr/bin/env python +import os +from .info import DATA_DIR +os.chdir(DATA_DIR) + # server stuff from bottle import Bottle, route, get, post, error, run, template, static_file, request, response, FormsDict, redirect, template, HTTPResponse, BaseRequest import waitress # monkey patching -import monkey +from . import monkey # rest of the project -import database -import htmlmodules -import htmlgenerators -import malojatime -import utilities -from utilities import resolveImage -from urihandler import uri_to_internal, remove_identical -import urihandler -import info +from . import database +from . import htmlmodules +from . import htmlgenerators +from . import malojatime +from . import utilities +from .utilities import resolveImage +from .urihandler import uri_to_internal, remove_identical +from . import urihandler +from . import info # doreah toolkit from doreah import settings from doreah.logging import log from doreah.timing import Clock # technical -from importlib.machinery import SourceFileLoader +#from importlib.machinery import SourceFileLoader +import importlib import _thread import sys import signal import os import setproctitle +import pkg_resources # url handling import urllib + + + #settings.config(files=["settings/default.ini","settings/settings.ini"]) #settings.update("settings/default.ini","settings/settings.ini") MAIN_PORT = settings.get_settings("WEB_PORT") HOST = settings.get_settings("HOST") THREADS = 12 BaseRequest.MEMFILE_MAX = 15 * 1024 * 1024 +WEBFOLDER = pkg_resources.resource_filename(__name__,"website") webserver = Bottle() +pthjoin = os.path.join -import lesscpy -css = "" -for f in os.listdir("website/less"): - css += lesscpy.compile("website/less/" + f) +#import lesscpy +#css = "" +#for f in os.listdir(pthjoin(WEBFOLDER,"less")): +# css += lesscpy.compile(pthjoin(WEBFOLDER,"less",f)) -os.makedirs("website/css",exist_ok=True) -with open("website/css/style.css","w") as f: - f.write(css) +#os.makedirs("website/css",exist_ok=True) +#with open("website/css/style.css","w") as f: +# f.write(css) @webserver.route("") @@ -69,16 +80,16 @@ def customerror(error): code = int(str(error).split(",")[0][1:]) log("HTTP Error: " + str(code),module="error") - if os.path.exists("website/errors/" + str(code) + ".html"): - return static_file("website/errors/" + str(code) + ".html",root="") + if os.path.exists(pthjoin(WEBFOLDER,"errors",str(code) + ".html")): + return static_file(pthjoin(WEBFOLDER,"errors",str(code) + ".html"),root="") else: - with open("website/errors/generic.html") as htmlfile: + with open(pthjoin(WEBFOLDER,"errors/generic.html")) as htmlfile: html = htmlfile.read() # apply global substitutions - with open("website/common/footer.html") as footerfile: + with open(pthjoin(WEBFOLDER,"common/footer.html")) as footerfile: footerhtml = footerfile.read() - with open("website/common/header.html") as headerfile: + with open(pthjoin(WEBFOLDER,"common/header.html")) as headerfile: headerhtml = headerfile.read() html = html.replace("",footerhtml + "").replace("",headerhtml + "") @@ -143,17 +154,18 @@ def static_image(pth): @webserver.route("/") @webserver.route("/") def static(name): - response = static_file("website/" + name,root="") + response = static_file(name,root=WEBFOLDER) response.set_header("Cache-Control", "public, max-age=3600") return response + @webserver.route("/") def static_html(name): linkheaders = ["; rel=preload; as=style"] keys = remove_identical(FormsDict.decode(request.query)) - pyhp_file = os.path.exists("website/" + name + ".pyhp") - html_file = os.path.exists("website/" + name + ".html") + pyhp_file = os.path.exists(pthjoin(WEBFOLDER,name + ".pyhp")) + html_file = os.path.exists(pthjoin(WEBFOLDER,name + ".html")) pyhp_pref = settings.get_settings("USE_PYHP") adminmode = request.cookies.get("adminmode") == "true" and database.checkAPIkey(request.cookies.get("apikey")) is not False @@ -183,29 +195,30 @@ def static_html(name): environ["filterkeys"], environ["limitkeys"], environ["delimitkeys"], environ["amountkeys"] = uri_to_internal(keys) #response.set_header("Content-Type","application/xhtml+xml") - res = file("website/" + name + ".pyhp",environ) + res = file(pthjoin(WEBFOLDER,name + ".pyhp"),environ) log("Generated page " + name + " in " + str(clock.stop()) + "s (PYHP)",module="debug") return res # if not, use the old way else: - with open("website/" + name + ".html") as htmlfile: + with open(pthjoin(WEBFOLDER,name + ".html")) as htmlfile: html = htmlfile.read() # apply global substitutions - with open("website/common/footer.html") as footerfile: + with open(pthjoin(WEBFOLDER,"common/footer.html")) as footerfile: footerhtml = footerfile.read() - with open("website/common/header.html") as headerfile: + 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("website/" + name + ".py"): + if os.path.exists(pthjoin(WEBFOLDER,name + ".py")): #txt_keys = SourceFileLoader(name,"website/" + name + ".py").load_module().replacedict(keys,DATABASE_PORT) try: - txt_keys,resources = SourceFileLoader(name,"website/" + name + ".py").load_module().instructions(keys) + module = importlib.import_module(".website." + 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 diff --git a/supervisor.py b/maloja/supervisor.py similarity index 81% rename from supervisor.py rename to maloja/supervisor.py index 824c2a2..edf9291 100644 --- a/supervisor.py +++ b/maloja/supervisor.py @@ -20,6 +20,6 @@ while True: except: log("Maloja is not running, restarting...",module="supervisor") try: - p = subprocess.Popen(["python3","server.py"],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL) + p = subprocess.Popen(["python3","-m","maloja.server"],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL) except e: log("Error starting Maloja: " + str(e),module="supervisor") diff --git a/urihandler.py b/maloja/urihandler.py similarity index 98% rename from urihandler.py rename to maloja/urihandler.py index 9ed7943..9a569c1 100644 --- a/urihandler.py +++ b/maloja/urihandler.py @@ -1,6 +1,6 @@ import urllib from bottle import FormsDict -from malojatime import time_fix, time_str, get_range_object +from .malojatime import time_fix, time_str, get_range_object import math # necessary because urllib.parse.urlencode doesnt handle multidicts diff --git a/utilities.py b/maloja/utilities.py similarity index 97% rename from utilities.py rename to maloja/utilities.py index d3850ef..f61ec25 100644 --- a/utilities.py +++ b/maloja/utilities.py @@ -14,7 +14,7 @@ from doreah import caching from doreah.logging import log from doreah.regular import yearly, daily -from external import api_request_track, api_request_artist +from .external import api_request_track, api_request_artist @@ -469,7 +469,7 @@ def set_image(b64,**keys): def update_medals(): - from database import MEDALS, MEDALS_TRACKS, STAMPS, get_charts_artists, get_charts_tracks + from .database import MEDALS, MEDALS_TRACKS, STAMPS, get_charts_artists, get_charts_tracks currentyear = datetime.datetime.utcnow().year try: @@ -505,8 +505,8 @@ def update_medals(): @daily def update_weekly(): - from database import WEEKLY_TOPTRACKS, WEEKLY_TOPARTISTS, get_charts_artists, get_charts_tracks - from malojatime import ranges, thisweek + from .database import WEEKLY_TOPTRACKS, WEEKLY_TOPARTISTS, get_charts_artists, get_charts_tracks + from .malojatime import ranges, thisweek WEEKLY_TOPARTISTS.clear() diff --git a/website/admin.pyhp b/maloja/website/admin.pyhp similarity index 100% rename from website/admin.pyhp rename to maloja/website/admin.pyhp diff --git a/website/artist.html b/maloja/website/artist.html similarity index 100% rename from website/artist.html rename to maloja/website/artist.html diff --git a/website/artist.py b/maloja/website/artist.py similarity index 92% rename from website/artist.py rename to maloja/website/artist.py index bf3f2fa..f75c884 100644 --- a/website/artist.py +++ b/maloja/website/artist.py @@ -1,13 +1,13 @@ import urllib -import database -from malojatime import today,thisweek,thismonth,thisyear +from .. import database +from ..malojatime import today,thisweek,thismonth,thisyear def instructions(keys): - from utilities import getArtistImage - from htmlgenerators import artistLink, artistLinks, link_address - from urihandler import compose_querystring, uri_to_internal - from htmlmodules import module_pulse, module_performance, module_trackcharts, module_scrobblelist + from ..utilities import getArtistImage + from ..htmlgenerators import artistLink, artistLinks, link_address + from ..urihandler import compose_querystring, uri_to_internal + from ..htmlmodules import module_pulse, module_performance, module_trackcharts, module_scrobblelist filterkeys, _, _, _ = uri_to_internal(keys,forceArtist=True) artist = filterkeys.get("artist") diff --git a/website/artist.pyhp b/maloja/website/artist.pyhp similarity index 100% rename from website/artist.pyhp rename to maloja/website/artist.pyhp diff --git a/website/charts_artists.html b/maloja/website/charts_artists.html similarity index 100% rename from website/charts_artists.html rename to maloja/website/charts_artists.html diff --git a/website/charts_artists.py b/maloja/website/charts_artists.py similarity index 79% rename from website/charts_artists.py rename to maloja/website/charts_artists.py index b5c951c..04cc582 100644 --- a/website/charts_artists.py +++ b/maloja/website/charts_artists.py @@ -2,10 +2,10 @@ import urllib def instructions(keys): - from utilities import getArtistImage - from urihandler import compose_querystring, uri_to_internal - from htmlmodules import module_artistcharts, module_filterselection, module_artistcharts_tiles - from malojatime import range_desc + from ..utilities import getArtistImage + from ..urihandler import compose_querystring, uri_to_internal + from ..htmlmodules import module_artistcharts, module_filterselection, module_artistcharts_tiles + from ..malojatime import range_desc from doreah.settings import get_settings diff --git a/website/charts_tracks.html b/maloja/website/charts_tracks.html similarity index 100% rename from website/charts_tracks.html rename to maloja/website/charts_tracks.html diff --git a/website/charts_tracks.py b/maloja/website/charts_tracks.py similarity index 81% rename from website/charts_tracks.py rename to maloja/website/charts_tracks.py index 843d0ff..0bfe9a4 100644 --- a/website/charts_tracks.py +++ b/maloja/website/charts_tracks.py @@ -2,11 +2,11 @@ import urllib def instructions(keys): - from utilities import getArtistImage, getTrackImage - from htmlgenerators import artistLink - from urihandler import compose_querystring, uri_to_internal - from htmlmodules import module_trackcharts, module_filterselection, module_trackcharts_tiles - from malojatime import range_desc + from ..utilities import getArtistImage, getTrackImage + from ..htmlgenerators import artistLink + from ..urihandler import compose_querystring, uri_to_internal + from ..htmlmodules import module_trackcharts, module_filterselection, module_trackcharts_tiles + from ..malojatime import range_desc from doreah.settings import get_settings filterkeys, timekeys, _, amountkeys = uri_to_internal(keys) diff --git a/website/common/footer.html b/maloja/website/common/footer.html similarity index 100% rename from website/common/footer.html rename to maloja/website/common/footer.html diff --git a/website/common/header.html b/maloja/website/common/header.html similarity index 100% rename from website/common/header.html rename to maloja/website/common/header.html diff --git a/website/compare.html b/maloja/website/compare.html similarity index 100% rename from website/compare.html rename to maloja/website/compare.html diff --git a/website/compare.py b/maloja/website/compare.py similarity index 96% rename from website/compare.py rename to maloja/website/compare.py index febbe64..5edd54c 100644 --- a/website/compare.py +++ b/maloja/website/compare.py @@ -1,8 +1,8 @@ import urllib -import database +from .. import database import json -from htmlgenerators import artistLink -from utilities import getArtistImage +from ..htmlgenerators import artistLink +from ..utilities import getArtistImage def instructions(keys): diff --git a/maloja/website/css/style.css b/maloja/website/css/style.css new file mode 100644 index 0000000..576b7a1 --- /dev/null +++ b/maloja/website/css/style.css @@ -0,0 +1,671 @@ +@font-face { + font-family: 'Ubuntu'; + font-style: normal; + font-weight: 400; + src: local('Ubuntu Regular'), local('Ubuntu-Regular'), url(https://fonts.gstatic.com/s/ubuntu/v14/4iCs6KVjbNBYlgoKcg72j00.woff2) format('woff2'); +} +@font-face { + font-family: 'Ubuntu'; + font-style: normal; + font-weight: 400; + src: local('Ubuntu Regular'), local('Ubuntu-Regular'), url(https://fonts.gstatic.com/s/ubuntu/v14/4iCs6KVjbNBYlgoKew72j00.woff2) format('woff2'); + unicode-range: U + 0400 -045F, U + 0490 -0491, U + 04 B0-04B1, U + 2116; +} +@font-face { + font-family: 'Ubuntu'; + font-style: normal; + font-weight: 400; + src: local('Ubuntu Regular'), local('Ubuntu-Regular'), url(https://fonts.gstatic.com/s/ubuntu/v14/4iCs6KVjbNBYlgoKcw72j00.woff2) format('woff2'); + unicode-range: U + 1 F00-1FFF; +} +@font-face { + font-family: 'Ubuntu'; + font-style: normal; + font-weight: 400; + src: local('Ubuntu Regular'), local('Ubuntu-Regular'), url(https://fonts.gstatic.com/s/ubuntu/v14/4iCs6KVjbNBYlgoKfA72j00.woff2) format('woff2'); + unicode-range: U + 0370 -03FF; +} +@font-face { + font-family: 'Ubuntu'; + font-style: normal; + font-weight: 400; + src: local('Ubuntu Regular'), local('Ubuntu-Regular'), url(https://fonts.gstatic.com/s/ubuntu/v14/4iCs6KVjbNBYlgoKcQ72j00.woff2) format('woff2'); +} +@font-face { + font-family: 'Ubuntu'; + font-style: normal; + font-weight: 400; + src: local('Ubuntu Regular'), local('Ubuntu-Regular'), url(https://fonts.gstatic.com/s/ubuntu/v14/4iCs6KVjbNBYlgoKfw72.woff2) format('woff2'); +}body { + background-color: #333337; + color: beige; + font-family: "Ubuntu"; +} +table.top_info td.image div { + margin-right: 20px; + margin-bottom: 20px; + background-size: cover; + background-position: center; + height: 174px; + width: 174px; +} +table.top_info td.text { + vertical-align: top; +} +table.top_info td.text h1 { + display: inline; + padding-right: 5px; +} +table.top_info td.text table.image_row td { + height: 50px; + width: 50px; + background-size: cover; + background-position: center; + background-repeat: no-repeat; + opacity: 0.5; + filter: grayscale(80%); +} +table.top_info td.text table.image_row td:hover { + opacity: 1; + filter: grayscale(0%); +} +::-webkit-scrollbar { + width: 8px; + cursor: pointer; +} +::-webkit-scrollbar-track { + background: grey; + background-color: rgba(0,255,255,0.1); +} +::-webkit-scrollbar-thumb { + background-color: rgba(103,85,0,0.7); +} +::-webkit-scrollbar-thumb:hover { + background: gold; +} +[onclick]:hover, +a:hover { + cursor: pointer; +} +div.grisons_bar { + background-color: rgba(0,255,255,0.1); +} +div.grisons_bar > div { + height: 100%; + background-color: rgba(103,85,0,0.7); +} +div.grisons_bar:hover > div { + background-color: gold; +} +a { + color: inherit; + text-decoration: none; +} +a.textlink { + color: yellow; +} +a.hidelink:hover { + text-decoration: none; +} +a:hover { + text-decoration: underline; +} +body { + padding: 15px; + padding-bottom: 35px; +} +input[type="date"] { + background-color: inherit; + color: inherit; + outline: none; + border: 0px; + font-family: inherit; + font-size: inherit; +} +div#settingsicon { + position: fixed; + right: 30px; + top: 30px; + fill: beige; +} +div#settingsicon:hover { + fill: yellow; +} +div.footer { + position: fixed; + height: 20px; + background-color: #0a0a0a; + bottom: 0px; + left: 0px; + right: 0px; + padding: 10px; + opacity: 1; +} +div.footer div:nth-child(1) { + display: inline-block; + width: 40%; + text-align: left; +} +div.footer div:nth-child(2) { + display: inline-block; + width: 19%; + text-align: center; + color: gold; + font-size: 110%; +} +div.footer div:nth-child(3) { + display: inline-block; + width: 40%; + text-align: right; +} +div.footer span a { + padding-left: 20px; + background-repeat: no-repeat; + background-size: contain; + background-position: left; + background-image: url("https://github.com/favicon.ico"); +} +div.footer input { + background-color: inherit; + border: 0px; + border-bottom: 1px solid beige; + color: beige; + font-size: 90%; + width: 70%; + padding-left: 5px; + padding-right: 5px; + padding-top: 2px; + padding-bottom: 2px; + font-family: "Ubuntu"; +} +div.footer input:focus { + outline: none; +} +div.searchresults { + position: fixed; + bottom: 50px; + right: 20px; + width: 500px; + background-color: rgba(10,10,10,0.99); + padding: 15px; +} +div.searchresults > span { + font-size: 20px; + font-weight: bold; +} +div.searchresults table { + width: 100%; + border-spacing: 0px 4px; +} +div.searchresults tr { + background-color: #05050501; + margin-top: 5px; + margin-bottom: 5px; + height: 50px; + cursor: pointer; +} +div.searchresults tr:hover { + background-color: #23232301; +} +div.searchresults tr td.image { + height: 50px; + width: 50px; + background-size: cover; + background-position: center; +} +div.searchresults tr td:nth-child(2) { + padding-left: 10px; +} +div.searchresults table.searchresults_tracks td span:nth-child(1) { + font-size: 12px; + color: grey; +} +@media (max-width:1000px) { + div.footer { + position: fixed; + background-color: #0a0a0a; + bottom: 0px; + left: 0px; + right: 0px; + padding: 3px; + opacity: 1; + } + div.footer div:nth-child(1) { + display: none; + } + div.footer div:nth-child(2) { + display: inline-block; + width: 20%; + text-align: center; + color: gold; + } + div.footer div:nth-child(3) { + display: inline-block; + width: 70%; + text-align: right; + } + div.footer input { + width: 90%; + } +} +p.desc a { + padding-left: 20px; + background-repeat: no-repeat; + background-size: contain; + background-position: left; + background-image: url("https://www.last.fm/static/images/lastfm_avatar_twitter.66cd2c48ce03.png"); +} +table.top_info + .stat_module_topartists table, +table.top_info + .stat_module_toptracks table { + margin: 15px 0; +} +.paginate { + text-align: center; + padding: 30px; +} +.stats { + color: grey; +} +.rank { + text-align: right; + color: grey; +} +.extra { + color: grey; + font-size: 80%; +} +input#apikey { + font-family: 'Ubuntu'; + outline: none; + border: 0px solid; + padding: 2px; +} +input.simpleinput { + font-family: 'Ubuntu'; + color: beige; + outline: none; + border-top: 0px solid; + border-left: 0px solid; + border-right: 0px solid; + padding: 2px; + background-color: inherit; + border-bottom: 1px solid beige; +} +a { + cursor: pointer; +} +span.stat_selector_pulse, +span.stat_selector_topartists, +span.stat_selector_toptracks { + cursor: pointer; +} +.medal { + top: 5px; + font-size: 80%; + padding: 3px; + margin: 2px; + border-radius: 2px; +} +.shiny { + overflow: hidden; + position: relative; + display: inline-block; +} +.shiny:after { + content: ""; + position: absolute; + top: -110%; + left: -210%; + width: 200%; + height: 200%; + opacity: 0; + transform: rotate(30deg); + background: rgba(255,255,255,0.13); + background: linear-gradient(to right,rgba(255,255,255,0.13)0%,rgba(255,255,255,0.13)77%,rgba(255,255,255,0.5)92%,rgba(255,255,255,0.0)100% ); +} +.shiny:hover:after { + opacity: 1; + top: -30%; + left: -30%; + transition-property: left, top, opacity; + transition-duration: 0.7s, 0.7s, 0.15s; + transition-timing-function: ease; +} +.shiny:active:after { + opacity: 0; +} +a.gold { + background-color: gold; + color: black; +} +a.silver { + background-color: silver; + color: black; +} +a.bronze { + background-color: #cd7f32; + color: black; +} +img.certrecord { + height: 30px; + vertical-align: text-bottom; +} +img.certrecord_small { + height: 20px; + vertical-align: text-bottom; +} +img.star { + height: 20px; + vertical-align: text-bottom; +} +.button { + padding: 3px; + padding-right: 6px; + padding-left: 6px; + background-color: beige; + color: #333337; + cursor: pointer; +} +.button:hover { + background-color: yellow; + color: #333337; +} +.button.locked { + background-color: grey; + color: black; + cursor: not-allowed; +} +table.twopart { + width: 100%; +} +table.twopart > tbody > tr > td { + width: 50%; +} +table.list { + border-collapse: collapse; +} +table.list tr td { + border-bottom: 2px solid; + border-color: rgba(0,0,0,0); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + padding-right: 15px; +} +table.list tr:nth-child(even) { + background-color: #37373b; +} +table.list tr:nth-child(5n) td { + border-color: rgba(120,120,120,0.2); +} +table.list tr:hover { + background-color: #404044; +} +table.list td.time { + width: 11%; + color: grey; +} +table.list tr td.rank { + padding-right: 4px; +} +table.list tr td.rankup { + color: green; + padding-right: 8px; + font-size: 80%; +} +table.list tr td.rankdown { + color: red; + padding-right: 8px; + font-size: 80%; +} +table.list tr td.ranksame { + color: grey; + padding-right: 8px; + font-size: 80%; +} +table.list tr td.icon { + padding: 0px; + padding-right: 5px; + width: 20px; +} +table.list td.icon div { + width: 20px; + height: 20px; + background-size: cover; + background-position: center; +} +table.list td.artists, +td.artist, +td.title, +td.track { + min-width: 100px; +} +table.list td.track span.artist_in_trackcolumn { + color: #bbbbbb; +} +table.list td.track a.trackProviderSearch { + margin-right: 5px; + padding: 0 10px; +} +table.list td.amount { + width: 50px; + text-align: right; +} +table.list td.bar { + width: 500px; + background-color: #333337; +} +table.list td.bar div { + background-color: beige; + height: 20px; + position: relative; +} +table.list tr:hover td.bar div { + background-color: yellow; + cursor: pointer; +} +table.list td.chart { + width: 500px; + background-color: #333337; +} +table.list td.chart div { + height: 20px; + background-color: rgba(0,0,0,0.1); + border-radius: 0px 30px 30px 0px; + background-image: url("/media/chartpos_normal.png"); + background-position: right; + background-repeat: no-repeat; + background-size: auto 100%; + position: relative; +} +table.list tr:hover td.chart div { + cursor: pointer; +} +table.list tr td.chart div.gold { + background-image: url("/media/chartpos_gold.png"); +} +table.list tr td.chart div.silver { + background-image: url("/media/chartpos_silver.png"); +} +table.list tr td.chart div.bronze { + background-image: url("/media/chartpos_bronze.png"); +} +table.list tr td.button { + width: 200px; + cursor: pointer; + border-color: rgba(0,0,0,0) !important; +} +table.list td.button div { + background-color: beige; + color: #333337; + padding: 3px; + border-radius: 4px; +} +table.list td.button div:hover { + background-color: yellow; + color: #333337; + padding: 3px; + border-radius: 4px; +} +td.button.important div { + background-color: red; + color: white; +} +table.misc { + margin-left: 20px; +} +table.misc td { + padding-right: 20px; + color: #bbbbaa; +} +table.misc th { + text-align: left; +} +table.misc td.interaction { + width: 65px; +} +table.tiles_top td { + padding: 0px; + border: 0px; +} +table.tiles_top:hover td td { + opacity: 0.5; + filter: grayscale(80%); +} +table.tiles_top:hover td td:hover { + opacity: 1; + filter: grayscale(0%); +} +table.tiles_top, +table.tiles_sub { + border-collapse: collapse; +} +table.tiles_top > tbody > tr > td { + height: 300px; + width: 300px; +} +table.tiles_sub { + height: 100%; + width: 100%; +} +table.tiles_top td { + background-size: cover; + background-position: center; + vertical-align: bottom; +} +table.tiles_top td span { + background-color: rgba(0,0,0,0.7); +} +table.tiles_1x1 td { + height: 100%; + width: 100%; + font-size: 100%; +} +table.tiles_2x2 td { + height: 50%; + width: 50%; + font-size: 90%; +} +table.tiles_3x3 td { + height: 33.333%; + width: 33.333%; + font-size: 70%; +} +span.stat_module_pulse, +span.stat_module_topartists, +span.stat_module_toptracks { + display: inline-block; + vertical-align: top; +} +@media (min-width:1600px) { + div.sidelist { + position: absolute; + right: 0px; + top: 0px; + width: 40%; + height: 100%; + background-color: #444447; + padding-left: 30px; + padding-right: 30px; + } +} +div.sidelist table { + width: 100%; + table-layout: fixed; +} +div.sidelist table.list td.time { + width: 17%; +}body { + background-color: #333337; + color: beige; + font-family: "Ubuntu"; +} +table.top_info td.image div { + margin-right: 20px; + margin-bottom: 20px; + background-size: cover; + background-position: center; + height: 174px; + width: 174px; +} +table.top_info td.text { + vertical-align: top; +} +table.top_info td.text h1 { + display: inline; + padding-right: 5px; +} +table.top_info td.text table.image_row td { + height: 50px; + width: 50px; + background-size: cover; + background-position: center; + background-repeat: no-repeat; + opacity: 0.5; + filter: grayscale(80%); +} +table.top_info td.text table.image_row td:hover { + opacity: 1; + filter: grayscale(0%); +} +::-webkit-scrollbar { + width: 8px; + cursor: pointer; +} +::-webkit-scrollbar-track { + background: grey; + background-color: rgba(0,255,255,0.1); +} +::-webkit-scrollbar-thumb { + background-color: rgba(103,85,0,0.7); +} +::-webkit-scrollbar-thumb:hover { + background: gold; +} +[onclick]:hover, +a:hover { + cursor: pointer; +} +div.grisons_bar { + background-color: rgba(0,255,255,0.1); +} +div.grisons_bar > div { + height: 100%; + background-color: rgba(103,85,0,0.7); +} +div.grisons_bar:hover > div { + background-color: gold; +} +a { + color: inherit; + text-decoration: none; +} +a.textlink { + color: yellow; +} +a.hidelink:hover { + text-decoration: none; +} +a:hover { + text-decoration: underline; +} \ No newline at end of file diff --git a/website/errors/generic.html b/maloja/website/errors/generic.html similarity index 100% rename from website/errors/generic.html rename to maloja/website/errors/generic.html diff --git a/website/favicon.ico b/maloja/website/favicon.ico similarity index 100% rename from website/favicon.ico rename to maloja/website/favicon.ico diff --git a/website/favicon.png b/maloja/website/favicon.png similarity index 100% rename from website/favicon.png rename to maloja/website/favicon.png diff --git a/website/issues.html b/maloja/website/issues.html similarity index 100% rename from website/issues.html rename to maloja/website/issues.html diff --git a/website/issues.py b/maloja/website/issues.py similarity index 96% rename from website/issues.py rename to maloja/website/issues.py index d389ad0..eac9cdc 100644 --- a/website/issues.py +++ b/maloja/website/issues.py @@ -1,6 +1,6 @@ import urllib -import database -from htmlgenerators import artistLink +from .. import database +from ..htmlgenerators import artistLink def instructions(keys): diff --git a/website/javascript/cookies.js b/maloja/website/javascript/cookies.js similarity index 100% rename from website/javascript/cookies.js rename to maloja/website/javascript/cookies.js diff --git a/website/javascript/datechange.js b/maloja/website/javascript/datechange.js similarity index 100% rename from website/javascript/datechange.js rename to maloja/website/javascript/datechange.js diff --git a/website/javascript/neopolitan.js b/maloja/website/javascript/neopolitan.js similarity index 83% rename from website/javascript/neopolitan.js rename to maloja/website/javascript/neopolitan.js index 5590597..5293bc7 100644 --- a/website/javascript/neopolitan.js +++ b/maloja/website/javascript/neopolitan.js @@ -15,7 +15,8 @@ else{for(var key in data){body+=encodeURIComponent(key)+"="+encodeURIComponent(d xhttp.send(body);console.log("Sent XHTTP request to",url)} function xhttprequest(url,data={},method="GET",json=true){var p=new Promise(resolve=>xhttpreq(url,data,method,resolve,json));return p;} function now(){return Math.floor(Date.now()/1000);} -return{getCookie:getCookie,setCookie:setCookie,getCookies:getCookies,saveCookies:saveCookies,xhttpreq:xhttpreq,xhttprequest:xhttprequest,now:now}}();document.addEventListener('DOMContentLoaded',function(){var elements=document.getElementsByClassName("seekable");for(var i=0;i