From abef221435bb417d36dfd6b982e482b86c719108 Mon Sep 17 00:00:00 2001 From: Krateng Date: Sat, 25 Jul 2020 05:09:58 +0200 Subject: [PATCH] Simplified structure for third party services --- maloja/__pkginfo__.py | 2 +- maloja/database.py | 7 +- maloja/thirdparty/__init__.py | 130 +++++++++++++++++++++++++ maloja/thirdparty/common/lastfm.py | 14 --- maloja/thirdparty/lastfm.py | 41 ++++++++ maloja/thirdparty/scrobble/__init__.py | 20 ---- maloja/thirdparty/scrobble/lastfm.py | 26 ----- 7 files changed, 177 insertions(+), 63 deletions(-) create mode 100644 maloja/thirdparty/__init__.py delete mode 100644 maloja/thirdparty/common/lastfm.py create mode 100644 maloja/thirdparty/lastfm.py delete mode 100644 maloja/thirdparty/scrobble/__init__.py delete mode 100644 maloja/thirdparty/scrobble/lastfm.py diff --git a/maloja/__pkginfo__.py b/maloja/__pkginfo__.py index 96e0c01..7662666 100644 --- a/maloja/__pkginfo__.py +++ b/maloja/__pkginfo__.py @@ -20,7 +20,7 @@ requires = [ "setproctitle>=1.1.10", "wand>=0.5.4", "lesscpy>=0.13", - "jinja2">2.11 + "jinja2>2.11" ] resources = [ "web/*/*", diff --git a/maloja/database.py b/maloja/database.py index c17fe77..7c0f0b2 100644 --- a/maloja/database.py +++ b/maloja/database.py @@ -6,7 +6,10 @@ from . import utilities from .malojatime import register_scrobbletime, time_stamps, ranges from .urihandler import uri_to_internal, internal_to_uri, compose_querystring from . import compliant_api -from .external import proxy_scrobble + +#from .external import proxy_scrobble +from .thirdparty import proxy_scrobble_all + from .__pkginfo__ import version from .globalconf import datadir # doreah toolkit @@ -155,7 +158,7 @@ def createScrobble(artists,title,time,album=None,duration=None,volatile=False): invalidate_caches() dblock.release() - proxy_scrobble(artists,title,time) + proxy_scrobble_all(artists,title,time) return get_track_dict(TRACKS[obj.track]) diff --git a/maloja/thirdparty/__init__.py b/maloja/thirdparty/__init__.py new file mode 100644 index 0000000..b861e2b --- /dev/null +++ b/maloja/thirdparty/__init__.py @@ -0,0 +1,130 @@ +# these different interfaces are for the different roles a third party service +# can fulfill. implementing them adds some generic functionality to attempt to +# actually perform the role, but this will have to be overwritten in most cases. +# functionality is separated into different layers to allow partial override + +# also yes, we're using singleton classes for the different providers +# pls don't sue me + +import xml.etree.ElementTree as ElementTree +import urllib.parse, urllib.request +from doreah.settings import get_settings +from doreah.logging import log + + + + +services = { + "proxyscrobble":[], + "import":[], + "metadata":[] +} + + +def proxy_scrobble_all(artists,title,timestamp): + for service in services["proxyscrobble"]: + service.scrobble(artists,title,timestamp) + + + + +class GenericInterface: + def active_proxyscrobble(self): + return False + def active_import(self): + return False + def active_metadata(self): + return False + + settings = {} + + proxyscrobble = {} + scrobbleimport = {} + metadata = {} + + def __init__(self): + # populate from settings file once on creation + # avoid constant disk access, restart on adding services is acceptable + for key in self.settings: + self.settings[key] = get_settings(self.settings[key]) + + def __init_subclass__(cls,abstract=False): + if not abstract: + s = cls() + if s.active_proxyscrobble(): + services["proxyscrobble"].append(s) + log(cls.name + "Registered as proxy scrobble target") + if s.active_import(): + services["import"].append(s) + log(cls.name + "Registered as scrobble import source") + if s.active_metadata(): + services["metadata"].append(s) + log(cls.name + "Registered for metadata provider") + +# proxy scrobbler +class ProxyScrobbleInterface(GenericInterface,abstract=True): + + proxyscrobble = { + "required_settings":[], + "activated_setting":None + } + + def active_proxyscrobble(self): + return ( + all(self.settings[key] not in [None,"ASK"] for key in self.proxyscrobble["required_settings"]) and + get_settings(self.proxyscrobble["activated_setting"]) + ) + + def scrobble(self,artists,title,timestamp): + response = urllib.request.urlopen( + self.proxyscrobble["scrobbleurl"], + data=utf(self.postdata(artists,title,timestamp))) + responsedata = response.read() + if self.proxyscrobble["response_type"] == "xml": + data = ElementTree.fromstring(responsedata) + return self.parse_response(data) + +# scrobble import +class ImportInterface(GenericInterface,abstract=True): + + scrobbleimport = { + "required_settings":[], + "activated_setting":None + } + + def active_import(self): + return ( + all(self.settings[key] not in [None,"ASK"] for key in self.scrobbleimport["required_settings"]) and + get_settings(self.scrobbleimport["activated_setting"]) + ) + + +# metadata +class MetadataInterface(GenericInterface,abstract=True): + + metadata = { + "required_settings":[], + "activated_setting":None + } + + def active_metadata(self): + return ( + all(self.settings[key] not in [None,"ASK"] for key in self.metadata["required_settings"]) and + get_settings(self.metadata["activated_setting"]) + ) + + + + + +### useful stuff + +def utf(st): + return st.encode(encoding="UTF-8") + + + + +### actually create everything + +from . import lastfm diff --git a/maloja/thirdparty/common/lastfm.py b/maloja/thirdparty/common/lastfm.py deleted file mode 100644 index a481a47..0000000 --- a/maloja/thirdparty/common/lastfm.py +++ /dev/null @@ -1,14 +0,0 @@ -import hashlib -import urllib - -class LastFMInterface: - def query_compose(self,parameters): - m = hashlib.md5() - keys = sorted(str(k) for k in parameters) - m.update(self.utf("".join(str(k) + str(parameters[k]) for k in keys))) - m.update(self.utf(get_settings("LASTFM_API_SECRET"))) - sig = m.hexdigest() - return urllib.parse.urlencode(parameters) + "&api_sig=" + sig - - def utf(self,st): - return st.encode(encoding="UTF-8") diff --git a/maloja/thirdparty/lastfm.py b/maloja/thirdparty/lastfm.py new file mode 100644 index 0000000..c8baadd --- /dev/null +++ b/maloja/thirdparty/lastfm.py @@ -0,0 +1,41 @@ +from . import MetadataInterface, ProxyScrobbleInterface, utf +import hashlib +import urllib.parse, urllib.request + +class LastFM(MetadataInterface, ProxyScrobbleInterface): + name = "LastFM" + + settings = { + "apikey":"LASTFM_API_KEY", + "sk":"LASTFM_API_SK", + "secret":"LASTFM_API_SECRET" + } + + proxyscrobble = { + "scrobbleurl": "http://ws.audioscrobbler.com/2.0/", + "response_type":"xml", + "required_settings": ["apikey","sk","secret"], + "activated_setting": "SCROBBLE_LASTFM" + } + + def parse_response(self,data): + return data.attrib.get("status") == "ok" and data.find("scrobbles").attrib.get("ignored") == "0" + + def postdata(self,artists,title,timestamp): + return self.query_compose({ + "method":"track.scrobble", + "artist[0]":", ".join(artists), + "track[0]":title, + "timestamp":timestamp, + "api_key":self.settings["apikey"], + "sk":self.settings["sk"] + }) + + # creates signature and returns full query string + def query_compose(self,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(self.settings["secret"])) + sig = m.hexdigest() + return urllib.parse.urlencode(parameters) + "&api_sig=" + sig diff --git a/maloja/thirdparty/scrobble/__init__.py b/maloja/thirdparty/scrobble/__init__.py deleted file mode 100644 index a1c1b68..0000000 --- a/maloja/thirdparty/scrobble/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -import xml.etree.ElementTree as ElementTree -import urllib - -class ScrobbleInterface: - - required_settings = [] - activated_setting = "" - - def active(self): - return ( - all(get_settings(settingname) not in [None,"ASK"] for settingname in self.required_settings) and - get_settings(self.activated_setting) - ) - - def scrobble(self,artists,title,timestamp): - response = urllib.request.urlopen(self.scrobbleurl,data=self.postdata(artists,title,timestamp)) - responsedata = response.read() - if self.response_type == "xml": - data = ElementTree.fromstring(responsedata) - return self.parse_response(data) diff --git a/maloja/thirdparty/scrobble/lastfm.py b/maloja/thirdparty/scrobble/lastfm.py deleted file mode 100644 index f7dbd60..0000000 --- a/maloja/thirdparty/scrobble/lastfm.py +++ /dev/null @@ -1,26 +0,0 @@ -from . import ScrobbleInterface - -from doreah.settings import get_settings - -class LastFMScrobbler(ScrobbleInterface,LastFMInterface): - - scrobbleurl = "http://ws.audioscrobbler.com/2.0/" - required_settings = [ - "LASTFM_API_KEY", - "LASTFM_API_SK", - "LASTFM_API_SECRET" - ] - activated_setting = "SCROBBLE_LASTFM" - - def parse_response(self,data): - return data.attrib.get("status") == "ok" and data.find("scrobbles").attrib.get("ignored") == "0" - - def postdata(self,artists,title,timestamp): - return self.query_compose({ - "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") - })