chiark / gitweb /
e258636eed6cfce43ea4b630fd134a62f1a9acf4
[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         acrorev1=re.sub(Revision$\([^$]*\)$,\1,acrorevision)
55         acrorev2=re.sub($\(.*\),\1,acrorev1)
56         # load the karma db
57         try:
58             f = open("karmadump", "r")
59             self.karma = cPickle.load(f)
60             f.close()
61         except IOError:
62             self.karma = Karma()
63         try:
64             f = open("trouts", "r")
65             self.trouts = [l.strip() for l in f.readlines() if l.find("%s") != -1]
66             f.close()
67         except IOError:
68             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."]
69
70     ## EVENT HANDLERS
71             
72     def on_welcome(self, conn, evt):
73         conn.join(self.channel)
74
75     def on_privmsg(self, conn, evt):
76         self.do_command(nm_to_n(evt.source()), evt.arguments()[0])
77         
78     def on_pubmsg(self, conn, evt):
79         payload = evt.arguments()[0]
80         a = string.split(evt.arguments()[0], " ", 1)
81         if len(a) > 1 \
82            and (irc_lower(a[0]) == irc_lower(self.connection.get_nickname())
83                 or irc_lower(a[0])[:-1] == irc_lower(self.connection.get_nickname())):
84
85             self.do_command(self.channel, string.strip(a[1]), public = 1)
86         if a[0].endswith("++"):
87             self.karmaup(a[0])
88         if a[0].endswith("--"):
89             self.karmadown(a[0])
90         if payload[0] == "!" and len(payload)>1:
91             self.do_command(self.channel, string.strip(payload[1:]), public=1)
92         if payload[0] == "~" and len(payload)>1:
93             self.do_command(self.channel, string.strip(payload[1:]), public=1)
94
95     # And now bot commands;
96
97     # increment karma
98     def karmaup(self, cmd):
99         if self.karma.dict.has_key(cmd.split()[0][:-2]):
100             self.karma.dict[cmd.split()[0][:-2]] += 1
101         else:
102             self.karma.dict[cmd.split()[0][:-2]] = 1
103
104     #decrement karma
105     def karmadown(self, cmd):
106         if self.karma.dict.has_key(cmd.split()[0][:-2]):
107             self.karma.dict[cmd.split()[0][:-2]] -= 1
108         else:
109             self.karma.dict[cmd.split()[0][:-2]] = -1
110
111     # query karma
112     def karmaq(self, cmd, conn, nick, public):
113         # in public
114         if public == 1:
115             try:
116                 if self.karma.dict.has_key(cmd.split()[1]):
117                     conn.privmsg(self.channel, "%s has karma %s."
118                                  %(cmd.split()[1],
119                                        self.karma.dict[cmd.split()[1]]))
120                 else:
121                     conn.privmsg(self.channel, "%s has no karma set." %
122                                  cmd.split()[1])
123             except IndexError:
124                 conn.privmsg(self.channel, "I have karma on %s items." %
125                              len(self.karma.dict.keys()))
126         # in private
127         else:
128             try:
129                 if self.karma.dict.has_key(cmd.split()[1]):
130                     conn.notice(nick, "%s has karma %s." %
131                                 (cmd.split()[1],
132                                  self.karma.dict[cmd.split()[1]]))
133                 else:
134                     conn.notice(nick, "I have karma on %s items." %
135                                 len(self.karma.dict.keys()))
136             except IndexError:
137                 conn.notice(nick, "I have karma on %s items." %
138                             len(self.karma.dict.keys()))
139     # query bot status
140     def infoq(self, cmd, nick, conn, public):
141         if public == 1:
142             conn.privmsg(self.channel,
143                          "I am Acrobat 0.2.1chiark, on %s, as nick %s." %
144                          (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 0.2.1chiark, on %s, as nick %s." %
150                         (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()