4 # Codé par 20-100 le 23/04/12
6 # Un bot IRC qui sort des déconnaissances
15 from remplace_accents
import remplace_accents
17 config_password
="Yamoussoukro"
18 config_pseudo
="Salesman"
19 config_chanlist
=["#bot","#flood"]
20 config_play_channels
=["#flood"]
21 config_stay_channels
=["#flood","#bot"]
22 config_overops
=["[20-100]","[20-100]_"]
23 config_ops
=["PEB","Petite-Peste"]
25 config_source_file
="capitales.txt"
26 config_played_file_template
="played.%s.txt" #il faut rajouter le nom du serveur
27 def get_config_played_file(serveur
):
28 serveurs
={"acoeur.crans.org":"acoeur","irc.crans.org":"crans"}
29 return config_played_file_template
%(serveurs
[serveur
])
30 ttrig
=120 #time trigger (normalement 120, mais diminué pour les tests)
31 Ttrig
=600 #between two enigms
32 config_time_incompressible
=15 #on peut pas retrigger en dessous de ce temps (60)
33 config_time_incompressible_clue
=60 #on peut pas forcer la demande d'indice en dessous
35 config_score_file
="scores.pickle"
37 config_tag_triggers
=[u
"t(|a)g",u
"ta gueule",u
"la ferme",u
"ferme( |-)la",u
"tais-toi",u
"chut"]
38 config_tag_actions
=[u
"se tait",u
"ferme sa gueule",u
"se la ferme",u
"la ferme"]
39 config_tag_answers
=[u
"J'me tais si j'veux !",
40 u
"Je t'entends pas :°",
42 u
"Non, j'ai pas envie",
43 u
"Peut-être quand toi tu la fermeras, et encore…"]
48 config_debug_stdout
= True
49 config_logfile_template
="Salesman.%s.log"
50 def get_config_logfile(serveur
):
51 serveurs
={"acoeur.crans.org":"acoeur","irc.crans.org":"crans"}
52 return config_logfile_template
%(serveurs
[serveur
])
54 config_quit_messages
=[u
"goto Tombouctou"]
56 config_leave_messages
=[u
"On continuera à jouer plus tard ;)"]
58 class UnicodeBotError(Exception):
60 def bot_unicode(chain
):
63 except UnicodeDecodeError:
66 def log(serveur
,channel
,auteur
=None,message
=None):
67 f
=open(get_config_logfile(serveur
),"a")
68 if auteur
==message
==None:
69 # alors c'est que c'est pas un channel mais juste une ligne de log
70 chain
="%s %s"%(time
.strftime("%F %T"),channel
)
72 chain
="%s [%s:%s] %s"%(time
.strftime("%F %T"),channel
,auteur
,message
)
74 if config_debug_stdout
:
79 def reussi(message
,answer
,auteur
):
80 if auteur
in config_level3
:
81 return answer
in message
82 if auteur
in config_level2
:
83 return remplace_accents(answer
) in message
85 print remplace_accents(answer
).lower(), '#?#', remplace_accents(message
).lower()
86 if re
.match(".*"+remplace_accents(answer
).lower(),remplace_accents(message
).lower()):
89 def is_something(chain
,matches
,avant
=u
".*(?:^| )",apres
=u
"(?:$|\.| |,|;).*",case_sensitive
=False,debug
=False):
91 chain
=unicode(chain
,"utf8")
93 chain
=unicode(chain
,"utf8").lower()
94 allmatches
="("+"|".join(matches
)+")"
95 reg
=(avant
+allmatches
+apres
).lower()
100 return is_something(chain
,config_tag_triggers
)
102 class RefuseError(Exception):
105 class Salesman(ircbot
.SingleServerIRCBot
):
106 def __init__(self
,serveur
,debug
=False):
107 temporary_pseudo
=config_pseudo
+str(random
.randrange(10000,100000))
108 ircbot
.SingleServerIRCBot
.__init
__(self
, [(serveur
, 6667)],
109 temporary_pseudo
,"Un bot irc.[flagellez 20-100, il le mérite]", 10)
112 self
.overops
=config_overops
113 self
.ops
=self
.overops
+config_ops
114 self
.chanlist
=config_chanlist
115 self
.stay_channels
=config_stay_channels
116 self
.play_channels
=config_play_channels
117 self
.play_status
={i
:[0] for i
in self
.play_channels
}
118 self
.quiet_channels
=[]
120 def give_me_my_pseudo(self
,serv
):
121 serv
.privmsg("NickServ","RECOVER %s %s"%(config_pseudo
,config_password
))
122 serv
.privmsg("NickServ","RELEASE %s %s"%(config_pseudo
,config_password
))
124 serv
.nick(config_pseudo
)
126 def on_welcome(self
, serv
, ev
):
127 self
.serv
=serv
# ça serv ira :)
128 self
.give_me_my_pseudo(serv
)
129 serv
.privmsg("NickServ","identify %s"%(config_password))
130 log(self
.serveur
,"Connected")
132 self
.chanlist
=["#bot"]
133 self
.play_channels
=["#bot"]
134 for c
in self
.chanlist
:
135 log(self
.serveur
,"JOIN %s"%(c))
137 for c
in self
.play_channels
:
138 token
=time
.time()-3600
139 self
.play_status
[c
]=[0,token
]
140 serv
.execute_delayed(random
.randrange(ttrig
),self
.start_enigme
,(serv
,c
,token
))
142 def start_enigme(self
,serv
,channel
,token
=None):
143 if self
.play_status
[channel
][0]==0 and channel
in self
.play_channels
:
145 if token
==self
.play_status
[channel
][-1]:
148 if time
.time() > self
.play_status
[channel
][-1]+config_time_incompressible
:
153 enigme
,answer
=self
.get_enigme()
154 log(self
.serveur
,channel
,u
"$Énigme$".encode("utf8"),("%s | %s"%(enigme
, answer
)).encode("utf8"))
155 serv
.privmsg(channel
,enigme
.encode("utf8"))
157 self
.play_status
[channel
]=[1,enigme
,answer
,token
]
158 # ce bot n'a pas d'indices
159 serv
.execute_delayed(random
.randrange(ttrig
*7,ttrig
*10),self
.give_answer
,(serv
,channel
,token
))
162 def give_answer(self
,serv
,channel
,token
):
163 if self
.play_status
[channel
][0]==1 and self
.play_status
[channel
][-1]==token
:
164 answer
=self
.play_status
[channel
][2]
165 serv
.privmsg(channel
,"C'était : %s"%(answer).encode("utf8"))
167 self
.play_status
[channel
]=[0,token
]
168 serv
.execute_delayed(random
.randrange(Ttrig
*5,Ttrig
*10),self
.start_enigme
,(serv
,channel
,token
))
170 def get_enigme(self
):
171 # on récupère les capitales
172 f
=open(config_source_file
)
173 l
=[i
.strip("\n") for i
in f
.readlines()]
175 l
=[i
.split(" | ") for i
in l
]
176 dec
={int(i
[0]):list(i
[1:]) for i
in l
}
177 # on va chercher combien de fois elles ont été jouées
178 played_file
=get_config_played_file(self
.serveur
)
182 l
=re
.findall("(.*):(.*)",t
)
183 played
={int(i
[0]):int(i
[1]) for i
in l
}
184 # on récupère le nombre d'occurrences le plus faible
185 mini
=min(played
.values())
186 # on choisit un id dans ceux qui ont ce nombre d'occurences
187 id_choisi
=random
.choice([k
for k
,v
in played
.items() if v
==mini
])
188 capitale
,pays
=dec
[id_choisi
]
189 # on peut jouer capitale -> pays ou pays -> capitale
190 enigme
,answer
=random
.choice([[capitale
,pays
],[pays
,capitale
]])
191 # on incrémente la choisie
193 # on enregistre le played_file
194 f
=open(played_file
,"w")
195 f
.write("\n".join(["%-3s : %s"%(k
,v
) for k
,v
in played
.items()]))
197 return map(lambda x
:x
.decode("utf8"), [enigme
,answer
])
199 def pourmoi(self
, serv
, message
):
202 if message
[:size
]==pseudo
and len(message
)>size
and message
[size
]==":":
203 return (True,message
[size
+1:].strip(" "))
205 return (False,message
)
207 def on_privmsg(self
, serv
, ev
):
208 message
=ev
.arguments()[0]
209 auteur
= irclib
.nm_to_n(ev
.source())
211 test
=bot_unicode(message
)
212 except UnicodeBotError
:
214 "Euh, tu fais de la merde avec ton encodage là, j'ai failli crasher…")
216 message
=message
.split()
217 cmd
=message
[0].lower()
220 helpmsg_default
="""Liste des commandes :
221 HELP Affiche ce message d'aide
222 SCORE Affiche ton score (SCORE TRANSFERT <pseudo> [<n>] pour transférer des points)
223 SCORES Affiche les scores"""
225 JOIN Faire rejoindre un channel (sans paramètres, donne la liste des chans actuels)
226 LEAVE Faire quitter un channel
227 PLAY Passe un channel en mode "jouer"
228 NOPLAY Passe un channel en mode "ne pas jouer"
229 QUIET Se taire sur un channel
230 NOQUIET Opposé de QUIET"""
232 SCORES {DEL|ADD|SUB} Tu veux un dessin ?
233 SAY Fais envoyer un message sur un chan ou à une personne
234 STAY Ignorera les prochains LEAVE pour un chan
235 NOSTAY Opposé de STAY
236 STATUS Montre l'état courant
238 helpmsg
=helpmsg_default
239 if auteur
in self
.ops
:
241 if auteur
in self
.overops
:
242 helpmsg
+=helpmsg_overops
243 for ligne
in helpmsg
.split("\n"):
244 serv
.privmsg(auteur
,ligne
)
246 if auteur
in self
.ops
:
248 if message
[1] in self
.chanlist
:
249 serv
.privmsg(auteur
,"Je suis déjà sur %s"%(message
[1]))
251 serv
.join(message
[1])
252 self
.chanlist
.append(message
[1])
253 serv
.privmsg(auteur
,"Channels : "+" ".join(self
.chanlist
))
254 log(self
.serveur
,"priv",auteur
," ".join(message
))
256 serv
.privmsg(auteur
,"Channels : "+" ".join(self
.chanlist
))
260 if auteur
in self
.ops
and len(message
)>1:
261 if message
[1] in self
.chanlist
:
262 if not (message
[1] in self
.stay_channels
) or auteur
in self
.overops
:
263 self
.quitter(message
[1]," ".join(message
[2:]))
264 self
.chanlist
.remove(message
[1])
265 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
267 serv
.privmsg(auteur
,"Non, je reste !")
268 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
270 serv
.privmsg(auteur
,"Je ne suis pas sur %s"%(message
[1]))
274 if auteur
in self
.overops
:
276 if message
[1] in self
.stay_channels
:
277 serv
.privmsg(auteur
,"Je stay déjà sur %s."%(message
[1]))
278 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
280 self
.stay_channels
.append(message
[1])
281 serv
.privmsg(auteur
,"Stay channels : "+" ".join(self
.stay_channels
))
282 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
284 serv
.privmsg(auteur
,"Stay channels : "+" ".join(self
.stay_channels
))
288 if auteur
in self
.overops
:
290 if message
[1] in self
.stay_channels
:
291 self
.stay_channels
.remove(message
[1])
292 serv
.privmsg(auteur
,"Stay channels : "+" ".join(self
.stay_channels
))
293 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
295 serv
.privmsg(auteur
,"Je ne stay pas sur %s."%(message
[1]))
296 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
300 if auteur
in self
.ops
:
302 if message
[1] in self
.play_channels
:
303 serv
.privmsg(auteur
,"Je play déjà sur %s."%(message
[1]))
304 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
306 self
.play_channels
.append(message
[1])
307 self
.play_status
[message
[1]]=[0,time
.time()-3600]
308 serv
.privmsg(auteur
,"Play channels : "+" ".join(self
.play_channels
))
309 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
311 serv
.privmsg(auteur
,"Play channels : "+" ".join(self
.play_channels
))
315 if auteur
in self
.ops
:
317 if message
[1] in self
.play_channels
:
318 self
.play_channels
.remove(message
[1])
319 serv
.privmsg(auteur
,"Play channels : "+" ".join(self
.play_channels
))
320 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
322 serv
.privmsg(auteur
,"Je ne play pas sur %s."%(message
[1]))
323 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
327 if auteur
in self
.ops
:
329 if message
[1] in self
.quiet_channels
:
330 serv
.privmsg(auteur
,"Je me la ferme déjà sur %s"%(message
[1]))
331 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
333 self
.quiet_channels
.append(message
[1])
334 serv
.privmsg(auteur
,"Quiet channels : "+" ".join(self
.quiet_channels
))
335 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
337 serv
.privmsg(auteur
,"Quiet channels : "+" ".join(self
.quiet_channels
))
341 if auteur
in self
.ops
:
343 if message
[1] in self
.quiet_channels
:
344 self
.quiet_channels
.remove(message
[1])
345 serv
.privmsg(auteur
,"Quiet channels : "+" ".join(self
.quiet_channels
))
346 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
348 serv
.privmsg(auteur
,"Je ne me la ferme pas sur %s."%(message
[1]))
349 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
352 elif cmd
in ["states","status"]:
353 if auteur
in self
.overops
:
354 for k
in self
.play_status
.keys():
355 serv
.privmsg(auteur
,(u
"%s : %s"%(k
," | ".join([unicode(i
) for i
in self
.play_status
[k
]]))).encode("utf8") )
357 if auteur
in self
.overops
and len(message
)>2:
358 serv
.privmsg(message
[1]," ".join(message
[2:]))
359 log(self
.serveur
,"priv",auteur
," ".join(message
))
360 elif len(message
)<=2:
361 serv
.privmsg(auteur
,"Syntaxe : SAY <channel> <message>")
365 if auteur
in self
.overops
:
366 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
370 if len(message
) in [3,4] and message
[1].lower()=="transfert":
371 scores
=self
.get_scores()
372 de
,to
=auteur
,message
[2]
373 value
=scores
.get(de
,0)
376 asked
=int(message
[3])
378 serv
.privmsg(auteur
,"Syntaxe : SCORE TRANSFERT <pseudo> [<n>]")
383 serv
.privmsg(auteur
,"Vous n'avez pas de points")
386 serv
.privmsg(auteur
,"Bien tenté…")
389 serv
.privmsg(auteur
,"Vous n'avez que %s points"%(value))
392 self
.add_score(de
,-asked
)
393 self
.add_score(to
,asked
)
394 serv
.privmsg(auteur
,"Transfert de %s points de %s à %s"%(asked
,de
,to
))
396 serv
.privmsg(auteur
,"Syntaxe : SCORE TRANSFERT <pseudo> [<n>]")
398 serv
.privmsg(auteur
,"Votre score : %s"%(self
.get_scores().get(auteur
,0)) )
401 scores
=self
.get_scores().items()
403 scores
.sort(lambda x
,y
:cmp(x
[1],y
[1]))
405 serv
.privmsg(auteur
,"Scores by score : "+" ; ".join(["%s %s"%(i
[0],i
[1]) for i
in scores
]))
407 scores
.sort(lambda x
,y
:cmp(x
[0].lower(),y
[0].lower()))
408 serv
.privmsg(auteur
,"Scores by pseudo : "+" ; ".join(["%s %s"%(i
[0],i
[1]) for i
in scores
]))
409 elif auteur
in self
.overops
:
410 souscmd
=message
[1].lower()
414 scores
=self
.get_scores()
415 if scores
.has_key(todelete
):
417 self
.save_scores(scores
)
418 serv
.privmsg(auteur
,"Score de %s supprimé"%(todelete))
420 serv
.privmsg(auteur
,"Ce score n'existe pas : %s"%(todelete))
422 serv
.privmsg(auteur
,"Syntaxe : SCORES DEL <pseudo>")
423 elif souscmd
in ["add","sub"]:
425 toadd
,val
=message
[2],message
[3]
429 serv
.privmsg(auteur
,"Syntaxe : SCORES {ADD|SUB} <pseudo> <n>")
433 self
.add_score(toadd
,val
)
434 serv
.privmsg(auteur
,"Done")
436 serv
.privmsg(auteur
,"Syntaxe : SCORES {ADD|SUB} <pseudo> <n>")
438 serv
.privmsg(auteur
,"Syntaxe : SCORES {DEL|ADD|SUB} <pseudo> [<n>]")
444 serv
.privmsg(auteur
,"Je n'ai pas compris. Essaye HELP…")
446 def on_pubmsg(self
, serv
, ev
):
447 auteur
= irclib
.nm_to_n(ev
.source())
449 message
= ev
.arguments()[0]
451 test
=bot_unicode(message
)
452 except UnicodeBotError
:
453 if not canal
in self
.quiet_channels
:
455 "%s: Euh, tu fais de la merde avec ton encodage là, j'ai failli crasher…"%(auteur))
458 pour_moi
,message
=self
.pourmoi(serv
,message
)
459 if pour_moi
and message
.split()!=[]:
460 cmd
=message
.split()[0].lower()
462 args
=" ".join(message
.split()[1:])
465 if cmd
in ["meurs","die","crève"]:
466 if auteur
in self
.overops
:
468 log(self
.serveur
,canal
,auteur
,message
+"[successful]")
470 serv
.privmsg(canal
,"%s: crève !"%(auteur))
471 log(self
.serveur
,canal
,auteur
,message
+"[failed]")
472 if cmd
in ["meur", "meurt","meurre","meurres"] and not canal
in self
.quiet_channels
:
473 serv
.privmsg(canal
,'%s: Mourir, impératif, 2ème personne du singulier : "meurs" (de rien)'%(auteur))
474 if cmd
in ["part","leave","dégage"]:
475 if auteur
in self
.ops
and (not (canal
in self
.stay_channels
)
476 or auteur
in self
.overops
):
478 log(self
.serveur
,canal
,auteur
,message
+"[successful]")
479 self
.chanlist
.remove(canal
)
481 serv
.privmsg(canal
,"%s: Non, je reste !"%(auteur))
482 log(self
.serveur
,canal
,auteur
,message
+"[failed]")
484 if cmd
in ["deviens","pseudo"]:
485 if auteur
in self
.ops
:
488 log(self
.serveur
,canal
,auteur
,message
+"[successful]")
489 if cmd
in ["coucou"] and not canal
in self
.quiet_channels
:
490 serv
.privmsg(canal
,"%s: coucou"%(auteur))
491 if cmd
in ["ping"] and not canal
in self
.quiet_channels
:
492 serv
.privmsg(canal
,"%s: pong"%(auteur))
493 if cmd
in ["ville","capitale","pays","énigme","enigme","encore"]:
494 if canal
in self
.play_channels
:
495 if self
.play_status
.get(canal
,[-1])[0]==0:
497 self
.start_enigme(serv
,canal
)
499 serv
.privmsg(canal
,"%s: Je peux souffler une minute ?"%(auteur))
501 serv
.privmsg(canal
,("%s: Rappel : %s"%(auteur
,self
.play_status
[canal
][1])).encode("utf8") )
503 serv
.privmsg(canal
,"%s: pas ici…"%(auteur))
504 if cmd
in ["score","!score"]:
505 serv
.privmsg(auteur
,"Votre score : %s"%(self
.get_scores().get(auteur
,0)) )
506 if cmd
in ["scores","!scores"]:
507 scores
=self
.get_scores().items()
509 scores
.sort(lambda x
,y
:cmp(x
[1],y
[1]))
511 serv
.privmsg(auteur
,"Scores by score : "+" ; ".join(["%s %s"%(i
[0],i
[1]) for i
in scores
]))
513 scores
.sort(lambda x
,y
:cmp(x
[0].lower(),y
[0].lower()))
514 serv
.privmsg(auteur
,"Scores by pseudo : "+" ; ".join(["%s %s"%(i
[0],i
[1]) for i
in scores
]))
515 if is_tag(message
) and not canal
in self
.quiet_channels
:
516 if auteur
in self
.ops
:
517 action
=random
.choice(config_tag_actions
)
518 serv
.action(canal
,action
.encode("utf8"))
519 self
.quiet_channels
.append(canal
)
521 answer
=random
.choice(config_tag_answers
)
522 for ligne
in answer
.split("\n"):
523 serv
.privmsg(canal
,"%s: %s"%(auteur
,ligne
.encode("utf8")))
529 if self
.play_status
.get(canal
,[-1])[0]==1:
530 answer
=self
.play_status
[canal
][2]
531 if reussi(message
.decode("utf8"),answer
,auteur
):
532 serv
.privmsg(canal
,(u
"%s: bravo ! (C'était %s)"%(auteur
,answer
)).encode("utf8"))
533 log(self
.serveur
,canal
,auteur
+"$win",message
)
534 self
.add_score(auteur
,1)
536 self
.play_status
[canal
]=[0,token
]
537 serv
.execute_delayed(random
.randrange(Ttrig
*5,Ttrig
*10),self
.start_enigme
,(serv
,canal
,token
))
539 def on_kick(self
,serv
,ev
):
540 auteur
= irclib
.nm_to_n(ev
.source())
541 channel
= ev
.target()
542 victime
= ev
.arguments()[0]
543 raison
= ev
.arguments()[1]
544 if victime
==self
.nick
:
545 log(self
.serveur
,"%s kické par %s (raison : %s)" %(victime
,auteur
,raison
))
548 # on ne dit rien au rejoin
549 #l1,l2=config_kick_answers,config_kick_actions
550 #n1,n2=len(l1),len(l2)
551 #i=random.randrange(n1+n2)
553 # serv.action(channel,l2[i-n1].format(auteur).encode("utf8"))
555 # serv.privmsg(channel,l1[i].format(auteur).encode("utf8"))
557 def quitter(self
,chan
,leave_message
=None):
558 if leave_message
==None:
559 leave_message
=random
.choice(config_leave_messages
)
560 self
.serv
.part(chan
,message
=leave_message
.encode("utf8"))
563 quit_message
=random
.choice(config_quit_messages
)
564 self
.die(msg
=quit_message
.encode("utf8"))
566 def get_scores(self
):
567 f
=open(config_score_file
)
568 scores
=pickle
.load(f
)
572 def add_score(self
,pseudo
,value
):
573 scores
=self
.get_scores()
574 if scores
.has_key(pseudo
):
575 scores
[pseudo
]+=value
578 self
.save_scores(scores
)
580 def save_scores(self
,scores
):
581 f
=open(config_score_file
,"w")
582 pickle
.dump(scores
,f
)
586 return self
.serv
.get_nickname()
587 nick
= property(_getnick
)
589 if __name__
=="__main__":
592 print "Usage : Salesman.py <serveur> [--debug]"
595 if "debug" in sys
.argv
or "--debug" in sys
.argv
:
599 serveurs
={"a♡":"acoeur.crans.org","acoeur":"acoeur.crans.org","acoeur.crans.org":"acoeur.crans.org",
600 "irc":"irc.crans.org","crans":"irc.crans.org","irc.crans.org":"irc.crans.org"}
602 serveur
=serveurs
[serveur
]
604 print "Server Unknown : %s"%(serveur)
606 salesman
=Salesman(serveur
,debug
)