chiark / gitweb /
CVS version magic
[irc.git] / acrobat-chiark-0.2.py
1 #!/usr/bin/env python2
2 # $Id$
3 #
4 # Joel Rosdahl <joel@rosdahl.net>
5 # Andrew Walkingshaw <andrew@lexical.org.uk>
6 # Peter Corbett <ptc24@cam.ac.uk>
7 # Matthew Vernon <matthew@debian.org>
8
9 # This file is part of Acrobat.
10 #
11 # Acrobat is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published
13 # by the Free Software Foundation; either version 2 of the License,
14 # or (at your option) any later version.
15 #
16 # Acrobat is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 # GNU General Public License for more details.
20
21 # You should have received a copy of the GNU General Public License
22 # along with Acrobat; if not, write to the Free Software
23 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
24 # USA.
25
26 """
27     disconnect -- Disconnect the bot.  The bot will try to reconnect
28                   after 60 seconds.
29
30     die -- Let the bot cease to exist.
31
32     google -- search, 'I'm Feeling Lucky', and notice the user who searches
33               back with the url.
34 """
35
36 import string, urllib, sys, cPickle, os, random, re
37 from ircbot import SingleServerIRCBot
38 from irclib import nm_to_n, irc_lower
39
40
41
42 class Karma:
43     def __init__(self):
44         self.dict = {}
45     
46 class Acrobat(SingleServerIRCBot):
47     def __init__(self, channel, nickname, server, owner, port=6667):
48         SingleServerIRCBot.__init__(self,
49                                     [(server, port)], nickname, nickname)
50         self.channel = channel
51         self.owner = owner
52         # version control magic
53         acrorevision=$Revision$
54         acroversion=re.sub($Revision$]*\)$,\1)
55         # load the karma db
56         try:
57             f = open("karmadump", "r")
58             self.karma = cPickle.load(f)
59             f.close()
60         except IOError:
61             self.karma = Karma()
62         try:
63             f = open("trouts", "r")
64             self.trouts = [l.strip() for l in f.readlines() if l.find("%s") != -1]
65             f.close()
66         except IOError:
67             self.trouts = [ "hits %s with a wet trout.", "thwaps %s.", "questions %s's parentage.", "pokes its tounge out at %s.", "bites its thumb at %s."]
68
69     ## EVENT HANDLERS
70             
71     def on_welcome(self, conn, evt):
72         conn.join(self.channel)
73
74     def on_privmsg(self, conn, evt):
75         self.do_command(nm_to_n(evt.source()), evt.arguments()[0])
76         
77     def on_pubmsg(self, conn, evt):
78         payload = evt.arguments()[0]
79         a = string.split(evt.arguments()[0], " ", 1)
80         if len(a) > 1 \
81            and (irc_lower(a[0]) == irc_lower(self.connection.get_nickname())
82                 or irc_lower(a[0])[:-1] == irc_lower(self.connection.get_nickname())):
83
84             self.do_command(self.channel, string.strip(a[1]), public = 1)
85         if a[0].endswith("++"):
86             self.karmaup(a[0])
87         if a[0].endswith("--"):
88             self.karmadown(a[0])
89         if payload[0] == "!" and len(payload)>1:
90             self.do_command(self.channel, string.strip(payload[1:]), public=1)
91         if payload[0] == "~" and len(payload)>1:
92             self.do_command(self.channel, string.strip(payload[1:]), public=1)
93
94     # And now bot commands;
95
96     # increment karma
97     def karmaup(self, cmd):
98         if self.karma.dict.has_key(cmd.split()[0][:-2]):
99             self.karma.dict[cmd.split()[0][:-2]] += 1
100         else:
101             self.karma.dict[cmd.split()[0][:-2]] = 1
102
103     #decrement karma
104     def karmadown(self, cmd):
105         if self.karma.dict.has_key(cmd.split()[0][:-2]):
106             self.karma.dict[cmd.split()[0][:-2]] -= 1
107         else:
108             self.karma.dict[cmd.split()[0][:-2]] = -1
109
110     # query karma
111     def karmaq(self, cmd, conn, nick, public):
112         # in public
113         if public == 1:
114             try:
115                 if self.karma.dict.has_key(cmd.split()[1]):
116                     conn.privmsg(self.channel, "%s has karma %s."
117                                  %(cmd.split()[1],
118                                        self.karma.dict[cmd.split()[1]]))
119                 else:
120                     conn.privmsg(self.channel, "%s has no karma set." %
121                                  cmd.split()[1])
122             except IndexError:
123                 conn.privmsg(self.channel, "I have karma on %s items." %
124                              len(self.karma.dict.keys()))
125         # in private
126         else:
127             try:
128                 if self.karma.dict.has_key(cmd.split()[1]):
129                     conn.notice(nick, "%s has karma %s." %
130                                 (cmd.split()[1],
131                                  self.karma.dict[cmd.split()[1]]))
132                 else:
133                     conn.notice(nick, "I have karma on %s items." %
134                                 len(self.karma.dict.keys()))
135             except IndexError:
136                 conn.notice(nick, "I have karma on %s items." %
137                             len(self.karma.dict.keys()))
138     # query bot status
139     def infoq(self, cmd, nick, conn, public):
140         if public == 1:
141             conn.privmsg(self.channel,
142                          "I am Acrobat 0.2.1chiark, on %s, as nick %s." %
143                          (self.channel, self.connection.get_nickname()))
144             conn.privmsg(self.channel,
145                          "My owner is %s; I have karma on %s items." %
146                          (self.owner, len(self.karma.dict.keys())))
147         else:
148             conn.notice(nick, "I am Acrobat 0.2.1chiark, on %s, as nick %s." %
149                         (self.channel, self.connection.get_nickname()))
150             conn.notice(nick, "My owner is %s; I have karma on %s items." %
151                         (self.owner, len(self.karma.dict.keys())))
152
153     # trout someone
154     def troutq(self, cmd, nick, conn, public):
155             try:
156                 target = string.join(cmd.split()[1:])
157                 me = self.connection.get_nickname()
158                 trout_msg = random.choice(self.trouts)
159 #                # The bot is loyal(ish)...
160 #                if target.lower() == self.owner.lower():
161 #                    target = nick
162                 # ...and touchy.
163                 if me.lower() == target.lower():
164                     target = nick
165                 conn.action(self.channel, trout_msg % target)
166                 if public == 0:
167                     if random.random() <= 0.1:
168                         conn.action(self.channel, "notes %s is conducting a whispering campaign" % nick)
169             except IndexError:
170                 conn.notice(nick, "Who do you wish me to trout?")
171
172     # stock up on trouts
173     def reloadq(self, cmd, nick, conn, public):
174         if irc_lower(nick) == irc_lower(self.owner):
175             tback = self.trouts
176             try:
177                 f = open("trouts", "r")
178                 self.trouts = [l.strip() for l in f.readlines() if l.find("%s") != -1]
179                 f.close()
180                 conn.notice(nick, "I am re-armed!")
181             except IOError:
182                 conn.notice(nick, "Trout re-arming failed!")
183                 self.trouts = tback
184         else:
185             conn.notice(nick, "This command can only be invoked by my owner.")
186
187     # quit irc
188     def quit(self, cmd, nick, conn, public):
189         if irc_lower(nick) == irc_lower(self.owner):
190             f = open("karmadump", "w")
191             cPickle.dump(self.karma, f)
192             f.close()
193             self.die(msg="I have been chosen!")
194         elif public == 1:
195             conn.privmsg(nick, "Such aggression in public!")
196         else:
197             conn.notice(nick, "You're not my owner.")
198             
199     # google for something
200     def googleq(self, cmd, nick, conn, public):
201         cmdrest = string.join(cmd.split()[1:])
202         # "I'm Feeling Lucky" rather than try and parse the html
203         targ = ("http://www.google.com/search?q=%s&btnI=I'm+Feeling+Lucky"
204                 % urllib.quote_plus(cmdrest))
205         try:
206             # get redirected and grab the resulting url for returning
207             gsearch = urllib.urlopen(targ).geturl()
208             if gsearch != targ: # we've found something
209                 if public == 0:
210                     conn.notice(nick, str(gsearch))
211                 else: # we haven't found anything.
212                     conn.privmsg(nick, str(gsearch))
213             else:
214                 if public == 0:
215                     conn.notice(nick, "No pages found.")
216                 else:
217                     conn.privmsg(nick, "No pages found.")
218         except IOError: # if the connection times out. This blocks. :(
219             if public == 0:
220                 conn,notice(nick, "The web's broken. Waah!")
221             else:
222                 conn.privmsg(nick, "The web's broken. Waah!")
223                 
224     # General query handler
225     def do_command(self, nick, cmd, public=0):
226         conn = self.connection
227         # karma: up
228         if cmd.split()[0].endswith("++"):
229             self.karmaup(cmd)
230         
231         # karma: down
232         if cmd.split()[0].endswith("--"):
233             self.karmadown(cmd)
234             
235         # karma: query
236         if cmd.split()[0] == "karma" or cmd.split()[0] == "Karma":
237             self.karmaq(cmd, conn, nick, public)
238             
239         # bot's vital statistics
240         if cmd == "info":
241             self.infoq(cmd, nick, conn, public)
242
243         # weaponry
244         if cmd.split()[0] == "trout":
245             self.troutq(cmd, nick, conn, public)
246         if cmd == "reload":
247             self.reloadq(cmd, nick, conn, public)
248         
249         #disconnect
250         if cmd == "disconnect": # hop off for 60s
251             self.disconnect(msg="Be right back.")
252
253         # say to msg/channel
254         elif cmd.split()[0] == "say" \
255              and irc_lower(nick) == irc_lower(self.owner):
256             conn.privmsg(self.channel, string.join(cmd.split()[1:]))
257
258         # action to msg/channel
259         elif cmd.split()[0] == "do" \
260              and irc_lower(nick) == irc_lower(self.owner):
261             conn.action(self.channel, string.join(cmd.split()[1:]))
262
263         # quit IRC
264         elif cmd == "die":
265             self.quit(cmd, nick, conn, public)
266
267         # Google!
268         elif (cmd.split()[0] == "google" or cmd.split()[0] == "Google"):
269             self.googleq(cmd, nick, conn, public)            
270
271 def main():
272     if len(sys.argv) != 5: # insufficient arguments
273         print "Usage: acrobat <server[:port]> <channel> <nickname> owner"
274         sys.exit(1)
275     sv_port = string.split(sys.argv[1], ":", 1) # tuple; (server, port)
276     server = sv_port[0]
277     if len(sv_port) == 2:
278         try:
279             port = int(sv_port[1])
280         except ValueError:
281             print "Error: Erroneous port."
282             sys.exit(1)
283     else:
284         port = 6667 # default irc port
285     channel = sys.argv[2]
286     nickname = sys.argv[3]
287     owner = sys.argv[4]
288     # initialize the bot
289     bot = Acrobat(channel, nickname, server, owner, port)
290     sys.stderr.write("Trying to connect...\n")
291     # and the event loop
292     bot.start()
293
294 if __name__ == "__main__":
295     main()