chiark / gitweb /
Removed suicide trout, following email request to do so
[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, time
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         self.quotatime = time.time()
53         #List of known !commands we respond to
54         self.known =['karma','trout','info','die','quiet','list','google','say','do','reload']
55         #Configurable stuff - how often do we add how many fish?
56         self.cur_fish=5
57         self.max_fish=5       #Maximum of 5 fish
58         self.fish_time_inc=60  #Add fish with 20s granularity
59         self.fish_inc=2        #Rate of increase is 2 fish per 60s
60         self.DoS=0             #Have we been told to shut up?
61         self.Boring_Git='Nobody' #Who told us to shut up?
62         # load the karma db
63         try:
64             f = open("karmadump", "r")
65             self.karma = cPickle.load(f)
66             f.close()
67         except IOError:
68             self.karma = Karma()
69         try:
70             f = open("trouts", "r")
71             self.trouts = [l.strip() for l in f.readlines() if l.find("%s") != -1]
72             f.close()
73         except IOError:
74             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."]
75
76     ## EVENT HANDLERS
77             
78     def on_welcome(self, conn, evt):
79         conn.join(self.channel)
80
81     def on_privmsg(self, conn, evt):
82         self.do_command(nm_to_n(evt.source()), evt.arguments()[0])
83         
84     def on_pubmsg(self, conn, evt):
85         payload = evt.arguments()[0]
86         a = string.split(evt.arguments()[0], " ", 1)
87         if len(a) > 1 \
88            and (irc_lower(a[0]) == irc_lower(self.connection.get_nickname())
89                 or irc_lower(a[0])[:-1] == irc_lower(self.connection.get_nickname())):
90
91             self.do_command(nm_to_n(evt.source()), string.strip(a[1]), public = 1)
92         if a[0].endswith("++"):
93             self.karmaup(a[0])
94         if a[0].endswith("--"):
95             self.karmadown(a[0])
96         if payload[0] == "!" and len(payload)>1:
97             self.do_command(nm_to_n(evt.source()), string.strip(payload[1:]), public=1)
98         if payload[0] == "~" and len(payload)>1:
99             self.do_command(nm_to_n(evt.source()), string.strip(payload[1:]), public=1)
100
101     # And now bot commands;
102
103     # increment karma
104     def karmaup(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     #decrement karma
111     def karmadown(self, cmd):
112         if self.karma.dict.has_key(cmd.split()[0][:-2]):
113             self.karma.dict[cmd.split()[0][:-2]] -= 1
114         else:
115             self.karma.dict[cmd.split()[0][:-2]] = -1
116
117     # query karma
118     def karmaq(self, cmd, conn, nick, public):
119         # in public
120         if public == 1:
121             try:
122                 if self.karma.dict.has_key(cmd.split()[1]):
123                     conn.privmsg(self.channel, "%s has karma %s."
124                                  %(cmd.split()[1],
125                                        self.karma.dict[cmd.split()[1]]))
126                 else:
127                     conn.privmsg(self.channel, "%s has no karma set." %
128                                  cmd.split()[1])
129             except IndexError:
130                 conn.privmsg(self.channel, "I have karma on %s items." %
131                              len(self.karma.dict.keys()))
132         # in private
133         else:
134             try:
135                 if self.karma.dict.has_key(cmd.split()[1]):
136                     conn.notice(nick, "%s has karma %s." %
137                                 (cmd.split()[1],
138                                  self.karma.dict[cmd.split()[1]]))
139                 else:
140                     conn.notice(nick, "I have karma on %s items." %
141                                 len(self.karma.dict.keys()))
142             except IndexError:
143                 conn.notice(nick, "I have karma on %s items." %
144                             len(self.karma.dict.keys()))
145     # query bot status
146     def infoq(self, cmd, nick, conn, public):
147         # version control magic
148         acrorevision="$Revision$"
149         acrorev1=re.sub(r'\$Revision: (.*)',r'\1',acrorevision)
150         acroversion=re.sub(r'(.*) \$',r'\1',acrorev1)
151         if public == 1:
152             conn.privmsg(self.channel,
153                          "I am Acrobat %s, on %s, as nick %s." %
154                          (acroversion, self.channel, self.connection.get_nickname()))
155             conn.privmsg(self.channel,
156                          "My owner is %s; I have karma on %s items." %
157                          (self.owner, len(self.karma.dict.keys())))
158         else:
159             conn.notice(nick, "I am Acrobat %s, on %s, as nick %s." %
160                         (acroversion, self.channel, self.connection.get_nickname()))
161             conn.notice(nick, "My owner is %s; I have karma on %s items." %
162                         (self.owner, len(self.karma.dict.keys())))
163
164     # list know commands
165     def listq(self, cmd, nick, conn, public):
166         conn.notice(nick, "%s" % string.join(self.known))
167
168     # trout someone
169     def troutq(self, cmd, nick, conn, public):
170         self.fish_quota()
171         if self.DoS == 1:
172             conn.notice(nick, "Sorry, but %s is being a spoilsport." %
173                         self.Boring_Git)
174             return
175         if self.cur_fish <= 0:
176             conn.notice(nick, "Fish stocks exhausted.")
177         else:
178             self.cur_fish -=1
179             try:
180                 target = string.join(cmd.split()[1:])
181                 me = self.connection.get_nickname()
182                 trout_msg = random.choice(self.trouts)
183 #                # The bot is loyal(ish)...
184 #                if target.lower() == self.owner.lower():
185 #                    target = nick
186                 # ...and touchy.
187                 if me.lower() == target.lower():
188                     target = nick
189                 if public == 0:
190                     if random.random() <= 0.1:
191                         trout_msg+= ' (at the instigation of %s)' % nick
192                 conn.action(self.channel, trout_msg % target)
193             except IndexError:
194                 conn.notice(nick, "Who do you wish me to trout?")
195
196     # stock up on trouts
197     def reloadq(self, cmd, nick, conn, public):
198         if irc_lower(nick) == irc_lower(self.owner):
199             tback = self.trouts
200             try:
201                 f = open("trouts", "r")
202                 self.trouts = [l.strip() for l in f.readlines() if l.find("%s") != -1]
203                 f.close()
204                 conn.notice(nick, "I am re-armed!")
205             except IOError:
206                 conn.notice(nick, "Trout re-arming failed!")
207                 self.trouts = tback
208         else:
209             conn.notice(nick, "This command can only be invoked by my owner.")
210     # Shut up trouting for a minute
211     def nofish(self, cmd, nick, conn, public):
212         self.cur_fish=0
213         self.DoS=1
214         self.Boring_Git=nick
215         self.quotatime=time.time()
216         self.quotatime+=60 #60 seconds of no fishing
217         conn.notice(nick, "Fish stocks depleted, as you wish.")
218     # Check on fish stocks
219     def fish_quota(self):
220         if self.DoS==1:
221             if time.time()>=self.quotatime:
222                 self.DoS=0
223             else:
224                 return
225         if self.DoS==0:
226             if (time.time()-self.quotatime)>self.fish_time_inc:
227                 self.cur_fish+=(((time.time()-self.quotatime)/self.fish_time_inc)*self.fish_inc)
228                 if self.cur_fish>self.max_fish:
229                     self.cur_fish=self.max_fish
230                 self.quotatime=time.time()
231
232     # quit irc
233     def quit(self, cmd, nick, conn, public):
234         if irc_lower(nick) == irc_lower(self.owner):
235             f = open("karmadump", "w")
236             cPickle.dump(self.karma, f)
237             f.close()
238             self.die(msg="I have been chosen!")
239         elif public == 1:
240             conn.privmsg(nick, "Such aggression in public!")
241         else:
242             conn.notice(nick, "You're not my owner.")
243             
244     # google for something
245     def googleq(self, cmd, nick, conn, public):
246         cmdrest = string.join(cmd.split()[1:])
247         # "I'm Feeling Lucky" rather than try and parse the html
248         targ = ("http://www.google.com/search?q=%s&btnI=I'm+Feeling+Lucky"
249                 % urllib.quote_plus(cmdrest))
250         try:
251             # get redirected and grab the resulting url for returning
252             gsearch = urllib.urlopen(targ).geturl()
253             if gsearch != targ: # we've found something
254                 if public == 0:
255                     conn.notice(nick, str(gsearch))
256                 else: # we haven't found anything.
257                     conn.privmsg(self.channel, str(gsearch))
258             else:
259                 if public == 0:
260                     conn.notice(nick, "No pages found.")
261                 else:
262                     conn.privmsg(self.channel, "No pages found.")
263         except IOError: # if the connection times out. This blocks. :(
264             if public == 0:
265                 conn,notice(nick, "The web's broken. Waah!")
266             else:
267                 conn.privmsg(self.channel, "The web's broken. Waah!")
268                 
269     # General query handler
270     def do_command(self, nick, cmd, public=0):
271         conn = self.connection
272         # karma: up
273         if cmd.split()[0].endswith("++"):
274             self.karmaup(cmd)
275         
276         # karma: down
277         if cmd.split()[0].endswith("--"):
278             self.karmadown(cmd)
279             
280         # karma: query
281         if cmd.split()[0] == "karma" or cmd.split()[0] == "Karma":
282             self.karmaq(cmd, conn, nick, public)
283             
284         # bot's vital statistics
285         if cmd == "info":
286             self.infoq(cmd, nick, conn, public)
287
288         # Known commands
289         if cmd == "list":
290             self.listq(cmd, nick, conn, public)
291         # weaponry
292         if cmd.split()[0] == "trout":
293             self.troutq(cmd, nick, conn, public)
294         if cmd == "reload":
295             self.reloadq(cmd, nick, conn, public)
296
297 #Disconnect disabled 'cos people hated it    
298 #        #disconnect
299 #        if cmd == "disconnect": # hop off for 60s
300 #            self.disconnect(msg="Be right back.")
301
302         # No more trout
303         if cmd.split()[0] == "quiet":
304             self.nofish(cmd,nick,conn, public)
305         
306         # say to msg/channel
307         elif cmd.split()[0] == "say" \
308              and irc_lower(nick) == irc_lower(self.owner):
309             conn.privmsg(self.channel, string.join(cmd.split()[1:]))
310
311         # action to msg/channel
312         elif cmd.split()[0] == "do" \
313              and irc_lower(nick) == irc_lower(self.owner):
314             conn.action(self.channel, string.join(cmd.split()[1:]))
315
316         # quit IRC
317         elif cmd == "die":
318             self.quit(cmd, nick, conn, public)
319
320         # Google!
321         elif (cmd.split()[0] == "google" or cmd.split()[0] == "Google"):
322             self.googleq(cmd, nick, conn, public)            
323
324 def main():
325     if len(sys.argv) != 5: # insufficient arguments
326         print "Usage: acrobat <server[:port]> <channel> <nickname> owner"
327         sys.exit(1)
328     sv_port = string.split(sys.argv[1], ":", 1) # tuple; (server, port)
329     server = sv_port[0]
330     if len(sv_port) == 2:
331         try:
332             port = int(sv_port[1])
333         except ValueError:
334             print "Error: Erroneous port."
335             sys.exit(1)
336     else:
337         port = 6667 # default irc port
338     channel = sys.argv[2]
339     nickname = sys.argv[3]
340     owner = sys.argv[4]
341     # initialize the bot
342     bot = Acrobat(channel, nickname, server, owner, port)
343     sys.stderr.write("Trying to connect...\n")
344     # and the event loop
345     bot.start()
346
347 if __name__ == "__main__":
348     main()