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