mirror of
https://github.com/krateng/maloja.git
synced 2023-08-10 21:12:55 +03:00
Merged master changes
This commit is contained in:
commit
95b4fc7426
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,7 +1,7 @@
|
|||||||
# generic temporary / dev files
|
# generic temporary / dev files
|
||||||
*.pyc
|
*.pyc
|
||||||
*.sh
|
*.sh
|
||||||
*.txt
|
*.note
|
||||||
*.xcf
|
*.xcf
|
||||||
nohup.out
|
nohup.out
|
||||||
/.dev
|
/.dev
|
||||||
|
18
README.md
18
README.md
@ -19,7 +19,9 @@ Also neat: You can use your **custom artist or track images**.
|
|||||||
* [python3](https://www.python.org/) - [GitHub](https://github.com/python/cpython)
|
* [python3](https://www.python.org/) - [GitHub](https://github.com/python/cpython)
|
||||||
* [bottle.py](https://bottlepy.org/) - [GitHub](https://github.com/bottlepy/bottle)
|
* [bottle.py](https://bottlepy.org/) - [GitHub](https://github.com/bottlepy/bottle)
|
||||||
* [waitress](https://docs.pylonsproject.org/projects/waitress/) - [GitHub](https://github.com/Pylons/waitress)
|
* [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.7.2)
|
* [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
|
||||||
@ -28,13 +30,15 @@ Also neat: You can use your **custom artist or track images**.
|
|||||||
|
|
||||||
./maloja install
|
./maloja install
|
||||||
|
|
||||||
2) Start the server with
|
2) Install required packages with
|
||||||
|
|
||||||
|
pip3 install -r requirements.txt
|
||||||
|
|
||||||
|
3) Start the server with
|
||||||
|
|
||||||
maloja start
|
maloja start
|
||||||
|
|
||||||
If you're missing packages, the console output will tell you so. Install them.
|
4) (Recommended) Put your server behind a reverse proxy for SSL encryption.
|
||||||
|
|
||||||
2) (Recommended) Put your server behind a reverse proxy for SSL encryption.
|
|
||||||
|
|
||||||
## How to use
|
## How to use
|
||||||
|
|
||||||
@ -58,13 +62,15 @@ If you didn't install Maloja from the package (and therefore don't have it in `/
|
|||||||
|
|
||||||
3) Various folders have `.info` files with more information on how to use their associated features.
|
3) Various folders have `.info` files with more information on how to use their associated features.
|
||||||
|
|
||||||
|
4) If you'd like to implement anything on top of Maloja, visit `/api_explorer`.
|
||||||
|
|
||||||
## How to scrobble
|
## How to scrobble
|
||||||
|
|
||||||
### 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 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`.
|
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.
|
||||||
|
|
||||||
### Standard-compliant API
|
### Standard-compliant API
|
||||||
|
|
||||||
|
@ -43,17 +43,12 @@ def handler(apiname,version):
|
|||||||
return cls
|
return cls
|
||||||
return deco
|
return deco
|
||||||
|
|
||||||
def handle(path,keys,headers,auth):
|
def handle(path,keys):
|
||||||
print("API request: " + str(path))
|
print("API request: " + str(path))
|
||||||
print("Keys:")
|
print("Keys:")
|
||||||
for k in keys:
|
for k in keys:
|
||||||
print("\t" + str(k) + ": " + str(keys.get(k)))
|
print("\t",k,":",keys.get(k))
|
||||||
print("Headers:")
|
|
||||||
for h in headers:
|
|
||||||
print("\t" + str(h) + ": " + str(headers.get(h)))
|
|
||||||
print("Auth: " + str(auth))
|
|
||||||
|
|
||||||
keys = {**keys,**headers}
|
|
||||||
|
|
||||||
if len(path)>1 and (path[0],path[1]) in handlers:
|
if len(path)>1 and (path[0],path[1]) in handlers:
|
||||||
handler = handlers[(path[0],path[1])]
|
handler = handlers[(path[0],path[1])]
|
||||||
@ -179,7 +174,7 @@ class LBrnz1(APIHandler):
|
|||||||
}
|
}
|
||||||
self.errors = {
|
self.errors = {
|
||||||
BadAuthException:(401,{"code":401,"error":"You need to provide an Authorization header."}),
|
BadAuthException:(401,{"code":401,"error":"You need to provide an Authorization header."}),
|
||||||
InvalidAuthException:(401,{"code":401,"error":"Bad Auth"}),
|
InvalidAuthException:(401,{"code":401,"error":"Incorrect Authorization"}),
|
||||||
InvalidMethodException:(200,{"code":200,"error":"Invalid Method"}),
|
InvalidMethodException:(200,{"code":200,"error":"Invalid Method"}),
|
||||||
MalformedJSONException:(400,{"code":400,"error":"Invalid JSON document submitted."}),
|
MalformedJSONException:(400,{"code":400,"error":"Invalid JSON document submitted."}),
|
||||||
ScrobblingException:(500,{"code":500,"error":"Unspecified server error."})
|
ScrobblingException:(500,{"code":500,"error":"Unspecified server error."})
|
||||||
@ -191,7 +186,7 @@ class LBrnz1(APIHandler):
|
|||||||
|
|
||||||
def submit(self,pathnodes,keys):
|
def submit(self,pathnodes,keys):
|
||||||
try:
|
try:
|
||||||
token = keys.get("Authorization").replace("token ","").strip()
|
token = keys.get("Authorization").replace("token ","").replace("Token ","").strip()
|
||||||
except:
|
except:
|
||||||
raise BadAuthException()
|
raise BadAuthException()
|
||||||
|
|
||||||
|
200
database.py
200
database.py
@ -1,19 +1,23 @@
|
|||||||
# server
|
# server
|
||||||
from bottle import request, response, FormsDict
|
from bottle import request, response, FormsDict
|
||||||
# rest of the project
|
# rest of the project
|
||||||
from cleanup import *
|
from cleanup import CleanerAgent, CollectorAgent
|
||||||
from utilities import *
|
import utilities
|
||||||
from malojatime import *
|
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
|
||||||
# doreah toolkit
|
# doreah toolkit
|
||||||
from doreah.logging import log
|
from doreah.logging import log
|
||||||
from doreah import tsv
|
from doreah import tsv
|
||||||
|
from doreah import settings
|
||||||
from doreah.caching import Cache, DeepCache
|
from doreah.caching import Cache, DeepCache
|
||||||
try:
|
try:
|
||||||
from doreah.persistence import DiskDict
|
from doreah.persistence import DiskDict
|
||||||
except: pass
|
except: pass
|
||||||
import doreah
|
import doreah
|
||||||
|
# nimrodel API
|
||||||
|
from nimrodel import EAPI as API
|
||||||
|
from nimrodel import Multi
|
||||||
# technical
|
# technical
|
||||||
import os
|
import os
|
||||||
import datetime
|
import datetime
|
||||||
@ -27,7 +31,6 @@ import urllib
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
dblock = Lock() #global database lock
|
dblock = Lock() #global database lock
|
||||||
|
|
||||||
SCROBBLES = [] # Format: tuple(track_ref,timestamp,saved)
|
SCROBBLES = [] # Format: tuple(track_ref,timestamp,saved)
|
||||||
@ -182,54 +185,13 @@ def getTrackID(artists,title):
|
|||||||
########
|
########
|
||||||
|
|
||||||
|
|
||||||
# silly patch to get old syntax working without dbserver
|
dbserver = API(delay=True,path="api")
|
||||||
|
|
||||||
# function to register all the functions to the real server
|
|
||||||
def register_subroutes(server,path):
|
|
||||||
for subpath in dbserver.handlers_get:
|
|
||||||
func = dbserver.handlers_get[subpath]
|
|
||||||
decorator = server.get(path + subpath)
|
|
||||||
decorator(func)
|
|
||||||
for subpath in dbserver.handlers_post:
|
|
||||||
func = dbserver.handlers_post[subpath]
|
|
||||||
decorator = server.post(path + subpath)
|
|
||||||
decorator(func)
|
|
||||||
|
|
||||||
|
|
||||||
# fake server
|
@dbserver.get("test")
|
||||||
class FakeBottle:
|
def test_server(key=None):
|
||||||
def __init__(self):
|
|
||||||
self.handlers_get = {}
|
|
||||||
self.handlers_post = {}
|
|
||||||
|
|
||||||
# these functions pretend that they're the bottle decorators, but only write
|
|
||||||
# down which functions asked for them so they can later report their names
|
|
||||||
# to the real bottle server
|
|
||||||
def get(self,path):
|
|
||||||
def register(func):
|
|
||||||
self.handlers_get[path] = func
|
|
||||||
return func
|
|
||||||
return register
|
|
||||||
def post(self,path):
|
|
||||||
def register(func):
|
|
||||||
self.handlers_post[path] = func
|
|
||||||
return func
|
|
||||||
return register
|
|
||||||
|
|
||||||
def route(self,path):
|
|
||||||
return self.get(path)
|
|
||||||
|
|
||||||
|
|
||||||
dbserver = FakeBottle()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@dbserver.route("/test")
|
|
||||||
def test_server():
|
|
||||||
apikey = request.query.get("key")
|
|
||||||
response.set_header("Access-Control-Allow-Origin","*")
|
response.set_header("Access-Control-Allow-Origin","*")
|
||||||
if apikey is not None and not (checkAPIkey(apikey)):
|
if key is not None and not (checkAPIkey(key)):
|
||||||
response.status = 403
|
response.status = 403
|
||||||
return "Wrong API key"
|
return "Wrong API key"
|
||||||
|
|
||||||
@ -247,9 +209,8 @@ def test_server():
|
|||||||
|
|
||||||
## All database functions are separated - the external wrapper only reads the request keys, converts them into lists and renames them where necessary, and puts the end result in a dict if not already so it can be returned as json
|
## All database functions are separated - the external wrapper only reads the request keys, converts them into lists and renames them where necessary, and puts the end result in a dict if not already so it can be returned as json
|
||||||
|
|
||||||
@dbserver.route("/scrobbles")
|
@dbserver.get("scrobbles")
|
||||||
def get_scrobbles_external():
|
def get_scrobbles_external(**keys):
|
||||||
keys = FormsDict.decode(request.query)
|
|
||||||
k_filter, k_time, _, k_amount = uri_to_internal(keys)
|
k_filter, k_time, _, k_amount = uri_to_internal(keys)
|
||||||
ckeys = {**k_filter, **k_time, **k_amount}
|
ckeys = {**k_filter, **k_time, **k_amount}
|
||||||
|
|
||||||
@ -277,9 +238,8 @@ def get_scrobbles(**keys):
|
|||||||
# return {"scrobbles":len(SCROBBLES),"tracks":len(TRACKS),"artists":len(ARTISTS)}
|
# return {"scrobbles":len(SCROBBLES),"tracks":len(TRACKS),"artists":len(ARTISTS)}
|
||||||
|
|
||||||
|
|
||||||
@dbserver.route("/numscrobbles")
|
@dbserver.get("numscrobbles")
|
||||||
def get_scrobbles_num_external():
|
def get_scrobbles_num_external(**keys):
|
||||||
keys = FormsDict.decode(request.query)
|
|
||||||
k_filter, k_time, _, k_amount = uri_to_internal(keys)
|
k_filter, k_time, _, k_amount = uri_to_internal(keys)
|
||||||
ckeys = {**k_filter, **k_time, **k_amount}
|
ckeys = {**k_filter, **k_time, **k_amount}
|
||||||
|
|
||||||
@ -342,9 +302,8 @@ def get_scrobbles_num(**keys):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
@dbserver.route("/tracks")
|
@dbserver.get("tracks")
|
||||||
def get_tracks_external():
|
def get_tracks_external(**keys):
|
||||||
keys = FormsDict.decode(request.query)
|
|
||||||
k_filter, _, _, _ = uri_to_internal(keys,forceArtist=True)
|
k_filter, _, _, _ = uri_to_internal(keys,forceArtist=True)
|
||||||
ckeys = {**k_filter}
|
ckeys = {**k_filter}
|
||||||
|
|
||||||
@ -366,7 +325,7 @@ def get_tracks(artist=None):
|
|||||||
#ls = [t for t in tracklist if (artist in t["artists"]) or (artist==None)]
|
#ls = [t for t in tracklist if (artist in t["artists"]) or (artist==None)]
|
||||||
|
|
||||||
|
|
||||||
@dbserver.route("/artists")
|
@dbserver.get("artists")
|
||||||
def get_artists_external():
|
def get_artists_external():
|
||||||
result = get_artists()
|
result = get_artists()
|
||||||
return {"list":result}
|
return {"list":result}
|
||||||
@ -380,9 +339,8 @@ def get_artists():
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
@dbserver.route("/charts/artists")
|
@dbserver.get("charts/artists")
|
||||||
def get_charts_artists_external():
|
def get_charts_artists_external(**keys):
|
||||||
keys = FormsDict.decode(request.query)
|
|
||||||
_, k_time, _, _ = uri_to_internal(keys)
|
_, k_time, _, _ = uri_to_internal(keys)
|
||||||
ckeys = {**k_time}
|
ckeys = {**k_time}
|
||||||
|
|
||||||
@ -397,9 +355,8 @@ def get_charts_artists(**keys):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
@dbserver.route("/charts/tracks")
|
@dbserver.get("charts/tracks")
|
||||||
def get_charts_tracks_external():
|
def get_charts_tracks_external(**keys):
|
||||||
keys = FormsDict.decode(request.query)
|
|
||||||
k_filter, k_time, _, _ = uri_to_internal(keys,forceArtist=True)
|
k_filter, k_time, _, _ = uri_to_internal(keys,forceArtist=True)
|
||||||
ckeys = {**k_filter, **k_time}
|
ckeys = {**k_filter, **k_time}
|
||||||
|
|
||||||
@ -417,9 +374,8 @@ def get_charts_tracks(**keys):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
@dbserver.route("/pulse")
|
@dbserver.get("pulse")
|
||||||
def get_pulse_external():
|
def get_pulse_external(**keys):
|
||||||
keys = FormsDict.decode(request.query)
|
|
||||||
k_filter, k_time, k_internal, k_amount = uri_to_internal(keys)
|
k_filter, k_time, k_internal, k_amount = uri_to_internal(keys)
|
||||||
ckeys = {**k_filter, **k_time, **k_internal, **k_amount}
|
ckeys = {**k_filter, **k_time, **k_internal, **k_amount}
|
||||||
|
|
||||||
@ -440,9 +396,8 @@ def get_pulse(**keys):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
@dbserver.route("/performance")
|
@dbserver.get("performance")
|
||||||
def get_performance_external():
|
def get_performance_external(**keys):
|
||||||
keys = FormsDict.decode(request.query)
|
|
||||||
k_filter, k_time, k_internal, k_amount = uri_to_internal(keys)
|
k_filter, k_time, k_internal, k_amount = uri_to_internal(keys)
|
||||||
ckeys = {**k_filter, **k_time, **k_internal, **k_amount}
|
ckeys = {**k_filter, **k_time, **k_internal, **k_amount}
|
||||||
|
|
||||||
@ -480,10 +435,8 @@ def get_performance(**keys):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
@dbserver.route("/top/artists")
|
@dbserver.get("top/artists")
|
||||||
def get_top_artists_external():
|
def get_top_artists_external(**keys):
|
||||||
|
|
||||||
keys = FormsDict.decode(request.query)
|
|
||||||
_, k_time, k_internal, _ = uri_to_internal(keys)
|
_, k_time, k_internal, _ = uri_to_internal(keys)
|
||||||
ckeys = {**k_time, **k_internal}
|
ckeys = {**k_time, **k_internal}
|
||||||
|
|
||||||
@ -513,9 +466,8 @@ def get_top_artists(**keys):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
@dbserver.route("/top/tracks")
|
@dbserver.get("top/tracks")
|
||||||
def get_top_tracks_external():
|
def get_top_tracks_external(**keys):
|
||||||
keys = FormsDict.decode(request.query)
|
|
||||||
_, k_time, k_internal, _ = uri_to_internal(keys)
|
_, k_time, k_internal, _ = uri_to_internal(keys)
|
||||||
ckeys = {**k_time, **k_internal}
|
ckeys = {**k_time, **k_internal}
|
||||||
|
|
||||||
@ -548,9 +500,8 @@ def get_top_tracks(**keys):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
@dbserver.route("/artistinfo")
|
@dbserver.get("artistinfo")
|
||||||
def artistInfo_external():
|
def artistInfo_external(**keys):
|
||||||
keys = FormsDict.decode(request.query)
|
|
||||||
k_filter, _, _, _ = uri_to_internal(keys,forceArtist=True)
|
k_filter, _, _, _ = uri_to_internal(keys,forceArtist=True)
|
||||||
ckeys = {**k_filter}
|
ckeys = {**k_filter}
|
||||||
|
|
||||||
@ -560,14 +511,16 @@ def artistInfo_external():
|
|||||||
def artistInfo(artist):
|
def artistInfo(artist):
|
||||||
|
|
||||||
charts = db_aggregate(by="ARTIST")
|
charts = db_aggregate(by="ARTIST")
|
||||||
scrobbles = len(db_query(artists=[artist])) #we cant take the scrobble number from the charts because that includes all countas scrobbles
|
scrobbles = len(db_query(artists=[artist]))
|
||||||
|
#we cant take the scrobble number from the charts because that includes all countas scrobbles
|
||||||
try:
|
try:
|
||||||
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)}
|
return {"scrobbles":scrobbles,"position":position,"associated":others,"medals":MEDALS.get(artist)}
|
||||||
except:
|
except:
|
||||||
# if the artist isnt in the charts, they are not being credited and we need to show information about the credited one
|
# if the artist isnt in the charts, they are not being credited and we
|
||||||
|
# need to show information about the credited one
|
||||||
artist = coa.getCredited(artist)
|
artist = coa.getCredited(artist)
|
||||||
c = [e for e in charts if e["artist"] == artist][0]
|
c = [e for e in charts if e["artist"] == artist][0]
|
||||||
position = c["rank"]
|
position = c["rank"]
|
||||||
@ -577,23 +530,37 @@ def artistInfo(artist):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
@dbserver.route("/trackinfo")
|
@dbserver.get("trackinfo")
|
||||||
def trackInfo_external():
|
def trackInfo_external(artist:Multi[str],**keys):
|
||||||
keys = FormsDict.decode(request.query)
|
# transform into a multidict so we can use our nomral uri_to_internal function
|
||||||
|
keys = FormsDict(keys)
|
||||||
|
for a in artist:
|
||||||
|
keys.append("artist",a)
|
||||||
k_filter, _, _, _ = uri_to_internal(keys,forceTrack=True)
|
k_filter, _, _, _ = uri_to_internal(keys,forceTrack=True)
|
||||||
ckeys = {**k_filter}
|
ckeys = {**k_filter}
|
||||||
|
|
||||||
results = trackInfo(**ckeys)
|
results = trackInfo(**ckeys)
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def trackInfo(artists,title):
|
def trackInfo(track):
|
||||||
charts = db_aggregate(by="TRACK")
|
charts = db_aggregate(by="TRACK")
|
||||||
#scrobbles = len(db_query(artists=artists,title=title)) #chart entry of track always has right scrobble number, no countas rules here
|
#scrobbles = len(db_query(artists=artists,title=title)) #chart entry of track always has right scrobble number, no countas rules here
|
||||||
c = [e for e in charts if set(e["track"]["artists"]) == set(artists) and e["track"]["title"] == title][0]
|
#c = [e for e in charts if set(e["track"]["artists"]) == set(artists) and e["track"]["title"] == title][0]
|
||||||
|
c = [e for e in charts if e["track"] == track][0]
|
||||||
scrobbles = c["scrobbles"]
|
scrobbles = c["scrobbles"]
|
||||||
position = c["rank"]
|
position = c["rank"]
|
||||||
|
cert = None
|
||||||
|
threshold_gold, threshold_platinum, threshold_diamond = settings.get_settings("SCROBBLES_GOLD","SCROBBLES_PLATINUM","SCROBBLES_DIAMOND")
|
||||||
|
if scrobbles >= threshold_diamond: cert = "diamond"
|
||||||
|
elif scrobbles >= threshold_platinum: cert = "platinum"
|
||||||
|
elif scrobbles >= threshold_gold: cert = "gold"
|
||||||
|
|
||||||
return {"scrobbles":scrobbles,"position":position,"medals":MEDALS_TRACKS.get((frozenset(artists),title))}
|
return {
|
||||||
|
"scrobbles":scrobbles,
|
||||||
|
"position":position,
|
||||||
|
"medals":MEDALS_TRACKS.get((frozenset(track["artists"]),track["title"])),
|
||||||
|
"certification":cert
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -601,9 +568,8 @@ def trackInfo(artists,title):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
@dbserver.get("/newscrobble")
|
@dbserver.get("newscrobble")
|
||||||
def pseudo_post_scrobble():
|
def pseudo_post_scrobble(**keys):
|
||||||
keys = FormsDict.decode(request.query) # The Dal★Shabet handler
|
|
||||||
artists = keys.get("artist")
|
artists = keys.get("artist")
|
||||||
title = keys.get("title")
|
title = keys.get("title")
|
||||||
apikey = keys.get("key")
|
apikey = keys.get("key")
|
||||||
@ -626,9 +592,8 @@ def pseudo_post_scrobble():
|
|||||||
|
|
||||||
return {"status":"success","track":trackdict}
|
return {"status":"success","track":trackdict}
|
||||||
|
|
||||||
@dbserver.post("/newscrobble")
|
@dbserver.post("newscrobble")
|
||||||
def post_scrobble():
|
def post_scrobble(**keys):
|
||||||
keys = FormsDict.decode(request.forms) # The Dal★Shabet handler
|
|
||||||
artists = keys.get("artist")
|
artists = keys.get("artist")
|
||||||
title = keys.get("title")
|
title = keys.get("title")
|
||||||
apikey = keys.get("key")
|
apikey = keys.get("key")
|
||||||
@ -659,28 +624,26 @@ def post_scrobble():
|
|||||||
|
|
||||||
# standard-compliant scrobbling methods
|
# standard-compliant scrobbling methods
|
||||||
|
|
||||||
@dbserver.post("/s/<path:path>")
|
@dbserver.post("s/{path}",pass_headers=True)
|
||||||
@dbserver.get("/s/<path:path>")
|
@dbserver.get("s/{path}",pass_headers=True)
|
||||||
def sapi(path):
|
def sapi(path:Multi,**keys):
|
||||||
path = path.split("/")
|
"""Scrobbles according to a standardized protocol.
|
||||||
|
|
||||||
|
:param string path: Path according to the scrobble protocol
|
||||||
|
:param string keys: Query keys according to the scrobble protocol
|
||||||
|
"""
|
||||||
path = list(filter(None,path))
|
path = list(filter(None,path))
|
||||||
headers = request.headers
|
return compliant_api.handle(path,keys)
|
||||||
if request.get_header("Content-Type") is not None and "application/json" in request.get_header("Content-Type"):
|
|
||||||
keys = request.json
|
|
||||||
else:
|
|
||||||
keys = FormsDict.decode(request.params)
|
|
||||||
auth = request.auth
|
|
||||||
return compliant_api.handle(path,keys,headers,auth)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@dbserver.route("/sync")
|
@dbserver.get("sync")
|
||||||
def abouttoshutdown():
|
def abouttoshutdown():
|
||||||
sync()
|
sync()
|
||||||
#sys.exit()
|
#sys.exit()
|
||||||
|
|
||||||
@dbserver.post("/newrule")
|
@dbserver.post("newrule")
|
||||||
def newrule():
|
def newrule():
|
||||||
keys = FormsDict.decode(request.forms)
|
keys = FormsDict.decode(request.forms)
|
||||||
apikey = keys.pop("key",None)
|
apikey = keys.pop("key",None)
|
||||||
@ -691,7 +654,7 @@ def newrule():
|
|||||||
db_rulestate = False
|
db_rulestate = False
|
||||||
|
|
||||||
|
|
||||||
@dbserver.route("/issues")
|
@dbserver.get("issues")
|
||||||
def issues_external(): #probably not even needed
|
def issues_external(): #probably not even needed
|
||||||
return issues()
|
return issues()
|
||||||
|
|
||||||
@ -787,7 +750,7 @@ def issues():
|
|||||||
return {"duplicates":duplicates,"combined":combined,"newartists":newartists,"inconsistent":inconsistent}
|
return {"duplicates":duplicates,"combined":combined,"newartists":newartists,"inconsistent":inconsistent}
|
||||||
|
|
||||||
|
|
||||||
@dbserver.post("/importrules")
|
@dbserver.post("importrules")
|
||||||
def import_rulemodule():
|
def import_rulemodule():
|
||||||
keys = FormsDict.decode(request.forms)
|
keys = FormsDict.decode(request.forms)
|
||||||
apikey = keys.pop("key",None)
|
apikey = keys.pop("key",None)
|
||||||
@ -807,7 +770,7 @@ def import_rulemodule():
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
@dbserver.post("/rebuild")
|
@dbserver.post("rebuild")
|
||||||
def rebuild():
|
def rebuild():
|
||||||
|
|
||||||
keys = FormsDict.decode(request.forms)
|
keys = FormsDict.decode(request.forms)
|
||||||
@ -827,9 +790,8 @@ def rebuild():
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
@dbserver.get("/search")
|
@dbserver.get("search")
|
||||||
def search():
|
def search(**keys):
|
||||||
keys = FormsDict.decode(request.query)
|
|
||||||
query = keys.get("query")
|
query = keys.get("query")
|
||||||
max_ = keys.get("max")
|
max_ = keys.get("max")
|
||||||
if max_ is not None: max_ = int(max_)
|
if max_ is not None: max_ = int(max_)
|
||||||
@ -923,10 +885,10 @@ def build_db():
|
|||||||
# coa.updateIDs(ARTISTS)
|
# coa.updateIDs(ARTISTS)
|
||||||
|
|
||||||
#start regular tasks
|
#start regular tasks
|
||||||
update_medals()
|
utilities.update_medals()
|
||||||
|
|
||||||
global db_rulestate
|
global db_rulestate
|
||||||
db_rulestate = consistentRulestate("scrobbles",cla.checksums)
|
db_rulestate = utilities.consistentRulestate("scrobbles",cla.checksums)
|
||||||
|
|
||||||
log("Database fully built!")
|
log("Database fully built!")
|
||||||
|
|
||||||
@ -959,7 +921,7 @@ def sync():
|
|||||||
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)
|
||||||
combineChecksums("scrobbles/" + e + ".tsv",cla.checksums)
|
utilities.combineChecksums("scrobbles/" + e + ".tsv",cla.checksums)
|
||||||
|
|
||||||
|
|
||||||
global lastsync
|
global lastsync
|
||||||
@ -989,7 +951,7 @@ cacheday = (0,0,0)
|
|||||||
def db_query(**kwargs):
|
def db_query(**kwargs):
|
||||||
check_cache_age()
|
check_cache_age()
|
||||||
global cache_query, cache_query_permanent
|
global cache_query, cache_query_permanent
|
||||||
key = serialize(kwargs)
|
key = utilities.serialize(kwargs)
|
||||||
if "timerange" in kwargs and not kwargs["timerange"].active():
|
if "timerange" in kwargs and not kwargs["timerange"].active():
|
||||||
if key in cache_query_permanent:
|
if key in cache_query_permanent:
|
||||||
#print("Hit")
|
#print("Hit")
|
||||||
@ -1014,7 +976,7 @@ else:
|
|||||||
def db_aggregate(**kwargs):
|
def db_aggregate(**kwargs):
|
||||||
check_cache_age()
|
check_cache_age()
|
||||||
global cache_aggregate, cache_aggregate_permanent
|
global cache_aggregate, cache_aggregate_permanent
|
||||||
key = serialize(kwargs)
|
key = utilities.serialize(kwargs)
|
||||||
if "timerange" in kwargs and not kwargs["timerange"].active():
|
if "timerange" in kwargs and not kwargs["timerange"].active():
|
||||||
if key in cache_aggregate_permanent: return copy.copy(cache_aggregate_permanent.get(key))
|
if key in cache_aggregate_permanent: return copy.copy(cache_aggregate_permanent.get(key))
|
||||||
result = db_aggregate_full(**kwargs)
|
result = db_aggregate_full(**kwargs)
|
||||||
|
47
external.py
47
external.py
@ -5,53 +5,60 @@ from doreah.settings import get_settings
|
|||||||
from doreah.logging import log
|
from doreah.logging import log
|
||||||
|
|
||||||
|
|
||||||
apis_artists = [
|
apis_artists = []
|
||||||
{
|
|
||||||
|
if get_settings("LASTFM_API_KEY") not in [None,"ASK"] and get_settings("FANARTTV_API_KEY") not in [None,"ASK"]:
|
||||||
|
apis_artists.append({
|
||||||
"name":"LastFM + Fanart.tv",
|
"name":"LastFM + Fanart.tv",
|
||||||
"check":get_settings("LASTFM_API_KEY") not in [None,"ASK"] and get_settings("FANARTTV_API_KEY") not in [None,"ASK"],
|
#"check":get_settings("LASTFM_API_KEY") not in [None,"ASK"] and get_settings("FANARTTV_API_KEY") not in [None,"ASK"],
|
||||||
"steps":[
|
"steps":[
|
||||||
("get","http://ws.audioscrobbler.com/2.0/?method=artist.getinfo&artist={artiststring}&api_key=" + get_settings("LASTFM_API_KEY") + "&format=json"),
|
("get","http://ws.audioscrobbler.com/2.0/?method=artist.getinfo&artist={artiststring}&api_key=" + str(get_settings("LASTFM_API_KEY")) + "&format=json"),
|
||||||
("parse",["artist","mbid"]),
|
("parse",["artist","mbid"]),
|
||||||
("get","http://webservice.fanart.tv/v3/music/{var}?api_key=" + get_settings("FANARTTV_API_KEY")),
|
("get","http://webservice.fanart.tv/v3/music/{var}?api_key=" + str(get_settings("FANARTTV_API_KEY"))),
|
||||||
("parse",["artistthumb",0,"url"])
|
("parse",["artistthumb",0,"url"])
|
||||||
]
|
]
|
||||||
},
|
})
|
||||||
{
|
|
||||||
|
if get_settings("SPOTIFY_API_ID") not in [None,"ASK"] and get_settings("SPOTIFY_API_SECRET") not in [None,"ASK"]:
|
||||||
|
apis_artists.append({
|
||||||
"name":"Spotify",
|
"name":"Spotify",
|
||||||
"check":get_settings("SPOTIFY_API_ID") not in [None,"ASK"] and get_settings("SPOTIFY_API_SECRET") not in [None,"ASK"],
|
#"check":get_settings("SPOTIFY_API_ID") not in [None,"ASK"] and get_settings("SPOTIFY_API_SECRET") not in [None,"ASK"],
|
||||||
"steps":[
|
"steps":[
|
||||||
("post","https://accounts.spotify.com/api/token",{"Authorization":"Basic " + base64.b64encode(bytes(get_settings("SPOTIFY_API_ID") + ":" + get_settings("SPOTIFY_API_SECRET"),encoding="utf-8")).decode("utf-8")},{"grant_type":"client_credentials"}),
|
("post","https://accounts.spotify.com/api/token",{"Authorization":"Basic " + base64.b64encode(bytes(get_settings("SPOTIFY_API_ID") + ":" + get_settings("SPOTIFY_API_SECRET"),encoding="utf-8")).decode("utf-8")},{"grant_type":"client_credentials"}),
|
||||||
("parse",["access_token"]),
|
("parse",["access_token"]),
|
||||||
("get","https://api.spotify.com/v1/search?q={artiststring}&type=artist&access_token={var}"),
|
("get","https://api.spotify.com/v1/search?q={artiststring}&type=artist&access_token={var}"),
|
||||||
("parse",["artists","items",0,"images",0,"url"])
|
("parse",["artists","items",0,"images",0,"url"])
|
||||||
]
|
]
|
||||||
}
|
})
|
||||||
]
|
|
||||||
|
|
||||||
apis_tracks = [
|
apis_tracks = []
|
||||||
{
|
|
||||||
|
if get_settings("LASTFM_API_KEY") not in [None,"ASK"]:
|
||||||
|
apis_tracks.append({
|
||||||
"name":"LastFM",
|
"name":"LastFM",
|
||||||
"check":get_settings("LASTFM_API_KEY") not in [None,"ASK"],
|
#"check":get_settings("LASTFM_API_KEY") not in [None,"ASK"],
|
||||||
"steps":[
|
"steps":[
|
||||||
("get","https://ws.audioscrobbler.com/2.0/?method=track.getinfo&track={titlestring}&artist={artiststring}&api_key=" + get_settings("LASTFM_API_KEY") + "&format=json"),
|
("get","https://ws.audioscrobbler.com/2.0/?method=track.getinfo&track={titlestring}&artist={artiststring}&api_key=" + get_settings("LASTFM_API_KEY") + "&format=json"),
|
||||||
("parse",["track","album","image",3,"#text"])
|
("parse",["track","album","image",3,"#text"])
|
||||||
]
|
]
|
||||||
},
|
})
|
||||||
{
|
|
||||||
|
if get_settings("SPOTIFY_API_ID") not in [None,"ASK"] and get_settings("SPOTIFY_API_SECRET") not in [None,"ASK"]:
|
||||||
|
apis_tracks.append({
|
||||||
"name":"Spotify",
|
"name":"Spotify",
|
||||||
"check":get_settings("SPOTIFY_API_ID") not in [None,"ASK"] and get_settings("SPOTIFY_API_SECRET") not in [None,"ASK"],
|
#"check":get_settings("SPOTIFY_API_ID") not in [None,"ASK"] and get_settings("SPOTIFY_API_SECRET") not in [None,"ASK"],
|
||||||
"steps":[
|
"steps":[
|
||||||
("post","https://accounts.spotify.com/api/token",{"Authorization":"Basic " + base64.b64encode(bytes(get_settings("SPOTIFY_API_ID") + ":" + get_settings("SPOTIFY_API_SECRET"),encoding="utf-8")).decode("utf-8")},{"grant_type":"client_credentials"}),
|
("post","https://accounts.spotify.com/api/token",{"Authorization":"Basic " + base64.b64encode(bytes(get_settings("SPOTIFY_API_ID") + ":" + get_settings("SPOTIFY_API_SECRET"),encoding="utf-8")).decode("utf-8")},{"grant_type":"client_credentials"}),
|
||||||
("parse",["access_token"]),
|
("parse",["access_token"]),
|
||||||
("get","https://api.spotify.com/v1/search?q={artiststring}%20{titlestring}&type=track&access_token={var}"),
|
("get","https://api.spotify.com/v1/search?q={artiststring}%20{titlestring}&type=track&access_token={var}"),
|
||||||
("parse",["tracks","items",0,"album","images",0,"url"])
|
("parse",["tracks","items",0,"album","images",0,"url"])
|
||||||
]
|
]
|
||||||
}
|
})
|
||||||
]
|
|
||||||
|
|
||||||
def api_request_artist(artist):
|
def api_request_artist(artist):
|
||||||
for api in apis_artists:
|
for api in apis_artists:
|
||||||
if api["check"]:
|
if True:
|
||||||
log("API: " + api["name"] + "; Image request: " + artist,module="external")
|
log("API: " + api["name"] + "; Image request: " + artist,module="external")
|
||||||
try:
|
try:
|
||||||
artiststring = urllib.parse.quote(artist)
|
artiststring = urllib.parse.quote(artist)
|
||||||
@ -85,7 +92,7 @@ def api_request_artist(artist):
|
|||||||
def api_request_track(track):
|
def api_request_track(track):
|
||||||
artists, title = track
|
artists, title = track
|
||||||
for api in apis_tracks:
|
for api in apis_tracks:
|
||||||
if api["check"]:
|
if True:
|
||||||
log("API: " + api["name"] + "; Image request: " + "/".join(artists) + " - " + title,module="external")
|
log("API: " + api["name"] + "; Image request: " + "/".join(artists) + " - " + title,module="external")
|
||||||
try:
|
try:
|
||||||
artiststring = urllib.parse.quote(", ".join(artists))
|
artiststring = urllib.parse.quote(", ".join(artists))
|
||||||
|
@ -14,8 +14,9 @@ def entity_column(element,counting=[],image=None):
|
|||||||
|
|
||||||
if "artists" in element:
|
if "artists" in element:
|
||||||
# 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>"
|
||||||
else:
|
else:
|
||||||
# artist
|
# artist
|
||||||
html += "<td class='artist'>" + html_link(element)
|
html += "<td class='artist'>" + html_link(element)
|
||||||
|
9
maloja
9
maloja
@ -13,7 +13,8 @@ neededmodules = [
|
|||||||
"bottle",
|
"bottle",
|
||||||
"waitress",
|
"waitress",
|
||||||
"setproctitle",
|
"setproctitle",
|
||||||
"doreah"
|
"doreah",
|
||||||
|
"nimrodel"
|
||||||
]
|
]
|
||||||
|
|
||||||
recommendedmodules = [
|
recommendedmodules = [
|
||||||
@ -117,10 +118,10 @@ def install():
|
|||||||
|
|
||||||
if toinstall != [] or toinstallr != []:
|
if toinstall != [] or toinstallr != []:
|
||||||
if os.geteuid() != 0:
|
if os.geteuid() != 0:
|
||||||
print("Installing python modules should be fairly straight-forward, but Maloja can try to install them automatically. For this, you need to run this script as a root user.")
|
print("You can install them with",yellow("pip install -r requirements.txt"),"or Maloja can try to install them automatically. For this, you need to run this script as a root user.")
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
print("Installing python modules should be fairly straight-forward, but Maloja can try to install them automatically, This might or might not work / bloat your system / cause a nuclear war.")
|
print("You can install them with",yellow("pip install -r requirements.txt"),"or Maloja can try to install them automatically, This might or might not work / bloat your system / cause a nuclear war.")
|
||||||
fail = False
|
fail = False
|
||||||
if toinstall != []:
|
if toinstall != []:
|
||||||
print("Attempt to install required modules? [Y/n]")
|
print("Attempt to install required modules? [Y/n]")
|
||||||
@ -283,7 +284,7 @@ def update():
|
|||||||
|
|
||||||
os.chmod("./maloja",os.stat("./maloja").st_mode | stat.S_IXUSR)
|
os.chmod("./maloja",os.stat("./maloja").st_mode | stat.S_IXUSR)
|
||||||
|
|
||||||
print("Make sure to install the latest version of doreah! (" + yellow("pip3 install --upgrade --no-cache-dir doreah") + ")")
|
print("Make sure to update required modules! (" + yellow("pip3 install -r requirements.txt --upgrade --no-cache-dir") + ")")
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
@ -66,6 +66,9 @@ class MRangeDescriptor:
|
|||||||
def unlimited(self):
|
def unlimited(self):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def active(self):
|
||||||
|
return (self.last_stamp() > datetime.datetime.utcnow().timestamp())
|
||||||
|
|
||||||
# returns the description of the range including buttons to go back and forth
|
# returns the description of the range including buttons to go back and forth
|
||||||
#def desc_interactive(self,**kwargs):
|
#def desc_interactive(self,**kwargs):
|
||||||
# if self.next(1) is None:
|
# if self.next(1) is None:
|
||||||
@ -99,7 +102,7 @@ class MTime(MRangeDescriptor):
|
|||||||
|
|
||||||
# whether we currently live or will ever again live in this range
|
# whether we currently live or will ever again live in this range
|
||||||
def active(self):
|
def active(self):
|
||||||
tod = datetime.date.today()
|
tod = datetime.datetime.utcnow().date()
|
||||||
if tod.year > self.year: return False
|
if tod.year > self.year: return False
|
||||||
if self.precision == 1: return True
|
if self.precision == 1: return True
|
||||||
if tod.year == self.year:
|
if tod.year == self.year:
|
||||||
@ -235,13 +238,13 @@ class MTimeWeek(MRangeDescriptor):
|
|||||||
return str(self)
|
return str(self)
|
||||||
|
|
||||||
# whether we currently live or will ever again live in this range
|
# whether we currently live or will ever again live in this range
|
||||||
def active(self):
|
# def active(self):
|
||||||
tod = datetime.date.today()
|
# tod = datetime.date.today()
|
||||||
if tod.year > self.year: return False
|
# if tod.year > self.year: return False
|
||||||
if tod.year == self.year:
|
# if tod.year == self.year:
|
||||||
if tod.chrcalendar()[1] > self.week: return False
|
# if tod.chrcalendar()[1] > self.week: return False
|
||||||
|
#
|
||||||
return True
|
# return True
|
||||||
|
|
||||||
def urikeys(self):
|
def urikeys(self):
|
||||||
return {"in":str(self)}
|
return {"in":str(self)}
|
||||||
|
6
requirements.txt
Normal file
6
requirements.txt
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
bottle>=0.12.16
|
||||||
|
waitress>=1.3
|
||||||
|
doreah>=0.9.1
|
||||||
|
nimrodel>=0.4.9
|
||||||
|
setproctitle>=1.1.10
|
||||||
|
wand>=0.5.4
|
@ -5,6 +5,7 @@
|
|||||||
countas Selena Gomez & The Scene Selena Gomez
|
countas Selena Gomez & The Scene Selena Gomez
|
||||||
countas The Police Sting
|
countas The Police Sting
|
||||||
countas Trouble Maker HyunA
|
countas Trouble Maker HyunA
|
||||||
|
countas S Club 7 Tina Barrett
|
||||||
# Group more famous than single artist
|
# Group more famous than single artist
|
||||||
countas RenoakRhythm Approaching Nirvana
|
countas RenoakRhythm Approaching Nirvana
|
||||||
countas Shirley Manson Garbage
|
countas Shirley Manson Garbage
|
||||||
|
Can't render this file because it has a wrong number of fields in line 5.
|
@ -46,6 +46,9 @@ replacetitle 종이 심장 (Paper Heart) Paper Heart
|
|||||||
replacetitle 나비 (Butterfly) Butterfly
|
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 시그널 (Signal) Signal
|
||||||
|
replacetitle 미행 (그림자 : Shadow) Shadow
|
||||||
|
|
||||||
# Stellar
|
# Stellar
|
||||||
replaceartist STELLAR Stellar
|
replaceartist STELLAR Stellar
|
||||||
@ -111,6 +114,7 @@ replacetitle 음오아예 (Um Oh Ah Yeh) (Um Oh Ah Yeh) Um Oh Ah Yeh
|
|||||||
replacetitle 따끔 (a little bit) A Little Bit
|
replacetitle 따끔 (a little bit) A Little Bit
|
||||||
|
|
||||||
# Hello Venus
|
# Hello Venus
|
||||||
|
replaceartist HELLOVENUS Hello Venus
|
||||||
replaceartist Hello/Venus Hello Venus
|
replaceartist Hello/Venus Hello Venus
|
||||||
|
|
||||||
# BESTie
|
# BESTie
|
||||||
|
Can't render this file because it has a wrong number of fields in line 5.
|
@ -20,3 +20,4 @@ notanartist Aniron Theme For Aragorn And Arwen
|
|||||||
notanartist Lament for Gandalf
|
notanartist Lament for Gandalf
|
||||||
replaceartist James Galway Sir James Galway
|
replaceartist James Galway Sir James Galway
|
||||||
replaceartist Ben del Maestro Ben Del Maestro
|
replaceartist Ben del Maestro Ben Del Maestro
|
||||||
|
replacetitle Anduril Andúril
|
||||||
|
Can't render this file because it has a wrong number of fields in line 4.
|
@ -202,7 +202,8 @@ class Controller {
|
|||||||
// Already played full song
|
// Already played full song
|
||||||
while (this.alreadyPlayed > this.currentLength) {
|
while (this.alreadyPlayed > this.currentLength) {
|
||||||
this.alreadyPlayed = this.alreadyPlayed - this.currentLength
|
this.alreadyPlayed = this.alreadyPlayed - this.currentLength
|
||||||
scrobble(this.currentArtist,this.currentTitle,this.currentLength)
|
var secondsago = this.alreadyPlayed
|
||||||
|
scrobble(this.currentArtist,this.currentTitle,this.currentLength,secondsago)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setUpdate()
|
this.setUpdate()
|
||||||
@ -248,7 +249,8 @@ class Controller {
|
|||||||
// Already played full song
|
// Already played full song
|
||||||
while (this.alreadyPlayed > this.currentLength) {
|
while (this.alreadyPlayed > this.currentLength) {
|
||||||
this.alreadyPlayed = this.alreadyPlayed - this.currentLength
|
this.alreadyPlayed = this.alreadyPlayed - this.currentLength
|
||||||
scrobble(this.currentArtist,this.currentTitle,this.currentLength)
|
var secondsago = this.alreadyPlayed
|
||||||
|
scrobble(this.currentArtist,this.currentTitle,this.currentLength,secondsago)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.currentlyPlaying = false
|
this.currentlyPlaying = false
|
||||||
@ -282,17 +284,22 @@ class Controller {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
function scrobble(artist,title,seconds) {
|
function scrobble(artist,title,seconds,secondsago=0) {
|
||||||
console.log("Scrobbling " + artist + " - " + title + "; " + seconds + " seconds playtime")
|
console.log("Scrobbling " + artist + " - " + title + "; " + seconds + " seconds playtime, " + secondsago + " seconds ago")
|
||||||
artiststring = encodeURIComponent(artist)
|
var artiststring = encodeURIComponent(artist)
|
||||||
titlestring = encodeURIComponent(title)
|
var titlestring = encodeURIComponent(title)
|
||||||
|
var d = new Date()
|
||||||
|
var time = Math.floor(d.getTime()/1000) - secondsago
|
||||||
|
//console.log("Time: " + time)
|
||||||
|
var requestbody = "artist=" + artiststring + "&title=" + titlestring + "&duration=" + seconds + "&time=" + time
|
||||||
chrome.storage.local.get("apikey",function(result) {
|
chrome.storage.local.get("apikey",function(result) {
|
||||||
APIKEY = result["apikey"]
|
APIKEY = result["apikey"]
|
||||||
chrome.storage.local.get("serverurl",function(result) {
|
chrome.storage.local.get("serverurl",function(result) {
|
||||||
URL = result["serverurl"]
|
URL = result["serverurl"]
|
||||||
var xhttp = new XMLHttpRequest();
|
var xhttp = new XMLHttpRequest();
|
||||||
xhttp.open("POST",URL + "/api/newscrobble",true);
|
xhttp.open("POST",URL + "/api/newscrobble",true);
|
||||||
xhttp.send("artist=" + artiststring + "&title=" + titlestring + "&duration=" + seconds + "&key=" + APIKEY)
|
xhttp.send(requestbody + "&key=" + APIKEY)
|
||||||
|
//console.log("Sent: " + requestbody)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Binary file not shown.
@ -124,6 +124,7 @@ def static_image(pth):
|
|||||||
@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>")
|
||||||
|
@webserver.route("/<name:re:.*\\.txt>")
|
||||||
def static(name):
|
def static(name):
|
||||||
response = static_file("website/" + name,root="")
|
response = static_file("website/" + name,root="")
|
||||||
response.set_header("Cache-Control", "public, max-age=3600")
|
response.set_header("Cache-Control", "public, max-age=3600")
|
||||||
@ -214,7 +215,8 @@ setproctitle.setproctitle("Maloja")
|
|||||||
|
|
||||||
## start database
|
## start database
|
||||||
database.start_db()
|
database.start_db()
|
||||||
database.register_subroutes(webserver,"/api")
|
#database.register_subroutes(webserver,"/api")
|
||||||
|
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='::', port=MAIN_PORT, server='waitress')
|
||||||
|
@ -30,6 +30,13 @@ DEFAULT_RANGE_CHARTS_TRACKS = year
|
|||||||
# can be days, weeks, months, years
|
# can be days, weeks, months, years
|
||||||
DEFAULT_RANGE_PULSE = months
|
DEFAULT_RANGE_PULSE = months
|
||||||
|
|
||||||
|
[Fluff]
|
||||||
|
|
||||||
|
# how many scrobbles a track needs to aquire this status
|
||||||
|
SCROBBLES_GOLD = 250
|
||||||
|
SCROBBLES_PLATINUM = 500
|
||||||
|
SCROBBLES_DIAMOND = 1000
|
||||||
|
|
||||||
[Misc]
|
[Misc]
|
||||||
|
|
||||||
EXPERIMENTAL_FEATURES = no
|
EXPERIMENTAL_FEATURES = no
|
||||||
|
@ -375,6 +375,12 @@ a.bronze {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
img.certrecord {
|
||||||
|
height:30px;
|
||||||
|
vertical-align: text-bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
**
|
**
|
||||||
**
|
**
|
||||||
@ -463,12 +469,16 @@ table.list td.icon div {
|
|||||||
background-position:center;
|
background-position:center;
|
||||||
}
|
}
|
||||||
|
|
||||||
table.list td.artists,td.artist,td.title {
|
table.list td.artists,td.artist,td.title,td.track {
|
||||||
min-width:100px;
|
min-width:100px;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table.list td.track span.artist_in_trackcolumn {
|
||||||
|
color:#bbb;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
BIN
website/media/record_diamond.png
Normal file
BIN
website/media/record_diamond.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 65 KiB |
BIN
website/media/record_gold.png
Normal file
BIN
website/media/record_gold.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 58 KiB |
BIN
website/media/record_platinum.png
Normal file
BIN
website/media/record_platinum.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 81 KiB |
2
website/robots.txt
Normal file
2
website/robots.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
User-agent: *
|
||||||
|
Disallow: /api/
|
@ -15,7 +15,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class="text">
|
<td class="text">
|
||||||
<span>KEY_ARTISTS</span><br/>
|
<span>KEY_ARTISTS</span><br/>
|
||||||
<h1>KEY_TRACKTITLE</h1> <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?max=100">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>
|
||||||
|
|
||||||
|
@ -16,11 +16,14 @@ def instructions(keys):
|
|||||||
imgurl = getTrackImage(track["artists"],track["title"],fast=True)
|
imgurl = getTrackImage(track["artists"],track["title"],fast=True)
|
||||||
pushresources = [{"file":imgurl,"type":"image"}] if imgurl.startswith("/") else []
|
pushresources = [{"file":imgurl,"type":"image"}] if imgurl.startswith("/") else []
|
||||||
|
|
||||||
data = database.trackInfo(track["artists"],track["title"])
|
data = database.trackInfo(track)
|
||||||
|
|
||||||
scrobblesnum = str(data["scrobbles"])
|
scrobblesnum = str(data["scrobbles"])
|
||||||
pos = "#" + str(data["position"])
|
pos = "#" + str(data["position"])
|
||||||
|
|
||||||
|
html_cert = ""
|
||||||
|
if data["certification"] is not None:
|
||||||
|
html_cert = "<img class='certrecord' src='/media/record_{cert}.png' title='This track has reached {certc} status' />".format(cert=data["certification"],certc=data["certification"].capitalize())
|
||||||
|
|
||||||
html_medals = ""
|
html_medals = ""
|
||||||
if "medals" in data and data["medals"] is not None:
|
if "medals" in data and data["medals"] is not None:
|
||||||
@ -61,6 +64,7 @@ def instructions(keys):
|
|||||||
"KEY_IMAGEURL":imgurl,
|
"KEY_IMAGEURL":imgurl,
|
||||||
"KEY_SCROBBLELINK":compose_querystring(keys),
|
"KEY_SCROBBLELINK":compose_querystring(keys),
|
||||||
"KEY_MEDALS":html_medals,
|
"KEY_MEDALS":html_medals,
|
||||||
|
"KEY_CERTS":html_cert,
|
||||||
"KEY_SCROBBLELIST":html_scrobbles,
|
"KEY_SCROBBLELIST":html_scrobbles,
|
||||||
# pulse
|
# pulse
|
||||||
"KEY_PULSE_MONTHS":html_pulse_months,
|
"KEY_PULSE_MONTHS":html_pulse_months,
|
||||||
|
Loading…
Reference in New Issue
Block a user