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>
9 # This file is part of Acrobat.
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.
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.
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
27 disconnect -- Disconnect the bot. The bot will try to reconnect
30 die -- Let the bot cease to exist.
32 google -- search, 'I'm Feeling Lucky', and notice the user who searches
36 import string, urllib, sys, cPickle, os, random, re, time
37 from ircbot import SingleServerIRCBot
38 from irclib import nm_to_n, irc_lower
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
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?
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?
64 f = open("karmadump", "r")
65 self.karma = cPickle.load(f)
70 f = open("trouts", "r")
71 self.trouts = [l.strip() for l in f.readlines() if l.find("%s") != -1]
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."]
78 def on_welcome(self, conn, evt):
79 conn.join(self.channel)
81 def on_privmsg(self, conn, evt):
82 self.do_command(nm_to_n(evt.source()), evt.arguments()[0])
84 def on_pubmsg(self, conn, evt):
85 payload = evt.arguments()[0]
86 a = string.split(evt.arguments()[0], " ", 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())):
91 self.do_command(nm_to_n(evt.source()), string.strip(a[1]), public = 1)
92 if a[0].endswith("++"):
94 if a[0].endswith("--"):
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)
101 # And now bot commands;
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
108 self.karma.dict[cmd.split()[0][:-2]] = 1
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
115 self.karma.dict[cmd.split()[0][:-2]] = -1
118 def karmaq(self, cmd, conn, nick, public):
122 if self.karma.dict.has_key(cmd.split()[1]):
123 conn.privmsg(self.channel, "%s has karma %s."
125 self.karma.dict[cmd.split()[1]]))
127 conn.privmsg(self.channel, "%s has no karma set." %
130 conn.privmsg(self.channel, "I have karma on %s items." %
131 len(self.karma.dict.keys()))
135 if self.karma.dict.has_key(cmd.split()[1]):
136 conn.notice(nick, "%s has karma %s." %
138 self.karma.dict[cmd.split()[1]]))
140 conn.notice(nick, "I have karma on %s items." %
141 len(self.karma.dict.keys()))
143 conn.notice(nick, "I have karma on %s items." %
144 len(self.karma.dict.keys()))
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)
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())))
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())))
165 def listq(self, cmd, nick, conn, public):
166 conn.notice(nick, "%s" % string.join(self.known))
169 def troutq(self, cmd, nick, conn, public):
172 conn.notice(nick, "Sorry, but %s is being a spoilsport." %
175 if self.cur_fish <= 0:
176 conn.notice(nick, "Fish stocks exhausted.")
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():
187 if me.lower() == target.lower():
190 if random.random() <= 0.1:
191 trout_msg+= ' (at the instigation of %s)' % nick
192 conn.action(self.channel, trout_msg % target)
194 conn.notice(nick, "Who do you wish me to trout?")
197 def reloadq(self, cmd, nick, conn, public):
198 if irc_lower(nick) == irc_lower(self.owner):
201 f = open("trouts", "r")
202 self.trouts = [l.strip() for l in f.readlines() if l.find("%s") != -1]
204 conn.notice(nick, "I am re-armed!")
206 conn.notice(nick, "Trout re-arming failed!")
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):
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):
221 if time.time()>=self.quotatime:
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()
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)
238 self.die(msg="I have been chosen!")
240 conn.privmsg(nick, "Such aggression in public!")
242 conn.notice(nick, "You're not my owner.")
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))
251 # get redirected and grab the resulting url for returning
252 gsearch = urllib.urlopen(targ).geturl()
253 if gsearch != targ: # we've found something
255 conn.notice(nick, str(gsearch))
256 else: # we haven't found anything.
257 conn.privmsg(self.channel, str(gsearch))
260 conn.notice(nick, "No pages found.")
262 conn.privmsg(self.channel, "No pages found.")
263 except IOError: # if the connection times out. This blocks. :(
265 conn,notice(nick, "The web's broken. Waah!")
267 conn.privmsg(self.channel, "The web's broken. Waah!")
269 # General query handler
270 def do_command(self, nick, cmd, public=0):
271 conn = self.connection
273 if cmd.split()[0].endswith("++"):
277 if cmd.split()[0].endswith("--"):
281 if cmd.split()[0] == "karma" or cmd.split()[0] == "Karma":
282 self.karmaq(cmd, conn, nick, public)
284 # bot's vital statistics
286 self.infoq(cmd, nick, conn, public)
290 self.listq(cmd, nick, conn, public)
292 if cmd.split()[0] == "trout":
293 self.troutq(cmd, nick, conn, public)
295 self.reloadq(cmd, nick, conn, public)
297 #Disconnect disabled 'cos people hated it
299 # if cmd == "disconnect": # hop off for 60s
300 # self.disconnect(msg="Be right back.")
303 if cmd.split()[0] == "quiet":
304 self.nofish(cmd,nick,conn, public)
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:]))
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:]))
318 self.quit(cmd, nick, conn, public)
321 elif (cmd.split()[0] == "google" or cmd.split()[0] == "Google"):
322 self.googleq(cmd, nick, conn, public)
325 if len(sys.argv) != 5: # insufficient arguments
326 print "Usage: acrobat <server[:port]> <channel> <nickname> owner"
328 sv_port = string.split(sys.argv[1], ":", 1) # tuple; (server, port)
330 if len(sv_port) == 2:
332 port = int(sv_port[1])
334 print "Error: Erroneous port."
337 port = 6667 # default irc port
338 channel = sys.argv[2]
339 nickname = sys.argv[3]
342 bot = Acrobat(channel, nickname, server, owner, port)
343 sys.stderr.write("Trying to connect...\n")
347 if __name__ == "__main__":