mirror of
https://github.com/krateng/maloja.git
synced 2025-07-09 03:04:07 -04:00
Merge branch 'master' into pyhp
This commit is contained in:
commit
b955777637
14
.doreah
14
.doreah
@ -1,4 +1,10 @@
|
|||||||
logging.logfolder = logs
|
logging:
|
||||||
settings.files = [ "settings/default.ini" , "settings/settings.ini" ]
|
logfolder: "logs"
|
||||||
caching.folder = "cache/"
|
settings:
|
||||||
regular.autostart = false
|
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
|
# generic temporary / dev files
|
||||||
*.pyc
|
*.pyc
|
||||||
*.sh
|
*.sh
|
||||||
|
!/update_requirements.sh
|
||||||
*.note
|
*.note
|
||||||
*.xcf
|
*.xcf
|
||||||
nohup.out
|
nohup.out
|
||||||
@ -10,10 +11,10 @@ nohup.out
|
|||||||
*.tsv
|
*.tsv
|
||||||
*.rulestate
|
*.rulestate
|
||||||
*.log
|
*.log
|
||||||
|
*.css
|
||||||
|
|
||||||
# currently not using
|
# currently not using
|
||||||
/screenshot*.png
|
/screenshot*.png
|
||||||
/proxyscrobble.py
|
|
||||||
|
|
||||||
# only for development, normally external
|
# only for development, normally external
|
||||||
/doreah
|
/doreah
|
||||||
|
12
README.md
12
README.md
@ -16,12 +16,8 @@ Also neat: You can use your **custom artist or track images**.
|
|||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
* [python3](https://www.python.org/) - [GitHub](https://github.com/python/cpython)
|
* Python 3
|
||||||
* [bottle.py](https://bottlepy.org/) - [GitHub](https://github.com/bottlepy/bottle)
|
* Pip packages specified in `requirements.txt`
|
||||||
* [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)
|
|
||||||
* 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!
|
* 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
|
## 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
|
### 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
|
### Standard-compliant API
|
||||||
|
|
||||||
|
29
cleanup.py
29
cleanup.py
@ -1,6 +1,6 @@
|
|||||||
import re
|
import re
|
||||||
import utilities
|
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
|
# need to do this as a class so it can retain loaded settings from file
|
||||||
# apparently this is not true
|
# apparently this is not true
|
||||||
@ -11,11 +11,16 @@ class CleanerAgent:
|
|||||||
self.updateRules()
|
self.updateRules()
|
||||||
|
|
||||||
def updateRules(self):
|
def updateRules(self):
|
||||||
raw = tsv.parse_all("rules","string","string","string")
|
raw = tsv.parse_all("rules","string","string","string","string")
|
||||||
self.rules_belongtogether = [b for [a,b,c] in raw if a=="belongtogether"]
|
self.rules_belongtogether = [b for [a,b,c,d] in raw if a=="belongtogether"]
|
||||||
self.rules_notanartist = [b for [a,b,c] in raw if a=="notanartist"]
|
self.rules_notanartist = [b for [a,b,c,d] in raw if a=="notanartist"]
|
||||||
self.rules_replacetitle = {b.lower():c for [a,b,c] in raw if a=="replacetitle"}
|
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] in raw if a=="replaceartist"}
|
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
|
# we always need to be able to tell if our current database is made with the current rules
|
||||||
self.checksums = utilities.checksumTSV("rules")
|
self.checksums = utilities.checksumTSV("rules")
|
||||||
@ -27,6 +32,12 @@ class CleanerAgent:
|
|||||||
title = self.parseTitle(self.removespecial(title))
|
title = self.parseTitle(self.removespecial(title))
|
||||||
(title,moreartists) = self.parseTitleForArtists(title)
|
(title,moreartists) = self.parseTitleForArtists(title)
|
||||||
artists += moreartists
|
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 = list(set(artists))
|
||||||
artists.sort()
|
artists.sort()
|
||||||
|
|
||||||
@ -52,6 +63,12 @@ class CleanerAgent:
|
|||||||
|
|
||||||
def parseArtists(self,a):
|
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() == "":
|
if a.strip() == "":
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
@ -68,6 +68,7 @@ def handle(path,keys):
|
|||||||
|
|
||||||
def scrobbletrack(artiststr,titlestr,timestamp):
|
def scrobbletrack(artiststr,titlestr,timestamp):
|
||||||
try:
|
try:
|
||||||
|
log("Incoming scrobble (compliant API): ARTISTS: " + artiststr + ", TRACK: " + titlestr,module="debug")
|
||||||
(artists,title) = cla.fullclean(artiststr,titlestr)
|
(artists,title) = cla.fullclean(artiststr,titlestr)
|
||||||
database.createScrobble(artists,title,timestamp)
|
database.createScrobble(artists,title,timestamp)
|
||||||
database.sync()
|
database.sync()
|
||||||
|
92
database.py
92
database.py
@ -6,6 +6,7 @@ import utilities
|
|||||||
from malojatime import register_scrobbletime, time_stamps, ranges
|
from malojatime import register_scrobbletime, time_stamps, ranges
|
||||||
from urihandler import uri_to_internal, internal_to_uri, compose_querystring
|
from urihandler import uri_to_internal, internal_to_uri, compose_querystring
|
||||||
import compliant_api
|
import compliant_api
|
||||||
|
from external import proxy_scrobble
|
||||||
# doreah toolkit
|
# doreah toolkit
|
||||||
from doreah.logging import log
|
from doreah.logging import log
|
||||||
from doreah import tsv
|
from doreah import tsv
|
||||||
@ -49,8 +50,11 @@ TRACKS_LOWER = []
|
|||||||
ARTISTS_LOWER = []
|
ARTISTS_LOWER = []
|
||||||
ARTIST_SET = set()
|
ARTIST_SET = set()
|
||||||
TRACK_SET = set()
|
TRACK_SET = set()
|
||||||
|
|
||||||
MEDALS = {} #literally only changes once per year, no need to calculate that on the fly
|
MEDALS = {} #literally only changes once per year, no need to calculate that on the fly
|
||||||
MEDALS_TRACKS = {}
|
MEDALS_TRACKS = {}
|
||||||
|
WEEKLY_TOPTRACKS = {}
|
||||||
|
WEEKLY_TOPARTISTS = {}
|
||||||
|
|
||||||
cla = CleanerAgent()
|
cla = CleanerAgent()
|
||||||
coa = CollectorAgent()
|
coa = CollectorAgent()
|
||||||
@ -73,7 +77,12 @@ def loadAPIkeys():
|
|||||||
log("Authenticated Machines: " + ", ".join([m[1] for m in clients]))
|
log("Authenticated Machines: " + ", ".join([m[1] for m in clients]))
|
||||||
|
|
||||||
def checkAPIkey(k):
|
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():
|
def allAPIkeys():
|
||||||
return [k for [k,d] in clients]
|
return [k for [k,d] in clients]
|
||||||
|
|
||||||
@ -102,10 +111,23 @@ def get_track_dict(o):
|
|||||||
|
|
||||||
|
|
||||||
def createScrobble(artists,title,time,volatile=False):
|
def createScrobble(artists,title,time,volatile=False):
|
||||||
|
|
||||||
|
if len(artists) == 0 or title == "":
|
||||||
|
return {}
|
||||||
|
|
||||||
dblock.acquire()
|
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):
|
while (time in SCROBBLESDICT):
|
||||||
time += 1
|
time += 1
|
||||||
i = getTrackID(artists,title)
|
|
||||||
obj = Scrobble(i,time,volatile) # if volatile generated, we simply pretend we have already saved it to disk
|
obj = Scrobble(i,time,volatile) # if volatile generated, we simply pretend we have already saved it to disk
|
||||||
#SCROBBLES.append(obj)
|
#SCROBBLES.append(obj)
|
||||||
# immediately insert scrobble correctly so we can guarantee sorted list
|
# immediately insert scrobble correctly so we can guarantee sorted list
|
||||||
@ -116,6 +138,8 @@ def createScrobble(artists,title,time,volatile=False):
|
|||||||
invalidate_caches()
|
invalidate_caches()
|
||||||
dblock.release()
|
dblock.release()
|
||||||
|
|
||||||
|
proxy_scrobble(artists,title,time)
|
||||||
|
|
||||||
return get_track_dict(TRACKS[obj.track])
|
return get_track_dict(TRACKS[obj.track])
|
||||||
|
|
||||||
|
|
||||||
@ -225,7 +249,22 @@ def get_scrobbles(**keys):
|
|||||||
# return r
|
# return r
|
||||||
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]
|
c = [e for e in charts if e["artist"] == artist][0]
|
||||||
others = [a for a in coa.getAllAssociated(artist) if a in ARTISTS]
|
others = [a for a in coa.getAllAssociated(artist) if a in ARTISTS]
|
||||||
position = c["rank"]
|
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:
|
except:
|
||||||
# if the artist isnt in the charts, they are not being credited and we
|
# if the artist isnt in the charts, they are not being credited and we
|
||||||
# need to show information about the credited one
|
# need to show information about the credited one
|
||||||
@ -555,11 +601,13 @@ def trackInfo(track):
|
|||||||
elif scrobbles >= threshold_platinum: cert = "platinum"
|
elif scrobbles >= threshold_platinum: cert = "platinum"
|
||||||
elif scrobbles >= threshold_gold: cert = "gold"
|
elif scrobbles >= threshold_gold: cert = "gold"
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"scrobbles":scrobbles,
|
"scrobbles":scrobbles,
|
||||||
"position":position,
|
"position":position,
|
||||||
"medals":MEDALS_TRACKS.get((frozenset(track["artists"]),track["title"])),
|
"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")
|
artists = keys.get("artist")
|
||||||
title = keys.get("title")
|
title = keys.get("title")
|
||||||
apikey = keys.get("key")
|
apikey = keys.get("key")
|
||||||
if not (checkAPIkey(apikey)):
|
client = checkAPIkey(apikey)
|
||||||
|
if client == False: # empty string allowed!
|
||||||
response.status = 403
|
response.status = 403
|
||||||
return ""
|
return ""
|
||||||
try:
|
try:
|
||||||
time = int(keys.get("time"))
|
time = int(keys.get("time"))
|
||||||
except:
|
except:
|
||||||
time = int(datetime.datetime.now(tz=datetime.timezone.utc).timestamp())
|
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)
|
(artists,title) = cla.fullclean(artists,title)
|
||||||
|
|
||||||
## this is necessary for localhost testing
|
## this is necessary for localhost testing
|
||||||
@ -587,8 +638,9 @@ def pseudo_post_scrobble(**keys):
|
|||||||
|
|
||||||
trackdict = createScrobble(artists,title,time)
|
trackdict = createScrobble(artists,title,time)
|
||||||
|
|
||||||
if (time - lastsync) > 3600:
|
sync()
|
||||||
sync()
|
|
||||||
|
|
||||||
|
|
||||||
return {"status":"success","track":trackdict}
|
return {"status":"success","track":trackdict}
|
||||||
|
|
||||||
@ -597,7 +649,8 @@ def post_scrobble(**keys):
|
|||||||
artists = keys.get("artist")
|
artists = keys.get("artist")
|
||||||
title = keys.get("title")
|
title = keys.get("title")
|
||||||
apikey = keys.get("key")
|
apikey = keys.get("key")
|
||||||
if not (checkAPIkey(apikey)):
|
client = checkAPIkey(apikey)
|
||||||
|
if client == False: # empty string allowed!
|
||||||
response.status = 403
|
response.status = 403
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@ -605,6 +658,8 @@ def post_scrobble(**keys):
|
|||||||
time = int(keys.get("time"))
|
time = int(keys.get("time"))
|
||||||
except:
|
except:
|
||||||
time = int(datetime.datetime.now(tz=datetime.timezone.utc).timestamp())
|
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)
|
(artists,title) = cla.fullclean(artists,title)
|
||||||
|
|
||||||
## this is necessary for localhost testing
|
## this is necessary for localhost testing
|
||||||
@ -612,12 +667,11 @@ def post_scrobble(**keys):
|
|||||||
|
|
||||||
trackdict = createScrobble(artists,title,time)
|
trackdict = createScrobble(artists,title,time)
|
||||||
|
|
||||||
#if (time - lastsync) > 3600:
|
|
||||||
# sync()
|
|
||||||
sync()
|
sync()
|
||||||
#always sync, one filesystem access every three minutes shouldn't matter
|
#always sync, one filesystem access every three minutes shouldn't matter
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return {"status":"success","track":trackdict}
|
return {"status":"success","track":trackdict}
|
||||||
|
|
||||||
|
|
||||||
@ -644,8 +698,7 @@ def abouttoshutdown():
|
|||||||
#sys.exit()
|
#sys.exit()
|
||||||
|
|
||||||
@dbserver.post("newrule")
|
@dbserver.post("newrule")
|
||||||
def newrule():
|
def newrule(**keys):
|
||||||
keys = FormsDict.decode(request.forms)
|
|
||||||
apikey = keys.pop("key",None)
|
apikey = keys.pop("key",None)
|
||||||
if (checkAPIkey(apikey)):
|
if (checkAPIkey(apikey)):
|
||||||
tsv.add_entry("rules/webmade.tsv",[k for k in keys])
|
tsv.add_entry("rules/webmade.tsv",[k for k in keys])
|
||||||
@ -751,8 +804,7 @@ def issues():
|
|||||||
|
|
||||||
|
|
||||||
@dbserver.post("importrules")
|
@dbserver.post("importrules")
|
||||||
def import_rulemodule():
|
def import_rulemodule(**keys):
|
||||||
keys = FormsDict.decode(request.forms)
|
|
||||||
apikey = keys.pop("key",None)
|
apikey = keys.pop("key",None)
|
||||||
|
|
||||||
if (checkAPIkey(apikey)):
|
if (checkAPIkey(apikey)):
|
||||||
@ -771,9 +823,7 @@ def import_rulemodule():
|
|||||||
|
|
||||||
|
|
||||||
@dbserver.post("rebuild")
|
@dbserver.post("rebuild")
|
||||||
def rebuild():
|
def rebuild(**keys):
|
||||||
|
|
||||||
keys = FormsDict.decode(request.forms)
|
|
||||||
apikey = keys.pop("key",None)
|
apikey = keys.pop("key",None)
|
||||||
if (checkAPIkey(apikey)):
|
if (checkAPIkey(apikey)):
|
||||||
log("Database rebuild initiated!")
|
log("Database rebuild initiated!")
|
||||||
@ -886,6 +936,7 @@ def build_db():
|
|||||||
|
|
||||||
#start regular tasks
|
#start regular tasks
|
||||||
utilities.update_medals()
|
utilities.update_medals()
|
||||||
|
utilities.update_weekly()
|
||||||
|
|
||||||
global db_rulestate
|
global db_rulestate
|
||||||
db_rulestate = utilities.consistentRulestate("scrobbles",cla.checksums)
|
db_rulestate = utilities.consistentRulestate("scrobbles",cla.checksums)
|
||||||
@ -899,6 +950,7 @@ def sync():
|
|||||||
|
|
||||||
# all entries by file collected
|
# all entries by file collected
|
||||||
# so we don't open the same file for every entry
|
# so we don't open the same file for every entry
|
||||||
|
#log("Syncing",module="debug")
|
||||||
entries = {}
|
entries = {}
|
||||||
|
|
||||||
for idx in range(len(SCROBBLES)):
|
for idx in range(len(SCROBBLES)):
|
||||||
@ -918,15 +970,19 @@ def sync():
|
|||||||
|
|
||||||
SCROBBLES[idx] = (SCROBBLES[idx][0],SCROBBLES[idx][1],True)
|
SCROBBLES[idx] = (SCROBBLES[idx][0],SCROBBLES[idx][1],True)
|
||||||
|
|
||||||
|
#log("Sorted into months",module="debug")
|
||||||
|
|
||||||
for e in entries:
|
for e in entries:
|
||||||
tsv.add_entries("scrobbles/" + e + ".tsv",entries[e],comments=False)
|
tsv.add_entries("scrobbles/" + e + ".tsv",entries[e],comments=False)
|
||||||
#addEntries("scrobbles/" + e + ".tsv",entries[e],escape=False)
|
#addEntries("scrobbles/" + e + ".tsv",entries[e],escape=False)
|
||||||
utilities.combineChecksums("scrobbles/" + e + ".tsv",cla.checksums)
|
utilities.combineChecksums("scrobbles/" + e + ".tsv",cla.checksums)
|
||||||
|
|
||||||
|
#log("Written files",module="debug")
|
||||||
|
|
||||||
|
|
||||||
global lastsync
|
global lastsync
|
||||||
lastsync = int(datetime.datetime.now(tz=datetime.timezone.utc).timestamp())
|
lastsync = int(datetime.datetime.now(tz=datetime.timezone.utc).timestamp())
|
||||||
log("Database saved to disk.")
|
#log("Database saved to disk.")
|
||||||
|
|
||||||
# save cached images
|
# save cached images
|
||||||
#saveCache()
|
#saveCache()
|
||||||
|
50
external.py
50
external.py
@ -3,6 +3,10 @@ import json
|
|||||||
import base64
|
import base64
|
||||||
from doreah.settings import get_settings
|
from doreah.settings import get_settings
|
||||||
from doreah.logging import log
|
from doreah.logging import log
|
||||||
|
import hashlib
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
### PICTURES
|
||||||
|
|
||||||
|
|
||||||
apis_artists = []
|
apis_artists = []
|
||||||
@ -130,3 +134,49 @@ def api_request_track(track):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
return None
|
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
|
from bottle import FormsDict
|
||||||
import datetime
|
import datetime
|
||||||
from urihandler import compose_querystring
|
from urihandler import compose_querystring
|
||||||
|
import urllib.parse
|
||||||
|
from doreah.settings import get_settings
|
||||||
|
|
||||||
|
|
||||||
# returns the proper column(s) for an artist or track
|
# returns the proper column(s) for an artist or track
|
||||||
@ -16,7 +18,9 @@ def entity_column(element,counting=[],image=None):
|
|||||||
# track
|
# track
|
||||||
# html += "<td class='artists'>" + html_links(element["artists"]) + "</td>"
|
# html += "<td class='artists'>" + html_links(element["artists"]) + "</td>"
|
||||||
# html += "<td class='title'>" + html_link(element) + "</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:
|
else:
|
||||||
# artist
|
# artist
|
||||||
html += "<td class='artist'>" + html_link(element)
|
html += "<td class='artist'>" + html_link(element)
|
||||||
@ -74,6 +78,33 @@ def trackLink(track):
|
|||||||
#artists,title = track["artists"],track["title"]
|
#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>"
|
#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(artists,title,timekeys,amount=None,pixels=None):
|
||||||
def scrobblesTrackLink(track,timekeys,amount=None,percent=None):
|
def scrobblesTrackLink(track,timekeys,amount=None,percent=None):
|
||||||
artists,title = track["artists"],track["title"]
|
artists,title = track["artists"],track["title"]
|
||||||
|
314
htmlmodules.py
314
htmlmodules.py
@ -18,29 +18,38 @@ import math
|
|||||||
# result.append(element.get("image"))
|
# result.append(element.get("image"))
|
||||||
|
|
||||||
|
|
||||||
# artist=None,track=None,since=None,to=None,within=None,associated=False,max_=None,pictures=False
|
#max_ indicates that no pagination should occur (because this is not the primary module)
|
||||||
def module_scrobblelist(max_=None,pictures=False,shortTimeDesc=False,earlystop=False,**kwargs):
|
def module_scrobblelist(page=0,perpage=100,max_=None,pictures=False,shortTimeDesc=False,earlystop=False,**kwargs):
|
||||||
|
|
||||||
kwargs_filter = pickKeys(kwargs,"artist","track","associated")
|
kwargs_filter = pickKeys(kwargs,"artist","track","associated")
|
||||||
kwargs_time = pickKeys(kwargs,"timerange","since","to","within")
|
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
|
# 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
|
# 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)
|
scrobbles = database.get_scrobbles(**kwargs_time,**kwargs_filter,**maxkey)
|
||||||
if pictures:
|
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 = [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 = ["/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]
|
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
|
representative = scrobbles[0] if len(scrobbles) is not 0 else None
|
||||||
|
|
||||||
# build list
|
# build list
|
||||||
i = 0
|
i = 0
|
||||||
html = "<table class='list'>"
|
html = "<table class='list'>"
|
||||||
for s in scrobbles:
|
for s in scrobbles:
|
||||||
|
if i<firstindex:
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
|
||||||
html += "<tr>"
|
html += "<tr>"
|
||||||
html += "<td class='time'>" + timestamp_desc(s["time"],short=shortTimeDesc) + "</td>"
|
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]
|
img = scrobbleimages[i]
|
||||||
else: img = None
|
else: img = None
|
||||||
html += entity_column(s,image=img)
|
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>"
|
html += "</tr>"
|
||||||
|
|
||||||
i += 1
|
i += 1
|
||||||
if max_ is not None and i>=max_:
|
if i>=lastindex:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
html += "</table>"
|
html += "</table>"
|
||||||
|
|
||||||
|
if max_ is None: html += module_paginate(page=page,pages=pages,perpage=perpage,**kwargs)
|
||||||
|
|
||||||
return (html,len(scrobbles),representative)
|
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
|
from doreah.timing import clock, clockp
|
||||||
|
|
||||||
kwargs_filter = pickKeys(kwargs,"artist","track","associated")
|
kwargs_filter = pickKeys(kwargs,"artist","track","associated")
|
||||||
kwargs_time = pickKeys(kwargs,"since","to","within","timerange","step","stepn","trail")
|
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)
|
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 time range not explicitly specified, only show from first appearance
|
||||||
# if "since" not in kwargs:
|
# if "since" not in kwargs:
|
||||||
@ -94,19 +109,27 @@ def module_pulse(max_=None,**kwargs):
|
|||||||
html += "</tr>"
|
html += "</tr>"
|
||||||
html += "</table>"
|
html += "</table>"
|
||||||
|
|
||||||
|
if max_ is None: html += module_paginate(page=page,pages=pages,perpage=perpage,**kwargs)
|
||||||
|
|
||||||
return html
|
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_filter = pickKeys(kwargs,"artist","track")
|
||||||
kwargs_time = pickKeys(kwargs,"since","to","within","timerange","step","stepn","trail")
|
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)
|
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 time range not explicitly specified, only show from first appearance
|
||||||
# if "since" not in kwargs:
|
# if "since" not in kwargs:
|
||||||
@ -130,18 +153,26 @@ def module_performance(max_=None,**kwargs):
|
|||||||
html += "</tr>"
|
html += "</tr>"
|
||||||
html += "</table>"
|
html += "</table>"
|
||||||
|
|
||||||
|
if max_ is None: html += module_paginate(page=page,pages=pages,perpage=perpage,**kwargs)
|
||||||
|
|
||||||
return html
|
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_filter = pickKeys(kwargs,"artist","associated")
|
||||||
kwargs_time = pickKeys(kwargs,"timerange","since","to","within")
|
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)
|
tracks = database.get_charts_tracks(**kwargs_filter,**kwargs_time)
|
||||||
|
|
||||||
|
pages = math.ceil(len(tracks) / perpage)
|
||||||
|
|
||||||
# last time range (to compare)
|
# last time range (to compare)
|
||||||
try:
|
try:
|
||||||
trackslast = database.get_charts_tracks(**kwargs_filter,timerange=kwargs_time["timerange"].next(step=-1))
|
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
|
i = 0
|
||||||
html = "<table class='list'>"
|
html = "<table class='list'>"
|
||||||
for e in tracks:
|
for e in tracks:
|
||||||
|
if i<firstindex:
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
i += 1
|
i += 1
|
||||||
if max_ is not None and i>max_:
|
if i>lastindex:
|
||||||
break
|
break
|
||||||
html += "<tr>"
|
html += "<tr>"
|
||||||
# rank
|
# rank
|
||||||
if i == 1 or e["scrobbles"] < prev["scrobbles"]:
|
if i == firstindex+1 or e["scrobbles"] < prev["scrobbles"]:
|
||||||
html += "<td class='rank'>#" + str(i) + "</td>"
|
html += "<td class='rank'>#" + str(e["rank"]) + "</td>"
|
||||||
else:
|
else:
|
||||||
html += "<td class='rank'></td>"
|
html += "<td class='rank'></td>"
|
||||||
# rank change
|
# rank change
|
||||||
@ -196,16 +230,26 @@ def module_trackcharts(max_=None,**kwargs):
|
|||||||
prev = e
|
prev = e
|
||||||
html += "</table>"
|
html += "</table>"
|
||||||
|
|
||||||
|
if max_ is None: html += module_paginate(page=page,pages=pages,perpage=perpage,**kwargs)
|
||||||
|
|
||||||
return (html,representative)
|
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_filter = pickKeys(kwargs,"associated") #not used right now
|
||||||
kwargs_time = pickKeys(kwargs,"timerange","since","to","within")
|
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)
|
artists = database.get_charts_artists(**kwargs_filter,**kwargs_time)
|
||||||
|
|
||||||
|
pages = math.ceil(len(artists) / perpage)
|
||||||
|
|
||||||
|
|
||||||
# last time range (to compare)
|
# last time range (to compare)
|
||||||
try:
|
try:
|
||||||
#from malojatime import _get_next
|
#from malojatime import _get_next
|
||||||
@ -231,13 +275,16 @@ def module_artistcharts(max_=None,**kwargs):
|
|||||||
i = 0
|
i = 0
|
||||||
html = "<table class='list'>"
|
html = "<table class='list'>"
|
||||||
for e in artists:
|
for e in artists:
|
||||||
|
if i<firstindex:
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
i += 1
|
i += 1
|
||||||
if max_ is not None and i>max_:
|
if i>lastindex:
|
||||||
break
|
break
|
||||||
html += "<tr>"
|
html += "<tr>"
|
||||||
# rank
|
# rank
|
||||||
if i == 1 or e["scrobbles"] < prev["scrobbles"]:
|
if i == firstindex+1 or e["scrobbles"] < prev["scrobbles"]:
|
||||||
html += "<td class='rank'>#" + str(i) + "</td>"
|
html += "<td class='rank'>#" + str(e["rank"]) + "</td>"
|
||||||
else:
|
else:
|
||||||
html += "<td class='rank'></td>"
|
html += "<td class='rank'></td>"
|
||||||
# rank change
|
# rank change
|
||||||
@ -262,6 +309,8 @@ def module_artistcharts(max_=None,**kwargs):
|
|||||||
|
|
||||||
html += "</table>"
|
html += "</table>"
|
||||||
|
|
||||||
|
if max_ is None: html += module_paginate(page=page,pages=pages,perpage=perpage,**kwargs)
|
||||||
|
|
||||||
return (html, representative)
|
return (html, representative)
|
||||||
|
|
||||||
|
|
||||||
@ -308,7 +357,7 @@ def module_toptracks(pictures=True,**kwargs):
|
|||||||
if pictures:
|
if pictures:
|
||||||
html += "<td><div></div></td>"
|
html += "<td><div></div></td>"
|
||||||
html += "<td class='stats'>" + "No scrobbles" + "</td>"
|
html += "<td class='stats'>" + "No scrobbles" + "</td>"
|
||||||
html += "<td>" + "" + "</td>"
|
#html += "<td>" + "" + "</td>"
|
||||||
html += "<td class='amount'>" + "0" + "</td>"
|
html += "<td class='amount'>" + "0" + "</td>"
|
||||||
html += "<td class='bar'>" + "" + "</td>"
|
html += "<td class='bar'>" + "" + "</td>"
|
||||||
else:
|
else:
|
||||||
@ -478,22 +527,60 @@ def module_trackcharts_tiles(**kwargs):
|
|||||||
return html
|
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!!!
|
# THIS FUNCTION USES THE ORIGINAL URI KEYS!!!
|
||||||
def module_filterselection(keys,time=True,delimit=False):
|
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
|
# drop keys that are not relevant so they don't clutter the URI
|
||||||
if not time: timekeys = {}
|
if not time: timekeys = {}
|
||||||
if not delimit: delimitkeys = {}
|
if not delimit: delimitkeys = {}
|
||||||
|
if "page" in extrakeys: del extrakeys["page"]
|
||||||
|
internalkeys = {**filterkeys,**timekeys,**delimitkeys,**extrakeys}
|
||||||
|
|
||||||
html = ""
|
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
|
# 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 += "to <input id='dateselect_to' onchange='datechange()' type='date' value='" + "-".join(todate) + "'/>"
|
||||||
# html += "</div>"
|
# 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
|
# relative to current range
|
||||||
|
|
||||||
html += "<div>"
|
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:
|
thisrange = timekeys.get("timerange")
|
||||||
prevrange = timekeys.get("timerange").next(-1)
|
prevrange = thisrange.next(-1)
|
||||||
html += "<a href='?" + compose_querystring(unchangedkeys,internal_to_uri({"timerange":prevrange})) + "'><span class='stat_selector'>" + prevrange.desc() + "</span></a>"
|
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 += " « "
|
html += " « "
|
||||||
if timekeys.get("timerange").next(-1) is not None or 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;'>" + timekeys.get("timerange").desc() + "</span>"
|
html += "<span class='stat_selector' style='opacity:0.5;'>" + thisrange.desc() + "</span>"
|
||||||
if timekeys.get("timerange").next(1) is not None:
|
if nextrange is not None:
|
||||||
html += " » "
|
html += " » "
|
||||||
nextrange = timekeys.get("timerange").next(1)
|
link = compose_querystring(internal_to_uri({**internalkeys,"timerange":nextrange}))
|
||||||
html += "<a href='?" + compose_querystring(unchangedkeys,internal_to_uri({"timerange":nextrange})) + "'><span class='stat_selector'>" + nextrange.desc() + "</span></a>"
|
html += "<a href='?" + link + "'><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>"
|
|
||||||
|
|
||||||
html += "</div>"
|
html += "</div>"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
unchangedkeys_sub = internal_to_uri({k:delimitkeys[k] for k in delimitkeys if k != "trail"})
|
|
||||||
|
|
||||||
html += "<div>"
|
categories = [
|
||||||
if delimitkeys.get("trail") == 1:
|
{
|
||||||
html += "<span class='stat_selector' style='opacity:0.5;'>Standard</span>"
|
"active":time,
|
||||||
else:
|
"options":{
|
||||||
html += "<a href='?" + compose_querystring(unchangedkeys,unchangedkeys_sub,{"trail":"1"}) + "'><span class='stat_selector'>Standard</span></a>"
|
"Today":{"timerange":today()},
|
||||||
html += " | "
|
"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:
|
for c in categories:
|
||||||
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 += " | "
|
|
||||||
|
|
||||||
if delimitkeys.get("trail") == math.inf:
|
if c["active"]:
|
||||||
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>"
|
|
||||||
|
|
||||||
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
|
return html
|
||||||
|
22
maloja
22
maloja
@ -183,12 +183,21 @@ def getInstance():
|
|||||||
except:
|
except:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def getInstanceSupervisor():
|
||||||
|
try:
|
||||||
|
output = subprocess.check_output(["pidof","maloja_supervisor"])
|
||||||
|
pid = int(output)
|
||||||
|
return pid
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
def start():
|
def start():
|
||||||
if install():
|
if install():
|
||||||
|
|
||||||
if gotodir():
|
if gotodir():
|
||||||
setup()
|
setup()
|
||||||
p = subprocess.Popen(["python3","server.py"],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL)
|
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))
|
print(green("Maloja started!") + " PID: " + str(p.pid))
|
||||||
|
|
||||||
from doreah import settings
|
from doreah import settings
|
||||||
@ -221,8 +230,12 @@ def restart():
|
|||||||
return wasrunning
|
return wasrunning
|
||||||
|
|
||||||
def stop():
|
def stop():
|
||||||
|
pid_sv = getInstanceSupervisor()
|
||||||
|
if pid_sv is not None:
|
||||||
|
os.kill(pid_sv,signal.SIGTERM)
|
||||||
|
|
||||||
pid = getInstance()
|
pid = getInstance()
|
||||||
if pid == None:
|
if pid is None:
|
||||||
print("Server is not running")
|
print("Server is not running")
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
@ -283,8 +296,13 @@ def update():
|
|||||||
print("Done!")
|
print("Done!")
|
||||||
|
|
||||||
os.chmod("./maloja",os.stat("./maloja").st_mode | stat.S_IXUSR)
|
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
|
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,MRangeDescriptor): return t
|
||||||
|
|
||||||
if isinstance(t, str):
|
if isinstance(t, str):
|
||||||
|
if t in ["alltime"]: return None
|
||||||
tod = datetime.datetime.utcnow()
|
tod = datetime.datetime.utcnow()
|
||||||
months = ["january","february","march","april","may","june","july","august","september","october","november","december"]
|
months = ["january","february","march","april","may","june","july","august","september","october","november","december"]
|
||||||
weekdays = ["sunday","monday","tuesday","wednesday","thursday","friday","saturday"]
|
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):
|
def delimit_desc(step="month",stepn=1,trail=1):
|
||||||
txt = ""
|
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()]
|
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"
|
if trail is math.inf: txt += " Cumulative"
|
||||||
elif trail is not 1: txt += " Trailing" #we don't need all the info in the title
|
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
|
d_start = d_start.next(stepn-1) #last part of first included range
|
||||||
i = 0
|
i = 0
|
||||||
current_end = d_start
|
current_end = d_start
|
||||||
|
current_start = current_end.next((stepn*trail-1)*-1)
|
||||||
#ranges = []
|
#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:
|
if current_start == current_end:
|
||||||
yield current_start
|
yield current_start
|
||||||
#ranges.append(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)
|
yield MRange(current_start,current_end)
|
||||||
#ranges.append(MRange(current_start,current_end))
|
#ranges.append(MRange(current_start,current_end))
|
||||||
current_end = current_end.next(stepn)
|
current_end = current_end.next(stepn)
|
||||||
|
current_start = current_end.next((stepn*trail-1)*-1)
|
||||||
|
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
@ -619,6 +621,8 @@ def thismonth():
|
|||||||
def thisyear():
|
def thisyear():
|
||||||
tod = datetime.datetime.utcnow()
|
tod = datetime.datetime.utcnow()
|
||||||
return MTime(tod.year)
|
return MTime(tod.year)
|
||||||
|
def alltime():
|
||||||
|
return MRange(None,None)
|
||||||
|
|
||||||
#def _get_start_of(timestamp,unit):
|
#def _get_start_of(timestamp,unit):
|
||||||
# date = datetime.datetime.utcfromtimestamp(timestamp)
|
# date = datetime.datetime.utcfromtimestamp(timestamp)
|
||||||
|
Binary file not shown.
@ -1,6 +1,7 @@
|
|||||||
bottle>=0.12.16
|
bottle>=0.12.16
|
||||||
waitress>=1.3
|
waitress>=1.3
|
||||||
doreah>=0.9.1
|
doreah>=1.1.7
|
||||||
nimrodel>=0.4.9
|
nimrodel>=0.4.9
|
||||||
setproctitle>=1.1.10
|
setproctitle>=1.1.10
|
||||||
wand>=0.5.4
|
wand>=0.5.4
|
||||||
|
lesscpy>=0.13
|
||||||
|
@ -10,4 +10,5 @@ countas S Club 7 Tina Barrett
|
|||||||
countas RenoakRhythm Approaching Nirvana
|
countas RenoakRhythm Approaching Nirvana
|
||||||
countas Shirley Manson Garbage
|
countas Shirley Manson Garbage
|
||||||
countas Lewis Brindley The Yogscast
|
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
|
countas 2Yoon 4Minute
|
||||||
replaceartist 4minute 4Minute
|
replaceartist 4minute 4Minute
|
||||||
replacetitle 미쳐 Crazy
|
replacetitle 미쳐 Crazy
|
||||||
|
addartists HyunA Change Jun Hyung
|
||||||
|
|
||||||
# BLACKPINK
|
# BLACKPINK
|
||||||
countas Jennie BLACKPINK
|
countas Jennie BLACKPINK
|
||||||
@ -47,8 +48,8 @@ replacetitle 나비 (Butterfly) Butterfly
|
|||||||
replacetitle Déjà vu Déjà Vu
|
replacetitle Déjà vu Déjà Vu
|
||||||
replacetitle 라차타 (LA chA TA) LA chA TA
|
replacetitle 라차타 (LA chA TA) LA chA TA
|
||||||
replacetitle 여우 같은 내 친구 (No More) No More
|
replacetitle 여우 같은 내 친구 (No More) No More
|
||||||
replacetitle 시그널 (Signal) Signal
|
replacetitle 시그널 (Signal) Signal
|
||||||
replacetitle 미행 (그림자 : Shadow) Shadow
|
replacetitle 미행 (그림자 : Shadow) Shadow
|
||||||
|
|
||||||
# Stellar
|
# Stellar
|
||||||
replaceartist STELLAR Stellar
|
replaceartist STELLAR Stellar
|
||||||
@ -58,6 +59,7 @@ replacetitle 찔려 Sting Sting
|
|||||||
|
|
||||||
# Red Velvet
|
# Red Velvet
|
||||||
countas Seulgi Red Velvet
|
countas Seulgi Red Velvet
|
||||||
|
countas Joy Red Velvet
|
||||||
replacetitle 러시안 룰렛 Russian Roulette Russian Roulette
|
replacetitle 러시안 룰렛 Russian Roulette Russian Roulette
|
||||||
replacetitle 피카부 Peek-a-Boo Peek-A-Boo
|
replacetitle 피카부 Peek-a-Boo Peek-A-Boo
|
||||||
replacetitle 빨간 맛 Red Flavor Red Flavor
|
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 Like Ooh-Ahh
|
||||||
replacetitle OOH-AHH Like Ooh-Ahh
|
replacetitle OOH-AHH Like Ooh-Ahh
|
||||||
replacetitle LIKEY Likey
|
replacetitle LIKEY Likey
|
||||||
|
countas Tzuyu TWICE
|
||||||
|
|
||||||
# AOA
|
# AOA
|
||||||
countas AOA Black AOA
|
countas AOA Black AOA
|
||||||
@ -145,5 +148,8 @@ replaceartist A pink Apink
|
|||||||
# Chungha & IOI
|
# Chungha & IOI
|
||||||
replaceartist CHUNG HA Chungha
|
replaceartist CHUNG HA Chungha
|
||||||
replaceartist 청하 CHUNGHA 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
|
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.
|
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
|
Second column is the artist
|
||||||
Third column the replacement artist / grouping label
|
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'
|
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.
|
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 Dal Shabet Dal★Shabet
|
||||||
replaceartist Mr FijiWiji, AgNO3 Mr FijiWiji␟AgNO3 # one artist is replaced by two artists
|
replaceartist Mr FijiWiji, AgNO3 Mr FijiWiji␟AgNO3 # one artist is replaced by two artists
|
||||||
countas Trouble Maker HyunA
|
countas Trouble Maker HyunA
|
||||||
|
addartists HyunA Change Jun Hyung
|
||||||
|
@ -28,6 +28,18 @@ pages = {
|
|||||||
"https://open.spotify.com"
|
"https://open.spotify.com"
|
||||||
],
|
],
|
||||||
"script":"spotify.js"
|
"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"];
|
patterns = pages[page]["patterns"];
|
||||||
//console.log("Page was managed by a " + page + " manager")
|
//console.log("Page was managed by a " + page + " manager")
|
||||||
for (var i=0;i<patterns.length;i++) {
|
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!")
|
//console.log("Still on same page!")
|
||||||
tabManagers[tabId].update();
|
tabManagers[tabId].update();
|
||||||
|
|
||||||
@ -67,7 +79,7 @@ function onTabUpdated(tabId, changeInfo, tab) {
|
|||||||
if (pages.hasOwnProperty(key)) {
|
if (pages.hasOwnProperty(key)) {
|
||||||
patterns = pages[key]["patterns"];
|
patterns = pages[key]["patterns"];
|
||||||
for (var i=0;i<patterns.length;i++) {
|
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!");
|
console.log("New page on tab " + tabId + " will be handled by new " + key + " manager!");
|
||||||
tabManagers[tabId] = new Controller(tabId,key);
|
tabManagers[tabId] = new Controller(tabId,key);
|
||||||
updateTabNum();
|
updateTabNum();
|
||||||
@ -166,8 +178,13 @@ class Controller {
|
|||||||
actuallyupdate() {
|
actuallyupdate() {
|
||||||
this.messageID++;
|
this.messageID++;
|
||||||
//console.log("Update! Our page is " + this.page + ", our tab id " + this.tabId)
|
//console.log("Update! Our page is " + this.page + ", our tab id " + this.tabId)
|
||||||
chrome.tabs.executeScript(this.tabId,{"file":"sites/" + pages[this.page]["script"]});
|
try {
|
||||||
chrome.tabs.executeScript(this.tabId,{"file":"sitescript.js"});
|
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;
|
this.alreadyQueued = false;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "Maloja Scrobbler",
|
"name": "Maloja Scrobbler",
|
||||||
"version": "1.3",
|
"version": "1.4",
|
||||||
"description": "Scrobbles tracks from various sites to your Maloja server",
|
"description": "Scrobbles tracks from various sites to your Maloja server",
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"permissions": ["activeTab",
|
"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_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);
|
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");
|
console.log("Not playing right now");
|
||||||
chrome.runtime.sendMessage({type:"stopPlayback",time:Math.floor(Date.now()),artist:artist,title:title});
|
chrome.runtime.sendMessage({type:"stopPlayback",time:Math.floor(Date.now()),artist:artist,title:title});
|
||||||
//stopPlayback()
|
//stopPlayback()
|
||||||
}
|
}
|
||||||
else if (control == "Pause") {
|
else if (control == label_playing) {
|
||||||
console.log("Playing " + artist + " - " + title + " (" + durationSeconds + " sec)");
|
console.log("Playing " + artist + " - " + title + " (" + durationSeconds + " sec)");
|
||||||
chrome.runtime.sendMessage({type:"startPlayback",time:Math.floor(Date.now()),artist:artist,title:title,duration:durationSeconds});
|
chrome.runtime.sendMessage({type:"startPlayback",time:Math.floor(Date.now()),artist:artist,title:title,duration:durationSeconds});
|
||||||
//startPlayback(artist,title,durationSeconds)
|
//startPlayback(artist,title,durationSeconds)
|
||||||
|
42
server.py
42
server.py
@ -1,17 +1,17 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
# server stuff
|
# 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
|
import waitress
|
||||||
# monkey patching
|
# monkey patching
|
||||||
import monkey
|
import monkey
|
||||||
# rest of the project
|
# rest of the project
|
||||||
import database
|
import database
|
||||||
import utilities
|
|
||||||
import htmlmodules
|
import htmlmodules
|
||||||
import htmlgenerators
|
import htmlgenerators
|
||||||
import malojatime
|
import malojatime
|
||||||
from utilities import *
|
import utilities
|
||||||
|
from utilities import resolveImage
|
||||||
from urihandler import uri_to_internal, remove_identical
|
from urihandler import uri_to_internal, remove_identical
|
||||||
import urihandler
|
import urihandler
|
||||||
# doreah toolkit
|
# doreah toolkit
|
||||||
@ -26,20 +26,28 @@ import os
|
|||||||
import setproctitle
|
import setproctitle
|
||||||
# url handling
|
# url handling
|
||||||
import urllib
|
import urllib
|
||||||
import urllib.request
|
|
||||||
import urllib.parse
|
|
||||||
from urllib.error import *
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#settings.config(files=["settings/default.ini","settings/settings.ini"])
|
#settings.config(files=["settings/default.ini","settings/settings.ini"])
|
||||||
#settings.update("settings/default.ini","settings/settings.ini")
|
#settings.update("settings/default.ini","settings/settings.ini")
|
||||||
MAIN_PORT = settings.get_settings("WEB_PORT")
|
MAIN_PORT = settings.get_settings("WEB_PORT")
|
||||||
|
HOST = settings.get_settings("HOST")
|
||||||
|
|
||||||
|
|
||||||
webserver = Bottle()
|
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("")
|
||||||
@webserver.route("/")
|
@webserver.route("/")
|
||||||
def mainpage():
|
def mainpage():
|
||||||
@ -77,7 +85,11 @@ def customerror(error):
|
|||||||
|
|
||||||
def graceful_exit(sig=None,frame=None):
|
def graceful_exit(sig=None,frame=None):
|
||||||
#urllib.request.urlopen("http://[::1]:" + str(DATABASE_PORT) + "/sync")
|
#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...")
|
log("Server shutting down...")
|
||||||
os._exit(42)
|
os._exit(42)
|
||||||
|
|
||||||
@ -121,6 +133,7 @@ def static_image(pth):
|
|||||||
#@webserver.route("/<name:re:.*\\.html>")
|
#@webserver.route("/<name:re:.*\\.html>")
|
||||||
@webserver.route("/<name:re:.*\\.js>")
|
@webserver.route("/<name:re:.*\\.js>")
|
||||||
@webserver.route("/<name:re:.*\\.css>")
|
@webserver.route("/<name:re:.*\\.css>")
|
||||||
|
@webserver.route("/<name:re:.*\\.less>")
|
||||||
@webserver.route("/<name:re:.*\\.png>")
|
@webserver.route("/<name:re:.*\\.png>")
|
||||||
@webserver.route("/<name:re:.*\\.jpeg>")
|
@webserver.route("/<name:re:.*\\.jpeg>")
|
||||||
@webserver.route("/<name:re:.*\\.ico>")
|
@webserver.route("/<name:re:.*\\.ico>")
|
||||||
@ -132,7 +145,7 @@ def static(name):
|
|||||||
|
|
||||||
@webserver.route("/<name>")
|
@webserver.route("/<name>")
|
||||||
def static_html(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))
|
keys = remove_identical(FormsDict.decode(request.query))
|
||||||
|
|
||||||
# if a pyhp file exists, use this
|
# if a pyhp file exists, use this
|
||||||
@ -206,6 +219,16 @@ def static_html(name):
|
|||||||
return html
|
return html
|
||||||
#return static_file("website/" + name + ".html",root="")
|
#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
|
#set graceful shutdown
|
||||||
signal.signal(signal.SIGINT, graceful_exit)
|
signal.signal(signal.SIGINT, graceful_exit)
|
||||||
signal.signal(signal.SIGTERM, graceful_exit)
|
signal.signal(signal.SIGTERM, graceful_exit)
|
||||||
@ -215,8 +238,7 @@ setproctitle.setproctitle("Maloja")
|
|||||||
|
|
||||||
## start database
|
## start database
|
||||||
database.start_db()
|
database.start_db()
|
||||||
#database.register_subroutes(webserver,"/api")
|
|
||||||
database.dbserver.mount(server=webserver)
|
database.dbserver.mount(server=webserver)
|
||||||
|
|
||||||
log("Starting up Maloja server...")
|
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]
|
[HTTP]
|
||||||
|
|
||||||
WEB_PORT = 42010
|
WEB_PORT = 42010
|
||||||
|
HOST = "::" # You most likely want either :: for IPv6 or 0.0.0.0 for IPv4 here
|
||||||
|
|
||||||
[Third Party Services]
|
[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_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"
|
FANARTTV_API_KEY = "ASK"
|
||||||
SPOTIFY_API_ID = "ASK"
|
SPOTIFY_API_ID = "ASK"
|
||||||
SPOTIFY_API_SECRET = "ASK"
|
SPOTIFY_API_SECRET = "ASK"
|
||||||
CACHE_EXPIRE_NEGATIVE = 30 # after how many days negative results should be tried again
|
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
|
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]
|
[Database]
|
||||||
|
|
||||||
DB_CACHE_SIZE = 8192 # how many MB on disk each database cache should have available.
|
DB_CACHE_SIZE = 8192 # how many MB on disk each database cache should have available.
|
||||||
|
INVALID_ARTISTS = ["[Unknown Artist]","Unknown Artist"]
|
||||||
|
|
||||||
[Local Images]
|
[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_ARTISTS = year
|
||||||
DEFAULT_RANGE_CHARTS_TRACKS = year
|
DEFAULT_RANGE_CHARTS_TRACKS = year
|
||||||
# same for pulse view
|
# same for pulse view
|
||||||
# can be days, weeks, months, years
|
# can be day, week, month, year
|
||||||
DEFAULT_RANGE_PULSE = months
|
DEFAULT_STEP_PULSE = month
|
||||||
|
|
||||||
|
# display top tiles on artist and track chart pages
|
||||||
|
CHARTS_DISPLAY_TILES = false
|
||||||
|
|
||||||
[Fluff]
|
[Fluff]
|
||||||
|
|
||||||
@ -36,6 +50,8 @@ DEFAULT_RANGE_PULSE = months
|
|||||||
SCROBBLES_GOLD = 250
|
SCROBBLES_GOLD = 250
|
||||||
SCROBBLES_PLATINUM = 500
|
SCROBBLES_PLATINUM = 500
|
||||||
SCROBBLES_DIAMOND = 1000
|
SCROBBLES_DIAMOND = 1000
|
||||||
|
# name for comparisons
|
||||||
|
NAME = "Generic Maloja User"
|
||||||
|
|
||||||
[Misc]
|
[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
|
#4
|
||||||
resultkeys4 = {"max_":300}
|
resultkeys4 = {"page":0,"perpage":100}
|
||||||
if "max" in keys: resultkeys4["max_"] = int(keys["max"])
|
# 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
|
return resultkeys1, resultkeys2, resultkeys3, resultkeys4
|
||||||
@ -146,8 +150,12 @@ def internal_to_uri(keys):
|
|||||||
urikeys.append("trail",str(keys["trail"]))
|
urikeys.append("trail",str(keys["trail"]))
|
||||||
|
|
||||||
# stuff
|
# stuff
|
||||||
if "max_" in keys:
|
#if "max_" in keys:
|
||||||
urikeys.append("max",str(keys["max_"]))
|
# 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
|
return urikeys
|
||||||
|
35
utilities.py
35
utilities.py
@ -99,14 +99,11 @@ def consistentRulestate(folder,checksums):
|
|||||||
if (scrobblefile.endswith(".tsv")):
|
if (scrobblefile.endswith(".tsv")):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
f = open(folder + "/" + scrobblefile + ".rulestate","r")
|
with open(folder + "/" + scrobblefile + ".rulestate","r") as f:
|
||||||
if f.read() != checksums:
|
if f.read() != checksums:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
except:
|
except:
|
||||||
return False
|
return False
|
||||||
finally:
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -442,8 +439,12 @@ def update_medals():
|
|||||||
|
|
||||||
from database import MEDALS, MEDALS_TRACKS, STAMPS, get_charts_artists, get_charts_tracks
|
from database import MEDALS, MEDALS_TRACKS, STAMPS, get_charts_artists, get_charts_tracks
|
||||||
|
|
||||||
firstyear = datetime.datetime.utcfromtimestamp(STAMPS[0]).year
|
|
||||||
currentyear = datetime.datetime.utcnow().year
|
currentyear = datetime.datetime.utcnow().year
|
||||||
|
try:
|
||||||
|
firstyear = datetime.datetime.utcfromtimestamp(STAMPS[0]).year
|
||||||
|
except:
|
||||||
|
firstyear = currentyear
|
||||||
|
|
||||||
|
|
||||||
MEDALS.clear()
|
MEDALS.clear()
|
||||||
for year in range(firstyear,currentyear):
|
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"] == 2: MEDALS_TRACKS.setdefault(track,{}).setdefault("silver",[]).append(year)
|
||||||
elif t["rank"] == 3: MEDALS_TRACKS.setdefault(track,{}).setdefault("bronze",[]).append(year)
|
elif t["rank"] == 3: MEDALS_TRACKS.setdefault(track,{}).setdefault("bronze",[]).append(year)
|
||||||
else: break
|
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>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<title>Maloja - KEY_ARTISTNAME</title>
|
<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>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
@ -15,13 +16,13 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class="text">
|
<td class="text">
|
||||||
<h1>KEY_ARTISTNAME</h1>
|
<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/>
|
<br/>
|
||||||
<span>KEY_ASSOCIATED</span>
|
<span>KEY_ASSOCIATED</span>
|
||||||
<p class="stats"><a href="/scrobbles?artist=KEY_ENC_ARTISTNAME">KEY_SCROBBLES Scrobbles</a></p>
|
<p class="stats"><a href="/scrobbles?artist=KEY_ENC_ARTISTNAME">KEY_SCROBBLES Scrobbles</a></p>
|
||||||
|
|
||||||
<p class="desc">KEY_DESCRIPTION</p>
|
<p class="desc">KEY_DESCRIPTION</p>
|
||||||
<span>KEY_MEDALS</span>
|
<span>KEY_MEDALS</span> <span>KEY_TOPWEEKS</span> <span>KEY_CERTS</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
@ -32,35 +33,35 @@
|
|||||||
<table class="twopart">
|
<table class="twopart">
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<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="showRangeManual('pulse','day')" class="stat_selector_pulse selector_pulse_day">7 days</span>
|
||||||
| <span onclick="showRange('pulse','weeks')" class="stat_selector_pulse selector_pulse_weeks">12 weeks</span>
|
| <span onclick="showRangeManual('pulse','week')" class="stat_selector_pulse selector_pulse_week">12 weeks</span>
|
||||||
| <span onclick="showRange('pulse','months')" class="stat_selector_pulse selector_pulse_months" style="opacity:0.5;">12 months</span>
|
| <span onclick="showRangeManual('pulse','month')" class="stat_selector_pulse selector_pulse_month" 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','year')" class="stat_selector_pulse selector_pulse_year">10 years</span>
|
||||||
|
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
|
|
||||||
<span class="stat_module_pulse pulse_months">KEY_PULSE_MONTHS</span>
|
<span class="stat_module_pulse pulse_month">KEY_PULSE_MONTHS</span>
|
||||||
<span class="stat_module_pulse pulse_days" style="display:none;">KEY_PULSE_DAYS</span>
|
<span class="stat_module_pulse pulse_day" 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_year" 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_week" style="display:none;">KEY_PULSE_WEEKS</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<!-- We use the same classes / function calls here because we want it to switch together with pulse -->
|
<!-- 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>
|
<h2><a class="stat_link_pulse" href='/performance?artist=KEY_ENC_CREDITEDARTISTNAME&trail=1&step=month'>Performance</a></h2>
|
||||||
<span onclick="showRange('pulse','days')" class="stat_selector_pulse selector_pulse_days">7 days</span>
|
<span onclick="showRangeManual('pulse','day')" class="stat_selector_pulse selector_pulse_day">7 days</span>
|
||||||
| <span onclick="showRange('pulse','weeks')" class="stat_selector_pulse selector_pulse_weeks">12 weeks</span>
|
| <span onclick="showRangeManual('pulse','week')" class="stat_selector_pulse selector_pulse_week">12 weeks</span>
|
||||||
| <span onclick="showRange('pulse','months')" class="stat_selector_pulse selector_pulse_months" style="opacity:0.5;">12 months</span>
|
| <span onclick="showRangeManual('pulse','month')" class="stat_selector_pulse selector_pulse_month" 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','year')" class="stat_selector_pulse selector_pulse_year">10 years</span>
|
||||||
|
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
|
|
||||||
<span class="stat_module_pulse pulse_months">KEY_PERFORMANCE_MONTHS</span>
|
<span class="stat_module_pulse pulse_month">KEY_PERFORMANCE_MONTHS</span>
|
||||||
<span class="stat_module_pulse pulse_days" style="display:none;">KEY_PERFORMANCE_DAYS</span>
|
<span class="stat_module_pulse pulse_day" 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_year" 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_week" style="display:none;">KEY_PERFORMANCE_WEEKS</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
@ -5,7 +5,7 @@ from malojatime import today,thisweek,thismonth,thisyear
|
|||||||
|
|
||||||
def instructions(keys):
|
def instructions(keys):
|
||||||
from utilities import getArtistImage
|
from utilities import getArtistImage
|
||||||
from htmlgenerators import artistLink, artistLinks
|
from htmlgenerators import artistLink, artistLinks, link_address
|
||||||
from urihandler import compose_querystring, uri_to_internal
|
from urihandler import compose_querystring, uri_to_internal
|
||||||
from htmlmodules import module_pulse, module_performance, module_trackcharts, module_scrobblelist
|
from htmlmodules import module_pulse, module_performance, module_trackcharts, module_scrobblelist
|
||||||
|
|
||||||
@ -22,13 +22,31 @@ def instructions(keys):
|
|||||||
if "medals" in data and data["medals"] is not None:
|
if "medals" in data and data["medals"] is not None:
|
||||||
if "gold" in data["medals"]:
|
if "gold" in data["medals"]:
|
||||||
for y in data["medals"]["gold"]:
|
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"]:
|
if "silver" in data["medals"]:
|
||||||
for y in data["medals"]["silver"]:
|
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"]:
|
if "bronze" in data["medals"]:
|
||||||
for y in data["medals"]["bronze"]:
|
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")
|
credited = data.get("replace")
|
||||||
includestr = " "
|
includestr = " "
|
||||||
@ -69,6 +87,8 @@ def instructions(keys):
|
|||||||
"KEY_POSITION":pos,
|
"KEY_POSITION":pos,
|
||||||
"KEY_ASSOCIATED":includestr,
|
"KEY_ASSOCIATED":includestr,
|
||||||
"KEY_MEDALS":html_medals,
|
"KEY_MEDALS":html_medals,
|
||||||
|
"KEY_CERTS":html_cert,
|
||||||
|
"KEY_TOPWEEKS":html_topweeks,
|
||||||
# tracks
|
# tracks
|
||||||
"KEY_TRACKLIST":html_tracks,
|
"KEY_TRACKLIST":html_tracks,
|
||||||
# pulse
|
# pulse
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
<table class="top_info">
|
<table class="top_info">
|
||||||
<tr>
|
<tr>
|
||||||
<td class="image">
|
<td class="image">
|
||||||
<div style="background-image:url('KEY_TOPARTIST_IMAGEURL')"></div>
|
KEY_TOPARTIST_IMAGEDIV
|
||||||
</td>
|
</td>
|
||||||
<td class="text">
|
<td class="text">
|
||||||
<h1>Artist Charts</h1><a href="/top_artists"><span>View #1 Artists</span></a><br/>
|
<h1>Artist Charts</h1><a href="/top_artists"><span>View #1 Artists</span></a><br/>
|
||||||
@ -24,6 +24,9 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<span class="stat_module_topartists">
|
||||||
|
KEY_ARTISTCHART
|
||||||
|
</span>
|
||||||
|
|
||||||
KEY_ARTISTLIST
|
KEY_ARTISTLIST
|
||||||
|
|
||||||
|
@ -4,8 +4,9 @@ import urllib
|
|||||||
def instructions(keys):
|
def instructions(keys):
|
||||||
from utilities import getArtistImage
|
from utilities import getArtistImage
|
||||||
from urihandler import compose_querystring, uri_to_internal
|
from urihandler import compose_querystring, uri_to_internal
|
||||||
from htmlmodules import module_artistcharts, module_filterselection
|
from htmlmodules import module_artistcharts, module_filterselection, module_artistcharts_tiles
|
||||||
from malojatime import range_desc
|
from malojatime import range_desc
|
||||||
|
from doreah.settings import get_settings
|
||||||
|
|
||||||
|
|
||||||
_, timekeys, _, amountkeys = uri_to_internal(keys)
|
_, timekeys, _, amountkeys = uri_to_internal(keys)
|
||||||
@ -16,6 +17,7 @@ def instructions(keys):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
html_charts, rep = module_artistcharts(**amountkeys,**timekeys)
|
html_charts, rep = module_artistcharts(**amountkeys,**timekeys)
|
||||||
|
|
||||||
if rep is not None:
|
if rep is not None:
|
||||||
@ -23,12 +25,23 @@ def instructions(keys):
|
|||||||
else:
|
else:
|
||||||
imgurl = ""
|
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 []
|
pushresources = [{"file":imgurl,"type":"image"}] if imgurl.startswith("/") else []
|
||||||
|
|
||||||
|
|
||||||
replace = {"KEY_TOPARTIST_IMAGEURL":imgurl,
|
replace = {
|
||||||
"KEY_ARTISTLIST":html_charts,
|
"KEY_TOPARTIST_IMAGEDIV":imgdiv,
|
||||||
"KEY_RANGE":limitstring,
|
"KEY_ARTISTCHART":html_tiles,
|
||||||
"KEY_FILTERSELECTOR":html_filterselector}
|
"KEY_ARTISTLIST":html_charts,
|
||||||
|
"KEY_RANGE":limitstring,
|
||||||
|
"KEY_FILTERSELECTOR":html_filterselector
|
||||||
|
}
|
||||||
|
|
||||||
return (replace,pushresources)
|
return (replace,pushresources)
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
<table class="top_info">
|
<table class="top_info">
|
||||||
<tr>
|
<tr>
|
||||||
<td class="image">
|
<td class="image">
|
||||||
<div style="background-image:url('KEY_TOPARTIST_IMAGEURL')"></div>
|
KEY_TOPARTIST_IMAGEDIV
|
||||||
</td>
|
</td>
|
||||||
<td class="text">
|
<td class="text">
|
||||||
<h1>Track Charts</h1>TOP_TRACKS_LINK<br/>
|
<h1>Track Charts</h1>TOP_TRACKS_LINK<br/>
|
||||||
@ -22,6 +22,9 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<span class="stat_module_topartists">
|
||||||
|
KEY_TRACKCHART
|
||||||
|
</span>
|
||||||
|
|
||||||
KEY_TRACKLIST
|
KEY_TRACKLIST
|
||||||
|
|
||||||
|
@ -5,8 +5,9 @@ def instructions(keys):
|
|||||||
from utilities import getArtistImage, getTrackImage
|
from utilities import getArtistImage, getTrackImage
|
||||||
from htmlgenerators import artistLink
|
from htmlgenerators import artistLink
|
||||||
from urihandler import compose_querystring, uri_to_internal
|
from urihandler import compose_querystring, uri_to_internal
|
||||||
from htmlmodules import module_trackcharts, module_filterselection
|
from htmlmodules import module_trackcharts, module_filterselection, module_trackcharts_tiles
|
||||||
from malojatime import range_desc
|
from malojatime import range_desc
|
||||||
|
from doreah.settings import get_settings
|
||||||
|
|
||||||
filterkeys, timekeys, _, amountkeys = uri_to_internal(keys)
|
filterkeys, timekeys, _, amountkeys = uri_to_internal(keys)
|
||||||
|
|
||||||
@ -23,6 +24,9 @@ def instructions(keys):
|
|||||||
html_charts, rep = module_trackcharts(**amountkeys,**timekeys,**filterkeys)
|
html_charts, rep = module_trackcharts(**amountkeys,**timekeys,**filterkeys)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
html_tiles = ""
|
||||||
|
|
||||||
if filterkeys.get("artist") is not None:
|
if filterkeys.get("artist") is not None:
|
||||||
imgurl = getArtistImage(filterkeys.get("artist"))
|
imgurl = getArtistImage(filterkeys.get("artist"))
|
||||||
limitstring = "by " + artistLink(filterkeys.get("artist"))
|
limitstring = "by " + artistLink(filterkeys.get("artist"))
|
||||||
@ -31,6 +35,15 @@ def instructions(keys):
|
|||||||
else:
|
else:
|
||||||
imgurl = ""
|
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)
|
limitstring += " " + timekeys["timerange"].desc(prefix=True)
|
||||||
|
|
||||||
pushresources = [{"file":imgurl,"type":"image"}] if imgurl.startswith("/") else []
|
pushresources = [{"file":imgurl,"type":"image"}] if imgurl.startswith("/") else []
|
||||||
@ -38,7 +51,8 @@ def instructions(keys):
|
|||||||
|
|
||||||
|
|
||||||
replace = {
|
replace = {
|
||||||
"KEY_TOPARTIST_IMAGEURL":imgurl,
|
"KEY_TOPARTIST_IMAGEDIV":imgdiv,
|
||||||
|
"KEY_TRACKCHART":html_tiles,
|
||||||
"KEY_TRACKLIST":html_charts,
|
"KEY_TRACKLIST":html_charts,
|
||||||
"KEY_LIMITS":limitstring,
|
"KEY_LIMITS":limitstring,
|
||||||
"KEY_FILTERSELECTOR":html_filterselector,
|
"KEY_FILTERSELECTOR":html_filterselector,
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
<meta name="description" content='Maloja is a self-hosted music scrobble server.' />
|
<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" />
|
<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>
|
<script src="javascript/cookies.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body onload="insertAPIKeyFromCookie()">
|
<body>
|
||||||
<table class="top_info">
|
<table class="top_info">
|
||||||
<tr>
|
<tr>
|
||||||
<td class="image">
|
<td class="image">
|
||||||
|
@ -1,32 +1,81 @@
|
|||||||
apikeycorrect = false;
|
apikeycorrect = false;
|
||||||
|
|
||||||
function insertAPIKeyFromCookie() {
|
var cookies = {};
|
||||||
cookies = decodeURIComponent(document.cookie).split(';');
|
|
||||||
for(var i = 0; i <cookies.length; i++) {
|
function getCookies() {
|
||||||
cookies[i] = cookies[i].trim()
|
cookiestrings = decodeURIComponent(document.cookie).split(';');
|
||||||
if (cookies[i].startsWith("apikey=")) {
|
for(var i = 0; i <cookiestrings.length; i++) {
|
||||||
document.getElementById("apikey").value = cookies[i].replace("apikey=","")
|
cookiestrings[i] = cookiestrings[i].trim();
|
||||||
checkAPIkey()
|
[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() {
|
function saveAPIkey() {
|
||||||
key = document.getElementById("apikey").value
|
key = APIkey();
|
||||||
document.cookie = "apikey=" + encodeURIComponent(key)
|
setCookie("apikey",key,false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function checkAPIkey() {
|
function checkAPIkey() {
|
||||||
saveAPIkey()
|
|
||||||
url = "/api/test?key=" + document.getElementById("apikey").value
|
url = "/api/test?key=" + APIkey()
|
||||||
var xhttp = new XMLHttpRequest();
|
var xhttp = new XMLHttpRequest();
|
||||||
xhttp.onreadystatechange = function() {
|
xhttp.onreadystatechange = function() {
|
||||||
if (this.readyState == 4 && (this.status == 204 || this.status == 205)) {
|
if (this.readyState == 4 && (this.status == 204 || this.status == 205)) {
|
||||||
document.getElementById("apikey").style.backgroundColor = "lawngreen"
|
document.getElementById("apikey").style.backgroundColor = "lawngreen"
|
||||||
apikeycorrect = true
|
apikeycorrect = true
|
||||||
|
saveAPIkey();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
document.getElementById("apikey").style.backgroundColor = "red"
|
document.getElementById("apikey").style.backgroundColor = "red"
|
||||||
|
@ -1,28 +1,53 @@
|
|||||||
function showRange(identifier,unit) {
|
function showRange(identifier,unit) {
|
||||||
// Make all modules disappear
|
// Make all modules disappear
|
||||||
modules = document.getElementsByClassName("stat_module_" + identifier)
|
modules = document.getElementsByClassName("stat_module_" + identifier);
|
||||||
for (var i=0;i<modules.length;i++) {
|
for (var i=0;i<modules.length;i++) {
|
||||||
//modules[i].setAttribute("style","width:0px;overflow:hidden;")
|
//modules[i].setAttribute("style","width:0px;overflow:hidden;")
|
||||||
// cheesy trick to make the allocated space always whatever the biggest module needs
|
// cheesy trick to make the allocated space always whatever the biggest module needs
|
||||||
// somehow that messes up pulse on the start page tho
|
// 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
|
// Make requested module appear
|
||||||
reactivate = document.getElementsByClassName(identifier + "_" + unit)
|
reactivate = document.getElementsByClassName(identifier + "_" + unit);
|
||||||
for (var i=0;i<reactivate.length;i++) {
|
for (var i=0;i<reactivate.length;i++) {
|
||||||
reactivate[i].setAttribute("style","")
|
reactivate[i].setAttribute("style","");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set all selectors to unselected
|
// 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++) {
|
for (var i=0;i<selectors.length;i++) {
|
||||||
selectors[i].setAttribute("style","")
|
selectors[i].setAttribute("style","");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the active selector to selected
|
// 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++) {
|
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 {
|
body {
|
||||||
background-color:#333337;
|
|
||||||
color:beige;
|
|
||||||
font-family:"Ubuntu";
|
|
||||||
padding:15px;
|
padding:15px;
|
||||||
padding-bottom:35px;
|
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"] {
|
input[type="date"] {
|
||||||
@ -42,6 +24,9 @@ input[type="date"] {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Header (unused)
|
Header (unused)
|
||||||
**/
|
**/
|
||||||
@ -76,7 +61,7 @@ div.footer {
|
|||||||
position:fixed;
|
position:fixed;
|
||||||
height:20px;
|
height:20px;
|
||||||
/**width:100%;**/
|
/**width:100%;**/
|
||||||
background-color:rgba(10,10,10,0.9);
|
background-color:@BASE_COLOR_DARK;
|
||||||
bottom:0px;
|
bottom:0px;
|
||||||
left:0px;
|
left:0px;
|
||||||
right:0px;
|
right:0px;
|
||||||
@ -185,7 +170,7 @@ div.searchresults table.searchresults_tracks td span:nth-child(1) {
|
|||||||
position:fixed;
|
position:fixed;
|
||||||
/*height:30px;*/
|
/*height:30px;*/
|
||||||
/**width:100%;**/
|
/**width:100%;**/
|
||||||
background-color:rgba(10,10,10,0.9);
|
background-color:@BASE_COLOR_DARK;
|
||||||
bottom:0px;
|
bottom:0px;
|
||||||
left:0px;
|
left:0px;
|
||||||
right: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 {
|
p.desc a {
|
||||||
padding-left:20px;
|
padding-left:20px;
|
||||||
@ -257,7 +212,10 @@ p.desc a {
|
|||||||
background-image:url("https://www.last.fm/static/images/lastfm_avatar_twitter.66cd2c48ce03.png");
|
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 {
|
.stats {
|
||||||
color:grey;
|
color:@TEXT_COLOR_TERTIARY;
|
||||||
}
|
}
|
||||||
.rank {
|
.rank {
|
||||||
text-align:right;
|
text-align:right;
|
||||||
color:grey;
|
color:@TEXT_COLOR_TERTIARY;
|
||||||
}
|
}
|
||||||
.extra {
|
.extra {
|
||||||
color:gray; /*sue me*/
|
color:@TEXT_COLOR_TERTIARY;
|
||||||
font-size:80%;
|
font-size:80%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,14 +255,14 @@ input#apikey {
|
|||||||
|
|
||||||
input.simpleinput {
|
input.simpleinput {
|
||||||
font-family:'Ubuntu';
|
font-family:'Ubuntu';
|
||||||
color:beige;
|
color:@TEXT_COLOR;
|
||||||
outline:none;
|
outline:none;
|
||||||
border-top: 0px solid;
|
border-top: 0px solid;
|
||||||
border-left: 0px solid;
|
border-left: 0px solid;
|
||||||
border-right: 0px solid;
|
border-right: 0px solid;
|
||||||
padding:2px;
|
padding:2px;
|
||||||
background-color:inherit;
|
background-color:inherit;
|
||||||
border-bottom: 1px solid beige;
|
border-bottom: 1px solid @TEXT_COLOR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -379,6 +342,15 @@ img.certrecord {
|
|||||||
height:30px;
|
height:30px;
|
||||||
vertical-align: text-bottom;
|
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 {
|
table.list td.time {
|
||||||
width:11%;
|
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 {
|
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 {
|
table.list td.bar {
|
||||||
width:500px;
|
width:500px;
|
||||||
background-color:#333337;
|
background-color:@BASE_COLOR;
|
||||||
/* Remove 5er separators for bars */
|
/* Remove 5er separators for bars */
|
||||||
/*border-color:rgba(0,0,0,0)!important;*/
|
/*border-color:rgba(0,0,0,0)!important;*/
|
||||||
}
|
}
|
||||||
table.list td.bar div {
|
table.list td.bar div {
|
||||||
background-color:beige;
|
background-color:@TEXT_COLOR;
|
||||||
height:20px; /* can only do this absolute apparently */
|
height:20px; /* can only do this absolute apparently */
|
||||||
position:relative;
|
position:relative;
|
||||||
}
|
}
|
||||||
table.list tr:hover td.bar div {
|
table.list tr:hover td.bar div {
|
||||||
background-color:yellow;
|
background-color:@FOCUS_COLOR;
|
||||||
cursor:pointer;
|
cursor:pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
table.list td.chart {
|
table.list td.chart {
|
||||||
width:500px;
|
width:500px;
|
||||||
background-color:#333337;
|
background-color:@BASE_COLOR;
|
||||||
/* Remove 5er separators for bars */
|
/* Remove 5er separators for bars */
|
||||||
/*border-color:rgba(0,0,0,0)!important;*/
|
/*border-color:rgba(0,0,0,0)!important;*/
|
||||||
}
|
}
|
||||||
@ -581,8 +556,14 @@ table.list tr td.button {
|
|||||||
|
|
||||||
|
|
||||||
table.list td.button div {
|
table.list td.button div {
|
||||||
background-color:yellow;
|
background-color:@BUTTON_BG_COLOR;
|
||||||
color:#333337;
|
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;
|
padding:3px;
|
||||||
border-radius:4px;
|
border-radius:4px;
|
||||||
}
|
}
|
@ -154,7 +154,7 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<body onload="insertAPIKeyFromCookie()">
|
<body>
|
||||||
|
|
||||||
<table class="top_info">
|
<table class="top_info">
|
||||||
<tr>
|
<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 htmlmodules import module_performance, module_filterselection
|
||||||
from malojatime import range_desc, delimit_desc
|
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
|
#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 = "/pulse?" + compose_querystring(pulselink_keys)
|
||||||
|
|
||||||
pulselink = "<a href=\"" + pulselink + "\"><span>View Pulse</span></a>"
|
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 = {
|
replace = {
|
||||||
"KEY_PULSE_LINK":pulselink,
|
"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 htmlmodules import module_pulse, module_filterselection
|
||||||
from malojatime import range_desc, delimit_desc
|
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
|
#equivalent performance chart if we're not looking at the overall pulse
|
||||||
if len(filterkeys) != 0:
|
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 = "/performance?" + compose_querystring(performancelink_keys)
|
||||||
|
|
||||||
performancelink = "<a href=\"" + performancelink + "\"><span>View Rankings</span></a>"
|
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 = {
|
replace = {
|
||||||
"KEY_RANKINGS_LINK":performancelink,
|
"KEY_RANKINGS_LINK":performancelink,
|
||||||
|
@ -58,7 +58,7 @@
|
|||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body onload="replace();insertAPIKeyFromCookie()">
|
<body onload="replace()">
|
||||||
<table class="top_info">
|
<table class="top_info">
|
||||||
<tr>
|
<tr>
|
||||||
<td class="image">
|
<td class="image">
|
||||||
|
@ -4,10 +4,12 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<title>Maloja</title>
|
<title>Maloja</title>
|
||||||
<script src="javascript/rangeselect.js"></script>
|
|
||||||
<script>document.addEventListener('DOMContentLoaded',function() {
|
<script>document.addEventListener('DOMContentLoaded',function() {
|
||||||
KEY_JS_INIT_RANGES
|
KEY_JS_INIT_RANGES
|
||||||
})</script>
|
})</script>
|
||||||
|
<script src="javascript/cookies.js"></script>
|
||||||
|
<script src="javascript/rangeselect.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|
||||||
@ -18,15 +20,15 @@
|
|||||||
</div>-->
|
</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-->
|
<!--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="showRangeManual('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="showRangeManual('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="showRangeManual('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','alltime')" class="stat_selector_topartists selector_topartists_alltime" style="opacity:0.5;">All Time</span>
|
||||||
|
|
||||||
<br/><br/>
|
<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','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="showRangeManual('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="showRangeManual('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','alltime')" class="stat_selector_toptracks selector_toptracks_alltime" style="opacity:0.5;">All Time</span>
|
||||||
|
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
|
|
||||||
@ -59,7 +61,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<div class="sidelist">
|
<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">Today</span> KEY_SCROBBLE_NUM_TODAY
|
||||||
<span class="stats">This Week</span> KEY_SCROBBLE_NUM_WEEK
|
<span class="stats">This Week</span> KEY_SCROBBLE_NUM_WEEK
|
||||||
<span class="stats">This Month</span> KEY_SCROBBLE_NUM_MONTH
|
<span class="stats">This Month</span> KEY_SCROBBLE_NUM_MONTH
|
||||||
@ -73,7 +75,7 @@
|
|||||||
<br/>
|
<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=day&trail=1">Days</a>
|
||||||
<a href="/pulse?step=week&trail=1">Weeks</a>
|
<a href="/pulse?step=week&trail=1">Weeks</a>
|
||||||
@ -81,10 +83,10 @@
|
|||||||
<a href="/pulse?step=year&trail=1">Years</a>
|
<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="showRangeManual('pulse','day')" class="stat_selector_pulse selector_pulse_day">7 days</span>
|
||||||
| <span onclick="showRange('pulse','weeks')" class="stat_selector_pulse selector_pulse_weeks">12 weeks</span>
|
| <span onclick="showRangeManual('pulse','week')" class="stat_selector_pulse selector_pulse_week">12 weeks</span>
|
||||||
| <span onclick="showRange('pulse','months')" class="stat_selector_pulse selector_pulse_months" style="opacity:0.5;">12 months</span>
|
| <span onclick="showRangeManual('pulse','month')" class="stat_selector_pulse selector_pulse_month" 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','year')" class="stat_selector_pulse selector_pulse_year">10 years</span>
|
||||||
<!--
|
<!--
|
||||||
### this is for extra views of the current canonical week / month / year
|
### this is for extra views of the current canonical week / month / year
|
||||||
<br/>
|
<br/>
|
||||||
@ -94,10 +96,10 @@
|
|||||||
-->
|
-->
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
|
|
||||||
<span class="stat_module_pulse pulse_months">KEY_PULSE_MONTHS</span>
|
<span class="stat_module_pulse pulse_month">KEY_PULSE_MONTHS</span>
|
||||||
<span class="stat_module_pulse pulse_days" style="display:none;">KEY_PULSE_DAYS</span>
|
<span class="stat_module_pulse pulse_day" 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_year" 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_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_week" style="display:none;">KEY_PULSE_WEEK</span>
|
||||||
<span class="stat_module_pulse pulse_month" style="display:none;">KEY_PULSE_MONTH</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
|
# commands to execute on load for default ranges
|
||||||
js_command = "showRange('topartists','" + get_settings("DEFAULT_RANGE_CHARTS_ARTISTS") + "');"
|
js_command = "showRange('topartists','" + get_settings("DEFAULT_RANGE_CHARTS_ARTISTS") + "');"
|
||||||
js_command += "showRange('toptracks','" + get_settings("DEFAULT_RANGE_CHARTS_TRACKS") + "');"
|
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()
|
clock()
|
||||||
|
@ -4,7 +4,8 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<title>Maloja - KEY_TRACKTITLE</title>
|
<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>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
@ -15,12 +16,12 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class="text">
|
<td class="text">
|
||||||
<span>KEY_ARTISTS</span><br/>
|
<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="stats"><a href="/scrobbles?KEY_SCROBBLELINK">KEY_SCROBBLES Scrobbles</a></p>
|
||||||
|
|
||||||
<p class="desc"></p>
|
<p class="desc"></p>
|
||||||
<span>KEY_MEDALS</span>
|
<span>KEY_MEDALS</span> <span>KEY_TOPWEEKS</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
@ -29,32 +30,32 @@
|
|||||||
<table class="twopart">
|
<table class="twopart">
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<h2><a href='/pulse?KEY_SCROBBLELINK&step=year&trail=1'>Pulse</a></h2>
|
<h2><a class="stat_link_pulse" href='/pulse?KEY_SCROBBLELINK&trail=1&step=month'>Pulse</a></h2>
|
||||||
<span onclick="showRange('pulse','days')" class="stat_selector_pulse selector_pulse_days">7 days</span>
|
<span onclick="showRangeManual('pulse','day')" class="stat_selector_pulse selector_pulse_day">7 days</span>
|
||||||
| <span onclick="showRange('pulse','weeks')" class="stat_selector_pulse selector_pulse_weeks">12 weeks</span>
|
| <span onclick="showRangeManual('pulse','week')" class="stat_selector_pulse selector_pulse_week">12 weeks</span>
|
||||||
| <span onclick="showRange('pulse','months')" class="stat_selector_pulse selector_pulse_months" style="opacity:0.5;">12 months</span>
|
| <span onclick="showRangeManual('pulse','month')" class="stat_selector_pulse selector_pulse_month" 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','year')" class="stat_selector_pulse selector_pulse_year">10 years</span>
|
||||||
|
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
|
|
||||||
<span class="stat_module_pulse pulse_months">KEY_PULSE_MONTHS</span>
|
<span class="stat_module_pulse pulse_month">KEY_PULSE_MONTHS</span>
|
||||||
<span class="stat_module_pulse pulse_days" style="display:none;">KEY_PULSE_DAYS</span>
|
<span class="stat_module_pulse pulse_day" 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_year" 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_week" style="display:none;">KEY_PULSE_WEEKS</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<h2><a href='/performance?KEY_SCROBBLELINK&step=year&trail=1'>Performance</a></h2>
|
<h2><a class="stat_link_pulse" href='/performance?KEY_SCROBBLELINK&trail=1&step=month'>Performance</a></h2>
|
||||||
<span onclick="showRange('pulse','days')" class="stat_selector_pulse selector_pulse_days">7 days</span>
|
<span onclick="showRangeManual('pulse','day')" class="stat_selector_pulse selector_pulse_day">7 days</span>
|
||||||
| <span onclick="showRange('pulse','weeks')" class="stat_selector_pulse selector_pulse_weeks">12 weeks</span>
|
| <span onclick="showRangeManual('pulse','week')" class="stat_selector_pulse selector_pulse_week">12 weeks</span>
|
||||||
| <span onclick="showRange('pulse','months')" class="stat_selector_pulse selector_pulse_months" style="opacity:0.5;">12 months</span>
|
| <span onclick="showRangeManual('pulse','month')" class="stat_selector_pulse selector_pulse_month" 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','year')" class="stat_selector_pulse selector_pulse_year">10 years</span>
|
||||||
|
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
|
|
||||||
<span class="stat_module_pulse pulse_months">KEY_PERFORMANCE_MONTHS</span>
|
<span class="stat_module_pulse pulse_month">KEY_PERFORMANCE_MONTHS</span>
|
||||||
<span class="stat_module_pulse pulse_days" style="display:none;">KEY_PERFORMANCE_DAYS</span>
|
<span class="stat_module_pulse pulse_day" 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_year" 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_week" style="display:none;">KEY_PERFORMANCE_WEEKS</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
@ -29,13 +29,19 @@ def instructions(keys):
|
|||||||
if "medals" in data and data["medals"] is not None:
|
if "medals" in data and data["medals"] is not None:
|
||||||
if "gold" in data["medals"]:
|
if "gold" in data["medals"]:
|
||||||
for y in data["medals"]["gold"]:
|
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"]:
|
if "silver" in data["medals"]:
|
||||||
for y in data["medals"]["silver"]:
|
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"]:
|
if "bronze" in data["medals"]:
|
||||||
for y in data["medals"]["bronze"]:
|
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_SCROBBLELINK":compose_querystring(keys),
|
||||||
"KEY_MEDALS":html_medals,
|
"KEY_MEDALS":html_medals,
|
||||||
"KEY_CERTS":html_cert,
|
"KEY_CERTS":html_cert,
|
||||||
|
"KEY_TOPWEEKS":html_topweeks,
|
||||||
"KEY_SCROBBLELIST":html_scrobbles,
|
"KEY_SCROBBLELIST":html_scrobbles,
|
||||||
# pulse
|
# pulse
|
||||||
"KEY_PULSE_MONTHS":html_pulse_months,
|
"KEY_PULSE_MONTHS":html_pulse_months,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user