chiark / gitweb /
Fixed a slight RCS merge collision
[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         # load the karma db
53         try:
54             f = open("karmadump", "r")
55             self.karma = cPickle.load(f)
56             f.close()
57         except IOError:
58             self.karma = Karma()
59         try:
60             f = open("trouts", "r")
61             self.trouts = [l.strip() for l in f.readlines() if l.find("%s") != -1]
62             f.close()
63         except IOError:
64             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."]
65
66     ## EVENT HANDLERS
67             
68     def on_welcome(self, conn, evt):
69         conn.join(self.channel)
70
71     def on_privmsg(self, conn, evt):
72         self.do_command(nm_to_n(evt.source()), evt.arguments()[0])
73         
74     def on_pubmsg(self, conn, evt):
75         payload = evt.arguments()[0]
76         a = string.split(evt.arguments()[0], " ", 1)
77         if len(a) > 1 \
78            and (irc_lower(a[0]) == irc_lower(self.connection.get_nickname())
79                 or irc_lower(a[0])[:-1] == irc_lower(self.connection.get_nickname())):
80
81             self.do_command(self.channel, string.strip(a[1]), public = 1)
82         if a[0].endswith("++"):
83             self.karmaup(a[0])
84         if a[0].endswith("--"):
85             self.karmadown(a[0])
86         if payload[0] == "!" and len(payload)>1:
87             self.do_command(self.channel, string.strip(payload[1:]), public=1)
88         if payload[0] == "~" and len(payload)>1:
89             self.do_command(self.channel, string.strip(payload[1:]), public=1)
90
91     # And now bot commands;
92
93     # increment karma
94     def karmaup(self, cmd):
95         if self.karma.dict.has_key(cmd.split()[0][:-2]):
96             self.karma.dict[cmd.split()[0][:-2]] += 1
97         else:
98             self.karma.dict[cmd.split()[0][:-2]] = 1
99
100     #decrement karma
101     def karmadown(self, cmd):
102         if self.karma.dict.has_key(cmd.split()[0][:-2]):
103             self.karma.dict[cmd.split()[0][:-2]] -= 1
104         else:
105             self.karma.dict[cmd.split()[0][:-2]] = -1
106
107     # query karma
108     def karmaq(self, cmd, conn, nick, public):
109         # in public
110         if public == 1:
111             try:
112                 if self.karma.dict.has_key(cmd.split()[1]):
113                     conn.privmsg(self.channel, "%s has karma %s."
114                                  %(cmd.split()[1],
115                                        self.karma.dict[cmd.split()[1]]))
116                 else:
117                     conn.privmsg(self.channel, "%s has no karma set." %
118                                  cmd.split()[1])
119             except IndexError:
120                 conn.privmsg(self.channel, "I have karma on %s items." %
121                              len(self.karma.dict.keys()))
122         # in private
123         else:
124             try:
125                 if self.karma.dict.has_key(cmd.split()[1]):
126                     conn.notice(nick, "%s has karma %s." %
127                                 (cmd.split()[1],
128                                  self.karma.dict[cmd.split()[1]]))
129                 else:
130                     conn.notice(nick, "I have karma on %s items." %
131                                 len(self.karma.dict.keys()))
132             except IndexError:
133                 conn.notice(nick, "I have karma on %s items." %
134                             len(self.karma.dict.keys()))
135     # query bot status
136     def infoq(self, cmd, nick, conn, public):
137         # version control magic
138         acrorevision="$Revision$"
139         acrorev1=re.sub(r'\$Revision: (.*)',r'\1',acrorevision)
140         acroversion=re.sub(r'(.*) \$',r'\1',acrorev1)
141         if public == 1:
142             conn.privmsg(self.channel,
143                          "I am Acrobat %s, on %s, as nick %s." %
144                          (acroversion, self.channel, self.connection.get_nickname()))
145             conn.privmsg(self.channel,
146                          "My owner is %s; I have karma on %s items." %
147                          (self.owner, len(self.karma.dict.keys())))
148         else:
149             conn.notice(nick, "I am Acrobat %s, on %s, as nick %s." %
150                         (acroversion, self.channel, self.connection.get_nickname()))
151             conn.notice(nick, "My owner is %s; I have karma on %s items." %
152                         (self.owner, len(self.karma.dict.keys())))
153
154     # trout someone
155     def troutq(self, cmd, nick, conn, public):
156             try:
157                 target = string.join(cmd.split()[1:])
158                 me = self.connection.get_nickname()
159                 trout_msg = random.choice(self.trouts)
160 #                # The bot is loyal(ish)...
161 #                if target.lower() == self.owner.lower():
162 #                    target = nick
163                 # ...and touchy.
164                 if me.lower() == target.lower():
165                     target = nick
166                 conn.action(self.channel, trout_msg % target)
167                 if public == 0:
168                     if random.random() <= 0.1:
169                         conn.action(self.channel, "notes %s is conducting a whispering campaign" % nick)
170             except IndexError:
171                 conn.notice(nick, "Who do you wish me to trout?")
172
173     # stock up on trouts
174     def reloadq(self, cmd, nick, conn, public):
175         if irc_lower(nick) == irc_lower(self.owner):
176             tback = self.trouts
177             try:
178                 f = open("trouts", "r")
179                 self.trouts = [l.strip() for l in f.readlines() if l.find("%s") != -1]
180                 f.close()
181                 conn.notice(nick, "I am re-armed!")
182             except IOError:
183                 conn.notice(nick, "Trout re-arming failed!")
184                 self.trouts = tback
185         else:
186             conn.notice(nick, "This command can only be invoked by my owner.")
187
188     # quit irc
189     def quit(self, cmd, nick, conn, public):
190         if irc_lower(nick) == irc_lower(self.owner):
191             f = open("karmadump", "w")
192             cPickle.dump(self.karma, f)
193             f.close()
194             self.die(msg="I have been chosen!")
195         elif public == 1:
196             conn.privmsg(nick, "Such aggression in public!")
197         else:
198             conn.notice(nick, "You're not my owner.")
199             
200     # google for something
201     def googleq(self, cmd, nick, conn, public):
202         cmdrest = string.join(cmd.split()[1:])
203         # "I'm Feeling Lucky" rather than try and parse the html
204         targ = ("http://www.google.com/search?q=%s&btnI=I'm+Feeling+Lucky"
205                 % urllib.quote_plus(cmdrest))
206         try:
207             # get redirected and grab the resulting url for returning
208             gsearch = urllib.urlopen(targ).geturl()
209             if gsearch != targ: # we've found something
210                 if public == 0:
211                     conn.notice(nick, str(gsearch))
212                 else: # we haven't found anything.
213                     conn.privmsg(nick, str(gsearch))
214             else:
215                 if public == 0:
216                     conn.notice(nick, "No pages found.")
217                 else:
218                     conn.privmsg(nick, "No pages found.")
219         except IOError: # if the connection times out. This blocks. :(
220             if public == 0:
221                 conn,notice(nick, "The web's broken. Waah!")
222             else:
223                 conn.privmsg(nick, "The web's broken. Waah!")
224                 
225     # General query handler
226     def do_command(self, nick, cmd, public=0):
227         conn = self.connection
228         # karma: up
229         if cmd.split()[0].endswith("++"):
230             self.karmaup(cmd)
231         
232         # karma: down
233         if cmd.split()[0].endswith("--"):
234             self.karmadown(cmd)
235             
236         # karma: query
237         if cmd.split()[0] == "karma" or cmd.split()[0] == "Karma":
238             self.karmaq(cmd, conn, nick, public)
239             
240         # bot's vital statistics
241         if cmd == "info":
242             self.infoq(cmd, nick, conn, public)
243
244         # weaponry
245         if cmd.split()[0] == "trout":
246             self.troutq(cmd, nick, conn, public)
247         if cmd == "reload":
248             self.reloadq(cmd, nick, conn, public)
249         
250         #disconnect
251         if cmd == "disconnect": # hop off for 60s
252             self.disconnect(msg="Be right back.")
253
254         # say to msg/channel
255         elif cmd.split()[0] == "say" \
256              and irc_lower(nick) == irc_lower(self.owner):
257             conn.privmsg(self.channel, string.join(cmd.split()[1:]))
258
259         # action to msg/channel
260         elif cmd.split()[0] == "do" \
261              and irc_lower(nick) == irc_lower(self.owner):
262             conn.action(self.channel, string.join(cmd.split()[1:]))
263
264         # quit IRC
265         elif cmd == "die":
266             self.quit(cmd, nick, conn, public)
267
268         # Google!
269         elif (cmd.split()[0] == "google" or cmd.split()[0] == "Google"):
270             self.googleq(cmd, nick, conn, public)            
271
272 def main():
273     if len(sys.argv) != 5: # insufficient arguments
274         print "Usage: acrobat <server[:port]> <channel> <nickname> owner"
275         sys.exit(1)
276     sv_port = string.split(sys.argv[1], ":", 1) # tuple; (server, port)
277     server = sv_port[0]
278     if len(sv_port) == 2:
279         try:
280             port = int(sv_port[1])
281         except ValueError:
282             print "Error: Erroneous port."
283             sys.exit(1)
284     else:
285         port = 6667 # default irc port
286     channel = sys.argv[2]
287     nickname = sys.argv[3]
288     owner = sys.argv[4]
289     # initialize the bot
290     bot = Acrobat(channel, nickname, server, owner, port)
291     sys.stderr.write("Trying to connect...\n")
292     # and the event loop
293     bot.start()
294
295 if __name__ == "__main__":
296     main()