chiark / gitweb /
f520749ea7b233833cf7cb0174c106ec77b7ece0
[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                 print self.cur_fish
232                 print self.quotatime
233
234     # quit irc
235     def quit(self, cmd, nick, conn, public):
236         if irc_lower(nick) == irc_lower(self.owner):
237             f = open("karmadump", "w")
238             cPickle.dump(self.karma, f)
239             f.close()
240             self.die(msg="I have been chosen!")
241         elif public == 1:
242             conn.privmsg(nick, "Such aggression in public!")
243         else:
244             conn.notice(nick, "You're not my owner.")
245             
246     # google for something
247     def googleq(self, cmd, nick, conn, public):
248         cmdrest = string.join(cmd.split()[1:])
249         # "I'm Feeling Lucky" rather than try and parse the html
250         targ = ("http://www.google.com/search?q=%s&btnI=I'm+Feeling+Lucky"
251                 % urllib.quote_plus(cmdrest))
252         try:
253             # get redirected and grab the resulting url for returning
254             gsearch = urllib.urlopen(targ).geturl()
255             if gsearch != targ: # we've found something
256                 if public == 0:
257                     conn.notice(nick, str(gsearch))
258                 else: # we haven't found anything.
259                     conn.privmsg(self.channel, str(gsearch))
260             else:
261                 if public == 0:
262                     conn.notice(nick, "No pages found.")
263                 else:
264                     conn.privmsg(self.channel, "No pages found.")
265         except IOError: # if the connection times out. This blocks. :(
266             if public == 0:
267                 conn,notice(nick, "The web's broken. Waah!")
268             else:
269                 conn.privmsg(self.channel, "The web's broken. Waah!")
270                 
271     # General query handler
272     def do_command(self, nick, cmd, public=0):
273         conn = self.connection
274         # karma: up
275         if cmd.split()[0].endswith("++"):
276             self.karmaup(cmd)
277         
278         # karma: down
279         if cmd.split()[0].endswith("--"):
280             self.karmadown(cmd)
281             
282         # karma: query
283         if cmd.split()[0] == "karma" or cmd.split()[0] == "Karma":
284             self.karmaq(cmd, conn, nick, public)
285             
286         # bot's vital statistics
287         if cmd == "info":
288             self.infoq(cmd, nick, conn, public)
289
290         # Known commands
291         if cmd == "list":
292             self.listq(cmd, nick, conn, public)
293         # weaponry
294         if cmd.split()[0] == "trout":
295             self.troutq(cmd, nick, conn, public)
296         if cmd == "reload":
297             self.reloadq(cmd, nick, conn, public)
298
299 #Disconnect disabled 'cos people hated it    
300 #        #disconnect
301 #        if cmd == "disconnect": # hop off for 60s
302 #            self.disconnect(msg="Be right back.")
303
304         # No more trout
305         if cmd.split()[0] == "quiet":
306             self.nofish(cmd,nick,conn, public)
307         
308         # say to msg/channel
309         elif cmd.split()[0] == "say" \
310              and irc_lower(nick) == irc_lower(self.owner):
311             conn.privmsg(self.channel, string.join(cmd.split()[1:]))
312
313         # action to msg/channel
314         elif cmd.split()[0] == "do" \
315              and irc_lower(nick) == irc_lower(self.owner):
316             conn.action(self.channel, string.join(cmd.split()[1:]))
317
318         # quit IRC
319         elif cmd == "die":
320             self.quit(cmd, nick, conn, public)
321
322         # Google!
323         elif (cmd.split()[0] == "google" or cmd.split()[0] == "Google"):
324             self.googleq(cmd, nick, conn, public)            
325
326 def main():
327     if len(sys.argv) != 5: # insufficient arguments
328         print "Usage: acrobat <server[:port]> <channel> <nickname> owner"
329         sys.exit(1)
330     sv_port = string.split(sys.argv[1], ":", 1) # tuple; (server, port)
331     server = sv_port[0]
332     if len(sv_port) == 2:
333         try:
334             port = int(sv_port[1])
335         except ValueError:
336             print "Error: Erroneous port."
337             sys.exit(1)
338     else:
339         port = 6667 # default irc port
340     channel = sys.argv[2]
341     nickname = sys.argv[3]
342     owner = sys.argv[4]
343     # initialize the bot
344     bot = Acrobat(channel, nickname, server, owner, port)
345     sys.stderr.write("Trying to connect...\n")
346     # and the event loop
347     bot.start()
348
349 if __name__ == "__main__":
350     main()