1
###
2
# Copyright (c) 2006, Ilya Kuznetsov
3
# Copyright (c) 2008, Kevin Funk
4
# All rights reserved.
5
#
6
# Redistribution and use in source and binary forms, with or without
7
# modification, are permitted provided that the following conditions are met:
8
#
9
#   * Redistributions of source code must retain the above copyright notice,
10
#     this list of conditions, and the following disclaimer.
11
#   * Redistributions in binary form must reproduce the above copyright notice,
12
#     this list of conditions, and the following disclaimer in the
13
#     documentation and/or other materials provided with the distribution.
14
#   * Neither the name of the author of this software nor the name of
15
#     contributors to this software may be used to endorse or promote products
16
#     derived from this software without specific prior written consent.
17
#
18
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21
# ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
22
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28
# POSSIBILITY OF SUCH DAMAGE.
29
30
###
31
32
import supybot.utils as utils
33
from supybot.commands import *
34
import supybot.conf as conf
35
import supybot.plugins as plugins
36
import supybot.ircutils as ircutils
37
import supybot.callbacks as callbacks
38
import supybot.world as world
39
40
import urllib2
41
from xml.dom import minidom
42
from time import time
43
44
from LastFMDB import *
45
46
class LastFM(callbacks.Plugin):
47
    BASEURL = "http://ws.audioscrobbler.com/1.0/user"
48
    APIKEY = "b25b959554ed76058ac220b7b2e0a026" # FIXME: Get own key
49
    APIURL = "http://ws.audioscrobbler.com/2.0/?api_key=%s&" % APIKEY
50
51
    def __init__(self, irc):
52
        self.__parent = super(LastFM, self)
53
        self.__parent.__init__(irc)
54
        self.db = LastFMDB(dbfilename)
55
        world.flushers.append(self.db.flush)
56
57
    def die(self):
58
        if self.db.flush in world.flushers:
59
            world.flushers.remove(self.db.flush)
60
        self.db.close()
61
        self.__parent.die()
62
63
    def lastfm(self, irc, msg, args, method, optionalId):
64
        """<method> [<id>]
65
66
        Lists LastFM info where <method> is in
67
        [friends, neighbours, profile, recenttracks, tags, topalbums,
68
        topartists, toptracks].
69
        Set your LastFM ID with the set method (default is your current nick)
70
        or specify <id> to switch for one call.
71
        """
72
73
        id = (optionalId or self.db.getId(msg.nick) or msg.nick)
74
        channel = msg.args[0]
75
        maxResults = self.registryValue("maxResults", channel)
76
        method = method.lower()
77
78
        try:
79
            f = urllib2.urlopen("%s/%s/%s.txt" % (self.BASEURL, id, method))
80
        except urllib2.HTTPError:
81
            irc.error("Unknown ID (%s) or unknown method (%s)"
82
                    % (msg.nick, method))
83
            return
84
85
86
        lines = f.read().split("\n")
87
        content = map(lambda s: s.split(",")[-1], lines)
88
89
        irc.reply("%s's %s: %s (with a total number of %i entries)"
90
                % (id, method, ", ".join(content[0:maxResults]),
91
                    len(content)))
92
93
    lastfm = wrap(lastfm, ["something", optional("something")])
94
95
    def np(self, irc, msg, args, optionalId):
96
        """[<id>]
97
98
        Announces the now playing track of the specified LastFM ID.
99
        Set your LastFM ID with the set method (default is your current nick)
100
        or specify <id> to switch for one call.
101
        """
102
103
        id = (optionalId or self.db.getId(msg.nick) or msg.nick)
104
105
        try:
106
            f = urllib2.urlopen("%s&method=user.getrecenttracks&user=%s"
107
                    % (self.APIURL, id))
108
        except urllib2.HTTPError:
109
            irc.error("Unknown ID (%s)" % id)
110
            return
111
112
        xml = minidom.parse(f).getElementsByTagName("recenttracks")[0]
113
        user = xml.getAttribute("user")
114
        t = xml.getElementsByTagName("track")[0] # most recent track
115
        isNowplaying = (t.getAttribute("nowplaying") == "true")
116
        artist = t.getElementsByTagName("artist")[0].firstChild.data
117
        track = t.getElementsByTagName("name")[0].firstChild.data
118
        try:
119
            album = "["+t.getElementsByTagName("album")[0].firstChild.data+"]"
120
        except:
121
            album = ""
122
123
        if isNowplaying:
124
            irc.reply(('%s is listening to "%s" by %s %s'
125
                    % (user, track, artist, album)).encode("utf8"))
126
        else:
127
            time = int(t.getElementsByTagName("date")[0].getAttribute("uts"))
128
            irc.reply(('%s listened to "%s" by %s %s more than %s'
129
                    % (user, track, artist, album,
130
                        self._formatTimeago(time))).encode("utf-8"))
131
132
    np = wrap(np, [optional("something")])
133
134
    def set(self, irc, msg, args, newId):
135
        """<id>
136
137
        Sets the LastFM ID for the caller and saves it in a database.
138
        """
139
140
        self.db.set(msg.nick, newId)
141
142
        irc.reply("LastFM ID changed.")
143
        self.profile(irc, msg, args)
144
145
    set = wrap(set, ["something"])
146
147
    def profile(self, irc, msg, args, optionalId):
148
        """[<id>]
149
150
        Prints the profile info for the specified LastFM ID.
151
        Set your LastFM ID with the set method (default is your current nick)
152
        or specify <id> to switch for one call.
153
        """
154
155
        id = (optionalId or self.db.getId(msg.nick) or msg.nick)
156
157
        try:
158
            f = urllib2.urlopen("%s/%s/profile.xml" % (self.BASEURL, id))
159
        except urllib2.HTTPError:
160
            irc.error("Unknown user (%s)" % id)
161
            return
162
163
        xml = minidom.parse(f).getElementsByTagName("profile")[0]
164
        keys = "realname registered age gender country playcount".split()
165
        profile = tuple([self._parse(xml, node) for node in keys])
166
167
        irc.reply(("%s (realname: %s) registered on %s; age: %s / %s; \
168
Country: %s; Tracks played: %s" % ((id,) + profile)).encode("utf8"))
169
170
    profile = wrap(profile, [optional("something")])
171
172
    def _parse(self, data, node, exceptMsg="not specified"):
173
            try:
174
                return data.getElementsByTagName(node)[0].firstChild.data
175
            except IndexError:
176
                return exceptMsg
177
178
    def _formatTimeago(self, unixtime):
179
        t = int(time()-unixtime)
180
        if t/86400 > 0:
181
            return "%i days ago" % (t/86400)
182
        if t/3600 > 0:
183
            return "%i hours ago" % (t/3600)
184
        if t/60 > 0:
185
            return "%i minutes ago" % (t/60)
186
        if t > 0:
187
            return "%i seconds ago" % (t)
188
189
dbfilename = conf.supybot.directories.data.dirize("LastFM.db")
190
191
Class = LastFM
192
193
194
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: