chiark / gitweb /
51c4bbc19478a9e7c390708e15ff4ef2ad900af0
[irc.git] / Servus-chiark.py
1 # This file is part of Acrobat.
2 #
3 # Acrobat is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published
5 # by the Free Software Foundation; either version 2 of the License,
6 # or (at your option) any later version.
7 #
8 # Acrobat is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 # GNU General Public License for more details.
12 #
13 # You should have received a copy of the GNU General Public License
14 # along with Acrobat; if not, write to the Free Software
15 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
16 # USA.
17
18 # Andrew Walkingshaw <andrew@lexical.org.uk>
19 # Peter Corbett <ptc24@cam.ac.uk>
20 # Matthew Vernon <matthew@debian.org>
21 # Stephen Early <steve@greenend.org.uk>
22 # Richard Kettlewell <rjk@greenend.org.uk
23
24 # Acrobat configuration file
25
26 # The following definitions are required to be present in this module:
27 server = "rapun"
28 port = 6667
29 nickname = "Servus"
30 channel = "#chiark"
31 owner = "Emperor"
32 # Also a function called "command"; see later.
33
34 # Everything else in this file is configuration-specific.
35
36 import os, time, re
37
38 # Most command implementations are stored in a separate module.
39 import commands as c
40
41 # This fishpond is shared between trouts and flirts.  It doesn't have to be;
42 # you can define as many ponds as you like.
43 class fish:
44         cur_fish=5
45         max_fish=5
46         nofish_time=60
47         fish_time_inc=60
48         fish_inc=2
49         DoS=0
50         Boring_Git='Nobody'
51         quotatime=0
52
53 # load a file full of flirts or trouts
54 def __load(filename):
55     try:
56         f = open(filename, "r")
57         r = [l.strip() for l in f.readlines() if l.find("%s") != -1]
58         f.close()
59     except IOError:
60         r = [ "doesn't know what to do about %s." ]
61     return r
62
63 # (troutlist,selftroutmsg,DoSmsg,notargetmsg,nofishmsg,fishpond,selftroutprob)
64 troutcfg = (
65         __load("trouts"),
66         ' (at the instigation of %s)',
67         "Sorry, but %s is being a spoilsport.",
68         "Who do you wish me to trout?",
69         "Fish stocks exhausted.",
70         fish,
71         0.1)
72
73 flirtcfg = (
74         __load("flirts"),
75         ' (but %s is their secret admirer)',
76         "Sorry, but %s made me take Holy Orders.",
77         "Who do you wish me to flirt with?",
78         "My libido is over-used!",
79         fish,
80         0.1)
81
82 slashcfg= ( 
83         __load("slashes"),
84         ' (while %s watches)',
85         "Sorry, but %s stole my pen.",
86         "Who do you want to slash?",
87         "I have writer's block!",
88         fish,
89         0.1)
90
91 # Hacky command to output the current fishpond state
92 def fishq(bot, cmd, nick, conn, public,f):
93         from irclib import irc_lower
94         if not public and irc_lower(nick) == irc_lower(bot.owner):
95                 state=("Fishpond state: cur_fish=%d, max_fish=%d, nofish_time=%d, "
96                        +"fish_time_inc=%d, fish_inc=%d, DoS=%d, Boring_Git=%s, "
97                        +"quotatime=%d")%(f.cur_fish,f.max_fish,f.nofish_time,
98                                          f.fish_time_inc,f.fish_inc,f.DoS,f.Boring_Git,
99                                          f.quotatime)
100                 bot.automsg(public,nick,state)
101                     
102 # Karma implementation
103 import cPickle
104 karmafilename = "chiark-karma"
105 # load the karma db
106 try:
107     f = open(karmafilename, "r")
108     karmadb = cPickle.load(f)
109     f.close()
110 except IOError:
111     karmadb = {}
112 # Modify karma
113 def karma(cmd, amount):
114     thing=cmd.split()[0][:-2].lower()
115     if karmadb.has_key(thing):
116         karmadb[thing] += amount
117     else:
118         karmadb[thing] = amount
119     savekarma()
120 def savekarma():
121     tmp = "%s.tmp" % karmafilename
122     try:
123         f = open(tmp, "w")
124         cPickle.dump(karmadb, f)
125         f.close()
126         os.rename(tmp, karmafilename)
127     except IOError, e:
128         sys.stderr.write("error writing karma: %s" % e)
129
130 def quit(bot,cmd,nick,conn,public):
131     c.quitq(bot,cmd,nick,conn,public)
132 def reload(bot,cmd,nick,conn,public):
133     c.reloadq(bot,cmd,nick,conn,public)
134
135 #The game...
136 class game:
137         trigger="Servus"
138         grace=time.time()
139         minlose=24*60*60 #1 day
140         maxlose=14*minlose #2 weeks
141         losetime=time.time()+300000
142
143 def canonical_url(urlstring):
144 # canonicalise BBC URLs
145   if (urlstring.find("news.bbc.co.uk") != -1):
146     for middle in ("/low/","/mobile/"):
147       x = urlstring.find(middle)
148       if (x != -1):
149         urlstring.replace(middle,"/hi/")
150   return urlstring
151
152 def nicetime(tempus):
153   if (tempus<120):
154     tm="%d seconds ago"%int(tempus)
155   elif (tempus<7200):
156     tm="%d minutes ago"%int(tempus/60)
157   if (tempus>7200):
158     tm="%d hours ago"%int(tempus/3600)
159   return tm
160
161 urldb = {}
162 urlre = re.compile("(https?://[^ ]+)( |$)")
163 urlcomplaints = [" contemporary","n interesting"," fascinating","n overused"," vastly overused"]
164
165 def urlq(bot, cmd, nick, conn, public):
166   print "debug: ",bot,cmd,nick,conn,public
167   if (not urlre.search(cmd)):
168     bot.automsg(False,nick,"Please use 'url' only with http URLs")
169     return
170
171   url="".join(cmd.split(" ")[1:])
172   print "##",url,"##"
173
174   url=canonical_url(url)
175   if (url in urldb):
176     users = urldb[url]
177     complaint="The url %s was mentioned %s by %s"%(url,nicetime(time.time()-users[-1][1]),users[-1][0])
178     if (public):
179       complaint=complaint+". Furthermore it defeats the point of this command to use it other than via /msg."
180     bot.automsg(False,nick,complaint)
181   else:
182     if (public):
183       bot.automsg(False,nick,"That URL was unique. There is little point in using !url out loud; please use it via /msg")
184     else:
185       conn.privmsg(bot.channel,"%s would like to draw your attention to %s"%(nick,url))
186     urldb[url]=[[nick,time.time()]]
187   print [bot, cmd, nick, conn]
188
189 def dourl(bot,conn,nick,command):
190   urlstring=urlre.search(command).group(1)
191   print "dourl: ",urlstring
192   urlstring=canonical_url(urlstring)
193
194   if urlstring in urldb:
195     T=urldb[urlstring]
196     print T
197     uci = len(T)
198     if uci >= len(urlcomplaints):
199       uci = len(urlcomplaints)
200     message="observes a"+urlcomplaints[uci-1]+" URL: mentioned by "
201     if (len(T)>5): 
202       cutoff=len(T)-5
203     else:
204       cutoff=-1
205     for t in range(len(T)-1,cutoff,-1):
206       tempus = time.time()-T[t][1]
207       message += T[t][0]+" ("+nicetime(tempus)+")"
208       if (t!=cutoff+1):
209         message += ", "
210     if (cutoff != -1):
211       message += ", amongst others"
212     conn.action(bot.channel, message)
213     urldb[urlstring]=[[nick,time.time()]]+urldb[urlstring]
214   else:
215     urldb[urlstring]=[[nick,time.time()]]
216
217 # Command processing: whenever something is said that the bot can hear,
218 # "command" is invoked and must decide what to do.  This configuration
219 # defines a couple of special cases (for karma) but is otherwise driven
220 # by a dictionary of commands.
221
222 commands = {"karma": (c.karmaq,karmadb),
223             "karmalist": (c.listkeysq,karmadb),
224             "karmadel": (c.karmadelq,karmadb),
225             "info": (c.infoq,karmadb),
226             "trout": (c.troutq,troutcfg),
227             "slash": (c.slashq, slashcfg),
228             "rot13": c.rot13q,
229             "fish": (fishq,fish),
230             "flirt": (c.troutq,flirtcfg),
231             "quiet": (c.nofishq,fish),
232             "reload": reload,
233             "quit": quit,
234             "die": quit,
235             "define": c.defineq,
236             "google": c.googleq,
237             "url": urlq,
238             "units": c.unitq,
239             "help": c.helpq,
240 #           "game": (c.gameq,game),
241             "say": c.sayq,
242             "do": c.doq }
243 # disconnect and hop annoy people
244 #            "disconnect": c.disconnq,
245 #            "hop": c.disconnq }
246 commands["list"]=(c.listkeysq,commands,True)
247
248 triggers = ("!", "~") # what character should the bot be invoked by:
249                       # eg !trout, ~trout etc.
250
251 def command(bot, cmd, nick, conn, public):
252     ours=0
253     try:
254             if public and cmd[0] in triggers:
255                     ours=1
256                     cmd=cmd[1:]
257             if not public:
258                     ours=1
259             command = cmd.split()[0]
260     except IndexError:
261             command=""
262
263     if public:
264       if cmd.find("http") != -1:
265         dourl(bot,conn,nick,cmd)
266          
267     # karma: up
268     if command.endswith("++"):
269         karma(cmd,1)
270     # karma: down
271     if command.endswith("--"):
272         karma(cmd,-1)
273
274     if ours and command.lower() in commands.keys():
275         e=commands[command.lower()]
276         if callable(e):
277             e(bot,cmd,nick,conn,public)
278         else:
279             e[0](bot,cmd,nick,conn,public,*e[1:])
280 #    elif public:
281 #        if cmd.find("GAME")!=-1: #someone else lost
282 #           grace.grace=time.time()+60*20
283 #       elif cmd.find(game.trigger)!=-1 and len(game.trigger)>2: #we lost!
284 #           c.gameq(bot,"pad "+game.trigger,bot.owner,conn,False,game)
285 #       elif time.time()>game.losetime: #we randomly lost, take new trigger
286 #           c.gameq(bot,cmd,bot.owner,conn,False,game)
287 #