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