Refactored into Python Package
4
.gitignore
vendored
@ -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
|
||||
|
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")
|
19
maloja/__init__.py
Normal file
@ -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 utilities
|
||||
from . import utilities
|
||||
from doreah import tsv, settings
|
||||
|
||||
# need to do this as a class so it can retain loaded settings from file
|
@ -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()
|
165
maloja/controller.py
Executable file
@ -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.
|
0
maloja/data_files/scrobbles/dummy
Normal file
@ -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")
|
1
maloja/doreah
Symbolic link
@ -0,0 +1 @@
|
||||
../../tools/doreah/doreah
|
@ -1,6 +1,6 @@
|
||||
import os
|
||||
import re
|
||||
from cleanup import CleanerAgent
|
||||
from .cleanup import CleanerAgent
|
||||
from doreah.logging import log
|
||||
import difflib
|
||||
|
@ -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
|
||||
|
@ -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
|
19
maloja/info.py
Normal file
@ -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
|
||||
from cleanup import *
|
||||
from utilities import *
|
||||
from .cleanup import *
|
||||
from .utilities import *
|
||||
|
||||
|
||||
log = open(sys.argv[1])
|
@ -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("</body>",footerhtml + "</body>").replace("</head>",headerhtml + "</head>")
|
||||
|
||||
@ -143,17 +154,18 @@ def static_image(pth):
|
||||
@webserver.route("/<name:re:.*\\.ico>")
|
||||
@webserver.route("/<name:re:.*\\.txt>")
|
||||
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("/<name>")
|
||||
def static_html(name):
|
||||
linkheaders = ["</css/style.css>; 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("</body>",footerhtml + "</body>").replace("</head>",headerhtml + "</head>")
|
||||
|
||||
|
||||
# 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
|
@ -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")
|
@ -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
|
@ -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()
|
@ -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")
|
@ -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
|
||||
|
||||
|
@ -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)
|
@ -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):
|
671
maloja/website/css/style.css
Normal file
@ -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 database
|
||||
from htmlgenerators import artistLink
|
||||
from .. import database
|
||||
from ..htmlgenerators import artistLink
|
||||
|
||||
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)}
|
||||
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<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=[]
|
||||
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
|
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 database
|
||||
from .. import database
|
||||
|
||||
|
||||
def instructions(keys):
|
||||
from utilities import getArtistImage, getTrackImage
|
||||
from htmlgenerators import artistLink, artistLinks, trackLink, scrobblesLink
|
||||
from urihandler import compose_querystring, uri_to_internal, internal_to_uri
|
||||
from htmlmodules import module_performance, module_filterselection
|
||||
from malojatime import range_desc, delimit_desc
|
||||
from ..utilities import getArtistImage, getTrackImage
|
||||
from ..htmlgenerators import artistLink, artistLinks, trackLink, scrobblesLink
|
||||
from ..urihandler import compose_querystring, uri_to_internal, internal_to_uri
|
||||
from ..htmlmodules import module_performance, module_filterselection
|
||||
from ..malojatime import range_desc, delimit_desc
|
||||
|
||||
filterkeys, timekeys, delimitkeys, paginatekeys = uri_to_internal(keys)
|
||||
|
@ -3,8 +3,8 @@ import urllib.request
|
||||
import hashlib
|
||||
import xml.etree.ElementTree as ET
|
||||
from bottle import redirect, request
|
||||
from database import checkAPIkey
|
||||
from external import lfmbuild
|
||||
from ..database import checkAPIkey
|
||||
from ..external import lfmbuild
|
||||
|
||||
def instructions(keys):
|
||||
authenticated = False
|
@ -1,13 +1,13 @@
|
||||
import urllib
|
||||
import database
|
||||
from .. import database
|
||||
|
||||
|
||||
def instructions(keys):
|
||||
from utilities import getArtistImage, getTrackImage
|
||||
from htmlgenerators import artistLink, artistLinks, trackLink, scrobblesLink
|
||||
from urihandler import compose_querystring, uri_to_internal, internal_to_uri
|
||||
from htmlmodules import module_pulse, module_filterselection
|
||||
from malojatime import range_desc, delimit_desc
|
||||
from ..utilities import getArtistImage, getTrackImage
|
||||
from ..htmlgenerators import artistLink, artistLinks, trackLink, scrobblesLink
|
||||
from ..urihandler import compose_querystring, uri_to_internal, internal_to_uri
|
||||
from ..htmlmodules import module_pulse, module_filterselection
|
||||
from ..malojatime import range_desc, delimit_desc
|
||||
|
||||
filterkeys, timekeys, delimitkeys, paginatekeys = uri_to_internal(keys)
|
||||
|
@ -1,13 +1,13 @@
|
||||
import urllib
|
||||
import database
|
||||
from .. import database
|
||||
|
||||
|
||||
def instructions(keys):
|
||||
from utilities import getArtistImage, getTrackImage
|
||||
from htmlgenerators import artistLink, artistLinks, trackLink
|
||||
from urihandler import compose_querystring, uri_to_internal
|
||||
from htmlmodules import module_scrobblelist, module_filterselection
|
||||
from malojatime import range_desc
|
||||
from ..utilities import getArtistImage, getTrackImage
|
||||
from ..htmlgenerators import artistLink, artistLinks, trackLink
|
||||
from ..urihandler import compose_querystring, uri_to_internal
|
||||
from ..htmlmodules import module_scrobblelist, module_filterselection
|
||||
from ..malojatime import range_desc
|
||||
|
||||
|
||||
filterkeys, timekeys, _, amountkeys = uri_to_internal(keys)
|
@ -1,10 +1,10 @@
|
||||
import urllib
|
||||
from datetime import datetime, timedelta
|
||||
import database
|
||||
from .. import database
|
||||
from doreah.timing import clock, clockp
|
||||
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):
|
||||
@ -17,7 +17,7 @@ def instructions(keys):
|
||||
|
||||
#clock()
|
||||
|
||||
from malojatime import today,thisweek,thismonth,thisyear
|
||||
from ..malojatime import today,thisweek,thismonth,thisyear
|
||||
|
||||
# artists
|
||||
|
@ -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_topartists, module_filterselection
|
||||
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_topartists, module_filterselection
|
||||
from ..malojatime import range_desc
|
||||
|
||||
_, timekeys, delimitkeys, _ = uri_to_internal(keys)
|
||||
|
@ -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_toptracks, module_filterselection
|
||||
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_toptracks, module_filterselection
|
||||
from ..malojatime import range_desc
|
||||
|
||||
filterkeys, timekeys, delimitkeys, _ = uri_to_internal(keys)
|
||||
|
@ -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, getTrackImage
|
||||
from htmlgenerators import artistLinks
|
||||
from urihandler import compose_querystring, uri_to_internal
|
||||
from htmlmodules import module_scrobblelist, module_pulse, module_performance
|
||||
from ..utilities import getArtistImage, getTrackImage
|
||||
from ..htmlgenerators import artistLinks
|
||||
from ..urihandler import compose_querystring, uri_to_internal
|
||||
from ..htmlmodules import module_scrobblelist, module_pulse, module_performance
|
||||
|
||||
|
||||
filterkeys, _, _, _ = uri_to_internal(keys,forceTrack=True)
|