]> gitweb.pimeys.fr Git - bots/saturnin.git/blob - saturnin.py
Version 0.1
[bots/saturnin.git] / saturnin.py
1 #!/usr/bin/python
2 # -*- encoding: utf-8 -*-
3
4 # Codé par 20-100
5
6 # Un bot IRC pour remplacer le canard.
7 # parce que le canard, c'est le bien et que braice ne pong pas
8
9 import irclib
10 import ircbot
11 import threading
12 import random
13 import time
14 import socket, ssl, json
15 import pickle
16 import re
17 import os
18 from commands import getstatusoutput as ex
19
20 # on récupère la config
21 import config
22
23
24
25 def get_config_logfile(serveur):
26 serveurs={"acoeur.crans.org":"acoeur","irc.crans.org":"crans"}
27 return config.logfile_template%(serveurs[serveur])
28
29 def log(serveur,channel,auteur=None,message=None):
30 f=open(get_config_logfile(serveur),"a")
31 if auteur==message==None:
32 # alors c'est que c'est pas un channel mais juste une ligne de log
33 chain="%s %s"%(time.strftime("%F %T"),channel)
34 else:
35 chain="%s [%s:%s] %s"%(time.strftime("%F %T"),channel,auteur,message)
36 f.write(chain+"\n")
37 if config.debug_stdout:
38 print chain
39 f.close()
40
41 def is_something(chain,matches,avant=u".*(?:^| )",apres=u"(?:$|\.| |,|;).*",case_sensitive=False,debug=False):
42 if case_sensitive:
43 chain=unicode(chain,"utf8")
44 else:
45 chain=unicode(chain,"utf8").lower()
46 allmatches="("+"|".join(matches)+")"
47 reg=(avant+allmatches+apres).lower()
48 o=re.match(reg,chain)
49 return o
50
51 regexp_pan = re.compile(u".*(" + "|".join(config.killwords) + u").*")
52 def is_pan(chain):
53 return regexp_pan.match(unicode(chain,"utf8").lower())
54
55 class UnicodeBotError(Exception):
56 pass
57 def bot_unicode(chain):
58 try:
59 unicode(chain,"utf8")
60 except UnicodeDecodeError as exc:
61 raise UnicodeBotError
62
63 class Saturnin(ircbot.SingleServerIRCBot):
64 def __init__(self,serveur,debug=False):
65 temporary_pseudo=config.irc_pseudo+str(random.randrange(10000,100000))
66 ircbot.SingleServerIRCBot.__init__(self, [(serveur, 6667)],
67 temporary_pseudo,"Coin ? ©braice [mais 'faut frapper 20-100]", 10)
68 self.debug=debug
69 self.serveur=serveur
70 self.overops=config.overops
71 self.ops=self.overops+config.ops
72 self.chanlist=config.chanlist
73 self.stay_channels=config.stay_channels
74 self.quiet_channels=config.quiet_channels
75 self.spawn_channels=config.spawn_channels
76 self.status = { chan : False for chan in self.spawn_channels }
77 self.last_perdu=0
78
79 def give_me_my_pseudo(self,serv):
80 serv.privmsg("NickServ","RECOVER %s %s"%(config.irc_pseudo,config.irc_password))
81 serv.privmsg("NickServ","RELEASE %s %s"%(config.irc_pseudo,config.irc_password))
82 time.sleep(0.3)
83 serv.nick(config.irc_pseudo)
84
85 def on_welcome(self, serv, ev):
86 self.serv=serv # ça serv ira :)
87 self.give_me_my_pseudo(serv)
88 serv.privmsg("NickServ","IDENTIFY %s"%(config.irc_password))
89 log(self.serveur,"Connected")
90 if self.debug:
91 self.chanlist = self.spawn_channels = ["#bot"]
92 self.status = { chan : False for chan in self.spawn_channels }
93 for c in self.chanlist:
94 log(self.serveur,"JOIN %s"%(c))
95 serv.join(c)
96
97 def pourmoi(self, serv, message):
98 """renvoie (False,lemessage) ou (True, le message amputé de "pseudo: ")"""
99 pseudo=self.nick
100 size=len(pseudo)
101 if message[:size]==pseudo and len(message)>size and message[size]==":":
102 return (True,message[size+1:].lstrip(" "))
103 else:
104 return (False,message)
105
106 def on_privmsg(self, serv, ev):
107 message=ev.arguments()[0]
108 auteur = irclib.nm_to_n(ev.source())
109 try:
110 test=bot_unicode(message)
111 except UnicodeBotError:
112 if config.utf8_trigger:
113 serv.privmsg(auteur, random.choice(config.utf8_fail_answers).encode("utf8"))
114 return
115 message=message.split()
116 cmd=message[0].lower()
117 notunderstood=False
118 if cmd=="help":
119 helpdico={"help":["""HELP <commande>
120 Affiche de l'aide sur la commande""",None,None],
121 "score":["""SCORE
122 Affiche votre score""", None, None],
123 "scores":["""SCORES
124 Afficher tous les scores""", None, None],
125 "join": [None, """JOIN <channel>
126 Me fait rejoindre le channel""",None],
127 "leave": [None,"""LEAVE <channel>
128 Me fait quitter le channel (sauf s'il est dans ma stay_list).""",None],
129 "quiet": [None,"""QUIET <channel>
130 Me rend silencieux sur le channel.""",None],
131 "noquiet": [None,"""NOQUIET <channel>
132 Me rend la parole sur le channel.""",None],
133 "say": [None,None,"""SAY <channel> <message>
134 Me fait parler sur le channel."""],
135 "do": [None,None,"""DO <channel> <action>
136 Me fait faitre une action (/me) sur le channel."""],
137 "stay": [None,None,"""STAY <channel>
138 Ajoute le channel à ma stay_list."""],
139 "nostay": [None,None,"""NOSTAY <channel>
140 Retire le channel de ma stay_list."""],
141 "ops": [None,None,"""OPS
142 Affiche la liste des ops."""],
143 "overops": [None,None,"""OVEROPS
144 Affiche la liste des overops."""],
145 "kick": [None,None,"""KICK <channel> <pseudo> [<raison>]
146 Kicke <pseudo> du channel (Il faut bien entendu que j'y sois op)."""],
147 "die": [None,None,"""DIE
148 Me déconnecte du serveur IRC."""]
149 }
150 helpmsg_default="Liste des commandes disponibles :\nHELP SCORE SCORES"
151 helpmsg_ops=" JOIN LEAVE QUIET NOQUIET LOST"
152 helpmsg_overops=" SAY DO STAY NOSTAY OPS OVEROPS KICK DIE"
153 op,overop=auteur in self.ops, auteur in self.overops
154 if len(message)==1:
155 helpmsg=helpmsg_default
156 if op:
157 helpmsg+=helpmsg_ops
158 if overop:
159 helpmsg+=helpmsg_overops
160 else:
161 helpmsgs=helpdico.get(message[1].lower(),["Commande inconnue.",None,None])
162 helpmsg=helpmsgs[0]
163 if op and helpmsgs[1]:
164 if helpmsg:
165 helpmsg+="\n"+helpmsgs[1]
166 else:
167 helpmsg=helpmsgs[1]
168 if overop and helpmsgs[2]:
169 if helpmsg:
170 helpmsg+="\n"+helpmsgs[2]
171 else:
172 helpmsg=helpmsgs[2]
173 for ligne in helpmsg.split("\n"):
174 serv.privmsg(auteur,ligne)
175 elif cmd=="join":
176 if auteur in self.ops:
177 if len(message)>1:
178 if message[1] in self.chanlist:
179 serv.privmsg(auteur,"Je suis déjà sur %s"%(message[1]))
180 else:
181 serv.join(message[1])
182 self.chanlist.append(message[1])
183 serv.privmsg(auteur,"Channels : "+" ".join(self.chanlist))
184 log(self.serveur,"priv",auteur," ".join(message))
185 else:
186 serv.privmsg(auteur,"Channels : "+" ".join(self.chanlist))
187 else:
188 notunderstood=True
189 elif cmd=="leave":
190 if auteur in self.ops and len(message)>1:
191 if message[1] in self.chanlist:
192 if not (message[1] in self.stay_channels) or auteur in self.overops:
193 self.quitter(message[1]," ".join(message[2:]))
194 self.chanlist.remove(message[1])
195 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
196 else:
197 serv.privmsg(auteur,"Non, je reste !")
198 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
199 else:
200 serv.privmsg(auteur,"Je ne suis pas sur %s"%(message[1]))
201 else:
202 notunderstood=True
203 elif cmd=="stay":
204 if auteur in self.overops:
205 if len(message)>1:
206 if message[1] in self.stay_channels:
207 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
208 serv.privmsg(auteur,"Je stay déjà sur %s."%(message[1]))
209 else:
210 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
211 self.stay_channels.append(message[1])
212 serv.privmsg(auteur,"Stay channels : "+" ".join(self.stay_channels))
213 else:
214 serv.privmsg(auteur,"Stay channels : "+" ".join(self.stay_channels))
215 else:
216 notunderstood=True
217 elif cmd=="nostay":
218 if auteur in self.overops:
219 if len(message)>1:
220 if message[1] in self.stay_channels:
221 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
222 self.stay_channels.remove(message[1])
223 serv.privmsg(auteur,"Stay channels : "+" ".join(self.stay_channels))
224 else:
225 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
226 serv.privmsg(auteur,"Je ne stay pas sur %s."%(message[1]))
227
228 else:
229 notunderstood=True
230 elif cmd=="die":
231 if auteur in self.overops:
232 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
233 self.mourir()
234 else:
235 notunderstood=True
236 elif cmd=="quiet":
237 if auteur in self.ops:
238 if len(message)>1:
239 if message[1] in self.quiet_channels:
240 serv.privmsg(auteur,"Je me la ferme déjà sur %s"%(message[1]))
241 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
242 else:
243 self.quiet_channels.append(message[1])
244 serv.privmsg(auteur,"Quiet channels : "+" ".join(self.quiet_channels))
245 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
246 else:
247 serv.privmsg(auteur,"Quiet channels : "+" ".join(self.quiet_channels))
248 else:
249 notunderstood=True
250 elif cmd=="noquiet":
251 if auteur in self.ops:
252 if len(message)>1:
253 if message[1] in self.quiet_channels:
254 self.quiet_channels.remove(message[1])
255 serv.privmsg(auteur,"Quiet channels : "+" ".join(self.quiet_channels))
256 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
257 else:
258 serv.privmsg(auteur,"Je ne me la ferme pas sur %s."%(message[1]))
259 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
260 else:
261 notunderstood=True
262 elif cmd=="say":
263 if auteur in self.overops and len(message)>2:
264 serv.privmsg(message[1]," ".join(message[2:]))
265 log(self.serveur,"priv",auteur," ".join(message))
266 elif len(message)<=2:
267 serv.privmsg(auteur,"Syntaxe : SAY <channel> <message>")
268 else:
269 notunderstood=True
270 elif cmd=="do":
271 if auteur in self.overops and len(message)>2:
272 serv.action(message[1]," ".join(message[2:]))
273 log(self.serveur,"priv",auteur," ".join(message))
274 elif len(message)<=2:
275 serv.privmsg(auteur,"Syntaxe : DO <channel> <action>")
276 else:
277 notunderstood=True
278 elif cmd=="kick":
279 if auteur in self.overops and len(message)>2:
280 serv.kick(message[1],message[2]," ".join(message[3:]))
281 log(self.serveur,"priv",auteur," ".join(message))
282 elif len(message)<=2:
283 serv.privmsg(auteur,"Syntaxe : KICK <channel> <pseudo> [<raison>]")
284 else:
285 notunderstood=True
286 elif cmd=="ops":
287 if auteur in self.overops:
288 serv.privmsg(auteur," ".join(self.ops))
289 else:
290 notunderstood=True
291 elif cmd=="overops":
292 if auteur in self.overops:
293 serv.privmsg(auteur," ".join(self.overops))
294 else:
295 notunderstood=True
296 elif cmd=="score":
297 if len(message)>1:
298 if len(message) in [3,4] and message[1].lower()=="transfert":
299 scores=self.get_scores()
300 de,to=auteur,message[2]
301 value=scores.get(de,0)
302 if len(message)==4:
303 try:
304 asked=int(message[3])
305 except ValueError:
306 serv.privmsg(auteur,"Syntaxe : SCORE TRANSFERT <pseudo> [<n>]")
307 return
308 else:
309 asked=value
310 if value==0:
311 serv.privmsg(auteur,"Vous n'avez pas de points")
312 return
313 elif asked<=0:
314 serv.privmsg(auteur,"Bien tenté…")
315 return
316 elif asked>value:
317 serv.privmsg(auteur,"Vous n'avez que %s points"%(value))
318 return
319 else:
320 self.add_score(de,-asked)
321 self.add_score(to,asked)
322 serv.privmsg(auteur,"Transfert de %s points de %s à %s"%(asked,de,to))
323 else:
324 serv.privmsg(auteur,"Syntaxe : SCORE TRANSFERT <pseudo> [<n>]")
325 else:
326 self.sendscore(auteur)
327 elif cmd=="scores":
328 if len(message)==1:
329 self.sendscores(auteur)
330 elif auteur in self.overops:
331 souscmd=message[1].lower()
332 if souscmd=="del":
333 if len(message)==3:
334 todelete=message[2]
335 scores=self.get_scores()
336 if scores.has_key(todelete):
337 del scores[todelete]
338 self.save_scores(scores)
339 serv.privmsg(auteur,"Score de %s supprimé"%(todelete))
340 else:
341 serv.privmsg(auteur,"Ce score n'existe pas : %s"%(todelete))
342 else:
343 serv.privmsg(auteur,"Syntaxe : SCORES DEL <pseudo>")
344 elif souscmd in ["add","sub"]:
345 if len(message)==4:
346 toadd,val=message[2],message[3]
347 try:
348 val=int(val)
349 except ValueError:
350 serv.privmsg(auteur,"Syntaxe : SCORES {ADD|SUB} <pseudo> <n>")
351 return
352 if souscmd=="sub":
353 val=-val
354 self.add_score(toadd,val)
355 serv.privmsg(auteur,"Done")
356 else:
357 serv.privmsg(auteur,"Syntaxe : SCORES {ADD|SUB} <pseudo> <n>")
358 else:
359 serv.privmsg(auteur,"Syntaxe : SCORES {DEL|ADD|SUB} <pseudo> [<n>]")
360 else:
361 notunderstood=True
362 else:
363 notunderstood=True
364 if notunderstood:
365 serv.privmsg(auteur,"Je n'ai pas compris. Essayez HELP…")
366
367 def sendscore(self, to):
368 self.serv.privmsg(to, "Votre score : %s"%(self.get_scores().get(to,0)) )
369
370 def sendscores(self, to):
371 scores=self.get_scores().items()
372 # trie par score
373 scores.sort(lambda x,y:cmp(x[1],y[1]))
374 scores.reverse()
375 self.serv.privmsg(auteur,"Scores by score : "+" ; ".join(["%s %s"%(i[0],i[1]) for i in scores]))
376 # trie par pseudo
377 scores.sort(lambda x,y:cmp(x[0].lower(),y[0].lower()))
378 self.serv.privmsg(auteur,"Scores by pseudo : "+" ; ".join(["%s %s"%(i[0],i[1]) for i in scores]))
379
380 def on_pubmsg(self, serv, ev):
381 auteur = irclib.nm_to_n(ev.source())
382 canal = ev.target()
383 message = ev.arguments()[0]
384 try:
385 test=bot_unicode(message)
386 except UnicodeBotError:
387 if config.utf8_trigger and not canal in self.quiet_channels:
388 serv.privmsg(canal, (u"%s: %s"%(auteur,random.choice(config.utf8_fail_answers))).encode("utf8"))
389 return
390 pour_moi,message=self.pourmoi(serv,message)
391 if pour_moi and message.split()!=[]:
392 cmd=message.split()[0].lower()
393 try:
394 args=" ".join(message.split()[1:])
395 except:
396 args=""
397 if cmd in ["meurs","die","crève"]:
398 if auteur in self.overops:
399 log(self.serveur,canal,auteur,message+"[successful]")
400 self.mourir()
401 else:
402 serv.privmsg(canal,("%s: %s"%(auteur,random.choice(config.quit_fail_messages))).encode("utf8"))
403 log(self.serveur,canal,auteur,message+"[failed]")
404 elif cmd in ["part","leave","dégage","va-t-en","tut'tiresailleurs,c'estmesgalets"]:
405 if auteur in self.ops and (not (canal in self.stay_channels)
406 or auteur in self.overops):
407 self.quitter(canal)
408 log(self.serveur,canal,auteur,message+"[successful]")
409 if canal in self.chanlist:
410 self.chanlist.remove(canal)
411 else:
412 serv.privmsg(canal,("%s: %s"%(auteur,random.choice(config.leave_fail_messages))).encode("utf8"))
413 log(self.serveur,canal,auteur,message+"[failed]")
414 elif cmd == "score":
415 self.sendscore(auteur)
416 elif cmd == "scores":
417 self.sendscores(auteur)
418 else:
419 if is_pan(message):
420 self.shot(auteur)
421
422 def on_action(self, serv, ev):
423 action = ev.arguments()[0]
424 auteur = irclib.nm_to_n(ev.source())
425 channel = ev.target()
426 #~ try:
427 #~ test=bot_unicode(action)
428 #~ except UnicodeBotError:
429 #~ if config.utf8_trigger and not channel in self.quiet_channels:
430 #~ serv.privmsg(channel, (u"%s: %s"%(auteur,random.choice(config.utf8_fail_answers))).encode("utf8"))
431 #~ return
432 #~ mypseudo=self.nick
433
434
435 def on_kick(self,serv,ev):
436 auteur = irclib.nm_to_n(ev.source())
437 channel = ev.target()
438 victime = ev.arguments()[0]
439 raison = ev.arguments()[1]
440 if victime==self.nick:
441 log(self.serveur,"%s kické de %s par %s (raison : %s)" %(victime,channel,auteur,raison))
442 time.sleep(2)
443 serv.join(channel)
444 #~ l1,l2=config.kick_answers,config.kick_actions
445 #~ n1,n2=len(l1),len(l2)
446 #~ i=random.randrange(n1+n2)
447 #~ if i>=n1:
448 #~ serv.action(channel,l2[i-n1].format(auteur).encode("utf8"))
449 #~ else:
450 #~ serv.privmsg(channel,l1[i].format(auteur).encode("utf8"))
451
452 def quitter(self,chan,leave_message=None):
453 if leave_message==None:
454 leave_message=random.choice(config.leave_messages)
455 self.serv.part(chan,message=leave_message.encode("utf8"))
456
457 def mourir(self):
458 quit_message=random.choice(config.quit_messages)
459 self.die(msg=quit_message.encode("utf8"))
460
461 def get_scores(self):
462 f=open(config.score_file)
463 scores=pickle.load(f)
464 f.close()
465 return scores
466
467 def add_score(self,pseudo,value):
468 scores=self.get_scores()
469 if scores.has_key(pseudo):
470 scores[pseudo]+=value
471 else:
472 scores[pseudo]=value
473 self.save_scores(scores)
474
475 def save_scores(self,scores):
476 f=open(config.score_file,"w")
477 pickle.dump(scores,f)
478 f.close()
479
480 def _getnick(self):
481 return self.serv.get_nickname()
482 nick=property(_getnick)
483
484
485 if __name__=="__main__":
486 import sys
487 if len(sys.argv)==1:
488 print "Usage : saturnin.py <serveur> [--debug]"
489 exit(1)
490 serveur=sys.argv[1]
491 if "debug" in sys.argv or "--debug" in sys.argv:
492 debug=True
493 else:
494 debug=False
495 if "--quiet" in sys.argv:
496 config.debug_stdout=False
497 serveurs={"a♡":"acoeur.crans.org","acoeur":"acoeur.crans.org","acoeur.crans.org":"acoeur.crans.org",
498 "irc":"irc.crans.org","crans":"irc.crans.org","irc.crans.org":"irc.crans.org"}
499 try:
500 serveur=serveurs[serveur]
501 except KeyError:
502 print "Server Unknown : %s"%(serveur)
503 exit(404)
504 bot = Saturnin(serveur,debug)
505 bot.start()