mirror of
https://github.com/jlu5/SupyPlugins.git
synced 2025-05-01 07:51:08 -05:00
LastFM: major code cleanup and refactoring
- 'lastfm 'command is now split into subcommands, wrapping around one shared function (Closes #2). - Use bolding for prettier formatting in output. - Update command help/documentation to be more consistent (use "user", not "id" to refer to configured users). - Rename variables named 'id' -> 'user', since the previous is collides with a built in function. - Update tests accordingly.
This commit is contained in:
parent
d6f0b5f4b6
commit
fd5ae25adf
203
plugin.py
203
plugin.py
@ -49,7 +49,6 @@ except ImportError:
|
|||||||
from .LastFMDB import *
|
from .LastFMDB import *
|
||||||
|
|
||||||
class LastFMParser:
|
class LastFMParser:
|
||||||
|
|
||||||
def parseRecentTracks(self, stream):
|
def parseRecentTracks(self, stream):
|
||||||
"""
|
"""
|
||||||
<stream>
|
<stream>
|
||||||
@ -80,6 +79,7 @@ class LastFMParser:
|
|||||||
|
|
||||||
class LastFM(callbacks.Plugin):
|
class LastFM(callbacks.Plugin):
|
||||||
threaded = True
|
threaded = True
|
||||||
|
|
||||||
def __init__(self, irc):
|
def __init__(self, irc):
|
||||||
self.__parent = super(LastFM, self)
|
self.__parent = super(LastFM, self)
|
||||||
self.__parent.__init__(irc)
|
self.__parent.__init__(irc)
|
||||||
@ -96,178 +96,235 @@ class LastFM(callbacks.Plugin):
|
|||||||
self.db.close()
|
self.db.close()
|
||||||
self.__parent.die()
|
self.__parent.die()
|
||||||
|
|
||||||
def lastfm(self, irc, msg, args, method, optionalId):
|
def lastfm(self, irc, msg, args, method, user):
|
||||||
"""<method> [<id>]
|
"""<method> [<user>]
|
||||||
|
|
||||||
Lists LastFM info where <method> is in
|
Lists LastFM info where <method> is in
|
||||||
[friends, neighbours, profile, recenttracks, tags, topalbums,
|
[friends, neighbours, profile, recenttracks, tags, topalbums,
|
||||||
topartists, toptracks].
|
topartists, toptracks].
|
||||||
Set your LastFM ID with the set method (default is your current nick)
|
|
||||||
or specify <id> to switch for one call.
|
|
||||||
"""
|
"""
|
||||||
if not self.apiKey:
|
if not self.apiKey:
|
||||||
irc.error("The API Key is not set for this plugin. Please set it via"
|
irc.error("The API Key is not set. Please set it via "
|
||||||
"config plugins.lastfm.apikey and reload the plugin. "
|
"'config plugins.lastfm.apikey' and reload the plugin. "
|
||||||
"You can sign up for an API Key using "
|
"You can sign up for an API Key using "
|
||||||
"http://www.last.fm/api/account/create", Raise=True)
|
"http://www.last.fm/api/account/create", Raise=True)
|
||||||
method = method.lower()
|
|
||||||
knownMethods = {'friends': 'user.getFriends',
|
knownMethods = {'friends': 'user.getFriends',
|
||||||
'neighbours': 'user.getNeighbours',
|
'neighbours': 'user.getNeighbours',
|
||||||
'profile': 'user.getInfo',
|
|
||||||
'recenttracks': 'user.getRecentTracks',
|
|
||||||
'tags': 'user.getTopTags',
|
'tags': 'user.getTopTags',
|
||||||
'topalbums': 'user.getTopAlbums',
|
'topalbums': 'user.getTopAlbums',
|
||||||
'topartists': 'user.getTopArtists',
|
'topartists': 'user.getTopArtists',
|
||||||
'toptracks': 'user.getTopTracks'}
|
'toptracks': 'user.getTopTracks',
|
||||||
if method not in knownMethods:
|
'recenttracks': 'user.getRecentTracks'}
|
||||||
irc.error("Unsupported method '%s'" % method, Raise=True)
|
user = (user or self.db.getId(msg.nick) or msg.nick)
|
||||||
id = (optionalId or self.db.getId(msg.nick) or msg.nick)
|
|
||||||
channel = msg.args[0]
|
channel = msg.args[0]
|
||||||
maxResults = self.registryValue("maxResults", channel)
|
maxResults = self.registryValue("maxResults", channel)
|
||||||
|
|
||||||
url = "%sapi_key=%s&method=%s&user=%s" % (self.APIURL,
|
url = "%sapi_key=%s&method=%s&user=%s" % (self.APIURL,
|
||||||
self.apiKey, knownMethods[method], id)
|
self.apiKey, knownMethods[method], user)
|
||||||
try:
|
try:
|
||||||
f = utils.web.getUrlFd(url)
|
f = utils.web.getUrlFd(url)
|
||||||
except utils.web.Error:
|
except utils.web.Error:
|
||||||
irc.error("Unknown ID (%s) or unknown method (%s)"
|
irc.error("Unknown user '%s'." % user, Raise=True)
|
||||||
% (msg.nick, method), Raise=True)
|
|
||||||
|
|
||||||
xml = minidom.parse(f).getElementsByTagName("lfm")[0]
|
xml = minidom.parse(f).getElementsByTagName("lfm")[0]
|
||||||
|
# Grab a list of item names
|
||||||
content = xml.childNodes[1].getElementsByTagName("name")
|
content = xml.childNodes[1].getElementsByTagName("name")
|
||||||
results = [res.firstChild.nodeValue.strip() for res in content[0:maxResults*2]]
|
# Fetch their values, strip leading/trailing spaces, and add bolding
|
||||||
if method in ('topalbums', 'toptracks'):
|
results = [ircutils.bold(res.firstChild.nodeValue.strip()) for res in
|
||||||
|
content[0:maxResults*2]]
|
||||||
|
if method in ('topalbums', 'toptracks', 'recenttracks'):
|
||||||
# Annoying, hackish way of grouping artist+album/track items
|
# Annoying, hackish way of grouping artist+album/track items
|
||||||
results = ["%s - %s" % (thing, artist) for thing, artist in izip(results[1::2], results[::2])]
|
results = ["%s - %s" % (thing, artist) for thing, artist in
|
||||||
|
izip(results[1::2], results[::2])]
|
||||||
|
if len(content) < 1:
|
||||||
|
irc.error("%s doesn't seem to have any %s on LastFM." % (user,
|
||||||
|
method), Raise=True)
|
||||||
irc.reply("%s's %s: %s (with a total number of %i entries)"
|
irc.reply("%s's %s: %s (with a total number of %i entries)"
|
||||||
% (id, method, ", ".join(results[0:maxResults]),
|
% (ircutils.bold(user), method,
|
||||||
len(content)))
|
", ".join(results[0:maxResults]), len(content)))
|
||||||
|
|
||||||
lastfm = wrap(lastfm, ["something", optional("something")])
|
@wrap([additional("something")])
|
||||||
|
def friends(self, irc, msg, args, user):
|
||||||
|
"""[<user>]
|
||||||
|
|
||||||
|
Shows friends for <user>. If <user> is not given, defaults
|
||||||
|
to the LastFM user configured for your current nick."""
|
||||||
|
self.lastfm(irc, msg, args, 'friends', user)
|
||||||
|
|
||||||
|
@wrap([additional("something")])
|
||||||
|
def neighbours(self, irc, msg, args, user):
|
||||||
|
"""[<user>]
|
||||||
|
|
||||||
|
Shows friends for <user>. If <user> is not given, defaults
|
||||||
|
to the LastFM user configured for your current nick."""
|
||||||
|
self.lastfm(irc, msg, args, 'neighbours', user)
|
||||||
|
|
||||||
|
@wrap([additional("something")])
|
||||||
|
def toptags(self, irc, msg, args, user):
|
||||||
|
"""[<user>]
|
||||||
|
|
||||||
|
Shows the top tags for <user>. If <user> is not given, defaults
|
||||||
|
to the LastFM user configured for your current nick."""
|
||||||
|
self.lastfm(irc, msg, args, 'tags', user)
|
||||||
|
|
||||||
|
@wrap([additional("something")])
|
||||||
|
def topalbums(self, irc, msg, args, user):
|
||||||
|
"""[<user>]
|
||||||
|
|
||||||
|
Shows the top albums for <user>. If <user> is not given, defaults
|
||||||
|
to the LastFM user configured for your current nick."""
|
||||||
|
self.lastfm(irc, msg, args, 'topalbums', user)
|
||||||
|
|
||||||
|
@wrap([additional("something")])
|
||||||
|
def toptracks(self, irc, msg, args, user):
|
||||||
|
"""[<user>]
|
||||||
|
|
||||||
|
Shows the top tracks for <user>. If <user> is not given, defaults
|
||||||
|
to the LastFM user configured for your current nick."""
|
||||||
|
self.lastfm(irc, msg, args, 'toptracks', user)
|
||||||
|
|
||||||
|
@wrap([additional("something")])
|
||||||
|
def topartists(self, irc, msg, args, user):
|
||||||
|
"""[<user>]
|
||||||
|
|
||||||
|
Shows the top artists for <user>. If <user> is not given, defaults
|
||||||
|
to the LastFM user configured for your current nick."""
|
||||||
|
self.lastfm(irc, msg, args, 'topartists', user)
|
||||||
|
|
||||||
|
@wrap([additional("something")])
|
||||||
|
def recenttracks(self, irc, msg, args, user):
|
||||||
|
"""[<user>]
|
||||||
|
|
||||||
|
Shows the recent tracks for <user>. If <user> is not given, defaults
|
||||||
|
to the LastFM user configured for your current nick."""
|
||||||
|
self.lastfm(irc, msg, args, 'recenttracks', user)
|
||||||
|
|
||||||
def nowPlaying(self, irc, msg, args, optionalId):
|
def nowPlaying(self, irc, msg, args, optionalId):
|
||||||
"""[<id>]
|
"""[<user>]
|
||||||
|
|
||||||
Announces the now playing track of the specified LastFM ID.
|
Announces the track currently being played by <user>. If <user>
|
||||||
Set your LastFM ID with the set method (default is your current nick)
|
is not given, defaults to the LastFM user configured for your
|
||||||
or specify <id> to switch for one call.
|
current nick.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not self.apiKey:
|
if not self.apiKey:
|
||||||
irc.error("The API Key is not set for this plugin. Please set it via"
|
irc.error("The API Key is not set. Please set it via "
|
||||||
"config plugins.lastfm.apikey and reload the plugin. "
|
"'config plugins.lastfm.apikey' and reload the plugin. "
|
||||||
"You can sign up for an API Key using "
|
"You can sign up for an API Key using "
|
||||||
"http://www.last.fm/api/account/create", Raise=True)
|
"http://www.last.fm/api/account/create", Raise=True)
|
||||||
id = (optionalId or self.db.getId(msg.nick) or msg.nick)
|
user = (optionalId or self.db.getId(msg.nick) or msg.nick)
|
||||||
|
|
||||||
# see http://www.lastfm.de/api/show/user.getrecenttracks
|
# see http://www.lastfm.de/api/show/user.getrecenttracks
|
||||||
url = "%sapi_key=%s&method=user.getrecenttracks&user=%s" % (self.APIURL, self.apiKey, id)
|
url = "%sapi_key=%s&method=user.getrecenttracks&user=%s" % (self.APIURL, self.apiKey, user)
|
||||||
try:
|
try:
|
||||||
f = utils.web.getUrlFd(url)
|
f = utils.web.getUrlFd(url)
|
||||||
except utils.web.Error:
|
except utils.web.Error:
|
||||||
irc.error("Unknown ID (%s)" % id, Raise=True)
|
irc.error("Unknown user '%s'." % user, Raise=True)
|
||||||
|
|
||||||
parser = LastFMParser()
|
parser = LastFMParser()
|
||||||
(user, isNowPlaying, artist, track, album, time) = parser.parseRecentTracks(f)
|
(user, isNowPlaying, artist, track, album, time) = parser.parseRecentTracks(f)
|
||||||
if track is None:
|
if track is None:
|
||||||
irc.reply("%s doesn't seem to have listened to anything." % id)
|
irc.reply("%s doesn't seem to have listened to anything." % user)
|
||||||
return
|
return
|
||||||
albumStr = ("[%s]" % album) if album else ""
|
albumStr = ("[%s]" % album) if album else ""
|
||||||
|
track, artist, albumStr = map(ircutils.bold, (track, artist, albumStr))
|
||||||
if isNowPlaying:
|
if isNowPlaying:
|
||||||
irc.reply('%s is listening to "%s" by %s %s'
|
irc.reply('%s is listening to %s by %s %s'
|
||||||
% (user, track, artist, albumStr))
|
% (user, track, artist, albumStr))
|
||||||
else:
|
else:
|
||||||
irc.reply('%s listened to "%s" by %s %s more than %s'
|
irc.reply('%s listened to %s by %s %s more than %s'
|
||||||
% (user, track, artist, albumStr,
|
% (user, track, artist, albumStr,
|
||||||
self._formatTimeago(time)))
|
self._formatTimeago(time)))
|
||||||
|
|
||||||
np = wrap(nowPlaying, [optional("something")])
|
np = wrap(nowPlaying, [optional("something")])
|
||||||
|
|
||||||
def setUserId(self, irc, msg, args, newId):
|
def setUserId(self, irc, msg, args, newId):
|
||||||
"""<id>
|
"""<user>
|
||||||
|
|
||||||
Sets the LastFM ID for the caller and saves it in a database.
|
Sets the LastFM username for the caller and saves it in a database.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.db.set(msg.nick, newId)
|
self.db.set(msg.nick, newId)
|
||||||
irc.reply("LastFM ID changed.")
|
irc.replySuccess()
|
||||||
|
|
||||||
set = wrap(setUserId, ["something"])
|
set = wrap(setUserId, ["something"])
|
||||||
|
|
||||||
def profile(self, irc, msg, args, optionalId):
|
def profile(self, irc, msg, args, optionalId):
|
||||||
"""[<id>]
|
"""[<user>]
|
||||||
|
|
||||||
Prints the profile info for the specified LastFM ID.
|
Prints the profile info for the specified LastFM user. If <user>
|
||||||
Set your LastFM ID with the set method (default is your current nick)
|
is not given, defaults to the LastFM user configured for your
|
||||||
or specify <id> to switch for one call.
|
current nick.
|
||||||
"""
|
"""
|
||||||
if not self.apiKey:
|
if not self.apiKey:
|
||||||
irc.error("The API Key is not set for this plugin. Please set it via"
|
irc.error("The API Key is not set. Please set it via "
|
||||||
"config plugins.lastfm.apikey and reload the plugin. "
|
"'config plugins.lastfm.apikey' and reload the plugin. "
|
||||||
"You can sign up for an API Key using "
|
"You can sign up for an API Key using "
|
||||||
"http://www.last.fm/api/account/create", Raise=True)
|
"http://www.last.fm/api/account/create", Raise=True)
|
||||||
id = (optionalId or self.db.getId(msg.nick) or msg.nick)
|
user = (optionalId or self.db.getId(msg.nick) or msg.nick)
|
||||||
|
|
||||||
url = "%sapi_key=%s&method=user.getInfo&user=%s" % (self.APIURL, self.apiKey, id)
|
url = "%sapi_key=%s&method=user.getInfo&user=%s" % (self.APIURL, self.apiKey, user)
|
||||||
try:
|
try:
|
||||||
f = utils.web.getUrlFd(url)
|
f = utils.web.getUrlFd(url)
|
||||||
except utils.web.Error:
|
except utils.web.Error:
|
||||||
irc.error("Unknown user (%s)" % id, Raise=True)
|
irc.error("Unknown user '%s'." % user, Raise=True)
|
||||||
|
|
||||||
xml = minidom.parse(f).getElementsByTagName("user")[0]
|
xml = minidom.parse(f).getElementsByTagName("user")[0]
|
||||||
keys = ("realname", "registered", "age", "gender", "country", "playcount")
|
keys = ("realname", "registered", "age", "gender", "country", "playcount")
|
||||||
profile = {"id": id}
|
profile = {"id": ircutils.bold(user)}
|
||||||
for tag in keys:
|
for tag in keys:
|
||||||
try:
|
try:
|
||||||
profile[tag] = xml.getElementsByTagName(tag)[0].firstChild.data.strip()
|
profile[tag] = ircutils.bold(xml.getElementsByTagName(tag)[0].firstChild.data.strip())
|
||||||
except AttributeError: # empty field
|
except AttributeError: # empty field
|
||||||
profile[tag] = 'unknown'
|
profile[tag] = ircutils.bold('unknown')
|
||||||
irc.reply(("%(id)s (realname: %(realname)s) registered on %(registered)s; age: %(age)s / %(gender)s; "
|
irc.reply(("%(id)s (realname: %(realname)s) registered on %(registered)s; age: %(age)s / %(gender)s; "
|
||||||
"Country: %(country)s; Tracks played: %(playcount)s") % profile)
|
"Country: %(country)s; Tracks played: %(playcount)s") % profile)
|
||||||
|
|
||||||
profile = wrap(profile, [optional("something")])
|
profile = wrap(profile, [optional("something")])
|
||||||
|
|
||||||
def compareUsers(self, irc, msg, args, user1, optionalUser2):
|
def compareUsers(self, irc, msg, args, user1, optionalUser2):
|
||||||
"""user1 [<user2>]
|
"""<user1> [<user2>]
|
||||||
|
|
||||||
Compares the taste from two users
|
Compares the music tastes of <user1> and <user2>. If <user2>
|
||||||
If <user2> is ommitted, the taste is compared against the ID of the calling user.
|
is not given, defaults to the LastFM user configured for your
|
||||||
|
current nick.
|
||||||
"""
|
"""
|
||||||
if not self.apiKey:
|
if not self.apiKey:
|
||||||
irc.error("The API Key is not set for this plugin. Please set it via"
|
irc.error("The API Key is not set. Please set it via "
|
||||||
"config plugins.lastfm.apikey and reload the plugin. "
|
"'config plugins.lastfm.apikey' and reload the plugin. "
|
||||||
"You can sign up for an API Key using "
|
"You can sign up for an API Key using "
|
||||||
"http://www.last.fm/api/account/create", Raise=True)
|
"http://www.last.fm/api/account/create", Raise=True)
|
||||||
user2 = (optionalUser2 or self.db.getId(msg.nick) or msg.nick)
|
user2 = (optionalUser2 or self.db.getId(msg.nick) or msg.nick)
|
||||||
|
|
||||||
channel = msg.args[0]
|
channel = msg.args[0]
|
||||||
maxResults = self.registryValue("maxResults", channel)
|
maxResults = self.registryValue("maxResults", channel)
|
||||||
# see http://www.lastfm.de/api/show/tasteometer.compare
|
url = ("%sapi_key=%s&method=tasteometer.compare&type1=user&type2=user"
|
||||||
url = "%sapi_key=%s&method=tasteometer.compare&type1=user&type2=user&value1=%s&value2=%s&limit=%s" % (
|
"&value1=%s&value2=%s&limit=%s" % (self.APIURL, self.apiKey,
|
||||||
self.APIURL, self.apiKey, user1, user2, maxResults)
|
user1, user2, maxResults))
|
||||||
try:
|
try:
|
||||||
f = utils.web.getUrlFd(url)
|
f = utils.web.getUrlFd(url)
|
||||||
except utils.web.Error as e:
|
except utils.web.Error as e:
|
||||||
irc.error("Failure: %s" % (e), Raise=True)
|
irc.error(str(e), Raise=True)
|
||||||
|
|
||||||
xml = minidom.parse(f)
|
xml = minidom.parse(f)
|
||||||
resultNode = xml.getElementsByTagName("result")[0]
|
resultNode = xml.getElementsByTagName("result")[0]
|
||||||
score = float(self._parse(resultNode, "score"))
|
try:
|
||||||
scoreStr = "%s (%s)" % (round(score, 2), self._formatRating(score))
|
score = resultNode.getElementsByTagName('score')[0].firstChild.data
|
||||||
# Note: XPath would be really cool here...
|
score = round(float(score), 3)
|
||||||
artists = [el for el in resultNode.getElementsByTagName("artist")]
|
except (IndexError, ValueError):
|
||||||
artistNames = [el.getElementsByTagName("name")[0].firstChild.data for el in artists]
|
scoreStr = "unknown"
|
||||||
irc.reply("Result of comparison between %s and %s: score: %s, common artists: %s" \
|
else:
|
||||||
% (user1, user2, scoreStr, ", ".join(artistNames)))
|
scoreStr = "%s (%s)" % (ircutils.bold(self._formatRating(score)),
|
||||||
|
score)
|
||||||
|
artists = resultNode.getElementsByTagName("artist")
|
||||||
|
artistNames = [ircutils.bold(el.getElementsByTagName("name")[0].firstChild.data)
|
||||||
|
for el in artists]
|
||||||
|
s = ("Result of comparison between %s and %s: score: %s, common "
|
||||||
|
"artists: %s" % (ircutils.bold(user1), ircutils.bold(user2),
|
||||||
|
scoreStr, ", ".join(artistNames)))
|
||||||
|
irc.reply(s)
|
||||||
|
|
||||||
compare = wrap(compareUsers, ["something", optional("something")])
|
compare = wrap(compareUsers, ["something", optional("something")])
|
||||||
|
|
||||||
def _parse(self, node, tagName, exceptMsg="not specified"):
|
|
||||||
try:
|
|
||||||
return node.getElementsByTagName(tagName)[0].firstChild.data
|
|
||||||
except IndexError:
|
|
||||||
return exceptMsg
|
|
||||||
|
|
||||||
def _formatTimeago(self, unixtime):
|
def _formatTimeago(self, unixtime):
|
||||||
t = int(time()-unixtime)
|
t = int(time()-unixtime)
|
||||||
if t/86400 >= 1:
|
if t/86400 >= 1:
|
||||||
|
35
test.py
35
test.py
@ -35,6 +35,7 @@ try:
|
|||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
|
import os
|
||||||
|
|
||||||
class LastFMTestCase(PluginTestCase):
|
class LastFMTestCase(PluginTestCase):
|
||||||
plugins = ('LastFM',)
|
plugins = ('LastFM',)
|
||||||
@ -44,31 +45,31 @@ class LastFMTestCase(PluginTestCase):
|
|||||||
apiKey = os.environ.get('lastfm_apikey')
|
apiKey = os.environ.get('lastfm_apikey')
|
||||||
if not apiKey:
|
if not apiKey:
|
||||||
e = ("The LastFM API key has not been set. "
|
e = ("The LastFM API key has not been set. "
|
||||||
"Please set the environment variable 'lastfm_apikey' "
|
"Please set the environment variable 'lastfm_apikey' "
|
||||||
"and try again. ('export lastfm_apikey=<apikey>' for those "
|
"and try again.")
|
||||||
"using bash)")
|
|
||||||
raise callbacks.Error(e)
|
raise callbacks.Error(e)
|
||||||
conf.supybot.plugins.LastFM.apiKey.setValue(apiKey)
|
conf.supybot.plugins.LastFM.apiKey.setValue(apiKey)
|
||||||
|
|
||||||
def testLastfm(self):
|
def testRecentTracks(self):
|
||||||
self.assertNotError("lastfm recenttracks")
|
self.assertNotError("recenttracks")
|
||||||
self.assertError("lastfm TESTEXCEPTION")
|
self.assertNotError("recenttracks czshadow")
|
||||||
self.assertNotError("lastfm recenttracks czshadow")
|
|
||||||
self.assertNotError("lastfm np krf")
|
def testNowPlaying(self):
|
||||||
|
self.assertNotError("np krf")
|
||||||
|
|
||||||
def testLastfmDB(self):
|
def testLastfmDB(self):
|
||||||
self.assertNotError("lastfm set nick") # test db
|
self.assertNotError("set nick") # test db
|
||||||
self.assertNotError("lastfm set test") # test db unset
|
self.assertNotError("set test") # test db unset
|
||||||
|
|
||||||
def testLastfmProfile(self):
|
def testProfile(self):
|
||||||
self.assertNotError("lastfm profile czshadow")
|
self.assertNotError("profile czshadow")
|
||||||
self.assertNotError("lastfm profile test")
|
self.assertNotError("profile test")
|
||||||
|
|
||||||
def testLastfmCompare(self):
|
def testCompare(self):
|
||||||
self.assertNotError("lastfm compare krf czshadow")
|
self.assertNotError("compare krf czshadow")
|
||||||
self.assertNotError("lastfm compare krf")
|
self.assertNotError("compare krf")
|
||||||
|
|
||||||
def testLastFMParseRecentTracks(self):
|
def testParseRecentTracks(self):
|
||||||
"""Parser tests"""
|
"""Parser tests"""
|
||||||
|
|
||||||
# noalbum, nowplaying
|
# noalbum, nowplaying
|
||||||
|
Loading…
x
Reference in New Issue
Block a user