Refactored into Python Package
|
@ -1,17 +1,15 @@
|
||||||
# generic temporary / dev files
|
# generic temporary / dev files
|
||||||
*.pyc
|
*.pyc
|
||||||
*.sh
|
*.sh
|
||||||
!/update_requirements.sh
|
|
||||||
*.note
|
*.note
|
||||||
*.xcf
|
*.xcf
|
||||||
nohup.out
|
nohup.out
|
||||||
/.dev
|
/maloja.egg-info
|
||||||
|
|
||||||
# user files
|
# user files
|
||||||
*.tsv
|
*.tsv
|
||||||
*.rulestate
|
*.rulestate
|
||||||
*.log
|
*.log
|
||||||
*.css
|
|
||||||
|
|
||||||
# currently not using
|
# currently not using
|
||||||
/screenshot*.png
|
/screenshot*.png
|
||||||
|
|
10
info.py
|
@ -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")
|
|
350
maloja
|
@ -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")
|
|
|
@ -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"
|
||||||
|
]
|
|
@ -1,5 +1,5 @@
|
||||||
import re
|
import re
|
||||||
import utilities
|
from . import utilities
|
||||||
from doreah import tsv, settings
|
from doreah import tsv, settings
|
||||||
|
|
||||||
# need to do this as a class so it can retain loaded settings from file
|
# need to do this as a class so it can retain loaded settings from file
|
|
@ -1,11 +1,11 @@
|
||||||
from doreah.logging import log
|
from doreah.logging import log
|
||||||
import hashlib
|
import hashlib
|
||||||
import random
|
import random
|
||||||
import database
|
from . import database
|
||||||
import datetime
|
import datetime
|
||||||
import itertools
|
import itertools
|
||||||
import sys
|
import sys
|
||||||
from cleanup import CleanerAgent
|
from .cleanup import CleanerAgent
|
||||||
from bottle import response
|
from bottle import response
|
||||||
|
|
||||||
## GNU-FM-compliant scrobbling
|
## GNU-FM-compliant scrobbling
|
||||||
|
@ -68,7 +68,7 @@ def handle(path,keys):
|
||||||
|
|
||||||
def scrobbletrack(artiststr,titlestr,timestamp):
|
def scrobbletrack(artiststr,titlestr,timestamp):
|
||||||
try:
|
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)
|
(artists,title) = cla.fullclean(artiststr,titlestr)
|
||||||
database.createScrobble(artists,title,timestamp)
|
database.createScrobble(artists,title,timestamp)
|
||||||
database.sync()
|
database.sync()
|
|
@ -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()
|
Can't render this file because it has a wrong number of fields in line 4.
|
Can't render this file because it has a wrong number of fields in line 5.
|
Can't render this file because it has a wrong number of fields in line 5.
|
Can't render this file because it has a wrong number of fields in line 5.
|
Can't render this file because it has a wrong number of fields in line 5.
|
Can't render this file because it has a wrong number of fields in line 4.
|
Can't render this file because it has a wrong number of fields in line 4.
|
|
@ -1,12 +1,13 @@
|
||||||
# server
|
# server
|
||||||
from bottle import request, response, FormsDict
|
from bottle import request, response, FormsDict
|
||||||
# rest of the project
|
# rest of the project
|
||||||
from cleanup import CleanerAgent, CollectorAgent
|
from .cleanup import CleanerAgent, CollectorAgent
|
||||||
import utilities
|
from . import utilities
|
||||||
from malojatime import register_scrobbletime, time_stamps, ranges
|
from .malojatime import register_scrobbletime, time_stamps, ranges
|
||||||
from urihandler import uri_to_internal, internal_to_uri, compose_querystring
|
from .urihandler import uri_to_internal, internal_to_uri, compose_querystring
|
||||||
import compliant_api
|
from . import compliant_api
|
||||||
from external import proxy_scrobble
|
from .external import proxy_scrobble
|
||||||
|
from . import info
|
||||||
# doreah toolkit
|
# doreah toolkit
|
||||||
from doreah.logging import log
|
from doreah.logging import log
|
||||||
from doreah import tsv
|
from doreah import tsv
|
||||||
|
@ -232,7 +233,7 @@ def test_server(key=None):
|
||||||
|
|
||||||
@dbserver.get("serverinfo")
|
@dbserver.get("serverinfo")
|
||||||
def server_info():
|
def server_info():
|
||||||
import info
|
|
||||||
|
|
||||||
response.set_header("Access-Control-Allow-Origin","*")
|
response.set_header("Access-Control-Allow-Origin","*")
|
||||||
response.set_header("Content-Type","application/json")
|
response.set_header("Content-Type","application/json")
|
|
@ -0,0 +1 @@
|
||||||
|
../../tools/doreah/doreah
|
|
@ -1,6 +1,6 @@
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from cleanup import CleanerAgent
|
from .cleanup import CleanerAgent
|
||||||
from doreah.logging import log
|
from doreah.logging import log
|
||||||
import difflib
|
import difflib
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import urllib
|
import urllib
|
||||||
from bottle import FormsDict
|
from bottle import FormsDict
|
||||||
import datetime
|
import datetime
|
||||||
from urihandler import compose_querystring
|
from .urihandler import compose_querystring
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
from doreah.settings import get_settings
|
from doreah.settings import get_settings
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
from htmlgenerators import *
|
from .htmlgenerators import *
|
||||||
import database
|
from . import database
|
||||||
from utilities import getArtistImage, getTrackImage
|
from .utilities import getArtistImage, getTrackImage
|
||||||
from malojatime import *
|
from .malojatime import *
|
||||||
from urihandler import compose_querystring, internal_to_uri, uri_to_internal
|
from .urihandler import compose_querystring, internal_to_uri, uri_to_internal
|
||||||
import urllib
|
import urllib
|
||||||
import datetime
|
import datetime
|
||||||
import math
|
import math
|
|
@ -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)
|
|
@ -1,6 +1,6 @@
|
||||||
import sys, os, datetime, re, cleanup
|
import sys, os, datetime, re, cleanup
|
||||||
from cleanup import *
|
from .cleanup import *
|
||||||
from utilities import *
|
from .utilities import *
|
||||||
|
|
||||||
|
|
||||||
log = open(sys.argv[1])
|
log = open(sys.argv[1])
|
|
@ -1,55 +1,66 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
import os
|
||||||
|
from .info import DATA_DIR
|
||||||
|
os.chdir(DATA_DIR)
|
||||||
|
|
||||||
|
|
||||||
# server stuff
|
# 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
|
||||||
import waitress
|
import waitress
|
||||||
# monkey patching
|
# monkey patching
|
||||||
import monkey
|
from . import monkey
|
||||||
# rest of the project
|
# rest of the project
|
||||||
import database
|
from . import database
|
||||||
import htmlmodules
|
from . import htmlmodules
|
||||||
import htmlgenerators
|
from . import htmlgenerators
|
||||||
import malojatime
|
from . import malojatime
|
||||||
import utilities
|
from . import utilities
|
||||||
from utilities import resolveImage
|
from .utilities import resolveImage
|
||||||
from urihandler import uri_to_internal, remove_identical
|
from .urihandler import uri_to_internal, remove_identical
|
||||||
import urihandler
|
from . import urihandler
|
||||||
import info
|
from . import info
|
||||||
# doreah toolkit
|
# doreah toolkit
|
||||||
from doreah import settings
|
from doreah import settings
|
||||||
from doreah.logging import log
|
from doreah.logging import log
|
||||||
from doreah.timing import Clock
|
from doreah.timing import Clock
|
||||||
# technical
|
# technical
|
||||||
from importlib.machinery import SourceFileLoader
|
#from importlib.machinery import SourceFileLoader
|
||||||
|
import importlib
|
||||||
import _thread
|
import _thread
|
||||||
import sys
|
import sys
|
||||||
import signal
|
import signal
|
||||||
import os
|
import os
|
||||||
import setproctitle
|
import setproctitle
|
||||||
|
import pkg_resources
|
||||||
# url handling
|
# url handling
|
||||||
import urllib
|
import urllib
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#settings.config(files=["settings/default.ini","settings/settings.ini"])
|
#settings.config(files=["settings/default.ini","settings/settings.ini"])
|
||||||
#settings.update("settings/default.ini","settings/settings.ini")
|
#settings.update("settings/default.ini","settings/settings.ini")
|
||||||
MAIN_PORT = settings.get_settings("WEB_PORT")
|
MAIN_PORT = settings.get_settings("WEB_PORT")
|
||||||
HOST = settings.get_settings("HOST")
|
HOST = settings.get_settings("HOST")
|
||||||
THREADS = 12
|
THREADS = 12
|
||||||
BaseRequest.MEMFILE_MAX = 15 * 1024 * 1024
|
BaseRequest.MEMFILE_MAX = 15 * 1024 * 1024
|
||||||
|
WEBFOLDER = pkg_resources.resource_filename(__name__,"website")
|
||||||
|
|
||||||
|
|
||||||
webserver = Bottle()
|
webserver = Bottle()
|
||||||
|
|
||||||
|
pthjoin = os.path.join
|
||||||
|
|
||||||
import lesscpy
|
#import lesscpy
|
||||||
css = ""
|
#css = ""
|
||||||
for f in os.listdir("website/less"):
|
#for f in os.listdir(pthjoin(WEBFOLDER,"less")):
|
||||||
css += lesscpy.compile("website/less/" + f)
|
# css += lesscpy.compile(pthjoin(WEBFOLDER,"less",f))
|
||||||
|
|
||||||
os.makedirs("website/css",exist_ok=True)
|
#os.makedirs("website/css",exist_ok=True)
|
||||||
with open("website/css/style.css","w") as f:
|
#with open("website/css/style.css","w") as f:
|
||||||
f.write(css)
|
# f.write(css)
|
||||||
|
|
||||||
|
|
||||||
@webserver.route("")
|
@webserver.route("")
|
||||||
|
@ -69,16 +80,16 @@ def customerror(error):
|
||||||
code = int(str(error).split(",")[0][1:])
|
code = int(str(error).split(",")[0][1:])
|
||||||
log("HTTP Error: " + str(code),module="error")
|
log("HTTP Error: " + str(code),module="error")
|
||||||
|
|
||||||
if os.path.exists("website/errors/" + str(code) + ".html"):
|
if os.path.exists(pthjoin(WEBFOLDER,"errors",str(code) + ".html")):
|
||||||
return static_file("website/errors/" + str(code) + ".html",root="")
|
return static_file(pthjoin(WEBFOLDER,"errors",str(code) + ".html"),root="")
|
||||||
else:
|
else:
|
||||||
with open("website/errors/generic.html") as htmlfile:
|
with open(pthjoin(WEBFOLDER,"errors/generic.html")) as htmlfile:
|
||||||
html = htmlfile.read()
|
html = htmlfile.read()
|
||||||
|
|
||||||
# apply global substitutions
|
# apply global substitutions
|
||||||
with open("website/common/footer.html") as footerfile:
|
with open(pthjoin(WEBFOLDER,"common/footer.html")) as footerfile:
|
||||||
footerhtml = footerfile.read()
|
footerhtml = footerfile.read()
|
||||||
with open("website/common/header.html") as headerfile:
|
with open(pthjoin(WEBFOLDER,"common/header.html")) as headerfile:
|
||||||
headerhtml = headerfile.read()
|
headerhtml = headerfile.read()
|
||||||
html = html.replace("</body>",footerhtml + "</body>").replace("</head>",headerhtml + "</head>")
|
html = html.replace("</body>",footerhtml + "</body>").replace("</head>",headerhtml + "</head>")
|
||||||
|
|
||||||
|
@ -143,17 +154,18 @@ def static_image(pth):
|
||||||
@webserver.route("/<name:re:.*\\.ico>")
|
@webserver.route("/<name:re:.*\\.ico>")
|
||||||
@webserver.route("/<name:re:.*\\.txt>")
|
@webserver.route("/<name:re:.*\\.txt>")
|
||||||
def static(name):
|
def static(name):
|
||||||
response = static_file("website/" + name,root="")
|
response = static_file(name,root=WEBFOLDER)
|
||||||
response.set_header("Cache-Control", "public, max-age=3600")
|
response.set_header("Cache-Control", "public, max-age=3600")
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
@webserver.route("/<name>")
|
@webserver.route("/<name>")
|
||||||
def static_html(name):
|
def static_html(name):
|
||||||
linkheaders = ["</css/style.css>; rel=preload; as=style"]
|
linkheaders = ["</css/style.css>; rel=preload; as=style"]
|
||||||
keys = remove_identical(FormsDict.decode(request.query))
|
keys = remove_identical(FormsDict.decode(request.query))
|
||||||
|
|
||||||
pyhp_file = os.path.exists("website/" + name + ".pyhp")
|
pyhp_file = os.path.exists(pthjoin(WEBFOLDER,name + ".pyhp"))
|
||||||
html_file = os.path.exists("website/" + name + ".html")
|
html_file = os.path.exists(pthjoin(WEBFOLDER,name + ".html"))
|
||||||
pyhp_pref = settings.get_settings("USE_PYHP")
|
pyhp_pref = settings.get_settings("USE_PYHP")
|
||||||
|
|
||||||
adminmode = request.cookies.get("adminmode") == "true" and database.checkAPIkey(request.cookies.get("apikey")) is not False
|
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)
|
environ["filterkeys"], environ["limitkeys"], environ["delimitkeys"], environ["amountkeys"] = uri_to_internal(keys)
|
||||||
|
|
||||||
#response.set_header("Content-Type","application/xhtml+xml")
|
#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")
|
log("Generated page " + name + " in " + str(clock.stop()) + "s (PYHP)",module="debug")
|
||||||
return res
|
return res
|
||||||
|
|
||||||
# if not, use the old way
|
# if not, use the old way
|
||||||
else:
|
else:
|
||||||
|
|
||||||
with open("website/" + name + ".html") as htmlfile:
|
with open(pthjoin(WEBFOLDER,name + ".html")) as htmlfile:
|
||||||
html = htmlfile.read()
|
html = htmlfile.read()
|
||||||
|
|
||||||
# apply global substitutions
|
# apply global substitutions
|
||||||
with open("website/common/footer.html") as footerfile:
|
with open(pthjoin(WEBFOLDER,"common/footer.html")) as footerfile:
|
||||||
footerhtml = footerfile.read()
|
footerhtml = footerfile.read()
|
||||||
with open("website/common/header.html") as headerfile:
|
with open(pthjoin(WEBFOLDER,"common/header.html")) as headerfile:
|
||||||
headerhtml = headerfile.read()
|
headerhtml = headerfile.read()
|
||||||
html = html.replace("</body>",footerhtml + "</body>").replace("</head>",headerhtml + "</head>")
|
html = html.replace("</body>",footerhtml + "</body>").replace("</head>",headerhtml + "</head>")
|
||||||
|
|
||||||
|
|
||||||
# If a python file exists, it provides the replacement dict for the html file
|
# 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)
|
#txt_keys = SourceFileLoader(name,"website/" + name + ".py").load_module().replacedict(keys,DATABASE_PORT)
|
||||||
try:
|
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:
|
except Exception as e:
|
||||||
log("Error in website generation: " + str(sys.exc_info()),module="error")
|
log("Error in website generation: " + str(sys.exc_info()),module="error")
|
||||||
raise
|
raise
|
|
@ -20,6 +20,6 @@ while True:
|
||||||
except:
|
except:
|
||||||
log("Maloja is not running, restarting...",module="supervisor")
|
log("Maloja is not running, restarting...",module="supervisor")
|
||||||
try:
|
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:
|
except e:
|
||||||
log("Error starting Maloja: " + str(e),module="supervisor")
|
log("Error starting Maloja: " + str(e),module="supervisor")
|
|
@ -1,6 +1,6 @@
|
||||||
import urllib
|
import urllib
|
||||||
from bottle import FormsDict
|
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
|
import math
|
||||||
|
|
||||||
# necessary because urllib.parse.urlencode doesnt handle multidicts
|
# necessary because urllib.parse.urlencode doesnt handle multidicts
|
|
@ -14,7 +14,7 @@ from doreah import caching
|
||||||
from doreah.logging import log
|
from doreah.logging import log
|
||||||
from doreah.regular import yearly, daily
|
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():
|
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
|
currentyear = datetime.datetime.utcnow().year
|
||||||
try:
|
try:
|
||||||
|
@ -505,8 +505,8 @@ def update_medals():
|
||||||
@daily
|
@daily
|
||||||
def update_weekly():
|
def update_weekly():
|
||||||
|
|
||||||
from database import WEEKLY_TOPTRACKS, WEEKLY_TOPARTISTS, get_charts_artists, get_charts_tracks
|
from .database import WEEKLY_TOPTRACKS, WEEKLY_TOPARTISTS, get_charts_artists, get_charts_tracks
|
||||||
from malojatime import ranges, thisweek
|
from .malojatime import ranges, thisweek
|
||||||
|
|
||||||
|
|
||||||
WEEKLY_TOPARTISTS.clear()
|
WEEKLY_TOPARTISTS.clear()
|
|
@ -1,13 +1,13 @@
|
||||||
import urllib
|
import urllib
|
||||||
import database
|
from .. import database
|
||||||
from malojatime import today,thisweek,thismonth,thisyear
|
from ..malojatime import today,thisweek,thismonth,thisyear
|
||||||
|
|
||||||
|
|
||||||
def instructions(keys):
|
def instructions(keys):
|
||||||
from utilities import getArtistImage
|
from ..utilities import getArtistImage
|
||||||
from htmlgenerators import artistLink, artistLinks, link_address
|
from ..htmlgenerators import artistLink, artistLinks, link_address
|
||||||
from urihandler import compose_querystring, uri_to_internal
|
from ..urihandler import compose_querystring, uri_to_internal
|
||||||
from htmlmodules import module_pulse, module_performance, module_trackcharts, module_scrobblelist
|
from ..htmlmodules import module_pulse, module_performance, module_trackcharts, module_scrobblelist
|
||||||
|
|
||||||
filterkeys, _, _, _ = uri_to_internal(keys,forceArtist=True)
|
filterkeys, _, _, _ = uri_to_internal(keys,forceArtist=True)
|
||||||
artist = filterkeys.get("artist")
|
artist = filterkeys.get("artist")
|
|
@ -2,10 +2,10 @@ import urllib
|
||||||
|
|
||||||
|
|
||||||
def instructions(keys):
|
def instructions(keys):
|
||||||
from utilities import getArtistImage
|
from ..utilities import getArtistImage
|
||||||
from urihandler import compose_querystring, uri_to_internal
|
from ..urihandler import compose_querystring, uri_to_internal
|
||||||
from htmlmodules import module_artistcharts, module_filterselection, module_artistcharts_tiles
|
from ..htmlmodules import module_artistcharts, module_filterselection, module_artistcharts_tiles
|
||||||
from malojatime import range_desc
|
from ..malojatime import range_desc
|
||||||
from doreah.settings import get_settings
|
from doreah.settings import get_settings
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,11 @@ import urllib
|
||||||
|
|
||||||
|
|
||||||
def instructions(keys):
|
def instructions(keys):
|
||||||
from utilities import getArtistImage, getTrackImage
|
from ..utilities import getArtistImage, getTrackImage
|
||||||
from htmlgenerators import artistLink
|
from ..htmlgenerators import artistLink
|
||||||
from urihandler import compose_querystring, uri_to_internal
|
from ..urihandler import compose_querystring, uri_to_internal
|
||||||
from htmlmodules import module_trackcharts, module_filterselection, module_trackcharts_tiles
|
from ..htmlmodules import module_trackcharts, module_filterselection, module_trackcharts_tiles
|
||||||
from malojatime import range_desc
|
from ..malojatime import range_desc
|
||||||
from doreah.settings import get_settings
|
from doreah.settings import get_settings
|
||||||
|
|
||||||
filterkeys, timekeys, _, amountkeys = uri_to_internal(keys)
|
filterkeys, timekeys, _, amountkeys = uri_to_internal(keys)
|
|
@ -1,8 +1,8 @@
|
||||||
import urllib
|
import urllib
|
||||||
import database
|
from .. import database
|
||||||
import json
|
import json
|
||||||
from htmlgenerators import artistLink
|
from ..htmlgenerators import artistLink
|
||||||
from utilities import getArtistImage
|
from ..utilities import getArtistImage
|
||||||
|
|
||||||
|
|
||||||
def instructions(keys):
|
def instructions(keys):
|
|
@ -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;
|
||||||
|
}
|
Before Width: | Height: | Size: 866 B After Width: | Height: | Size: 866 B |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
@ -1,6 +1,6 @@
|
||||||
import urllib
|
import urllib
|
||||||
import database
|
from .. import database
|
||||||
from htmlgenerators import artistLink
|
from ..htmlgenerators import artistLink
|
||||||
|
|
||||||
def instructions(keys):
|
def instructions(keys):
|
||||||
|
|
|
@ -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)}
|
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 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);}
|
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<elements.length;i++){callback=elements[i].getAttribute("data-seekcallback");elements[i].addEventListener("click",function(evt){elmnt=evt.currentTarget;var percentage=evt.offsetX/elmnt.offsetWidth;elmnt.firstElementChild.style.width=(percentage*100)+"%";window[callback](percentage);})}
|
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<elements.length;i++){elements[i].addEventListener("click",function(evt){var elmnt=evt.currentTarget;var percentage=evt.offsetX/elmnt.offsetWidth;percentage=Math.max(0,Math.min(100,percentage));elmnt.firstElementChild.style.width=(percentage*100)+"%";var callback=elmnt.getAttribute("data-seekcallback");window[callback](percentage);})}
|
||||||
|
var elements=document.getElementsByClassName("scrollseekable");for(var i=0;i<elements.length;i++){elements[i].addEventListener("wheel",function(evt){var elmnt=evt.currentTarget;var currentPercentage=elmnt.firstElementChild.offsetWidth/elmnt.offsetWidth;var sensitivity=elmnt.getAttribute("data-scrollsensitivity")||1;var percentage=currentPercentage-evt.deltaY*sensitivity/1000;percentage=Math.max(0,Math.min(1,percentage));elmnt.firstElementChild.style.width=(percentage*100)+"%";var callback=elmnt.getAttribute("data-seekcallback");window[callback](percentage);})}
|
||||||
var elements2=document.getElementsByClassName("update");var functions=[]
|
var elements2=document.getElementsByClassName("update");var functions=[]
|
||||||
for(var i=0;i<elements2.length;i++){updatefunc=elements2[i].getAttribute("data-updatefrom");functions.push([elements2[i],updatefunc])}
|
for(var i=0;i<elements2.length;i++){updatefunc=elements2[i].getAttribute("data-updatefrom");functions.push([elements2[i],updatefunc])}
|
||||||
const SMOOTH_UPDATE=true;const update_delay=SMOOTH_UPDATE?40:500;function supervisor(){for(let entry of functions){var[element,func]=entry
|
const SMOOTH_UPDATE=true;const update_delay=SMOOTH_UPDATE?40:500;function supervisor(){for(let entry of functions){var[element,func]=entry
|
Before Width: | Height: | Size: 244 B After Width: | Height: | Size: 244 B |
Before Width: | Height: | Size: 239 B After Width: | Height: | Size: 239 B |
Before Width: | Height: | Size: 245 B After Width: | Height: | Size: 245 B |
Before Width: | Height: | Size: 240 B After Width: | Height: | Size: 240 B |
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 8.9 KiB |
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 9.2 KiB |
|
@ -1,13 +1,13 @@
|
||||||
import urllib
|
import urllib
|
||||||
import database
|
from .. import database
|
||||||
|
|
||||||
|
|
||||||
def instructions(keys):
|
def instructions(keys):
|
||||||
from utilities import getArtistImage, getTrackImage
|
from ..utilities import getArtistImage, getTrackImage
|
||||||
from htmlgenerators import artistLink, artistLinks, trackLink, scrobblesLink
|
from ..htmlgenerators import artistLink, artistLinks, trackLink, scrobblesLink
|
||||||
from urihandler import compose_querystring, uri_to_internal, internal_to_uri
|
from ..urihandler import compose_querystring, uri_to_internal, internal_to_uri
|
||||||
from htmlmodules import module_performance, module_filterselection
|
from ..htmlmodules import module_performance, module_filterselection
|
||||||
from malojatime import range_desc, delimit_desc
|
from ..malojatime import range_desc, delimit_desc
|
||||||
|
|
||||||
filterkeys, timekeys, delimitkeys, paginatekeys = uri_to_internal(keys)
|
filterkeys, timekeys, delimitkeys, paginatekeys = uri_to_internal(keys)
|
||||||
|
|
|
@ -3,8 +3,8 @@ import urllib.request
|
||||||
import hashlib
|
import hashlib
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
from bottle import redirect, request
|
from bottle import redirect, request
|
||||||
from database import checkAPIkey
|
from ..database import checkAPIkey
|
||||||
from external import lfmbuild
|
from ..external import lfmbuild
|
||||||
|
|
||||||
def instructions(keys):
|
def instructions(keys):
|
||||||
authenticated = False
|
authenticated = False
|
|
@ -1,13 +1,13 @@
|
||||||
import urllib
|
import urllib
|
||||||
import database
|
from .. import database
|
||||||
|
|
||||||
|
|
||||||
def instructions(keys):
|
def instructions(keys):
|
||||||
from utilities import getArtistImage, getTrackImage
|
from ..utilities import getArtistImage, getTrackImage
|
||||||
from htmlgenerators import artistLink, artistLinks, trackLink, scrobblesLink
|
from ..htmlgenerators import artistLink, artistLinks, trackLink, scrobblesLink
|
||||||
from urihandler import compose_querystring, uri_to_internal, internal_to_uri
|
from ..urihandler import compose_querystring, uri_to_internal, internal_to_uri
|
||||||
from htmlmodules import module_pulse, module_filterselection
|
from ..htmlmodules import module_pulse, module_filterselection
|
||||||
from malojatime import range_desc, delimit_desc
|
from ..malojatime import range_desc, delimit_desc
|
||||||
|
|
||||||
filterkeys, timekeys, delimitkeys, paginatekeys = uri_to_internal(keys)
|
filterkeys, timekeys, delimitkeys, paginatekeys = uri_to_internal(keys)
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import urllib
|
import urllib
|
||||||
import database
|
from .. import database
|
||||||
|
|
||||||
|
|
||||||
def instructions(keys):
|
def instructions(keys):
|
||||||
from utilities import getArtistImage, getTrackImage
|
from ..utilities import getArtistImage, getTrackImage
|
||||||
from htmlgenerators import artistLink, artistLinks, trackLink
|
from ..htmlgenerators import artistLink, artistLinks, trackLink
|
||||||
from urihandler import compose_querystring, uri_to_internal
|
from ..urihandler import compose_querystring, uri_to_internal
|
||||||
from htmlmodules import module_scrobblelist, module_filterselection
|
from ..htmlmodules import module_scrobblelist, module_filterselection
|
||||||
from malojatime import range_desc
|
from ..malojatime import range_desc
|
||||||
|
|
||||||
|
|
||||||
filterkeys, timekeys, _, amountkeys = uri_to_internal(keys)
|
filterkeys, timekeys, _, amountkeys = uri_to_internal(keys)
|
|
@ -1,10 +1,10 @@
|
||||||
import urllib
|
import urllib
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import database
|
from .. import database
|
||||||
from doreah.timing import clock, clockp
|
from doreah.timing import clock, clockp
|
||||||
from doreah.settings import get_settings
|
from doreah.settings import get_settings
|
||||||
|
|
||||||
from htmlmodules import module_scrobblelist, module_pulse, module_artistcharts_tiles, module_trackcharts_tiles
|
from ..htmlmodules import module_scrobblelist, module_pulse, module_artistcharts_tiles, module_trackcharts_tiles
|
||||||
|
|
||||||
|
|
||||||
def instructions(keys):
|
def instructions(keys):
|
||||||
|
@ -17,7 +17,7 @@ def instructions(keys):
|
||||||
|
|
||||||
#clock()
|
#clock()
|
||||||
|
|
||||||
from malojatime import today,thisweek,thismonth,thisyear
|
from ..malojatime import today,thisweek,thismonth,thisyear
|
||||||
|
|
||||||
# artists
|
# artists
|
||||||
|
|
|
@ -2,11 +2,11 @@ import urllib
|
||||||
|
|
||||||
|
|
||||||
def instructions(keys):
|
def instructions(keys):
|
||||||
from utilities import getArtistImage, getTrackImage
|
from ..utilities import getArtistImage, getTrackImage
|
||||||
from htmlgenerators import artistLink
|
from ..htmlgenerators import artistLink
|
||||||
from urihandler import compose_querystring, uri_to_internal
|
from ..urihandler import compose_querystring, uri_to_internal
|
||||||
from htmlmodules import module_topartists, module_filterselection
|
from ..htmlmodules import module_topartists, module_filterselection
|
||||||
from malojatime import range_desc
|
from ..malojatime import range_desc
|
||||||
|
|
||||||
_, timekeys, delimitkeys, _ = uri_to_internal(keys)
|
_, timekeys, delimitkeys, _ = uri_to_internal(keys)
|
||||||
|
|
|
@ -2,11 +2,11 @@ import urllib
|
||||||
|
|
||||||
|
|
||||||
def instructions(keys):
|
def instructions(keys):
|
||||||
from utilities import getArtistImage, getTrackImage
|
from ..utilities import getArtistImage, getTrackImage
|
||||||
from htmlgenerators import artistLink
|
from ..htmlgenerators import artistLink
|
||||||
from urihandler import compose_querystring, uri_to_internal
|
from ..urihandler import compose_querystring, uri_to_internal
|
||||||
from htmlmodules import module_toptracks, module_filterselection
|
from ..htmlmodules import module_toptracks, module_filterselection
|
||||||
from malojatime import range_desc
|
from ..malojatime import range_desc
|
||||||
|
|
||||||
filterkeys, timekeys, delimitkeys, _ = uri_to_internal(keys)
|
filterkeys, timekeys, delimitkeys, _ = uri_to_internal(keys)
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import urllib
|
import urllib
|
||||||
import database
|
from .. import database
|
||||||
from malojatime import today,thisweek,thismonth,thisyear
|
from ..malojatime import today,thisweek,thismonth,thisyear
|
||||||
|
|
||||||
|
|
||||||
def instructions(keys):
|
def instructions(keys):
|
||||||
from utilities import getArtistImage, getTrackImage
|
from ..utilities import getArtistImage, getTrackImage
|
||||||
from htmlgenerators import artistLinks
|
from ..htmlgenerators import artistLinks
|
||||||
from urihandler import compose_querystring, uri_to_internal
|
from ..urihandler import compose_querystring, uri_to_internal
|
||||||
from htmlmodules import module_scrobblelist, module_pulse, module_performance
|
from ..htmlmodules import module_scrobblelist, module_pulse, module_performance
|
||||||
|
|
||||||
|
|
||||||
filterkeys, _, _, _ = uri_to_internal(keys,forceTrack=True)
|
filterkeys, _, _, _ = uri_to_internal(keys,forceTrack=True)
|