mirror of
https://github.com/krateng/maloja.git
synced 2025-04-21 11:07:36 +03:00
Merge branch 'master' into pyhp
This commit is contained in:
commit
b955777637
14
.doreah
14
.doreah
@ -1,4 +1,10 @@
|
||||
logging.logfolder = logs
|
||||
settings.files = [ "settings/default.ini" , "settings/settings.ini" ]
|
||||
caching.folder = "cache/"
|
||||
regular.autostart = false
|
||||
logging:
|
||||
logfolder: "logs"
|
||||
settings:
|
||||
files:
|
||||
- "settings/default.ini"
|
||||
- "settings/settings.ini"
|
||||
caching:
|
||||
folder: "cache/"
|
||||
regular:
|
||||
autostart: false
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,6 +1,7 @@
|
||||
# generic temporary / dev files
|
||||
*.pyc
|
||||
*.sh
|
||||
!/update_requirements.sh
|
||||
*.note
|
||||
*.xcf
|
||||
nohup.out
|
||||
@ -10,10 +11,10 @@ nohup.out
|
||||
*.tsv
|
||||
*.rulestate
|
||||
*.log
|
||||
*.css
|
||||
|
||||
# currently not using
|
||||
/screenshot*.png
|
||||
/proxyscrobble.py
|
||||
|
||||
# only for development, normally external
|
||||
/doreah
|
||||
|
12
README.md
12
README.md
@ -16,12 +16,8 @@ Also neat: You can use your **custom artist or track images**.
|
||||
|
||||
## Requirements
|
||||
|
||||
* [python3](https://www.python.org/) - [GitHub](https://github.com/python/cpython)
|
||||
* [bottle.py](https://bottlepy.org/) - [GitHub](https://github.com/bottlepy/bottle)
|
||||
* [waitress](https://docs.pylonsproject.org/projects/waitress/) - [GitHub](https://github.com/Pylons/waitress)
|
||||
* [doreah](https://pypi.org/project/doreah/) - [GitHub](https://github.com/krateng/doreah) (at least Version 0.9.1)
|
||||
* [nimrodel](https://pypi.org/project/nimrodel/) - [GitHub](https://github.com/krateng/nimrodel) (at least Version 0.4.9)
|
||||
* [setproctitle](https://pypi.org/project/setproctitle/) - [GitHub](https://github.com/dvarrazzo/py-setproctitle)
|
||||
* Python 3
|
||||
* Pip packages specified in `requirements.txt`
|
||||
* If you'd like to display images, you will need API keys for [Last.fm](https://www.last.fm/api/account/create) and [Fanart.tv](https://fanart.tv/get-an-api-key/). These are free of charge!
|
||||
|
||||
## How to install
|
||||
@ -68,9 +64,9 @@ If you didn't install Maloja from the package (and therefore don't have it in `/
|
||||
|
||||
### Native API
|
||||
|
||||
If you use Plex Web or Youtube Music on Chromium, you can use the included extension (also available on the [Chrome Web Store](https://chrome.google.com/webstore/detail/maloja-scrobbler/cfnbifdmgbnaalphodcbandoopgbfeeh)). Make sure to enter the random key Maloja generates on first startup in the extension settings.
|
||||
If you use Plex Web, Spotify, Bandcamp, Soundcloud or Youtube Music on Chromium, you can use the included extension (also available on the [Chrome Web Store](https://chrome.google.com/webstore/detail/maloja-scrobbler/cfnbifdmgbnaalphodcbandoopgbfeeh)). Make sure to enter the random key Maloja generates on first startup in the extension settings.
|
||||
|
||||
If you want to implement your own method of scrobbling, it's very simple: You only need one POST request to `/api/newscrobble` with the keys `artist`, `title` and `key` - either as from-data or json.
|
||||
If you want to implement your own method of scrobbling, it's very simple: You only need one POST request to `/api/newscrobble` with the keys `artist`, `title` and `key` - either as form-data or json.
|
||||
|
||||
### Standard-compliant API
|
||||
|
||||
|
29
cleanup.py
29
cleanup.py
@ -1,6 +1,6 @@
|
||||
import re
|
||||
import utilities
|
||||
from doreah import tsv
|
||||
from doreah import tsv, settings
|
||||
|
||||
# need to do this as a class so it can retain loaded settings from file
|
||||
# apparently this is not true
|
||||
@ -11,11 +11,16 @@ class CleanerAgent:
|
||||
self.updateRules()
|
||||
|
||||
def updateRules(self):
|
||||
raw = tsv.parse_all("rules","string","string","string")
|
||||
self.rules_belongtogether = [b for [a,b,c] in raw if a=="belongtogether"]
|
||||
self.rules_notanartist = [b for [a,b,c] in raw if a=="notanartist"]
|
||||
self.rules_replacetitle = {b.lower():c for [a,b,c] in raw if a=="replacetitle"}
|
||||
self.rules_replaceartist = {b.lower():c for [a,b,c] in raw if a=="replaceartist"}
|
||||
raw = tsv.parse_all("rules","string","string","string","string")
|
||||
self.rules_belongtogether = [b for [a,b,c,d] in raw if a=="belongtogether"]
|
||||
self.rules_notanartist = [b for [a,b,c,d] in raw if a=="notanartist"]
|
||||
self.rules_replacetitle = {b.lower():c for [a,b,c,d] in raw if a=="replacetitle"}
|
||||
self.rules_replaceartist = {b.lower():c for [a,b,c,d] in raw if a=="replaceartist"}
|
||||
self.rules_ignoreartist = [b.lower() for [a,b,c,d] in raw if a=="ignoreartist"]
|
||||
self.rules_addartists = {c.lower():(b.lower(),d) for [a,b,c,d] in raw if a=="addartists"}
|
||||
#self.rules_regexartist = [[b,c] for [a,b,c,d] in raw if a=="regexartist"]
|
||||
#self.rules_regextitle = [[b,c] for [a,b,c,d] in raw if a=="regextitle"]
|
||||
# TODO
|
||||
|
||||
# we always need to be able to tell if our current database is made with the current rules
|
||||
self.checksums = utilities.checksumTSV("rules")
|
||||
@ -27,6 +32,12 @@ class CleanerAgent:
|
||||
title = self.parseTitle(self.removespecial(title))
|
||||
(title,moreartists) = self.parseTitleForArtists(title)
|
||||
artists += moreartists
|
||||
if title.lower() in self.rules_addartists:
|
||||
reqartists, allartists = self.rules_addartists[title.lower()]
|
||||
reqartists = reqartists.split("␟")
|
||||
allartists = allartists.split("␟")
|
||||
if set(reqartists).issubset(set(a.lower() for a in artists)):
|
||||
artists += allartists
|
||||
artists = list(set(artists))
|
||||
artists.sort()
|
||||
|
||||
@ -52,6 +63,12 @@ class CleanerAgent:
|
||||
|
||||
def parseArtists(self,a):
|
||||
|
||||
if a.strip() in settings.get_settings("INVALID_ARTISTS"):
|
||||
return []
|
||||
|
||||
if a.strip().lower() in self.rules_ignoreartist:
|
||||
return []
|
||||
|
||||
if a.strip() == "":
|
||||
return []
|
||||
|
||||
|
@ -68,6 +68,7 @@ def handle(path,keys):
|
||||
|
||||
def scrobbletrack(artiststr,titlestr,timestamp):
|
||||
try:
|
||||
log("Incoming scrobble (compliant API): ARTISTS: " + artiststr + ", TRACK: " + titlestr,module="debug")
|
||||
(artists,title) = cla.fullclean(artiststr,titlestr)
|
||||
database.createScrobble(artists,title,timestamp)
|
||||
database.sync()
|
||||
|
92
database.py
92
database.py
@ -6,6 +6,7 @@ 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
|
||||
# doreah toolkit
|
||||
from doreah.logging import log
|
||||
from doreah import tsv
|
||||
@ -49,8 +50,11 @@ TRACKS_LOWER = []
|
||||
ARTISTS_LOWER = []
|
||||
ARTIST_SET = set()
|
||||
TRACK_SET = set()
|
||||
|
||||
MEDALS = {} #literally only changes once per year, no need to calculate that on the fly
|
||||
MEDALS_TRACKS = {}
|
||||
WEEKLY_TOPTRACKS = {}
|
||||
WEEKLY_TOPARTISTS = {}
|
||||
|
||||
cla = CleanerAgent()
|
||||
coa = CollectorAgent()
|
||||
@ -73,7 +77,12 @@ def loadAPIkeys():
|
||||
log("Authenticated Machines: " + ", ".join([m[1] for m in clients]))
|
||||
|
||||
def checkAPIkey(k):
|
||||
return (k in [k for [k,d] in clients])
|
||||
#return (k in [k for [k,d] in clients])
|
||||
for key, identifier in clients:
|
||||
if key == k: return identifier
|
||||
|
||||
return False
|
||||
|
||||
def allAPIkeys():
|
||||
return [k for [k,d] in clients]
|
||||
|
||||
@ -102,10 +111,23 @@ def get_track_dict(o):
|
||||
|
||||
|
||||
def createScrobble(artists,title,time,volatile=False):
|
||||
|
||||
if len(artists) == 0 or title == "":
|
||||
return {}
|
||||
|
||||
dblock.acquire()
|
||||
|
||||
i = getTrackID(artists,title)
|
||||
|
||||
# idempotence
|
||||
if time in SCROBBLESDICT:
|
||||
if i == SCROBBLESDICT[time].track:
|
||||
dblock.release()
|
||||
return get_track_dict(TRACKS[i])
|
||||
# timestamp as unique identifier
|
||||
while (time in SCROBBLESDICT):
|
||||
time += 1
|
||||
i = getTrackID(artists,title)
|
||||
|
||||
obj = Scrobble(i,time,volatile) # if volatile generated, we simply pretend we have already saved it to disk
|
||||
#SCROBBLES.append(obj)
|
||||
# immediately insert scrobble correctly so we can guarantee sorted list
|
||||
@ -116,6 +138,8 @@ def createScrobble(artists,title,time,volatile=False):
|
||||
invalidate_caches()
|
||||
dblock.release()
|
||||
|
||||
proxy_scrobble(artists,title,time)
|
||||
|
||||
return get_track_dict(TRACKS[obj.track])
|
||||
|
||||
|
||||
@ -225,7 +249,22 @@ def get_scrobbles(**keys):
|
||||
# return r
|
||||
return r
|
||||
|
||||
# info for comparison
|
||||
@dbserver.get("info")
|
||||
def info_external(**keys):
|
||||
result = info()
|
||||
return result
|
||||
|
||||
def info():
|
||||
totalscrobbles = get_scrobbles_num()
|
||||
artists = {}
|
||||
|
||||
return {
|
||||
"name":settings.get_settings("NAME"),
|
||||
"artists":{
|
||||
chartentry["artist"]:round(chartentry["scrobbles"] * 100 / totalscrobbles,3)
|
||||
for chartentry in get_charts_artists() if chartentry["scrobbles"]/totalscrobbles >= 0}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -517,7 +556,14 @@ def artistInfo(artist):
|
||||
c = [e for e in charts if e["artist"] == artist][0]
|
||||
others = [a for a in coa.getAllAssociated(artist) if a in ARTISTS]
|
||||
position = c["rank"]
|
||||
return {"scrobbles":scrobbles,"position":position,"associated":others,"medals":MEDALS.get(artist)}
|
||||
performance = get_performance(artist=artist,step="week")
|
||||
return {
|
||||
"scrobbles":scrobbles,
|
||||
"position":position,
|
||||
"associated":others,
|
||||
"medals":MEDALS.get(artist),
|
||||
"topweeks":WEEKLY_TOPARTISTS.get(artist,0)
|
||||
}
|
||||
except:
|
||||
# if the artist isnt in the charts, they are not being credited and we
|
||||
# need to show information about the credited one
|
||||
@ -555,11 +601,13 @@ def trackInfo(track):
|
||||
elif scrobbles >= threshold_platinum: cert = "platinum"
|
||||
elif scrobbles >= threshold_gold: cert = "gold"
|
||||
|
||||
|
||||
return {
|
||||
"scrobbles":scrobbles,
|
||||
"position":position,
|
||||
"medals":MEDALS_TRACKS.get((frozenset(track["artists"]),track["title"])),
|
||||
"certification":cert
|
||||
"certification":cert,
|
||||
"topweeks":WEEKLY_TOPTRACKS.get(((frozenset(track["artists"]),track["title"])),0)
|
||||
}
|
||||
|
||||
|
||||
@ -573,13 +621,16 @@ def pseudo_post_scrobble(**keys):
|
||||
artists = keys.get("artist")
|
||||
title = keys.get("title")
|
||||
apikey = keys.get("key")
|
||||
if not (checkAPIkey(apikey)):
|
||||
client = checkAPIkey(apikey)
|
||||
if client == False: # empty string allowed!
|
||||
response.status = 403
|
||||
return ""
|
||||
try:
|
||||
time = int(keys.get("time"))
|
||||
except:
|
||||
time = int(datetime.datetime.now(tz=datetime.timezone.utc).timestamp())
|
||||
|
||||
log("Incoming scrobble (native API): Client " + client + ", ARTISTS: " + str(artists) + ", TRACK: " + title,module="debug")
|
||||
(artists,title) = cla.fullclean(artists,title)
|
||||
|
||||
## this is necessary for localhost testing
|
||||
@ -587,8 +638,9 @@ def pseudo_post_scrobble(**keys):
|
||||
|
||||
trackdict = createScrobble(artists,title,time)
|
||||
|
||||
if (time - lastsync) > 3600:
|
||||
sync()
|
||||
sync()
|
||||
|
||||
|
||||
|
||||
return {"status":"success","track":trackdict}
|
||||
|
||||
@ -597,7 +649,8 @@ def post_scrobble(**keys):
|
||||
artists = keys.get("artist")
|
||||
title = keys.get("title")
|
||||
apikey = keys.get("key")
|
||||
if not (checkAPIkey(apikey)):
|
||||
client = checkAPIkey(apikey)
|
||||
if client == False: # empty string allowed!
|
||||
response.status = 403
|
||||
return ""
|
||||
|
||||
@ -605,6 +658,8 @@ def post_scrobble(**keys):
|
||||
time = int(keys.get("time"))
|
||||
except:
|
||||
time = int(datetime.datetime.now(tz=datetime.timezone.utc).timestamp())
|
||||
|
||||
log("Incoming scrobble (native API): Client " + client + ", ARTISTS: " + str(artists) + ", TRACK: " + title,module="debug")
|
||||
(artists,title) = cla.fullclean(artists,title)
|
||||
|
||||
## this is necessary for localhost testing
|
||||
@ -612,12 +667,11 @@ def post_scrobble(**keys):
|
||||
|
||||
trackdict = createScrobble(artists,title,time)
|
||||
|
||||
#if (time - lastsync) > 3600:
|
||||
# sync()
|
||||
sync()
|
||||
#always sync, one filesystem access every three minutes shouldn't matter
|
||||
|
||||
|
||||
|
||||
return {"status":"success","track":trackdict}
|
||||
|
||||
|
||||
@ -644,8 +698,7 @@ def abouttoshutdown():
|
||||
#sys.exit()
|
||||
|
||||
@dbserver.post("newrule")
|
||||
def newrule():
|
||||
keys = FormsDict.decode(request.forms)
|
||||
def newrule(**keys):
|
||||
apikey = keys.pop("key",None)
|
||||
if (checkAPIkey(apikey)):
|
||||
tsv.add_entry("rules/webmade.tsv",[k for k in keys])
|
||||
@ -751,8 +804,7 @@ def issues():
|
||||
|
||||
|
||||
@dbserver.post("importrules")
|
||||
def import_rulemodule():
|
||||
keys = FormsDict.decode(request.forms)
|
||||
def import_rulemodule(**keys):
|
||||
apikey = keys.pop("key",None)
|
||||
|
||||
if (checkAPIkey(apikey)):
|
||||
@ -771,9 +823,7 @@ def import_rulemodule():
|
||||
|
||||
|
||||
@dbserver.post("rebuild")
|
||||
def rebuild():
|
||||
|
||||
keys = FormsDict.decode(request.forms)
|
||||
def rebuild(**keys):
|
||||
apikey = keys.pop("key",None)
|
||||
if (checkAPIkey(apikey)):
|
||||
log("Database rebuild initiated!")
|
||||
@ -886,6 +936,7 @@ def build_db():
|
||||
|
||||
#start regular tasks
|
||||
utilities.update_medals()
|
||||
utilities.update_weekly()
|
||||
|
||||
global db_rulestate
|
||||
db_rulestate = utilities.consistentRulestate("scrobbles",cla.checksums)
|
||||
@ -899,6 +950,7 @@ def sync():
|
||||
|
||||
# all entries by file collected
|
||||
# so we don't open the same file for every entry
|
||||
#log("Syncing",module="debug")
|
||||
entries = {}
|
||||
|
||||
for idx in range(len(SCROBBLES)):
|
||||
@ -918,15 +970,19 @@ def sync():
|
||||
|
||||
SCROBBLES[idx] = (SCROBBLES[idx][0],SCROBBLES[idx][1],True)
|
||||
|
||||
#log("Sorted into months",module="debug")
|
||||
|
||||
for e in entries:
|
||||
tsv.add_entries("scrobbles/" + e + ".tsv",entries[e],comments=False)
|
||||
#addEntries("scrobbles/" + e + ".tsv",entries[e],escape=False)
|
||||
utilities.combineChecksums("scrobbles/" + e + ".tsv",cla.checksums)
|
||||
|
||||
#log("Written files",module="debug")
|
||||
|
||||
|
||||
global lastsync
|
||||
lastsync = int(datetime.datetime.now(tz=datetime.timezone.utc).timestamp())
|
||||
log("Database saved to disk.")
|
||||
#log("Database saved to disk.")
|
||||
|
||||
# save cached images
|
||||
#saveCache()
|
||||
|
50
external.py
50
external.py
@ -3,6 +3,10 @@ import json
|
||||
import base64
|
||||
from doreah.settings import get_settings
|
||||
from doreah.logging import log
|
||||
import hashlib
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
### PICTURES
|
||||
|
||||
|
||||
apis_artists = []
|
||||
@ -130,3 +134,49 @@ def api_request_track(track):
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### SCROBBLING
|
||||
|
||||
# creates signature and returns full query string
|
||||
def lfmbuild(parameters):
|
||||
m = hashlib.md5()
|
||||
keys = sorted(str(k) for k in parameters)
|
||||
m.update(utf("".join(str(k) + str(parameters[k]) for k in keys)))
|
||||
m.update(utf(get_settings("LASTFM_API_SECRET")))
|
||||
sig = m.hexdigest()
|
||||
return urllib.parse.urlencode(parameters) + "&api_sig=" + sig
|
||||
|
||||
def utf(st):
|
||||
return st.encode(encoding="UTF-8")
|
||||
|
||||
|
||||
|
||||
apis_scrobble = []
|
||||
|
||||
if get_settings("LASTFM_API_SK") not in [None,"ASK"] and get_settings("LASTFM_API_SECRET") not in [None,"ASK"] and get_settings("LASTFM_API_KEY") not in [None,"ASK"]:
|
||||
apis_scrobble.append({
|
||||
"name":"LastFM",
|
||||
"scrobbleurl":"http://ws.audioscrobbler.com/2.0/",
|
||||
"requestbody":lambda artists,title,timestamp: lfmbuild({"method":"track.scrobble","artist[0]":", ".join(artists),"track[0]":title,"timestamp":timestamp,"api_key":get_settings("LASTFM_API_KEY"),"sk":get_settings("LASTFM_API_SK")})
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
def proxy_scrobble(artists,title,timestamp):
|
||||
for api in apis_scrobble:
|
||||
response = urllib.request.urlopen(api["scrobbleurl"],data=utf(api["requestbody"](artists,title,timestamp)))
|
||||
xml = response.read()
|
||||
data = ET.fromstring(xml)
|
||||
if data.attrib.get("status") == "ok":
|
||||
if data.find("scrobbles").attrib.get("ignored") == "0":
|
||||
log(api["name"] + ": Scrobble accepted: " + "/".join(artists) + " - " + title)
|
||||
else:
|
||||
log(api["name"] + ": Scrobble not accepted: " + "/".join(artists) + " - " + title)
|
||||
|
@ -2,6 +2,8 @@ import urllib
|
||||
from bottle import FormsDict
|
||||
import datetime
|
||||
from urihandler import compose_querystring
|
||||
import urllib.parse
|
||||
from doreah.settings import get_settings
|
||||
|
||||
|
||||
# returns the proper column(s) for an artist or track
|
||||
@ -16,7 +18,9 @@ def entity_column(element,counting=[],image=None):
|
||||
# track
|
||||
# html += "<td class='artists'>" + html_links(element["artists"]) + "</td>"
|
||||
# html += "<td class='title'>" + html_link(element) + "</td>"
|
||||
html += "<td class='track'><span class='artist_in_trackcolumn'>" + html_links(element["artists"]) + "</span> – " + html_link(element) + "</td>"
|
||||
html += "<td class='track'><span class='artist_in_trackcolumn'>"
|
||||
html += trackSearchLink(element)
|
||||
html += html_links(element["artists"]) + "</span> – " + html_link(element) + "</td>"
|
||||
else:
|
||||
# artist
|
||||
html += "<td class='artist'>" + html_link(element)
|
||||
@ -74,6 +78,33 @@ def trackLink(track):
|
||||
#artists,title = track["artists"],track["title"]
|
||||
#return "<a href='/track?title=" + urllib.parse.quote(title) + "&" + "&".join(["artist=" + urllib.parse.quote(a) for a in artists]) + "'>" + title + "</a>"
|
||||
|
||||
def trackSearchLink(track):
|
||||
searchProvider = get_settings("TRACK_SEARCH_PROVIDER")
|
||||
if searchProvider is None: return ""
|
||||
|
||||
link = "<a class='trackProviderSearch' href='"
|
||||
if searchProvider == "YouTube":
|
||||
link += "https://www.youtube.com/results?search_query="
|
||||
elif searchProvider == "YouTube Music":
|
||||
link += "https://music.youtube.com/search?q="
|
||||
elif searchProvider == "Google Play Music":
|
||||
link += "https://play.google.com/music/listen#/srs/"
|
||||
elif searchProvider == "Spotify":
|
||||
link += "https://open.spotify.com/search/results/"
|
||||
elif searchProvider == "Tidal":
|
||||
link += "https://listen.tidal.com/search/tracks?q="
|
||||
elif searchProvider == "SoundCloud":
|
||||
link += "https://soundcloud.com/search?q="
|
||||
elif searchProvider == "Amazon Music":
|
||||
link += "https://music.amazon.com/search/"
|
||||
elif searchProvider == "Deezer":
|
||||
link += "https://www.deezer.com/search/"
|
||||
else:
|
||||
link += "https://www.google.com/search?q=" # ¯\_(ツ)_/¯
|
||||
|
||||
link += urllib.parse.quote(", ".join(track["artists"]) + " " + track["title"]) + "'>🎵</a>"
|
||||
return link
|
||||
|
||||
#def scrobblesTrackLink(artists,title,timekeys,amount=None,pixels=None):
|
||||
def scrobblesTrackLink(track,timekeys,amount=None,percent=None):
|
||||
artists,title = track["artists"],track["title"]
|
||||
|
314
htmlmodules.py
314
htmlmodules.py
@ -18,29 +18,38 @@ import math
|
||||
# result.append(element.get("image"))
|
||||
|
||||
|
||||
# artist=None,track=None,since=None,to=None,within=None,associated=False,max_=None,pictures=False
|
||||
def module_scrobblelist(max_=None,pictures=False,shortTimeDesc=False,earlystop=False,**kwargs):
|
||||
#max_ indicates that no pagination should occur (because this is not the primary module)
|
||||
def module_scrobblelist(page=0,perpage=100,max_=None,pictures=False,shortTimeDesc=False,earlystop=False,**kwargs):
|
||||
|
||||
kwargs_filter = pickKeys(kwargs,"artist","track","associated")
|
||||
kwargs_time = pickKeys(kwargs,"timerange","since","to","within")
|
||||
|
||||
if max_ is not None: perpage,page=max_,0
|
||||
|
||||
firstindex = page * perpage
|
||||
lastindex = firstindex + perpage
|
||||
|
||||
# if earlystop, we don't care about the actual amount and only request as many from the db
|
||||
# without, we request everything and filter on site
|
||||
maxkey = {"max_":max_} if earlystop else {}
|
||||
maxkey = {"max_":lastindex} if earlystop else {}
|
||||
scrobbles = database.get_scrobbles(**kwargs_time,**kwargs_filter,**maxkey)
|
||||
if pictures:
|
||||
scrobbleswithpictures = scrobbles if max_ is None else scrobbles[:max_]
|
||||
scrobbleswithpictures = [""] * firstindex + scrobbles[firstindex:lastindex]
|
||||
#scrobbleimages = [e.get("image") for e in getTracksInfo(scrobbleswithpictures)] #will still work with scrobble objects as they are a technically a subset of track objects
|
||||
#scrobbleimages = ["/image?title=" + urllib.parse.quote(t["title"]) + "&" + "&".join(["artist=" + urllib.parse.quote(a) for a in t["artists"]]) for t in scrobbleswithpictures]
|
||||
scrobbleimages = [getTrackImage(t["artists"],t["title"],fast=True) for t in scrobbleswithpictures]
|
||||
|
||||
pages = math.ceil(len(scrobbles) / perpage)
|
||||
|
||||
representative = scrobbles[0] if len(scrobbles) is not 0 else None
|
||||
|
||||
# build list
|
||||
i = 0
|
||||
html = "<table class='list'>"
|
||||
for s in scrobbles:
|
||||
if i<firstindex:
|
||||
i += 1
|
||||
continue
|
||||
|
||||
html += "<tr>"
|
||||
html += "<td class='time'>" + timestamp_desc(s["time"],short=shortTimeDesc) + "</td>"
|
||||
@ -48,32 +57,38 @@ def module_scrobblelist(max_=None,pictures=False,shortTimeDesc=False,earlystop=F
|
||||
img = scrobbleimages[i]
|
||||
else: img = None
|
||||
html += entity_column(s,image=img)
|
||||
# Alternative way: Do it in one cell
|
||||
#html += "<td class='title'><span>" + artistLinks(s["artists"]) + "</span> — " + trackLink({"artists":s["artists"],"title":s["title"]}) + "</td>"
|
||||
html += "</tr>"
|
||||
|
||||
i += 1
|
||||
if max_ is not None and i>=max_:
|
||||
if i>=lastindex:
|
||||
break
|
||||
|
||||
|
||||
html += "</table>"
|
||||
|
||||
if max_ is None: html += module_paginate(page=page,pages=pages,perpage=perpage,**kwargs)
|
||||
|
||||
return (html,len(scrobbles),representative)
|
||||
|
||||
|
||||
def module_pulse(max_=None,**kwargs):
|
||||
def module_pulse(page=0,perpage=100,max_=None,**kwargs):
|
||||
|
||||
from doreah.timing import clock, clockp
|
||||
|
||||
kwargs_filter = pickKeys(kwargs,"artist","track","associated")
|
||||
kwargs_time = pickKeys(kwargs,"since","to","within","timerange","step","stepn","trail")
|
||||
|
||||
if max_ is not None: perpage,page=max_,0
|
||||
|
||||
firstindex = page * perpage
|
||||
lastindex = firstindex + perpage
|
||||
|
||||
|
||||
ranges = database.get_pulse(**kwargs_time,**kwargs_filter)
|
||||
|
||||
pages = math.ceil(len(ranges) / perpage)
|
||||
|
||||
if max_ is not None: ranges = ranges[:max_]
|
||||
ranges = ranges[firstindex:lastindex]
|
||||
|
||||
# if time range not explicitly specified, only show from first appearance
|
||||
# if "since" not in kwargs:
|
||||
@ -94,19 +109,27 @@ def module_pulse(max_=None,**kwargs):
|
||||
html += "</tr>"
|
||||
html += "</table>"
|
||||
|
||||
if max_ is None: html += module_paginate(page=page,pages=pages,perpage=perpage,**kwargs)
|
||||
|
||||
return html
|
||||
|
||||
|
||||
|
||||
def module_performance(max_=None,**kwargs):
|
||||
def module_performance(page=0,perpage=100,max_=None,**kwargs):
|
||||
|
||||
kwargs_filter = pickKeys(kwargs,"artist","track")
|
||||
kwargs_time = pickKeys(kwargs,"since","to","within","timerange","step","stepn","trail")
|
||||
|
||||
if max_ is not None: perpage,page=max_,0
|
||||
|
||||
firstindex = page * perpage
|
||||
lastindex = firstindex + perpage
|
||||
|
||||
ranges = database.get_performance(**kwargs_time,**kwargs_filter)
|
||||
|
||||
if max_ is not None: ranges = ranges[:max_]
|
||||
pages = math.ceil(len(ranges) / perpage)
|
||||
|
||||
ranges = ranges[firstindex:lastindex]
|
||||
|
||||
# if time range not explicitly specified, only show from first appearance
|
||||
# if "since" not in kwargs:
|
||||
@ -130,18 +153,26 @@ def module_performance(max_=None,**kwargs):
|
||||
html += "</tr>"
|
||||
html += "</table>"
|
||||
|
||||
if max_ is None: html += module_paginate(page=page,pages=pages,perpage=perpage,**kwargs)
|
||||
|
||||
return html
|
||||
|
||||
|
||||
|
||||
def module_trackcharts(max_=None,**kwargs):
|
||||
def module_trackcharts(page=0,perpage=100,max_=None,**kwargs):
|
||||
|
||||
kwargs_filter = pickKeys(kwargs,"artist","associated")
|
||||
kwargs_time = pickKeys(kwargs,"timerange","since","to","within")
|
||||
|
||||
if max_ is not None: perpage,page=max_,0
|
||||
|
||||
firstindex = page * perpage
|
||||
lastindex = firstindex + perpage
|
||||
|
||||
tracks = database.get_charts_tracks(**kwargs_filter,**kwargs_time)
|
||||
|
||||
pages = math.ceil(len(tracks) / perpage)
|
||||
|
||||
# last time range (to compare)
|
||||
try:
|
||||
trackslast = database.get_charts_tracks(**kwargs_filter,timerange=kwargs_time["timerange"].next(step=-1))
|
||||
@ -167,13 +198,16 @@ def module_trackcharts(max_=None,**kwargs):
|
||||
i = 0
|
||||
html = "<table class='list'>"
|
||||
for e in tracks:
|
||||
if i<firstindex:
|
||||
i += 1
|
||||
continue
|
||||
i += 1
|
||||
if max_ is not None and i>max_:
|
||||
if i>lastindex:
|
||||
break
|
||||
html += "<tr>"
|
||||
# rank
|
||||
if i == 1 or e["scrobbles"] < prev["scrobbles"]:
|
||||
html += "<td class='rank'>#" + str(i) + "</td>"
|
||||
if i == firstindex+1 or e["scrobbles"] < prev["scrobbles"]:
|
||||
html += "<td class='rank'>#" + str(e["rank"]) + "</td>"
|
||||
else:
|
||||
html += "<td class='rank'></td>"
|
||||
# rank change
|
||||
@ -196,16 +230,26 @@ def module_trackcharts(max_=None,**kwargs):
|
||||
prev = e
|
||||
html += "</table>"
|
||||
|
||||
if max_ is None: html += module_paginate(page=page,pages=pages,perpage=perpage,**kwargs)
|
||||
|
||||
return (html,representative)
|
||||
|
||||
|
||||
def module_artistcharts(max_=None,**kwargs):
|
||||
def module_artistcharts(page=0,perpage=100,max_=None,**kwargs):
|
||||
|
||||
kwargs_filter = pickKeys(kwargs,"associated") #not used right now
|
||||
kwargs_time = pickKeys(kwargs,"timerange","since","to","within")
|
||||
|
||||
if max_ is not None: perpage,page=max_,0
|
||||
|
||||
firstindex = page * perpage
|
||||
lastindex = firstindex + perpage
|
||||
|
||||
artists = database.get_charts_artists(**kwargs_filter,**kwargs_time)
|
||||
|
||||
pages = math.ceil(len(artists) / perpage)
|
||||
|
||||
|
||||
# last time range (to compare)
|
||||
try:
|
||||
#from malojatime import _get_next
|
||||
@ -231,13 +275,16 @@ def module_artistcharts(max_=None,**kwargs):
|
||||
i = 0
|
||||
html = "<table class='list'>"
|
||||
for e in artists:
|
||||
if i<firstindex:
|
||||
i += 1
|
||||
continue
|
||||
i += 1
|
||||
if max_ is not None and i>max_:
|
||||
if i>lastindex:
|
||||
break
|
||||
html += "<tr>"
|
||||
# rank
|
||||
if i == 1 or e["scrobbles"] < prev["scrobbles"]:
|
||||
html += "<td class='rank'>#" + str(i) + "</td>"
|
||||
if i == firstindex+1 or e["scrobbles"] < prev["scrobbles"]:
|
||||
html += "<td class='rank'>#" + str(e["rank"]) + "</td>"
|
||||
else:
|
||||
html += "<td class='rank'></td>"
|
||||
# rank change
|
||||
@ -262,6 +309,8 @@ def module_artistcharts(max_=None,**kwargs):
|
||||
|
||||
html += "</table>"
|
||||
|
||||
if max_ is None: html += module_paginate(page=page,pages=pages,perpage=perpage,**kwargs)
|
||||
|
||||
return (html, representative)
|
||||
|
||||
|
||||
@ -308,7 +357,7 @@ def module_toptracks(pictures=True,**kwargs):
|
||||
if pictures:
|
||||
html += "<td><div></div></td>"
|
||||
html += "<td class='stats'>" + "No scrobbles" + "</td>"
|
||||
html += "<td>" + "" + "</td>"
|
||||
#html += "<td>" + "" + "</td>"
|
||||
html += "<td class='amount'>" + "0" + "</td>"
|
||||
html += "<td class='bar'>" + "" + "</td>"
|
||||
else:
|
||||
@ -478,22 +527,60 @@ def module_trackcharts_tiles(**kwargs):
|
||||
return html
|
||||
|
||||
|
||||
|
||||
def module_paginate(page,pages,perpage,**keys):
|
||||
|
||||
unchangedkeys = internal_to_uri({**keys,"perpage":perpage})
|
||||
|
||||
html = "<div class='paginate'>"
|
||||
|
||||
if page > 1:
|
||||
html += "<a href='?" + compose_querystring(unchangedkeys,internal_to_uri({"page":0})) + "'><span class='stat_selector'>" + "1" + "</span></a>"
|
||||
html += " | "
|
||||
|
||||
if page > 2:
|
||||
html += " ... | "
|
||||
|
||||
if page > 0:
|
||||
html += "<a href='?" + compose_querystring(unchangedkeys,internal_to_uri({"page":page-1})) + "'><span class='stat_selector'>" + str(page) + "</span></a>"
|
||||
html += " « "
|
||||
|
||||
html += "<span style='opacity:0.5;' class='stat_selector'>" + str(page+1) + "</span>"
|
||||
|
||||
if page < pages-1:
|
||||
html += " » "
|
||||
html += "<a href='?" + compose_querystring(unchangedkeys,internal_to_uri({"page":page+1})) + "'><span class='stat_selector'>" + str(page+2) + "</span></a>"
|
||||
|
||||
if page < pages-3:
|
||||
html += " | ... "
|
||||
|
||||
if page < pages-2:
|
||||
html += " | "
|
||||
html += "<a href='?" + compose_querystring(unchangedkeys,internal_to_uri({"page":pages-1})) + "'><span class='stat_selector'>" + str(pages) + "</span></a>"
|
||||
|
||||
|
||||
html += "</div>"
|
||||
|
||||
return html
|
||||
|
||||
|
||||
|
||||
# THIS FUNCTION USES THE ORIGINAL URI KEYS!!!
|
||||
def module_filterselection(keys,time=True,delimit=False):
|
||||
|
||||
filterkeys, timekeys, delimitkeys, extrakeys = uri_to_internal(keys)
|
||||
from malojatime import today, thisweek, thismonth, thisyear, alltime
|
||||
|
||||
filterkeys, timekeys, delimitkeys, extrakeys = uri_to_internal(keys)
|
||||
# drop keys that are not relevant so they don't clutter the URI
|
||||
if not time: timekeys = {}
|
||||
if not delimit: delimitkeys = {}
|
||||
if "page" in extrakeys: del extrakeys["page"]
|
||||
internalkeys = {**filterkeys,**timekeys,**delimitkeys,**extrakeys}
|
||||
|
||||
html = ""
|
||||
|
||||
if time:
|
||||
# all other keys that will not be changed by clicking another filter
|
||||
#keystr = "?" + compose_querystring(keys,exclude=["since","to","in"])
|
||||
unchangedkeys = internal_to_uri({**filterkeys,**delimitkeys,**extrakeys})
|
||||
|
||||
if time:
|
||||
|
||||
# wonky selector for precise date range
|
||||
|
||||
@ -513,139 +600,78 @@ def module_filterselection(keys,time=True,delimit=False):
|
||||
# html += "to <input id='dateselect_to' onchange='datechange()' type='date' value='" + "-".join(todate) + "'/>"
|
||||
# html += "</div>"
|
||||
|
||||
from malojatime import today, thisweek, thismonth, thisyear
|
||||
|
||||
### temp!!! this will not allow weekly rank changes
|
||||
# weekday = ((now.isoweekday()) % 7)
|
||||
# weekbegin = now - datetime.timedelta(days=weekday)
|
||||
# weekend = weekbegin + datetime.timedelta(days=6)
|
||||
# weekbegin = [weekbegin.year,weekbegin.month,weekbegin.day]
|
||||
# weekend = [weekend.year,weekend.month,weekend.day]
|
||||
# weekbeginstr = "/".join((str(num) for num in weekbegin))
|
||||
# weekendstr = "/".join((str(num) for num in weekend))
|
||||
|
||||
|
||||
|
||||
# relative to current range
|
||||
|
||||
html += "<div>"
|
||||
# if timekeys.get("timerange").next(-1) is not None:
|
||||
# html += "<a href='?" + compose_querystring(unchangedkeys,internal_to_uri({"timerange":timekeys.get("timerange").next(-1)})) + "'><span class='stat_selector'>«</span></a>"
|
||||
# if timekeys.get("timerange").next(-1) is not None or timekeys.get("timerange").next(1) is not None:
|
||||
# html += " " + timekeys.get("timerange").desc() + " "
|
||||
# if timekeys.get("timerange").next(1) is not None:
|
||||
# html += "<a href='?" + compose_querystring(unchangedkeys,internal_to_uri({"timerange":timekeys.get("timerange").next(1)})) + "'><span class='stat_selector'>»</span></a>"
|
||||
|
||||
if timekeys.get("timerange").next(-1) is not None:
|
||||
prevrange = timekeys.get("timerange").next(-1)
|
||||
html += "<a href='?" + compose_querystring(unchangedkeys,internal_to_uri({"timerange":prevrange})) + "'><span class='stat_selector'>" + prevrange.desc() + "</span></a>"
|
||||
thisrange = timekeys.get("timerange")
|
||||
prevrange = thisrange.next(-1)
|
||||
nextrange = thisrange.next(1)
|
||||
|
||||
if prevrange is not None:
|
||||
link = compose_querystring(internal_to_uri({**internalkeys,"timerange":prevrange}))
|
||||
html += "<a href='?" + link + "'><span class='stat_selector'>" + prevrange.desc() + "</span></a>"
|
||||
html += " « "
|
||||
if timekeys.get("timerange").next(-1) is not None or timekeys.get("timerange").next(1) is not None:
|
||||
html += "<span class='stat_selector' style='opacity:0.5;'>" + timekeys.get("timerange").desc() + "</span>"
|
||||
if timekeys.get("timerange").next(1) is not None:
|
||||
if prevrange is not None or nextrange is not None:
|
||||
html += "<span class='stat_selector' style='opacity:0.5;'>" + thisrange.desc() + "</span>"
|
||||
if nextrange is not None:
|
||||
html += " » "
|
||||
nextrange = timekeys.get("timerange").next(1)
|
||||
html += "<a href='?" + compose_querystring(unchangedkeys,internal_to_uri({"timerange":nextrange})) + "'><span class='stat_selector'>" + nextrange.desc() + "</span></a>"
|
||||
|
||||
html += "</div>"
|
||||
|
||||
|
||||
# predefined ranges
|
||||
|
||||
html += "<div>"
|
||||
if timekeys.get("timerange") == today():
|
||||
html += "<span class='stat_selector' style='opacity:0.5;'>Today</span>"
|
||||
else:
|
||||
html += "<a href='?" + compose_querystring(unchangedkeys,{"in":"today"}) + "'><span class='stat_selector'>Today</span></a>"
|
||||
html += " | "
|
||||
|
||||
if timekeys.get("timerange") == thisweek():
|
||||
html += "<span class='stat_selector' style='opacity:0.5;'>This Week</span>"
|
||||
else:
|
||||
html += "<a href='?" + compose_querystring(unchangedkeys,{"in":"week"}) + "'><span class='stat_selector'>This Week</span></a>"
|
||||
html += " | "
|
||||
|
||||
if timekeys.get("timerange") == thismonth():
|
||||
html += "<span class='stat_selector' style='opacity:0.5;'>This Month</span>"
|
||||
else:
|
||||
html += "<a href='?" + compose_querystring(unchangedkeys,{"in":"month"}) + "'><span class='stat_selector'>This Month</span></a>"
|
||||
html += " | "
|
||||
|
||||
if timekeys.get("timerange") == thisyear():
|
||||
html += "<span class='stat_selector' style='opacity:0.5;'>This Year</span>"
|
||||
else:
|
||||
html += "<a href='?" + compose_querystring(unchangedkeys,{"in":"year"}) + "'><span class='stat_selector'>This Year</span></a>"
|
||||
html += " | "
|
||||
|
||||
if timekeys.get("timerange") is None or timekeys.get("timerange").unlimited():
|
||||
html += "<span class='stat_selector' style='opacity:0.5;'>All Time</span>"
|
||||
else:
|
||||
html += "<a href='?" + compose_querystring(unchangedkeys) + "'><span class='stat_selector'>All Time</span></a>"
|
||||
|
||||
html += "</div>"
|
||||
|
||||
if delimit:
|
||||
|
||||
#keystr = "?" + compose_querystring(keys,exclude=["step","stepn"])
|
||||
unchangedkeys = internal_to_uri({**filterkeys,**timekeys,**extrakeys})
|
||||
|
||||
# only for this element (delimit selector consists of more than one)
|
||||
unchangedkeys_sub = internal_to_uri({k:delimitkeys[k] for k in delimitkeys if k not in ["step","stepn"]})
|
||||
|
||||
html += "<div>"
|
||||
if delimitkeys.get("step") == "day" and delimitkeys.get("stepn") == 1:
|
||||
html += "<span class='stat_selector' style='opacity:0.5;'>Daily</span>"
|
||||
else:
|
||||
html += "<a href='?" + compose_querystring(unchangedkeys,unchangedkeys_sub,{"step":"day"}) + "'><span class='stat_selector'>Daily</span></a>"
|
||||
html += " | "
|
||||
|
||||
if delimitkeys.get("step") == "week" and delimitkeys.get("stepn") == 1:
|
||||
html += "<span class='stat_selector' style='opacity:0.5;'>Weekly</span>"
|
||||
else:
|
||||
html += "<a href='?" + compose_querystring(unchangedkeys,unchangedkeys_sub,{"step":"week"}) + "'><span class='stat_selector'>Weekly</span></a>"
|
||||
html += " | "
|
||||
|
||||
if delimitkeys.get("step") == "month" and delimitkeys.get("stepn") == 1:
|
||||
html += "<span class='stat_selector' style='opacity:0.5;'>Monthly</span>"
|
||||
else:
|
||||
html += "<a href='?" + compose_querystring(unchangedkeys,unchangedkeys_sub,{"step":"month"}) + "'><span class='stat_selector'>Monthly</span></a>"
|
||||
html += " | "
|
||||
|
||||
if delimitkeys.get("step") == "year" and delimitkeys.get("stepn") == 1:
|
||||
html += "<span class='stat_selector' style='opacity:0.5;'>Yearly</span>"
|
||||
else:
|
||||
html += "<a href='?" + compose_querystring(unchangedkeys,unchangedkeys_sub,{"step":"year"}) + "'><span class='stat_selector'>Yearly</span></a>"
|
||||
link = compose_querystring(internal_to_uri({**internalkeys,"timerange":nextrange}))
|
||||
html += "<a href='?" + link + "'><span class='stat_selector'>" + nextrange.desc() + "</span></a>"
|
||||
|
||||
html += "</div>"
|
||||
|
||||
|
||||
|
||||
unchangedkeys_sub = internal_to_uri({k:delimitkeys[k] for k in delimitkeys if k != "trail"})
|
||||
|
||||
html += "<div>"
|
||||
if delimitkeys.get("trail") == 1:
|
||||
html += "<span class='stat_selector' style='opacity:0.5;'>Standard</span>"
|
||||
else:
|
||||
html += "<a href='?" + compose_querystring(unchangedkeys,unchangedkeys_sub,{"trail":"1"}) + "'><span class='stat_selector'>Standard</span></a>"
|
||||
html += " | "
|
||||
categories = [
|
||||
{
|
||||
"active":time,
|
||||
"options":{
|
||||
"Today":{"timerange":today()},
|
||||
"This Week":{"timerange":thisweek()},
|
||||
"This Month":{"timerange":thismonth()},
|
||||
"This Year":{"timerange":thisyear()},
|
||||
"All Time":{"timerange":alltime()}
|
||||
}
|
||||
},
|
||||
{
|
||||
"active":delimit,
|
||||
"options":{
|
||||
"Daily":{"step":"day","stepn":1},
|
||||
"Weekly":{"step":"week","stepn":1},
|
||||
"Fortnightly":{"step":"week","stepn":2},
|
||||
"Monthly":{"step":"month","stepn":1},
|
||||
"Quarterly":{"step":"month","stepn":3},
|
||||
"Yearly":{"step":"year","stepn":1}
|
||||
}
|
||||
},
|
||||
{
|
||||
"active":delimit,
|
||||
"options":{
|
||||
"Standard":{"trail":1},
|
||||
"Trailing":{"trail":2},
|
||||
"Long Trailing":{"trail":3},
|
||||
"Inert":{"trail":10},
|
||||
"Cumulative":{"trail":math.inf}
|
||||
}
|
||||
}
|
||||
|
||||
if delimitkeys.get("trail") == 2:
|
||||
html += "<span class='stat_selector' style='opacity:0.5;'>Trailing</span>"
|
||||
else:
|
||||
html += "<a href='?" + compose_querystring(unchangedkeys,unchangedkeys_sub,{"trail":"2"}) + "'><span class='stat_selector'>Trailing</span></a>"
|
||||
html += " | "
|
||||
]
|
||||
|
||||
if delimitkeys.get("trail") == 3:
|
||||
html += "<span class='stat_selector' style='opacity:0.5;'>Long Trailing</span>"
|
||||
else:
|
||||
html += "<a href='?" + compose_querystring(unchangedkeys,unchangedkeys_sub,{"trail":"3"}) + "'><span class='stat_selector'>Long Trailing</span></a>"
|
||||
html += " | "
|
||||
for c in categories:
|
||||
|
||||
if delimitkeys.get("trail") == math.inf:
|
||||
html += "<span class='stat_selector' style='opacity:0.5;'>Cumulative</span>"
|
||||
else:
|
||||
html += "<a href='?" + compose_querystring(unchangedkeys,unchangedkeys_sub,{"cumulative":"yes"}) + "'><span class='stat_selector'>Cumulative</span></a>"
|
||||
if c["active"]:
|
||||
|
||||
html += "</div>"
|
||||
optionlist = []
|
||||
for option in c["options"]:
|
||||
values = c["options"][option]
|
||||
link = "?" + compose_querystring(internal_to_uri({**internalkeys,**values}))
|
||||
|
||||
if all(internalkeys.get(k) == values[k] for k in values):
|
||||
optionlist.append("<span class='stat_selector' style='opacity:0.5;'>" + option + "</span>")
|
||||
else:
|
||||
optionlist.append("<a href='" + link + "'><span class='stat_selector'>" + option + "</span></a>")
|
||||
|
||||
html += "<div>" + " | ".join(optionlist) + "</div>"
|
||||
|
||||
return html
|
||||
|
22
maloja
22
maloja
@ -183,12 +183,21 @@ def getInstance():
|
||||
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
|
||||
@ -221,8 +230,12 @@ def restart():
|
||||
return wasrunning
|
||||
|
||||
def stop():
|
||||
pid_sv = getInstanceSupervisor()
|
||||
if pid_sv is not None:
|
||||
os.kill(pid_sv,signal.SIGTERM)
|
||||
|
||||
pid = getInstance()
|
||||
if pid == None:
|
||||
if pid is None:
|
||||
print("Server is not running")
|
||||
return False
|
||||
else:
|
||||
@ -283,8 +296,13 @@ def update():
|
||||
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)
|
||||
|
||||
print("Make sure to update required modules! (" + yellow("pip3 install -r requirements.txt --upgrade --no-cache-dir") + ")")
|
||||
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
|
||||
|
||||
|
@ -391,6 +391,7 @@ def time_fix(t):
|
||||
if isinstance(t,MRangeDescriptor): return t
|
||||
|
||||
if isinstance(t, str):
|
||||
if t in ["alltime"]: return None
|
||||
tod = datetime.datetime.utcnow()
|
||||
months = ["january","february","march","april","may","june","july","august","september","october","november","december"]
|
||||
weekdays = ["sunday","monday","tuesday","wednesday","thursday","friday","saturday"]
|
||||
@ -545,9 +546,8 @@ def time_stamps(since=None,to=None,within=None,range=None):
|
||||
|
||||
def delimit_desc(step="month",stepn=1,trail=1):
|
||||
txt = ""
|
||||
if stepn is not 1: txt += _num(stepn) + "-"
|
||||
if stepn is not 1: txt += str(stepn) + "-"
|
||||
txt += {"year":"Yearly","month":"Monthly","week":"Weekly","day":"Daily"}[step.lower()]
|
||||
#if trail is not 1: txt += " " + _num(trail) + "-Trailing"
|
||||
if trail is math.inf: txt += " Cumulative"
|
||||
elif trail is not 1: txt += " Trailing" #we don't need all the info in the title
|
||||
|
||||
@ -587,10 +587,11 @@ def ranges(since=None,to=None,within=None,timerange=None,step="month",stepn=1,tr
|
||||
d_start = d_start.next(stepn-1) #last part of first included range
|
||||
i = 0
|
||||
current_end = d_start
|
||||
current_start = current_end.next((stepn*trail-1)*-1)
|
||||
#ranges = []
|
||||
while current_end.first_stamp() <= lastincluded and (max_ is None or i < max_):
|
||||
while current_end.first_stamp() < lastincluded and (max_ is None or i < max_):
|
||||
|
||||
|
||||
current_start = current_end.next((stepn*trail-1)*-1)
|
||||
if current_start == current_end:
|
||||
yield current_start
|
||||
#ranges.append(current_start)
|
||||
@ -598,6 +599,7 @@ def ranges(since=None,to=None,within=None,timerange=None,step="month",stepn=1,tr
|
||||
yield MRange(current_start,current_end)
|
||||
#ranges.append(MRange(current_start,current_end))
|
||||
current_end = current_end.next(stepn)
|
||||
current_start = current_end.next((stepn*trail-1)*-1)
|
||||
|
||||
i += 1
|
||||
|
||||
@ -619,6 +621,8 @@ def thismonth():
|
||||
def thisyear():
|
||||
tod = datetime.datetime.utcnow()
|
||||
return MTime(tod.year)
|
||||
def alltime():
|
||||
return MRange(None,None)
|
||||
|
||||
#def _get_start_of(timestamp,unit):
|
||||
# date = datetime.datetime.utcfromtimestamp(timestamp)
|
||||
|
Binary file not shown.
@ -1,6 +1,7 @@
|
||||
bottle>=0.12.16
|
||||
waitress>=1.3
|
||||
doreah>=0.9.1
|
||||
doreah>=1.1.7
|
||||
nimrodel>=0.4.9
|
||||
setproctitle>=1.1.10
|
||||
wand>=0.5.4
|
||||
lesscpy>=0.13
|
||||
|
@ -10,4 +10,5 @@ countas S Club 7 Tina Barrett
|
||||
countas RenoakRhythm Approaching Nirvana
|
||||
countas Shirley Manson Garbage
|
||||
countas Lewis Brindley The Yogscast
|
||||
countas Sips The Yogscast
|
||||
countas Sips The Yogscast
|
||||
countas Sjin The Yogscast
|
||||
|
Can't render this file because it has a wrong number of fields in line 5.
|
@ -16,6 +16,7 @@ replacetitle Cause I'm God Girl Roll Deep
|
||||
countas 2Yoon 4Minute
|
||||
replaceartist 4minute 4Minute
|
||||
replacetitle 미쳐 Crazy
|
||||
addartists HyunA Change Jun Hyung
|
||||
|
||||
# BLACKPINK
|
||||
countas Jennie BLACKPINK
|
||||
@ -47,8 +48,8 @@ replacetitle 나비 (Butterfly) Butterfly
|
||||
replacetitle Déjà vu Déjà Vu
|
||||
replacetitle 라차타 (LA chA TA) LA chA TA
|
||||
replacetitle 여우 같은 내 친구 (No More) No More
|
||||
replacetitle 시그널 (Signal) Signal
|
||||
replacetitle 미행 (그림자 : Shadow) Shadow
|
||||
replacetitle 시그널 (Signal) Signal
|
||||
replacetitle 미행 (그림자 : Shadow) Shadow
|
||||
|
||||
# Stellar
|
||||
replaceartist STELLAR Stellar
|
||||
@ -58,6 +59,7 @@ replacetitle 찔려 Sting Sting
|
||||
|
||||
# Red Velvet
|
||||
countas Seulgi Red Velvet
|
||||
countas Joy Red Velvet
|
||||
replacetitle 러시안 룰렛 Russian Roulette Russian Roulette
|
||||
replacetitle 피카부 Peek-a-Boo Peek-A-Boo
|
||||
replacetitle 빨간 맛 Red Flavor Red Flavor
|
||||
@ -81,6 +83,7 @@ replacetitle CHEER UP Cheer Up
|
||||
replacetitle OOH-AHH하게 Like OOH-AHH Like Ooh-Ahh
|
||||
replacetitle OOH-AHH Like Ooh-Ahh
|
||||
replacetitle LIKEY Likey
|
||||
countas Tzuyu TWICE
|
||||
|
||||
# AOA
|
||||
countas AOA Black AOA
|
||||
@ -145,5 +148,8 @@ replaceartist A pink Apink
|
||||
# Chungha & IOI
|
||||
replaceartist CHUNG HA Chungha
|
||||
replaceartist 청하 CHUNGHA Chungha
|
||||
#countas Chungha I.O.I # Chungha is too famous
|
||||
#countas Chungha I.O.I # Chungha is too famous
|
||||
replacetitle 벌써 12시 Gotta Go Gotta Go
|
||||
|
||||
# ITZY
|
||||
replacetitle 달라달라 (DALLA DALLA) Dalla Dalla
|
||||
|
Can't render this file because it has a wrong number of fields in line 5.
|
@ -16,6 +16,10 @@ The first column defines the type of the rule:
|
||||
This will not change the separation in the database and all effects of this rule will disappear as soon as it is no longer active.
|
||||
Second column is the artist
|
||||
Third column the replacement artist / grouping label
|
||||
addartists Defines a certain combination of artists and song title that should always have other artists added.
|
||||
Second column is artists that need to be already present for this rule to apply
|
||||
Third column is the song title
|
||||
Fourth column are artists that shoud be added, separated by ␟
|
||||
|
||||
Rules in non-tsv files are ignored. '#' is used for comments. Additional columns are ignored. To have a '#' in a name, use '\num'
|
||||
Comments are not supported in scrobble lists, but you probably never edit these manually anyway.
|
||||
@ -30,3 +34,4 @@ replacetitle 첫 사랑니 (Rum Pum Pum Pum) Rum Pum Pum Pum
|
||||
replaceartist Dal Shabet Dal★Shabet
|
||||
replaceartist Mr FijiWiji, AgNO3 Mr FijiWiji␟AgNO3 # one artist is replaced by two artists
|
||||
countas Trouble Maker HyunA
|
||||
addartists HyunA Change Jun Hyung
|
||||
|
@ -28,6 +28,18 @@ pages = {
|
||||
"https://open.spotify.com"
|
||||
],
|
||||
"script":"spotify.js"
|
||||
},
|
||||
"Bandcamp":{
|
||||
"patterns":[
|
||||
"bandcamp.com"
|
||||
],
|
||||
"script":"bandcamp.js"
|
||||
},
|
||||
"Soundcloud":{
|
||||
"patterns":[
|
||||
"https://soundcloud.com"
|
||||
],
|
||||
"script":"soundcloud.js"
|
||||
}
|
||||
|
||||
}
|
||||
@ -51,7 +63,7 @@ function onTabUpdated(tabId, changeInfo, tab) {
|
||||
patterns = pages[page]["patterns"];
|
||||
//console.log("Page was managed by a " + page + " manager")
|
||||
for (var i=0;i<patterns.length;i++) {
|
||||
if (tab.url.startsWith(patterns[i])) {
|
||||
if (tab.url.includes(patterns[i])) {
|
||||
//console.log("Still on same page!")
|
||||
tabManagers[tabId].update();
|
||||
|
||||
@ -67,7 +79,7 @@ function onTabUpdated(tabId, changeInfo, tab) {
|
||||
if (pages.hasOwnProperty(key)) {
|
||||
patterns = pages[key]["patterns"];
|
||||
for (var i=0;i<patterns.length;i++) {
|
||||
if (tab.url.startsWith(patterns[i])) {
|
||||
if (tab.url.includes(patterns[i])) {
|
||||
console.log("New page on tab " + tabId + " will be handled by new " + key + " manager!");
|
||||
tabManagers[tabId] = new Controller(tabId,key);
|
||||
updateTabNum();
|
||||
@ -166,8 +178,13 @@ class Controller {
|
||||
actuallyupdate() {
|
||||
this.messageID++;
|
||||
//console.log("Update! Our page is " + this.page + ", our tab id " + this.tabId)
|
||||
chrome.tabs.executeScript(this.tabId,{"file":"sites/" + pages[this.page]["script"]});
|
||||
chrome.tabs.executeScript(this.tabId,{"file":"sitescript.js"});
|
||||
try {
|
||||
chrome.tabs.executeScript(this.tabId,{"file":"sites/" + pages[this.page]["script"]});
|
||||
chrome.tabs.executeScript(this.tabId,{"file":"sitescript.js"});
|
||||
}
|
||||
catch (e) {
|
||||
console.log("Could not run site script. Tab probably closed or something idk.")
|
||||
}
|
||||
|
||||
this.alreadyQueued = false;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Maloja Scrobbler",
|
||||
"version": "1.3",
|
||||
"version": "1.4",
|
||||
"description": "Scrobbles tracks from various sites to your Maloja server",
|
||||
"manifest_version": 2,
|
||||
"permissions": ["activeTab",
|
||||
|
15
scrobblers/chromium-generic/sites/bandcamp.js
Normal file
15
scrobblers/chromium-generic/sites/bandcamp.js
Normal file
@ -0,0 +1,15 @@
|
||||
maloja_scrobbler_selector_playbar = "//div[contains(@class,'trackView')]"
|
||||
|
||||
|
||||
maloja_scrobbler_selector_metadata = "."
|
||||
// need to select everything as bar / metadata block because artist isn't shown in the inline player
|
||||
|
||||
maloja_scrobbler_selector_title = ".//span[@class='title']/text()"
|
||||
maloja_scrobbler_selector_artist = ".//span[contains(@itemprop,'byArtist')]/a/text()"
|
||||
maloja_scrobbler_selector_duration = ".//span[@class='time_total']/text()"
|
||||
|
||||
|
||||
maloja_scrobbler_selector_control = ".//td[@class='play_cell']/a[@role='button']/div[contains(@class,'playbutton')]/@class"
|
||||
|
||||
maloja_scrobbler_label_playing = "playbutton playing"
|
||||
maloja_scrobbler_label_paused = "playbutton"
|
14
scrobblers/chromium-generic/sites/soundcloud.js
Normal file
14
scrobblers/chromium-generic/sites/soundcloud.js
Normal file
@ -0,0 +1,14 @@
|
||||
maloja_scrobbler_selector_playbar = "//div[contains(@class,'playControls')]"
|
||||
|
||||
|
||||
maloja_scrobbler_selector_metadata = ".//div[contains(@class,'playControls__soundBadge')]//div[contains(@class,'playbackSoundBadge__titleContextContainer')]"
|
||||
|
||||
maloja_scrobbler_selector_title = ".//div/a/@title"
|
||||
maloja_scrobbler_selector_artist = ".//a/text()"
|
||||
maloja_scrobbler_selector_duration = ".//div[contains(@class,'playbackTimeline__duration')]//span[@aria-hidden='true']/text()"
|
||||
|
||||
|
||||
maloja_scrobbler_selector_control = ".//button[contains(@class,'playControl')]/@title"
|
||||
|
||||
maloja_scrobbler_label_playing = "Pause current"
|
||||
maloja_scrobbler_label_paused = "Play current"
|
@ -9,4 +9,4 @@ maloja_scrobbler_selector_artist = "./text()"
|
||||
maloja_scrobbler_selector_duration = ".//div[@class='playback-bar__progress-time'][2]/text()"
|
||||
|
||||
|
||||
maloja_scrobbler_selector_control = ".//div[contains(@class,'player-controls__buttons')]/button[3]/@title"
|
||||
maloja_scrobbler_selector_control = ".//div[contains(@class,'player-controls__buttons')]/div[3]/button/@title"
|
||||
|
@ -65,12 +65,24 @@ else {
|
||||
|
||||
|
||||
control = bar.xpath(maloja_scrobbler_selector_control, XPathResult.STRING_TYPE);
|
||||
if (control == "Play") {
|
||||
try {
|
||||
label_playing = maloja_scrobbler_label_playing
|
||||
}
|
||||
catch {
|
||||
label_playing = "Pause"
|
||||
}
|
||||
try {
|
||||
label_paused = maloja_scrobbler_label_paused
|
||||
}
|
||||
catch {
|
||||
label_paused = "Play"
|
||||
}
|
||||
if (control == label_paused) {
|
||||
console.log("Not playing right now");
|
||||
chrome.runtime.sendMessage({type:"stopPlayback",time:Math.floor(Date.now()),artist:artist,title:title});
|
||||
//stopPlayback()
|
||||
}
|
||||
else if (control == "Pause") {
|
||||
else if (control == label_playing) {
|
||||
console.log("Playing " + artist + " - " + title + " (" + durationSeconds + " sec)");
|
||||
chrome.runtime.sendMessage({type:"startPlayback",time:Math.floor(Date.now()),artist:artist,title:title,duration:durationSeconds});
|
||||
//startPlayback(artist,title,durationSeconds)
|
||||
|
42
server.py
42
server.py
@ -1,17 +1,17 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# server stuff
|
||||
from bottle import Bottle, route, get, post, error, run, template, static_file, request, response, FormsDict, redirect, template
|
||||
from bottle import Bottle, route, get, post, error, run, template, static_file, request, response, FormsDict, redirect, template, HTTPResponse
|
||||
import waitress
|
||||
# monkey patching
|
||||
import monkey
|
||||
# rest of the project
|
||||
import database
|
||||
import utilities
|
||||
import htmlmodules
|
||||
import htmlgenerators
|
||||
import malojatime
|
||||
from utilities import *
|
||||
import utilities
|
||||
from utilities import resolveImage
|
||||
from urihandler import uri_to_internal, remove_identical
|
||||
import urihandler
|
||||
# doreah toolkit
|
||||
@ -26,20 +26,28 @@ import os
|
||||
import setproctitle
|
||||
# url handling
|
||||
import urllib
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
from urllib.error import *
|
||||
|
||||
|
||||
|
||||
#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")
|
||||
|
||||
|
||||
webserver = Bottle()
|
||||
|
||||
|
||||
import lesscpy
|
||||
css = ""
|
||||
for f in os.listdir("website/less"):
|
||||
css += lesscpy.compile("website/less/" + f)
|
||||
|
||||
os.makedirs("website/css",exist_ok=True)
|
||||
with open("website/css/style.css","w") as f:
|
||||
f.write(css)
|
||||
|
||||
|
||||
@webserver.route("")
|
||||
@webserver.route("/")
|
||||
def mainpage():
|
||||
@ -77,7 +85,11 @@ def customerror(error):
|
||||
|
||||
def graceful_exit(sig=None,frame=None):
|
||||
#urllib.request.urlopen("http://[::1]:" + str(DATABASE_PORT) + "/sync")
|
||||
database.sync()
|
||||
log("Received signal to shutdown")
|
||||
try:
|
||||
database.sync()
|
||||
except Exception as e:
|
||||
log("Error while shutting down!",e)
|
||||
log("Server shutting down...")
|
||||
os._exit(42)
|
||||
|
||||
@ -121,6 +133,7 @@ def static_image(pth):
|
||||
#@webserver.route("/<name:re:.*\\.html>")
|
||||
@webserver.route("/<name:re:.*\\.js>")
|
||||
@webserver.route("/<name:re:.*\\.css>")
|
||||
@webserver.route("/<name:re:.*\\.less>")
|
||||
@webserver.route("/<name:re:.*\\.png>")
|
||||
@webserver.route("/<name:re:.*\\.jpeg>")
|
||||
@webserver.route("/<name:re:.*\\.ico>")
|
||||
@ -132,7 +145,7 @@ def static(name):
|
||||
|
||||
@webserver.route("/<name>")
|
||||
def static_html(name):
|
||||
linkheaders = ["</css/maloja.css>; rel=preload; as=style"]
|
||||
linkheaders = ["</css/style.css>; rel=preload; as=style"]
|
||||
keys = remove_identical(FormsDict.decode(request.query))
|
||||
|
||||
# if a pyhp file exists, use this
|
||||
@ -206,6 +219,16 @@ def static_html(name):
|
||||
return html
|
||||
#return static_file("website/" + name + ".html",root="")
|
||||
|
||||
|
||||
# Shortlinks
|
||||
|
||||
@webserver.get("/artist/<artist>")
|
||||
def redirect_artist(artist):
|
||||
redirect("/artist?artist=" + artist)
|
||||
@webserver.get("/track/<artists:path>/<title>")
|
||||
def redirect_track(artists,title):
|
||||
redirect("/track?title=" + title + "&" + "&".join("artist=" + artist for artist in artists.split("/")))
|
||||
|
||||
#set graceful shutdown
|
||||
signal.signal(signal.SIGINT, graceful_exit)
|
||||
signal.signal(signal.SIGTERM, graceful_exit)
|
||||
@ -215,8 +238,7 @@ setproctitle.setproctitle("Maloja")
|
||||
|
||||
## start database
|
||||
database.start_db()
|
||||
#database.register_subroutes(webserver,"/api")
|
||||
database.dbserver.mount(server=webserver)
|
||||
|
||||
log("Starting up Maloja server...")
|
||||
run(webserver, host='::', port=MAIN_PORT, server='waitress')
|
||||
run(webserver, host=HOST, port=MAIN_PORT, server='waitress')
|
||||
|
@ -1,19 +1,30 @@
|
||||
# Do not change settings in this file
|
||||
# Instead, simply write an entry with the same name in your own settings.ini file
|
||||
# Category headers in [brackets] are only for organization and not necessary
|
||||
|
||||
[HTTP]
|
||||
|
||||
WEB_PORT = 42010
|
||||
HOST = "::" # You most likely want either :: for IPv6 or 0.0.0.0 for IPv4 here
|
||||
|
||||
[Third Party Services]
|
||||
|
||||
LASTFM_API_KEY = "ASK" # 'ASK' signifies that the user has not yet indicated to not use any key at all.
|
||||
LASTFM_API_SECRET = "ASK"
|
||||
FANARTTV_API_KEY = "ASK"
|
||||
SPOTIFY_API_ID = "ASK"
|
||||
SPOTIFY_API_SECRET = "ASK"
|
||||
CACHE_EXPIRE_NEGATIVE = 30 # after how many days negative results should be tried again
|
||||
CACHE_EXPIRE_POSITIVE = 300 # after how many days positive results should be refreshed
|
||||
|
||||
# Can be 'YouTube', 'YouTube Music', 'Google Play Music', 'Spotify', 'Tidal', 'SoundCloud', 'Deezer', 'Amazon Music'
|
||||
# Omit or set to none to disable
|
||||
TRACK_SEARCH_PROVIDER = None
|
||||
|
||||
[Database]
|
||||
|
||||
DB_CACHE_SIZE = 8192 # how many MB on disk each database cache should have available.
|
||||
INVALID_ARTISTS = ["[Unknown Artist]","Unknown Artist"]
|
||||
|
||||
[Local Images]
|
||||
|
||||
@ -27,8 +38,11 @@ LOCAL_IMAGE_ROTATE = 3600 # when multiple images are present locally, how many s
|
||||
DEFAULT_RANGE_CHARTS_ARTISTS = year
|
||||
DEFAULT_RANGE_CHARTS_TRACKS = year
|
||||
# same for pulse view
|
||||
# can be days, weeks, months, years
|
||||
DEFAULT_RANGE_PULSE = months
|
||||
# can be day, week, month, year
|
||||
DEFAULT_STEP_PULSE = month
|
||||
|
||||
# display top tiles on artist and track chart pages
|
||||
CHARTS_DISPLAY_TILES = false
|
||||
|
||||
[Fluff]
|
||||
|
||||
@ -36,6 +50,8 @@ DEFAULT_RANGE_PULSE = months
|
||||
SCROBBLES_GOLD = 250
|
||||
SCROBBLES_PLATINUM = 500
|
||||
SCROBBLES_DIAMOND = 1000
|
||||
# name for comparisons
|
||||
NAME = "Generic Maloja User"
|
||||
|
||||
[Misc]
|
||||
|
||||
|
25
supervisor.py
Normal file
25
supervisor.py
Normal file
@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import subprocess
|
||||
import time
|
||||
import setproctitle
|
||||
import signal
|
||||
from doreah.logging import log
|
||||
|
||||
|
||||
setproctitle.setproctitle("maloja_supervisor")
|
||||
|
||||
|
||||
while True:
|
||||
time.sleep(60)
|
||||
|
||||
try:
|
||||
output = subprocess.check_output(["pidof","Maloja"])
|
||||
pid = int(output)
|
||||
log("Maloja is running, PID " + str(pid),module="supervisor")
|
||||
except:
|
||||
log("Maloja is not running, restarting...",module="supervisor")
|
||||
try:
|
||||
p = subprocess.Popen(["python3","server.py"],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL)
|
||||
except e:
|
||||
log("Error starting Maloja: " + str(e),module="supervisor")
|
2
update_requirements.sh
Normal file
2
update_requirements.sh
Normal file
@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
pip3 install -r requirements.txt --upgrade --no-cache-dir
|
@ -103,8 +103,12 @@ def uri_to_internal(keys,forceTrack=False,forceArtist=False):
|
||||
|
||||
|
||||
#4
|
||||
resultkeys4 = {"max_":300}
|
||||
if "max" in keys: resultkeys4["max_"] = int(keys["max"])
|
||||
resultkeys4 = {"page":0,"perpage":100}
|
||||
# if "max" in keys: resultkeys4["max_"] = int(keys["max"])
|
||||
if "max" in keys: resultkeys4["page"],resultkeys4["perpage"] = 0, int(keys["max"])
|
||||
#different max than the internal one! the user doesn't get to disable pagination
|
||||
if "page" in keys: resultkeys4["page"] = int(keys["page"])
|
||||
if "perpage" in keys: resultkeys4["perpage"] = int(keys["perpage"])
|
||||
|
||||
|
||||
return resultkeys1, resultkeys2, resultkeys3, resultkeys4
|
||||
@ -146,8 +150,12 @@ def internal_to_uri(keys):
|
||||
urikeys.append("trail",str(keys["trail"]))
|
||||
|
||||
# stuff
|
||||
if "max_" in keys:
|
||||
urikeys.append("max",str(keys["max_"]))
|
||||
#if "max_" in keys:
|
||||
# urikeys.append("max",str(keys["max_"]))
|
||||
if "page" in keys:
|
||||
urikeys.append("page",str(keys["page"]))
|
||||
if "perpage" in keys:
|
||||
urikeys.append("perpage",str(keys["perpage"]))
|
||||
|
||||
|
||||
return urikeys
|
||||
|
35
utilities.py
35
utilities.py
@ -99,14 +99,11 @@ def consistentRulestate(folder,checksums):
|
||||
if (scrobblefile.endswith(".tsv")):
|
||||
|
||||
try:
|
||||
f = open(folder + "/" + scrobblefile + ".rulestate","r")
|
||||
if f.read() != checksums:
|
||||
return False
|
||||
|
||||
with open(folder + "/" + scrobblefile + ".rulestate","r") as f:
|
||||
if f.read() != checksums:
|
||||
return False
|
||||
except:
|
||||
return False
|
||||
finally:
|
||||
f.close()
|
||||
|
||||
return True
|
||||
|
||||
@ -442,8 +439,12 @@ def update_medals():
|
||||
|
||||
from database import MEDALS, MEDALS_TRACKS, STAMPS, get_charts_artists, get_charts_tracks
|
||||
|
||||
firstyear = datetime.datetime.utcfromtimestamp(STAMPS[0]).year
|
||||
currentyear = datetime.datetime.utcnow().year
|
||||
try:
|
||||
firstyear = datetime.datetime.utcfromtimestamp(STAMPS[0]).year
|
||||
except:
|
||||
firstyear = currentyear
|
||||
|
||||
|
||||
MEDALS.clear()
|
||||
for year in range(firstyear,currentyear):
|
||||
@ -468,3 +469,23 @@ def update_medals():
|
||||
elif t["rank"] == 2: MEDALS_TRACKS.setdefault(track,{}).setdefault("silver",[]).append(year)
|
||||
elif t["rank"] == 3: MEDALS_TRACKS.setdefault(track,{}).setdefault("bronze",[]).append(year)
|
||||
else: break
|
||||
|
||||
@daily
|
||||
def update_weekly():
|
||||
|
||||
from database import WEEKLY_TOPTRACKS, WEEKLY_TOPARTISTS, get_charts_artists, get_charts_tracks
|
||||
from malojatime import ranges, thisweek
|
||||
|
||||
|
||||
WEEKLY_TOPARTISTS.clear()
|
||||
WEEKLY_TOPTRACKS.clear()
|
||||
|
||||
for week in ranges(step="week"):
|
||||
if week == thisweek(): break
|
||||
for a in get_charts_artists(timerange=week):
|
||||
artist = a["artist"]
|
||||
if a["rank"] == 1: WEEKLY_TOPARTISTS[artist] = WEEKLY_TOPARTISTS.setdefault(artist,0) + 1
|
||||
|
||||
for t in get_charts_tracks(timerange=week):
|
||||
track = (frozenset(t["track"]["artists"]),t["track"]["title"])
|
||||
if t["rank"] == 1: WEEKLY_TOPTRACKS[track] = WEEKLY_TOPTRACKS.setdefault(track,0) + 1
|
||||
|
@ -4,7 +4,8 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Maloja - KEY_ARTISTNAME</title>
|
||||
<script src="javascript/rangeselect.js" async></script>
|
||||
<script src="javascript/cookies.js"></script>
|
||||
<script src="javascript/rangeselect.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@ -15,13 +16,13 @@
|
||||
</td>
|
||||
<td class="text">
|
||||
<h1>KEY_ARTISTNAME</h1>
|
||||
<span class="rank"><a href="/charts_artists?max=100">KEY_POSITION</a></span>
|
||||
<span class="rank"><a href="/charts_artists">KEY_POSITION</a></span>
|
||||
<br/>
|
||||
<span>KEY_ASSOCIATED</span>
|
||||
<p class="stats"><a href="/scrobbles?artist=KEY_ENC_ARTISTNAME">KEY_SCROBBLES Scrobbles</a></p>
|
||||
|
||||
<p class="desc">KEY_DESCRIPTION</p>
|
||||
<span>KEY_MEDALS</span>
|
||||
<span>KEY_MEDALS</span> <span>KEY_TOPWEEKS</span> <span>KEY_CERTS</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@ -32,35 +33,35 @@
|
||||
<table class="twopart">
|
||||
<tr>
|
||||
<td>
|
||||
<h2><a href='/pulse?artist=KEY_ENC_ARTISTNAME&step=year&trail=1'>Pulse</a></h2>
|
||||
<h2><a class="stat_link_pulse" href='/pulse?artist=KEY_ENC_ARTISTNAME&trail=1&step=month'>Pulse</a></h2>
|
||||
|
||||
|
||||
<span onclick="showRange('pulse','days')" class="stat_selector_pulse selector_pulse_days">7 days</span>
|
||||
| <span onclick="showRange('pulse','weeks')" class="stat_selector_pulse selector_pulse_weeks">12 weeks</span>
|
||||
| <span onclick="showRange('pulse','months')" class="stat_selector_pulse selector_pulse_months" style="opacity:0.5;">12 months</span>
|
||||
| <span onclick="showRange('pulse','years')" class="stat_selector_pulse selector_pulse_years">10 years</span>
|
||||
<span onclick="showRangeManual('pulse','day')" class="stat_selector_pulse selector_pulse_day">7 days</span>
|
||||
| <span onclick="showRangeManual('pulse','week')" class="stat_selector_pulse selector_pulse_week">12 weeks</span>
|
||||
| <span onclick="showRangeManual('pulse','month')" class="stat_selector_pulse selector_pulse_month" style="opacity:0.5;">12 months</span>
|
||||
| <span onclick="showRangeManual('pulse','year')" class="stat_selector_pulse selector_pulse_year">10 years</span>
|
||||
|
||||
<br/><br/>
|
||||
|
||||
<span class="stat_module_pulse pulse_months">KEY_PULSE_MONTHS</span>
|
||||
<span class="stat_module_pulse pulse_days" style="display:none;">KEY_PULSE_DAYS</span>
|
||||
<span class="stat_module_pulse pulse_years" style="display:none;">KEY_PULSE_YEARS</span>
|
||||
<span class="stat_module_pulse pulse_weeks" style="display:none;">KEY_PULSE_WEEKS</span>
|
||||
<span class="stat_module_pulse pulse_month">KEY_PULSE_MONTHS</span>
|
||||
<span class="stat_module_pulse pulse_day" style="display:none;">KEY_PULSE_DAYS</span>
|
||||
<span class="stat_module_pulse pulse_year" style="display:none;">KEY_PULSE_YEARS</span>
|
||||
<span class="stat_module_pulse pulse_week" style="display:none;">KEY_PULSE_WEEKS</span>
|
||||
</td>
|
||||
<td>
|
||||
<!-- We use the same classes / function calls here because we want it to switch together with pulse -->
|
||||
<h2><a href='/performance?artist=KEY_ENC_CREDITEDARTISTNAME&step=year&trail=1'>Performance</a></h2>
|
||||
<span onclick="showRange('pulse','days')" class="stat_selector_pulse selector_pulse_days">7 days</span>
|
||||
| <span onclick="showRange('pulse','weeks')" class="stat_selector_pulse selector_pulse_weeks">12 weeks</span>
|
||||
| <span onclick="showRange('pulse','months')" class="stat_selector_pulse selector_pulse_months" style="opacity:0.5;">12 months</span>
|
||||
| <span onclick="showRange('pulse','years')" class="stat_selector_pulse selector_pulse_years">10 years</span>
|
||||
<h2><a class="stat_link_pulse" href='/performance?artist=KEY_ENC_CREDITEDARTISTNAME&trail=1&step=month'>Performance</a></h2>
|
||||
<span onclick="showRangeManual('pulse','day')" class="stat_selector_pulse selector_pulse_day">7 days</span>
|
||||
| <span onclick="showRangeManual('pulse','week')" class="stat_selector_pulse selector_pulse_week">12 weeks</span>
|
||||
| <span onclick="showRangeManual('pulse','month')" class="stat_selector_pulse selector_pulse_month" style="opacity:0.5;">12 months</span>
|
||||
| <span onclick="showRangeManual('pulse','year')" class="stat_selector_pulse selector_pulse_year">10 years</span>
|
||||
|
||||
<br/><br/>
|
||||
|
||||
<span class="stat_module_pulse pulse_months">KEY_PERFORMANCE_MONTHS</span>
|
||||
<span class="stat_module_pulse pulse_days" style="display:none;">KEY_PERFORMANCE_DAYS</span>
|
||||
<span class="stat_module_pulse pulse_years" style="display:none;">KEY_PERFORMANCE_YEARS</span>
|
||||
<span class="stat_module_pulse pulse_weeks" style="display:none;">KEY_PERFORMANCE_WEEKS</span>
|
||||
<span class="stat_module_pulse pulse_month">KEY_PERFORMANCE_MONTHS</span>
|
||||
<span class="stat_module_pulse pulse_day" style="display:none;">KEY_PERFORMANCE_DAYS</span>
|
||||
<span class="stat_module_pulse pulse_year" style="display:none;">KEY_PERFORMANCE_YEARS</span>
|
||||
<span class="stat_module_pulse pulse_week" style="display:none;">KEY_PERFORMANCE_WEEKS</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
@ -5,7 +5,7 @@ from malojatime import today,thisweek,thismonth,thisyear
|
||||
|
||||
def instructions(keys):
|
||||
from utilities import getArtistImage
|
||||
from htmlgenerators import artistLink, artistLinks
|
||||
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
|
||||
|
||||
@ -22,13 +22,31 @@ def instructions(keys):
|
||||
if "medals" in data and data["medals"] is not None:
|
||||
if "gold" in data["medals"]:
|
||||
for y in data["medals"]["gold"]:
|
||||
html_medals += "<a title='Best Artist in " + str(y) + "' class='hidelink medal shiny gold' href='/charts_artists?max=50&in=" + str(y) + "'><span>" + str(y) + "</span></a>"
|
||||
html_medals += "<a title='Best Artist in " + str(y) + "' class='hidelink medal shiny gold' href='/charts_artists?in=" + str(y) + "'><span>" + str(y) + "</span></a>"
|
||||
if "silver" in data["medals"]:
|
||||
for y in data["medals"]["silver"]:
|
||||
html_medals += "<a title='Second Best Artist in " + str(y) + "' class='hidelink medal shiny silver' href='/charts_artists?max=50&in=" + str(y) + "'><span>" + str(y) + "</span></a>"
|
||||
html_medals += "<a title='Second Best Artist in " + str(y) + "' class='hidelink medal shiny silver' href='/charts_artists?in=" + str(y) + "'><span>" + str(y) + "</span></a>"
|
||||
if "bronze" in data["medals"]:
|
||||
for y in data["medals"]["bronze"]:
|
||||
html_medals += "<a title='Third Best Artist in " + str(y) + "' class='hidelink medal shiny bronze' href='/charts_artists?max=50&in=" + str(y) + "'><span>" + str(y) + "</span></a>"
|
||||
html_medals += "<a title='Third Best Artist in " + str(y) + "' class='hidelink medal shiny bronze' href='/charts_artists?in=" + str(y) + "'><span>" + str(y) + "</span></a>"
|
||||
|
||||
html_cert = ""
|
||||
for track in database.get_tracks(artist=artist):
|
||||
info = database.trackInfo(track)
|
||||
if info.get("certification") is not None:
|
||||
img = "/media/record_{cert}.png".format(cert=info["certification"])
|
||||
trackname = track["title"].replace("'","'")
|
||||
tracklink = link_address(track)
|
||||
tooltip = "{title} has reached {cert} status".format(title=trackname,cert=info["certification"].capitalize())
|
||||
html_cert += "<a href='{link}'><img class='certrecord_small' src='{img}' title='{tooltip}' /></a>".format(tooltip=tooltip,img=img,link=tracklink)
|
||||
|
||||
|
||||
html_topweeks = ""
|
||||
if data.get("topweeks") not in [0,None]:
|
||||
link = "/performance?artist=" + urllib.parse.quote(keys["artist"]) + "&trail=1&step=week"
|
||||
title = str(data["topweeks"]) + " weeks on #1"
|
||||
html_topweeks = "<a title='" + title + "' href='" + link + "'><img class='star' src='/media/star.png' />" + str(data["topweeks"]) + "</a>"
|
||||
|
||||
|
||||
credited = data.get("replace")
|
||||
includestr = " "
|
||||
@ -69,6 +87,8 @@ def instructions(keys):
|
||||
"KEY_POSITION":pos,
|
||||
"KEY_ASSOCIATED":includestr,
|
||||
"KEY_MEDALS":html_medals,
|
||||
"KEY_CERTS":html_cert,
|
||||
"KEY_TOPWEEKS":html_topweeks,
|
||||
# tracks
|
||||
"KEY_TRACKLIST":html_tracks,
|
||||
# pulse
|
||||
|
@ -11,7 +11,7 @@
|
||||
<table class="top_info">
|
||||
<tr>
|
||||
<td class="image">
|
||||
<div style="background-image:url('KEY_TOPARTIST_IMAGEURL')"></div>
|
||||
KEY_TOPARTIST_IMAGEDIV
|
||||
</td>
|
||||
<td class="text">
|
||||
<h1>Artist Charts</h1><a href="/top_artists"><span>View #1 Artists</span></a><br/>
|
||||
@ -24,6 +24,9 @@
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<span class="stat_module_topartists">
|
||||
KEY_ARTISTCHART
|
||||
</span>
|
||||
|
||||
KEY_ARTISTLIST
|
||||
|
||||
|
@ -4,8 +4,9 @@ import urllib
|
||||
def instructions(keys):
|
||||
from utilities import getArtistImage
|
||||
from urihandler import compose_querystring, uri_to_internal
|
||||
from htmlmodules import module_artistcharts, module_filterselection
|
||||
from htmlmodules import module_artistcharts, module_filterselection, module_artistcharts_tiles
|
||||
from malojatime import range_desc
|
||||
from doreah.settings import get_settings
|
||||
|
||||
|
||||
_, timekeys, _, amountkeys = uri_to_internal(keys)
|
||||
@ -16,6 +17,7 @@ def instructions(keys):
|
||||
|
||||
|
||||
|
||||
|
||||
html_charts, rep = module_artistcharts(**amountkeys,**timekeys)
|
||||
|
||||
if rep is not None:
|
||||
@ -23,12 +25,23 @@ def instructions(keys):
|
||||
else:
|
||||
imgurl = ""
|
||||
|
||||
html_tiles = ""
|
||||
if get_settings("CHARTS_DISPLAY_TILES"):
|
||||
html_tiles = module_artistcharts_tiles(timerange=timekeys["timerange"])
|
||||
imgurl = "favicon.png"
|
||||
|
||||
imgdiv = '<div style="background-image:url('+imgurl+')"></div>'
|
||||
|
||||
|
||||
pushresources = [{"file":imgurl,"type":"image"}] if imgurl.startswith("/") else []
|
||||
|
||||
|
||||
replace = {"KEY_TOPARTIST_IMAGEURL":imgurl,
|
||||
"KEY_ARTISTLIST":html_charts,
|
||||
"KEY_RANGE":limitstring,
|
||||
"KEY_FILTERSELECTOR":html_filterselector}
|
||||
replace = {
|
||||
"KEY_TOPARTIST_IMAGEDIV":imgdiv,
|
||||
"KEY_ARTISTCHART":html_tiles,
|
||||
"KEY_ARTISTLIST":html_charts,
|
||||
"KEY_RANGE":limitstring,
|
||||
"KEY_FILTERSELECTOR":html_filterselector
|
||||
}
|
||||
|
||||
return (replace,pushresources)
|
||||
|
@ -10,7 +10,7 @@
|
||||
<table class="top_info">
|
||||
<tr>
|
||||
<td class="image">
|
||||
<div style="background-image:url('KEY_TOPARTIST_IMAGEURL')"></div>
|
||||
KEY_TOPARTIST_IMAGEDIV
|
||||
</td>
|
||||
<td class="text">
|
||||
<h1>Track Charts</h1>TOP_TRACKS_LINK<br/>
|
||||
@ -22,6 +22,9 @@
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<span class="stat_module_topartists">
|
||||
KEY_TRACKCHART
|
||||
</span>
|
||||
|
||||
KEY_TRACKLIST
|
||||
|
||||
|
@ -5,8 +5,9 @@ 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
|
||||
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)
|
||||
|
||||
@ -23,6 +24,9 @@ def instructions(keys):
|
||||
html_charts, rep = module_trackcharts(**amountkeys,**timekeys,**filterkeys)
|
||||
|
||||
|
||||
|
||||
html_tiles = ""
|
||||
|
||||
if filterkeys.get("artist") is not None:
|
||||
imgurl = getArtistImage(filterkeys.get("artist"))
|
||||
limitstring = "by " + artistLink(filterkeys.get("artist"))
|
||||
@ -31,6 +35,15 @@ def instructions(keys):
|
||||
else:
|
||||
imgurl = ""
|
||||
|
||||
html_tiles = ""
|
||||
if get_settings("CHARTS_DISPLAY_TILES"):
|
||||
html_tiles = module_trackcharts_tiles(timerange=timekeys["timerange"])
|
||||
imgurl = "favicon.png"
|
||||
|
||||
imgdiv = '<div style="background-image:url('+imgurl+')"></div>'
|
||||
|
||||
|
||||
|
||||
limitstring += " " + timekeys["timerange"].desc(prefix=True)
|
||||
|
||||
pushresources = [{"file":imgurl,"type":"image"}] if imgurl.startswith("/") else []
|
||||
@ -38,7 +51,8 @@ def instructions(keys):
|
||||
|
||||
|
||||
replace = {
|
||||
"KEY_TOPARTIST_IMAGEURL":imgurl,
|
||||
"KEY_TOPARTIST_IMAGEDIV":imgdiv,
|
||||
"KEY_TRACKCHART":html_tiles,
|
||||
"KEY_TRACKLIST":html_charts,
|
||||
"KEY_LIMITS":limitstring,
|
||||
"KEY_FILTERSELECTOR":html_filterselector,
|
||||
|
@ -1,3 +1,9 @@
|
||||
<meta name="description" content='Maloja is a self-hosted music scrobble server.' />
|
||||
<!--<link rel="stylesheet/less" href="/less/maloja.less" />
|
||||
<link rel="stylesheet/less" href="/less/grisons.less" />
|
||||
|
||||
<link rel="stylesheet" href="/css/maloja.css" />
|
||||
<script src="/javascript/search.js" async="yes"></script>
|
||||
<link rel="stylesheet" href="/css/grisons.css" />
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/less.js/3.9.0/less.min.js" ></script> -->
|
||||
<link rel="stylesheet" href="/css/style.css" />
|
||||
<script src="/javascript/search.js" async></script>
|
||||
|
78
website/compare.html
Normal file
78
website/compare.html
Normal file
@ -0,0 +1,78 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Maloja - Compare</title>
|
||||
<style>
|
||||
.comparecircle {
|
||||
height:500px;
|
||||
width:500px;
|
||||
border-radius:250px;
|
||||
border: 1px solid rgba(245,245,220,0.3);
|
||||
margin:auto;
|
||||
margin-top:100px;
|
||||
text-align:center;
|
||||
line-height:500px;
|
||||
font-size:60px;
|
||||
color:black;
|
||||
/* background-image: linear-gradient(to right,KEY_CIRCLE_CSS); */
|
||||
background-image: radial-gradient(#KEY_CICLE_COLOR KEY_FULLMATCHpx, transparent KEY_PARTIALMATCHpx);
|
||||
}
|
||||
|
||||
table tr td:first-child {
|
||||
text-align: left;
|
||||
padding:10px;
|
||||
width:33%;
|
||||
}
|
||||
table tr td {
|
||||
text-align: center;
|
||||
padding:10px;
|
||||
}
|
||||
|
||||
table tr td:last-child {
|
||||
text-align: right;
|
||||
padding:10px;
|
||||
width:33%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<table style="width:99%;">
|
||||
<tr>
|
||||
<td><h1>KEY_NAME_SELF</h1></td>
|
||||
<td>
|
||||
|
||||
<div class="comparecircle">
|
||||
KEY_MATCH%
|
||||
|
||||
</div>
|
||||
</td>
|
||||
<td><h1>KEY_NAME_OTHER</h1></td>
|
||||
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td style="font-size:70%;color:grey;">
|
||||
The size of the circle shows matching music taste.
|
||||
The fuzziness of its border indicates differences in quantity.
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<span>Common Favorite</span>
|
||||
<h2 style="margin:7px;">KEY_BESTARTIST_LINK</h2>
|
||||
<img src="KEY_BESTARTIST_IMAGE" style="width:80px;" />
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
88
website/compare.py
Normal file
88
website/compare.py
Normal file
@ -0,0 +1,88 @@
|
||||
import urllib
|
||||
import database
|
||||
import json
|
||||
from htmlgenerators import artistLink
|
||||
from utilities import getArtistImage
|
||||
|
||||
|
||||
def instructions(keys):
|
||||
|
||||
compareto = keys.get("to")
|
||||
compareurl = compareto + "/api/info"
|
||||
|
||||
response = urllib.request.urlopen(compareurl)
|
||||
strangerinfo = json.loads(response.read())
|
||||
|
||||
owninfo = database.info()
|
||||
|
||||
artists = {}
|
||||
|
||||
for a in owninfo["artists"]:
|
||||
artists[a.lower()] = {"name":a,"self":int(owninfo["artists"][a]*1000),"other":0}
|
||||
|
||||
for a in strangerinfo["artists"]:
|
||||
artists[a.lower()] = artists.setdefault(a.lower(),{"name":a,"self":0})
|
||||
artists[a.lower()]["other"] = int(strangerinfo["artists"][a]*1000)
|
||||
|
||||
for a in artists:
|
||||
common = min(artists[a]["self"],artists[a]["other"])
|
||||
artists[a]["self"] -= common
|
||||
artists[a]["other"] -= common
|
||||
artists[a]["common"] = common
|
||||
|
||||
best = sorted((artists[a]["name"] for a in artists),key=lambda x: artists[x.lower()]["common"],reverse=True)
|
||||
|
||||
result = {
|
||||
"unique_self":sum(artists[a]["self"] for a in artists if artists[a]["common"] == 0),
|
||||
"more_self":sum(artists[a]["self"] for a in artists if artists[a]["common"] != 0),
|
||||
# "common":{
|
||||
# **{
|
||||
# artists[a]["name"]:artists[a]["common"]
|
||||
# for a in best[:3]},
|
||||
# None: sum(artists[a]["common"] for a in artists if a not in best[:3])
|
||||
# },
|
||||
"common":sum(artists[a]["common"] for a in artists),
|
||||
"more_other":sum(artists[a]["other"] for a in artists if artists[a]["common"] != 0),
|
||||
"unique_other":sum(artists[a]["other"] for a in artists if artists[a]["common"] == 0)
|
||||
}
|
||||
|
||||
total = sum(result[c] for c in result)
|
||||
|
||||
percentages = {c:result[c]*100/total for c in result}
|
||||
css = []
|
||||
|
||||
cumulative = 0
|
||||
for color,category in [
|
||||
("rgba(255,255,255,0.2)","unique_self"),
|
||||
("rgba(255,255,255,0.5)","more_self"),
|
||||
("white","common"),
|
||||
("rgba(255,255,255,0.5)","more_other"),
|
||||
("rgba(255,255,255,0.2)","unique_other")]:
|
||||
cumulative += percentages[category]
|
||||
css.append(color + " " + str(cumulative) + "%")
|
||||
|
||||
|
||||
fullmatch = percentages["common"]
|
||||
partialmatch = percentages["more_self"] + percentages["more_other"]
|
||||
|
||||
match = fullmatch + (partialmatch)/2
|
||||
pixel_fullmatch = fullmatch * 2.5
|
||||
pixel_partialmatch = (fullmatch+partialmatch) * 2.5
|
||||
|
||||
match = min(match,100)
|
||||
|
||||
|
||||
matchcolor = format(int(min(1,match/50)*255),"02x") * 2 + format(int(max(0,match/50-1)*255),"02x")
|
||||
|
||||
|
||||
return {
|
||||
"KEY_CIRCLE_CSS":",".join(css),
|
||||
"KEY_CICLE_COLOR":matchcolor,
|
||||
"KEY_MATCH":str(round(match,2)),
|
||||
"KEY_FULLMATCH":str(int(pixel_fullmatch)),
|
||||
"KEY_PARTIALMATCH":str(int(pixel_partialmatch)),
|
||||
"KEY_NAME_SELF":owninfo["name"],
|
||||
"KEY_NAME_OTHER":strangerinfo["name"],
|
||||
"KEY_BESTARTIST_LINK":artistLink(best[0]),
|
||||
"KEY_BESTARTIST_IMAGE":getArtistImage(best[0])
|
||||
},[]
|
@ -7,7 +7,7 @@
|
||||
<script src="javascript/cookies.js"></script>
|
||||
</head>
|
||||
|
||||
<body onload="insertAPIKeyFromCookie()">
|
||||
<body>
|
||||
<table class="top_info">
|
||||
<tr>
|
||||
<td class="image">
|
||||
|
@ -1,32 +1,81 @@
|
||||
apikeycorrect = false;
|
||||
|
||||
function insertAPIKeyFromCookie() {
|
||||
cookies = decodeURIComponent(document.cookie).split(';');
|
||||
for(var i = 0; i <cookies.length; i++) {
|
||||
cookies[i] = cookies[i].trim()
|
||||
if (cookies[i].startsWith("apikey=")) {
|
||||
document.getElementById("apikey").value = cookies[i].replace("apikey=","")
|
||||
checkAPIkey()
|
||||
}
|
||||
var cookies = {};
|
||||
|
||||
function getCookies() {
|
||||
cookiestrings = decodeURIComponent(document.cookie).split(';');
|
||||
for(var i = 0; i <cookiestrings.length; i++) {
|
||||
cookiestrings[i] = cookiestrings[i].trim();
|
||||
[key,value] = cookiestrings[i].split("=");
|
||||
cookies[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// always on document load, but call specifically when needed early
|
||||
document.addEventListener("load",getCookies);
|
||||
|
||||
function setCookie(key,val,session=true) {
|
||||
cookies[key] = val;
|
||||
if (!session) {
|
||||
var d = new Date();
|
||||
d.setTime(d.getTime() + (500*24*60*60*1000));
|
||||
expirestr = "expires=" + d.toUTCString();
|
||||
}
|
||||
else {
|
||||
expirestr = ""
|
||||
}
|
||||
document.cookie = encodeURIComponent(key) + "=" + encodeURIComponent(val) + ";" + expirestr;
|
||||
}
|
||||
function saveCookies() {
|
||||
for (var c in cookies) {
|
||||
document.cookie = encodeURIComponent(c) + "=" + encodeURIComponent(cookies[c]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// RANGE SELECTORS
|
||||
|
||||
// in rangeselect.js
|
||||
|
||||
|
||||
/// API KEY
|
||||
|
||||
|
||||
|
||||
function insertAPIKeyFromCookie() {
|
||||
element = document.getElementById("apikey")
|
||||
if (element != null && element != undefined) {
|
||||
getCookies();
|
||||
key = cookies["apikey"];
|
||||
if (key != null && key != undefined) {
|
||||
element.value = key;
|
||||
checkAPIkey();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
window.addEventListener("load",insertAPIKeyFromCookie);
|
||||
|
||||
|
||||
function saveAPIkey() {
|
||||
key = document.getElementById("apikey").value
|
||||
document.cookie = "apikey=" + encodeURIComponent(key)
|
||||
key = APIkey();
|
||||
setCookie("apikey",key,false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function checkAPIkey() {
|
||||
saveAPIkey()
|
||||
url = "/api/test?key=" + document.getElementById("apikey").value
|
||||
|
||||
url = "/api/test?key=" + APIkey()
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function() {
|
||||
if (this.readyState == 4 && (this.status == 204 || this.status == 205)) {
|
||||
document.getElementById("apikey").style.backgroundColor = "lawngreen"
|
||||
apikeycorrect = true
|
||||
saveAPIkey();
|
||||
}
|
||||
else {
|
||||
document.getElementById("apikey").style.backgroundColor = "red"
|
||||
|
@ -1,28 +1,53 @@
|
||||
function showRange(identifier,unit) {
|
||||
// Make all modules disappear
|
||||
modules = document.getElementsByClassName("stat_module_" + identifier)
|
||||
modules = document.getElementsByClassName("stat_module_" + identifier);
|
||||
for (var i=0;i<modules.length;i++) {
|
||||
//modules[i].setAttribute("style","width:0px;overflow:hidden;")
|
||||
// cheesy trick to make the allocated space always whatever the biggest module needs
|
||||
// somehow that messes up pulse on the start page tho
|
||||
modules[i].setAttribute("style","display:none;")
|
||||
modules[i].setAttribute("style","display:none;");
|
||||
}
|
||||
|
||||
// Make requested module appear
|
||||
reactivate = document.getElementsByClassName(identifier + "_" + unit)
|
||||
reactivate = document.getElementsByClassName(identifier + "_" + unit);
|
||||
for (var i=0;i<reactivate.length;i++) {
|
||||
reactivate[i].setAttribute("style","")
|
||||
reactivate[i].setAttribute("style","");
|
||||
}
|
||||
|
||||
// Set all selectors to unselected
|
||||
selectors = document.getElementsByClassName("stat_selector_" + identifier)
|
||||
selectors = document.getElementsByClassName("stat_selector_" + identifier);
|
||||
for (var i=0;i<selectors.length;i++) {
|
||||
selectors[i].setAttribute("style","")
|
||||
selectors[i].setAttribute("style","");
|
||||
}
|
||||
|
||||
// Set the active selector to selected
|
||||
reactivate = document.getElementsByClassName("selector_" + identifier + "_" + unit)
|
||||
reactivate = document.getElementsByClassName("selector_" + identifier + "_" + unit);
|
||||
for (var i=0;i<reactivate.length;i++) {
|
||||
reactivate[i].setAttribute("style","opacity:0.5;")
|
||||
reactivate[i].setAttribute("style","opacity:0.5;");
|
||||
}
|
||||
|
||||
links = document.getElementsByClassName("stat_link_" + identifier);
|
||||
for (let l of links) {
|
||||
var a = l.href.split("=");
|
||||
a.splice(-1);
|
||||
a.push(unit);
|
||||
l.href = a.join("=");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function showRangeManual(identifier,unit) {
|
||||
showRange(identifier,unit);
|
||||
setCookie("rangeselect_" + identifier,unit);
|
||||
}
|
||||
|
||||
|
||||
|
||||
document.addEventListener('DOMContentLoaded',function() {
|
||||
getCookies();
|
||||
for (c in cookies) {
|
||||
if (c.startsWith("rangeselect_")) {
|
||||
showRange(c.slice(12),cookies[c]);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
134
website/less/grisons.less
Normal file
134
website/less/grisons.less
Normal file
@ -0,0 +1,134 @@
|
||||
/**
|
||||
COMMON STYLES FOR MALOJA, ALBULA AND POSSIBLY OTHERS
|
||||
**/
|
||||
|
||||
|
||||
@BASE_COLOR: #333337;
|
||||
@BASE_COLOR_DARK: #0a0a0a;
|
||||
@BASE_COLOR_LIGHT: #444447;
|
||||
|
||||
@TEXT_COLOR: beige;
|
||||
@TEXT_COLOR_SELECTED: fadeout(@TEXT_COLOR,40%);
|
||||
@TEXT_COLOR_SECONDARY: #bbb;
|
||||
@TEXT_COLOR_TERTIARY: grey;
|
||||
@FOCUS_COLOR: yellow;
|
||||
|
||||
@CONTROL_ELEMENT_BG_COLOR: rgba(0,255,255,0.1);
|
||||
@CONTROL_ELEMENT_FG_COLOR: rgba(103,85,0,0.7);
|
||||
@CONTROL_ELEMENT_FOCUS_COLOR: gold;
|
||||
|
||||
@BUTTON_BG_COLOR: @TEXT_COLOR;
|
||||
@BUTTON_FOCUS_BG_COLOR: @FOCUS_COLOR;
|
||||
@BUTTON_FG_COLOR: @BASE_COLOR;
|
||||
@BUTTON_FOCUS_FG_COLOR: @BASE_COLOR;
|
||||
|
||||
|
||||
//@import url('https://fonts.googleapis.com/css?family=Ubuntu');
|
||||
|
||||
|
||||
body {
|
||||
background-color: @BASE_COLOR;
|
||||
color: @TEXT_COLOR;
|
||||
font-family:"Ubuntu";
|
||||
}
|
||||
|
||||
/* TOP INFO TABLE */
|
||||
|
||||
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%);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/** SCROLLBAR **/
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: grey;
|
||||
background-color: @CONTROL_ELEMENT_BG_COLOR;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: @CONTROL_ELEMENT_FG_COLOR;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: @CONTROL_ELEMENT_FOCUS_COLOR;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
[onclick]:hover, a:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
/** HOVERABLE LOAD/PROGRESS BAR **/
|
||||
|
||||
|
||||
div.grisons_bar {
|
||||
background-color: @CONTROL_ELEMENT_BG_COLOR;
|
||||
}
|
||||
div.grisons_bar>div {
|
||||
height:100%;
|
||||
background-color: @CONTROL_ELEMENT_FG_COLOR;
|
||||
}
|
||||
div.grisons_bar:hover>div {
|
||||
background-color: @CONTROL_ELEMENT_FOCUS_COLOR;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/** LINKS **/
|
||||
|
||||
|
||||
a {
|
||||
color:inherit;
|
||||
text-decoration:none;
|
||||
}
|
||||
|
||||
// for links in running text
|
||||
a.textlink {
|
||||
color:@FOCUS_COLOR;
|
||||
}
|
||||
a.hidelink:hover {
|
||||
text-decoration:none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration:underline;
|
||||
}
|
45
website/less/grisonsfont.less
Normal file
45
website/less/grisonsfont.less
Normal file
@ -0,0 +1,45 @@
|
||||
/* cyrillic-ext */
|
||||
@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');
|
||||
}
|
||||
/* cyrillic */
|
||||
@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+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@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+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@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;
|
||||
}
|
||||
/* latin-ext */
|
||||
@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');
|
||||
}
|
||||
/* latin */
|
||||
@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');
|
||||
}
|
@ -1,9 +1,6 @@
|
||||
@import url('https://fonts.googleapis.com/css?family=Ubuntu');
|
||||
@import "website/less/grisons";
|
||||
|
||||
body {
|
||||
background-color:#333337;
|
||||
color:beige;
|
||||
font-family:"Ubuntu";
|
||||
padding:15px;
|
||||
padding-bottom:35px;
|
||||
/**
|
||||
@ -15,21 +12,6 @@ body {
|
||||
*/
|
||||
}
|
||||
|
||||
a {
|
||||
color:inherit;
|
||||
text-decoration:none;
|
||||
}
|
||||
|
||||
a.textlink {
|
||||
color:gold;
|
||||
}
|
||||
a.hidelink:hover {
|
||||
text-decoration:none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration:underline;
|
||||
}
|
||||
|
||||
|
||||
input[type="date"] {
|
||||
@ -42,6 +24,9 @@ input[type="date"] {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Header (unused)
|
||||
**/
|
||||
@ -76,7 +61,7 @@ div.footer {
|
||||
position:fixed;
|
||||
height:20px;
|
||||
/**width:100%;**/
|
||||
background-color:rgba(10,10,10,0.9);
|
||||
background-color:@BASE_COLOR_DARK;
|
||||
bottom:0px;
|
||||
left:0px;
|
||||
right:0px;
|
||||
@ -185,7 +170,7 @@ div.searchresults table.searchresults_tracks td span:nth-child(1) {
|
||||
position:fixed;
|
||||
/*height:30px;*/
|
||||
/**width:100%;**/
|
||||
background-color:rgba(10,10,10,0.9);
|
||||
background-color:@BASE_COLOR_DARK;
|
||||
bottom:0px;
|
||||
left:0px;
|
||||
right:0px;
|
||||
@ -218,36 +203,6 @@ div.searchresults table.searchresults_tracks td span:nth-child(1) {
|
||||
|
||||
|
||||
|
||||
/*
|
||||
**
|
||||
**
|
||||
** TOP INFO TABLE
|
||||
**
|
||||
**
|
||||
*/
|
||||
|
||||
table.top_info td.image {
|
||||
padding:20px;
|
||||
padding-left:0px;
|
||||
padding-top:0px;
|
||||
}
|
||||
|
||||
table.top_info td.image div {
|
||||
background-size:cover;
|
||||
background-position:center;
|
||||
height:174px;
|
||||
width:174px
|
||||
}
|
||||
|
||||
table.top_info td.text {
|
||||
vertical-align: top;
|
||||
padding-left: 30px;
|
||||
}
|
||||
|
||||
table.top_info td.text h1 {
|
||||
display:inline;
|
||||
padding-right:5px;
|
||||
}
|
||||
|
||||
p.desc a {
|
||||
padding-left:20px;
|
||||
@ -257,7 +212,10 @@ p.desc a {
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
**
|
||||
@ -268,17 +226,22 @@ p.desc a {
|
||||
*/
|
||||
|
||||
|
||||
.paginate {
|
||||
text-align: center;
|
||||
padding:30px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.stats {
|
||||
color:grey;
|
||||
color:@TEXT_COLOR_TERTIARY;
|
||||
}
|
||||
.rank {
|
||||
text-align:right;
|
||||
color:grey;
|
||||
color:@TEXT_COLOR_TERTIARY;
|
||||
}
|
||||
.extra {
|
||||
color:gray; /*sue me*/
|
||||
color:@TEXT_COLOR_TERTIARY;
|
||||
font-size:80%;
|
||||
}
|
||||
|
||||
@ -292,14 +255,14 @@ input#apikey {
|
||||
|
||||
input.simpleinput {
|
||||
font-family:'Ubuntu';
|
||||
color:beige;
|
||||
color:@TEXT_COLOR;
|
||||
outline:none;
|
||||
border-top: 0px solid;
|
||||
border-left: 0px solid;
|
||||
border-right: 0px solid;
|
||||
padding:2px;
|
||||
background-color:inherit;
|
||||
border-bottom: 1px solid beige;
|
||||
border-bottom: 1px solid @TEXT_COLOR;
|
||||
}
|
||||
|
||||
|
||||
@ -379,6 +342,15 @@ 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;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
@ -430,7 +402,7 @@ table.list tr:hover {
|
||||
|
||||
table.list td.time {
|
||||
width:11%;
|
||||
color:gray;
|
||||
color:@TEXT_COLOR_TERTIARY;
|
||||
}
|
||||
|
||||
|
||||
@ -476,10 +448,13 @@ table.list td.artists,td.artist,td.title,td.track {
|
||||
}
|
||||
|
||||
table.list td.track span.artist_in_trackcolumn {
|
||||
color:#bbb;
|
||||
color:@TEXT_COLOR_SECONDARY;
|
||||
}
|
||||
|
||||
|
||||
table.list td.track a.trackProviderSearch {
|
||||
margin-right: 5px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -519,23 +494,23 @@ table.list td.amount {
|
||||
}
|
||||
table.list td.bar {
|
||||
width:500px;
|
||||
background-color:#333337;
|
||||
background-color:@BASE_COLOR;
|
||||
/* Remove 5er separators for bars */
|
||||
/*border-color:rgba(0,0,0,0)!important;*/
|
||||
}
|
||||
table.list td.bar div {
|
||||
background-color:beige;
|
||||
background-color:@TEXT_COLOR;
|
||||
height:20px; /* can only do this absolute apparently */
|
||||
position:relative;
|
||||
}
|
||||
table.list tr:hover td.bar div {
|
||||
background-color:yellow;
|
||||
background-color:@FOCUS_COLOR;
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
table.list td.chart {
|
||||
width:500px;
|
||||
background-color:#333337;
|
||||
background-color:@BASE_COLOR;
|
||||
/* Remove 5er separators for bars */
|
||||
/*border-color:rgba(0,0,0,0)!important;*/
|
||||
}
|
||||
@ -581,8 +556,14 @@ table.list tr td.button {
|
||||
|
||||
|
||||
table.list td.button div {
|
||||
background-color:yellow;
|
||||
color:#333337;
|
||||
background-color:@BUTTON_BG_COLOR;
|
||||
color:@BUTTON_FG_COLOR;
|
||||
padding:3px;
|
||||
border-radius:4px;
|
||||
}
|
||||
table.list td.button div:hover {
|
||||
background-color:@BUTTON_FOCUS_BG_COLOR;
|
||||
color:@BUTTON_FOCUS_FG_COLOR;
|
||||
padding:3px;
|
||||
border-radius:4px;
|
||||
}
|
@ -154,7 +154,7 @@
|
||||
|
||||
|
||||
|
||||
<body onload="insertAPIKeyFromCookie()">
|
||||
<body>
|
||||
|
||||
<table class="top_info">
|
||||
<tr>
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 55 KiB |
BIN
website/media/record_gold_original.png
Normal file
BIN
website/media/record_gold_original.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 58 KiB |
BIN
website/media/star.png
Normal file
BIN
website/media/star.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.9 KiB |
BIN
website/media/star_alt.png
Normal file
BIN
website/media/star_alt.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.2 KiB |
@ -9,10 +9,10 @@ def instructions(keys):
|
||||
from htmlmodules import module_performance, module_filterselection
|
||||
from malojatime import range_desc, delimit_desc
|
||||
|
||||
filterkeys, timekeys, delimitkeys, _ = uri_to_internal(keys)
|
||||
filterkeys, timekeys, delimitkeys, paginatekeys = uri_to_internal(keys)
|
||||
|
||||
#equivalent pulse chart
|
||||
pulselink_keys = internal_to_uri({**filterkeys,**timekeys,**delimitkeys})
|
||||
pulselink_keys = internal_to_uri({**filterkeys,**timekeys,**delimitkeys,**paginatekeys})
|
||||
pulselink = "/pulse?" + compose_querystring(pulselink_keys)
|
||||
|
||||
pulselink = "<a href=\"" + pulselink + "\"><span>View Pulse</span></a>"
|
||||
@ -54,7 +54,7 @@ def instructions(keys):
|
||||
|
||||
|
||||
|
||||
html_performance = module_performance(**filterkeys,**timekeys,**delimitkeys)
|
||||
html_performance = module_performance(**filterkeys,**timekeys,**delimitkeys,**paginatekeys)
|
||||
|
||||
replace = {
|
||||
"KEY_PULSE_LINK":pulselink,
|
||||
|
46
website/proxy.html
Normal file
46
website/proxy.html
Normal file
@ -0,0 +1,46 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Maloja - Proxyscrobble</title>
|
||||
<script src="javascript/cookies.js"></script>
|
||||
|
||||
<script>
|
||||
window.addEventListener("load",function(){
|
||||
try {
|
||||
document.getElementById("lastfmlink").href += window.location.href;
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<table class="top_info">
|
||||
<tr>
|
||||
<td class="image">
|
||||
<div style="background-image:url('/favicon.png')"></div>
|
||||
</td>
|
||||
<td class="text">
|
||||
<h1>Proxyscrobble</h1>
|
||||
|
||||
<p class="desc">Duplicate your scrobbles to another service.
|
||||
Your API key is required to make any changes to the server: <input id='apikey' onchange='checkAPIkey()' style='width:300px;'/></p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table class="list">
|
||||
<tr>
|
||||
<td>Last.fm</td>
|
||||
KEY_STATUS_LASTFM
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
53
website/proxy.py
Normal file
53
website/proxy.py
Normal file
@ -0,0 +1,53 @@
|
||||
from doreah.settings import get_settings, update_settings
|
||||
import urllib.request
|
||||
import hashlib
|
||||
import xml.etree.ElementTree as ET
|
||||
from bottle import redirect, request
|
||||
from database import checkAPIkey
|
||||
from external import lfmbuild
|
||||
|
||||
def instructions(keys):
|
||||
authenticated = False
|
||||
if "Cookie" in request.headers:
|
||||
cookies = request.headers["Cookie"].split(";")
|
||||
for c in cookies:
|
||||
if c.strip().startswith("apikey="):
|
||||
authenticated = checkAPIkey(c.strip()[7:])
|
||||
|
||||
if "token" in keys and authenticated:
|
||||
token = keys.get("token")
|
||||
parameters = {
|
||||
"method":"auth.getSession",
|
||||
"token":token,
|
||||
"api_key":get_settings("LASTFM_API_KEY")
|
||||
}
|
||||
response = urllib.request.urlopen("http://ws.audioscrobbler.com/2.0/?" + lfmbuild(parameters))
|
||||
xml = response.read()
|
||||
data = ET.fromstring(xml)
|
||||
if data.attrib.get("status") == "ok":
|
||||
username = data.find("session").find("name").text
|
||||
sessionkey = data.find("session").find("key").text
|
||||
|
||||
update_settings("settings/settings.ini",{"LASTFM_API_SK":sessionkey,"LASTFM_USERNAME":username},create_new=True)
|
||||
|
||||
return "/proxy"
|
||||
|
||||
else:
|
||||
key,secret,sessionkey,name = get_settings("LASTFM_API_KEY","LASTFM_API_SECRET","LASTFM_API_SK","LASTFM_USERNAME")
|
||||
|
||||
if key is None:
|
||||
lastfm = "<td>No Last.fm key provided</td>"
|
||||
elif secret is None:
|
||||
lastfm = "<td>No Last.fm secret provided</td>"
|
||||
elif sessionkey is None and authenticated:
|
||||
url = "http://www.last.fm/api/auth/?api_key=" + key + "&cb="
|
||||
lastfm = "<td class='button'><a id='lastfmlink' href='" + url + "'><div>Connect</div></a></td>"
|
||||
elif sessionkey is None:
|
||||
lastfm = "<td>Not active</td>"
|
||||
else:
|
||||
|
||||
lastfm = "<td>Account: " + name + "</td>"
|
||||
|
||||
|
||||
|
||||
return {"KEY_STATUS_LASTFM":lastfm},[]
|
@ -9,11 +9,11 @@ def instructions(keys):
|
||||
from htmlmodules import module_pulse, module_filterselection
|
||||
from malojatime import range_desc, delimit_desc
|
||||
|
||||
filterkeys, timekeys, delimitkeys, _ = uri_to_internal(keys)
|
||||
filterkeys, timekeys, delimitkeys, paginatekeys = uri_to_internal(keys)
|
||||
|
||||
#equivalent performance chart if we're not looking at the overall pulse
|
||||
if len(filterkeys) != 0:
|
||||
performancelink_keys = internal_to_uri({**filterkeys,**timekeys,**delimitkeys})
|
||||
performancelink_keys = internal_to_uri({**filterkeys,**timekeys,**delimitkeys,**paginatekeys})
|
||||
performancelink = "/performance?" + compose_querystring(performancelink_keys)
|
||||
|
||||
performancelink = "<a href=\"" + performancelink + "\"><span>View Rankings</span></a>"
|
||||
@ -57,7 +57,7 @@ def instructions(keys):
|
||||
|
||||
|
||||
|
||||
html_pulse = module_pulse(**filterkeys,**timekeys,**delimitkeys)
|
||||
html_pulse = module_pulse(**filterkeys,**timekeys,**delimitkeys,**paginatekeys)
|
||||
|
||||
replace = {
|
||||
"KEY_RANKINGS_LINK":performancelink,
|
||||
|
@ -58,7 +58,7 @@
|
||||
|
||||
</head>
|
||||
|
||||
<body onload="replace();insertAPIKeyFromCookie()">
|
||||
<body onload="replace()">
|
||||
<table class="top_info">
|
||||
<tr>
|
||||
<td class="image">
|
||||
|
@ -4,10 +4,12 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Maloja</title>
|
||||
<script src="javascript/rangeselect.js"></script>
|
||||
|
||||
<script>document.addEventListener('DOMContentLoaded',function() {
|
||||
KEY_JS_INIT_RANGES
|
||||
})</script>
|
||||
<script src="javascript/cookies.js"></script>
|
||||
<script src="javascript/rangeselect.js"></script>
|
||||
</head>
|
||||
|
||||
|
||||
@ -18,15 +20,15 @@
|
||||
</div>-->
|
||||
|
||||
|
||||
<h1><a href="/charts_artists?max=50">Top Artists</a></h1>
|
||||
<h1><a class="stat_link_topartists" href="/charts_artists?in=alltime">Top Artists</a></h1>
|
||||
<!--All Time | This Year | This Month | This Week-->
|
||||
|
||||
|
||||
|
||||
<span onclick="showRange('topartists','week')" class="stat_selector_topartists selector_topartists_week">This Week</span>
|
||||
| <span onclick="showRange('topartists','month')" class="stat_selector_topartists selector_topartists_month">This Month</span>
|
||||
| <span onclick="showRange('topartists','year')" class="stat_selector_topartists selector_topartists_year">This Year</span>
|
||||
| <span onclick="showRange('topartists','alltime')" class="stat_selector_topartists selector_topartists_alltime" style="opacity:0.5;">All Time</span>
|
||||
<span onclick="showRangeManual('topartists','week')" class="stat_selector_topartists selector_topartists_week">This Week</span>
|
||||
| <span onclick="showRangeManual('topartists','month')" class="stat_selector_topartists selector_topartists_month">This Month</span>
|
||||
| <span onclick="showRangeManual('topartists','year')" class="stat_selector_topartists selector_topartists_year">This Year</span>
|
||||
| <span onclick="showRangeManual('topartists','alltime')" class="stat_selector_topartists selector_topartists_alltime" style="opacity:0.5;">All Time</span>
|
||||
|
||||
<br/><br/>
|
||||
|
||||
@ -38,13 +40,13 @@
|
||||
|
||||
|
||||
|
||||
<h1><a href="/charts_tracks?max=50">Top Tracks</a></h1>
|
||||
<h1><a class="stat_link_toptracks" href="/charts_tracks?in=alltime">Top Tracks</a></h1>
|
||||
|
||||
|
||||
<span onclick="showRange('toptracks','week')" class="stat_selector_toptracks selector_toptracks_week">This Week</span>
|
||||
| <span onclick="showRange('toptracks','month')" class="stat_selector_toptracks selector_toptracks_month">This Month</span>
|
||||
| <span onclick="showRange('toptracks','year')" class="stat_selector_toptracks selector_toptracks_year">This Year</span>
|
||||
| <span onclick="showRange('toptracks','alltime')" class="stat_selector_toptracks selector_toptracks_alltime" style="opacity:0.5;">All Time</span>
|
||||
| <span onclick="showRangeManual('toptracks','month')" class="stat_selector_toptracks selector_toptracks_month">This Month</span>
|
||||
| <span onclick="showRangeManual('toptracks','year')" class="stat_selector_toptracks selector_toptracks_year">This Year</span>
|
||||
| <span onclick="showRangeManual('toptracks','alltime')" class="stat_selector_toptracks selector_toptracks_alltime" style="opacity:0.5;">All Time</span>
|
||||
|
||||
<br/><br/>
|
||||
|
||||
@ -59,7 +61,7 @@
|
||||
|
||||
|
||||
<div class="sidelist">
|
||||
<h1><a href="/scrobbles?max=100">Last Scrobbles</a></h1>
|
||||
<h1><a href="/scrobbles">Last Scrobbles</a></h1>
|
||||
<span class="stats">Today</span> KEY_SCROBBLE_NUM_TODAY
|
||||
<span class="stats">This Week</span> KEY_SCROBBLE_NUM_WEEK
|
||||
<span class="stats">This Month</span> KEY_SCROBBLE_NUM_MONTH
|
||||
@ -73,7 +75,7 @@
|
||||
<br/>
|
||||
|
||||
|
||||
<h1><a href="/pulse?step=month&trail=1">Pulse</a></h1>
|
||||
<h1><a class="stat_link_pulse" href="/pulse?trail=1&step=month">Pulse</a></h1>
|
||||
<!--
|
||||
<a href="/pulse?step=day&trail=1">Days</a>
|
||||
<a href="/pulse?step=week&trail=1">Weeks</a>
|
||||
@ -81,10 +83,10 @@
|
||||
<a href="/pulse?step=year&trail=1">Years</a>
|
||||
-->
|
||||
|
||||
<span onclick="showRange('pulse','days')" class="stat_selector_pulse selector_pulse_days">7 days</span>
|
||||
| <span onclick="showRange('pulse','weeks')" class="stat_selector_pulse selector_pulse_weeks">12 weeks</span>
|
||||
| <span onclick="showRange('pulse','months')" class="stat_selector_pulse selector_pulse_months" style="opacity:0.5;">12 months</span>
|
||||
| <span onclick="showRange('pulse','years')" class="stat_selector_pulse selector_pulse_years">10 years</span>
|
||||
<span onclick="showRangeManual('pulse','day')" class="stat_selector_pulse selector_pulse_day">7 days</span>
|
||||
| <span onclick="showRangeManual('pulse','week')" class="stat_selector_pulse selector_pulse_week">12 weeks</span>
|
||||
| <span onclick="showRangeManual('pulse','month')" class="stat_selector_pulse selector_pulse_month" style="opacity:0.5;">12 months</span>
|
||||
| <span onclick="showRangeManual('pulse','year')" class="stat_selector_pulse selector_pulse_year">10 years</span>
|
||||
<!--
|
||||
### this is for extra views of the current canonical week / month / year
|
||||
<br/>
|
||||
@ -94,10 +96,10 @@
|
||||
-->
|
||||
<br/><br/>
|
||||
|
||||
<span class="stat_module_pulse pulse_months">KEY_PULSE_MONTHS</span>
|
||||
<span class="stat_module_pulse pulse_days" style="display:none;">KEY_PULSE_DAYS</span>
|
||||
<span class="stat_module_pulse pulse_years" style="display:none;">KEY_PULSE_YEARS</span>
|
||||
<span class="stat_module_pulse pulse_weeks" style="display:none;">KEY_PULSE_WEEKS</span>
|
||||
<span class="stat_module_pulse pulse_month">KEY_PULSE_MONTHS</span>
|
||||
<span class="stat_module_pulse pulse_day" style="display:none;">KEY_PULSE_DAYS</span>
|
||||
<span class="stat_module_pulse pulse_year" style="display:none;">KEY_PULSE_YEARS</span>
|
||||
<span class="stat_module_pulse pulse_week" style="display:none;">KEY_PULSE_WEEKS</span>
|
||||
<!--
|
||||
<span class="stat_module_pulse pulse_week" style="display:none;">KEY_PULSE_WEEK</span>
|
||||
<span class="stat_module_pulse pulse_month" style="display:none;">KEY_PULSE_MONTH</span>
|
||||
|
@ -12,7 +12,7 @@ def instructions(keys):
|
||||
# commands to execute on load for default ranges
|
||||
js_command = "showRange('topartists','" + get_settings("DEFAULT_RANGE_CHARTS_ARTISTS") + "');"
|
||||
js_command += "showRange('toptracks','" + get_settings("DEFAULT_RANGE_CHARTS_TRACKS") + "');"
|
||||
js_command += "showRange('pulse','" + get_settings("DEFAULT_RANGE_PULSE") + "');"
|
||||
js_command += "showRange('pulse','" + get_settings("DEFAULT_STEP_PULSE") + "');"
|
||||
|
||||
|
||||
clock()
|
||||
|
@ -4,7 +4,8 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Maloja - KEY_TRACKTITLE</title>
|
||||
<script src="javascript/rangeselect.js" async></script>
|
||||
<script src="javascript/cookies.js" ></script>
|
||||
<script src="javascript/rangeselect.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@ -15,12 +16,12 @@
|
||||
</td>
|
||||
<td class="text">
|
||||
<span>KEY_ARTISTS</span><br/>
|
||||
<h1>KEY_TRACKTITLE</h1> KEY_CERTS <span class="rank"><a href="/charts_tracks?max=100">KEY_POSITION</a></span>
|
||||
<h1>KEY_TRACKTITLE</h1> KEY_CERTS <span class="rank"><a href="/charts_tracks">KEY_POSITION</a></span>
|
||||
|
||||
<p class="stats"><a href="/scrobbles?KEY_SCROBBLELINK">KEY_SCROBBLES Scrobbles</a></p>
|
||||
|
||||
<p class="desc"></p>
|
||||
<span>KEY_MEDALS</span>
|
||||
<span>KEY_MEDALS</span> <span>KEY_TOPWEEKS</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@ -29,32 +30,32 @@
|
||||
<table class="twopart">
|
||||
<tr>
|
||||
<td>
|
||||
<h2><a href='/pulse?KEY_SCROBBLELINK&step=year&trail=1'>Pulse</a></h2>
|
||||
<span onclick="showRange('pulse','days')" class="stat_selector_pulse selector_pulse_days">7 days</span>
|
||||
| <span onclick="showRange('pulse','weeks')" class="stat_selector_pulse selector_pulse_weeks">12 weeks</span>
|
||||
| <span onclick="showRange('pulse','months')" class="stat_selector_pulse selector_pulse_months" style="opacity:0.5;">12 months</span>
|
||||
| <span onclick="showRange('pulse','years')" class="stat_selector_pulse selector_pulse_years">10 years</span>
|
||||
<h2><a class="stat_link_pulse" href='/pulse?KEY_SCROBBLELINK&trail=1&step=month'>Pulse</a></h2>
|
||||
<span onclick="showRangeManual('pulse','day')" class="stat_selector_pulse selector_pulse_day">7 days</span>
|
||||
| <span onclick="showRangeManual('pulse','week')" class="stat_selector_pulse selector_pulse_week">12 weeks</span>
|
||||
| <span onclick="showRangeManual('pulse','month')" class="stat_selector_pulse selector_pulse_month" style="opacity:0.5;">12 months</span>
|
||||
| <span onclick="showRangeManual('pulse','year')" class="stat_selector_pulse selector_pulse_year">10 years</span>
|
||||
|
||||
<br/><br/>
|
||||
|
||||
<span class="stat_module_pulse pulse_months">KEY_PULSE_MONTHS</span>
|
||||
<span class="stat_module_pulse pulse_days" style="display:none;">KEY_PULSE_DAYS</span>
|
||||
<span class="stat_module_pulse pulse_years" style="display:none;">KEY_PULSE_YEARS</span>
|
||||
<span class="stat_module_pulse pulse_weeks" style="display:none;">KEY_PULSE_WEEKS</span>
|
||||
<span class="stat_module_pulse pulse_month">KEY_PULSE_MONTHS</span>
|
||||
<span class="stat_module_pulse pulse_day" style="display:none;">KEY_PULSE_DAYS</span>
|
||||
<span class="stat_module_pulse pulse_year" style="display:none;">KEY_PULSE_YEARS</span>
|
||||
<span class="stat_module_pulse pulse_week" style="display:none;">KEY_PULSE_WEEKS</span>
|
||||
</td>
|
||||
<td>
|
||||
<h2><a href='/performance?KEY_SCROBBLELINK&step=year&trail=1'>Performance</a></h2>
|
||||
<span onclick="showRange('pulse','days')" class="stat_selector_pulse selector_pulse_days">7 days</span>
|
||||
| <span onclick="showRange('pulse','weeks')" class="stat_selector_pulse selector_pulse_weeks">12 weeks</span>
|
||||
| <span onclick="showRange('pulse','months')" class="stat_selector_pulse selector_pulse_months" style="opacity:0.5;">12 months</span>
|
||||
| <span onclick="showRange('pulse','years')" class="stat_selector_pulse selector_pulse_years">10 years</span>
|
||||
<h2><a class="stat_link_pulse" href='/performance?KEY_SCROBBLELINK&trail=1&step=month'>Performance</a></h2>
|
||||
<span onclick="showRangeManual('pulse','day')" class="stat_selector_pulse selector_pulse_day">7 days</span>
|
||||
| <span onclick="showRangeManual('pulse','week')" class="stat_selector_pulse selector_pulse_week">12 weeks</span>
|
||||
| <span onclick="showRangeManual('pulse','month')" class="stat_selector_pulse selector_pulse_month" style="opacity:0.5;">12 months</span>
|
||||
| <span onclick="showRangeManual('pulse','year')" class="stat_selector_pulse selector_pulse_year">10 years</span>
|
||||
|
||||
<br/><br/>
|
||||
|
||||
<span class="stat_module_pulse pulse_months">KEY_PERFORMANCE_MONTHS</span>
|
||||
<span class="stat_module_pulse pulse_days" style="display:none;">KEY_PERFORMANCE_DAYS</span>
|
||||
<span class="stat_module_pulse pulse_years" style="display:none;">KEY_PERFORMANCE_YEARS</span>
|
||||
<span class="stat_module_pulse pulse_weeks" style="display:none;">KEY_PERFORMANCE_WEEKS</span>
|
||||
<span class="stat_module_pulse pulse_month">KEY_PERFORMANCE_MONTHS</span>
|
||||
<span class="stat_module_pulse pulse_day" style="display:none;">KEY_PERFORMANCE_DAYS</span>
|
||||
<span class="stat_module_pulse pulse_year" style="display:none;">KEY_PERFORMANCE_YEARS</span>
|
||||
<span class="stat_module_pulse pulse_week" style="display:none;">KEY_PERFORMANCE_WEEKS</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
@ -29,13 +29,19 @@ def instructions(keys):
|
||||
if "medals" in data and data["medals"] is not None:
|
||||
if "gold" in data["medals"]:
|
||||
for y in data["medals"]["gold"]:
|
||||
html_medals += "<a title='Best Track in " + str(y) + "' class='hidelink medal shiny gold' href='/charts_tracks?max=50&in=" + str(y) + "'><span>" + str(y) + "</span></a>"
|
||||
html_medals += "<a title='Best Track in " + str(y) + "' class='hidelink medal shiny gold' href='/charts_tracks?in=" + str(y) + "'><span>" + str(y) + "</span></a>"
|
||||
if "silver" in data["medals"]:
|
||||
for y in data["medals"]["silver"]:
|
||||
html_medals += "<a title='Second Best Track in " + str(y) + "' class='hidelink medal shiny silver' href='/charts_tracks?max=50&in=" + str(y) + "'><span>" + str(y) + "</span></a>"
|
||||
html_medals += "<a title='Second Best Track in " + str(y) + "' class='hidelink medal shiny silver' href='/charts_tracks?in=" + str(y) + "'><span>" + str(y) + "</span></a>"
|
||||
if "bronze" in data["medals"]:
|
||||
for y in data["medals"]["bronze"]:
|
||||
html_medals += "<a title='Third Best Track in " + str(y) + "' class='hidelink medal shiny bronze' href='/charts_tracks?max=50&in=" + str(y) + "'><span>" + str(y) + "</span></a>"
|
||||
html_medals += "<a title='Third Best Track in " + str(y) + "' class='hidelink medal shiny bronze' href='/charts_tracks?in=" + str(y) + "'><span>" + str(y) + "</span></a>"
|
||||
|
||||
html_topweeks = ""
|
||||
if data.get("topweeks") not in [0,None]:
|
||||
link = "/performance?" + compose_querystring(keys) + "&trail=1&step=week"
|
||||
title = str(data["topweeks"]) + " weeks on #1"
|
||||
html_topweeks = "<a title='" + title + "' href='" + link + "'><img class='star' src='/media/star.png' />" + str(data["topweeks"]) + "</a>"
|
||||
|
||||
|
||||
|
||||
@ -65,6 +71,7 @@ def instructions(keys):
|
||||
"KEY_SCROBBLELINK":compose_querystring(keys),
|
||||
"KEY_MEDALS":html_medals,
|
||||
"KEY_CERTS":html_cert,
|
||||
"KEY_TOPWEEKS":html_topweeks,
|
||||
"KEY_SCROBBLELIST":html_scrobbles,
|
||||
# pulse
|
||||
"KEY_PULSE_MONTHS":html_pulse_months,
|
||||
|
Loading…
x
Reference in New Issue
Block a user