]> gitweb.pimeys.fr Git - bots/hung.git/blob - hung.py
Une fonction mourir, c'est plus propre pour factoriser
[bots/hung.git] / hung.py
1 #!/usr/bin/python
2 # -*- coding:utf8 -*-
3
4 # Codé par 20-100 le 23/04/12
5
6 # Un test de bot irc, parce que c'est cool
7
8 import irclib
9 import ircbot
10 import threading
11 import random
12 import time
13 import socket, ssl, json
14 import pickle
15 import re
16 import os
17 from commands import getstatusoutput as ex
18
19 import sys
20 config_debug_stdout=True
21 if "--quiet" in sys.argv:
22 config_debug_stdout=False
23
24 config_irc_password="I'mAHungMan"
25 config_irc_pseudo="Hung"
26 config_chanlist=["#bot","#flood"]
27 config_stay_channels=["#bot","#flood"]
28 config_play_channels=["#flood"]
29 config_quiet_channels=[]
30 config_logfile_template="hung.%s.log"
31 def get_config_logfile(serveur):
32 serveurs={"acoeur.crans.org":"acoeur","irc.crans.org":"crans"}
33 return config_logfile_template%(serveurs[serveur])
34 config_overops=["[20-100]","[20-100]_","Petite-Peste","PEB"]
35 config_ops=[]
36 config_report_bugs_to=["[20-100]"]
37
38 config_dico_mots="mots.txt"
39 config_dico_defs="definitions.txt"
40
41 config_scores_file="scores.pickle"
42
43 config_tag_triggers=[u"t(|a)g",u"ta gueule",u"la ferme",u"ferme( |-)la",u"tais-toi",u"chut"]
44 config_tag_actions=[u"se tait",u"ferme sa gueule",u"se la ferme",u"la ferme"]
45 config_tag_answers=[u"J'me tais si j'veux !",
46 u"Je t'entends pas :°",
47 u"Héhé, try again",
48 u"Non, j'ai pas envie",
49 u"Peut-être quand toi tu la fermeras, et encore…"]
50
51 config_quit_messages=[u"_ _ _, _ _ _ _ _ _ _ _ _ _!",
52 u"_ _ E, _ _ _ E _ _ _ _ _ _!",
53 u"_ _ E, _ _ _ E _ _ O _ _ _!",
54 u"_ _ E, _ _ U E _ _ O _ _ _!",
55 u"_ _ E, _ R U E _ _ O R _ _!",
56 u"_ _ E, _ R U E L _ O R L _!",
57 u"B _ E, _ R U E L _ O R L _!",
58 u"B _ E, C R U E L _ O R L _!",
59 u"B _ E, C R U E L _ O R L D!",
60 u"B Y E, C R U E L _ O R L D!",
61 u"B Y E, C R U E L W O R L D!",
62 ]
63
64 def log(serveur,channel,auteur=None,message=None):
65 f=open(get_config_logfile(serveur),"a")
66 if auteur==message==None:
67 # alors c'est que c'est pas un channel mais juste une ligne de log
68 chain="%s %s"%(time.strftime("%F %T"),channel)
69 else:
70 chain="%s [%s:%s] %s"%(time.strftime("%F %T"),channel,auteur,message)
71 f.write(chain+"\n")
72 if config_debug_stdout:
73 print chain
74 f.close()
75
76
77 class UnicodeBotError(Exception):
78 pass
79 def bot_unicode(chain):
80 try:
81 unicode(chain,"utf8")
82 except UnicodeDecodeError as exc:
83 raise UnicodeBotError
84
85 def remplace_accents(chaine):
86 chaine=chaine.lower()
87 remplacements = {u"á":u"a",u"à":u"a",u"â":u"a",u"ä":u"a",u"é":u"e",u"è":u"e",u"ê":u"e",u"ë":u"e",u"í":u"i",u"ì":u"i",u"î":u"i",u"ï":u"i",u"ó":u"o", u"ò":u"o",u"ô":u"o",u"ö":u"o",u"ú":u"u",u"ù":u"u",u"û":u"u",u"ü":u"u",u"ý":u"y",u"ỳ":u"y",u"ŷ":u"y",u"ÿ":u"y",u"œ":u"oe",u"æ":u"ae"}
88 for avant,apres in remplacements.items():
89 chaine=chaine.replace(avant,apres)
90 return chaine
91
92 def is_something(chain,matches,avant=u".*(?:^| )",apres=u"(?:$|\.| |,|;).*",case_sensitive=False,debug=False):
93 if case_sensitive:
94 chain=unicode(chain,"utf8")
95 else:
96 chain=unicode(chain,"utf8").lower()
97 allmatches="("+"|".join(matches)+")"
98 reg=(avant+allmatches+apres).lower()
99 o=re.match(reg,chain)
100 return o
101
102 def is_tag(chain):
103 return is_something(chain,config_tag_triggers)
104
105 class Hung(ircbot.SingleServerIRCBot):
106 def __init__(self,serveur,debug=False):
107 temporary_pseudo=config_irc_pseudo+str(random.randrange(10000,100000))
108 ircbot.SingleServerIRCBot.__init__(self, [(serveur, 6667)],
109 temporary_pseudo,"Bot irc pour jouer au pendu", 10)
110 self.debug=debug
111 self.serveur=serveur
112 self.overops=config_overops
113 self.ops=self.overops+config_ops
114 self.report_bugs_to=config_report_bugs_to
115 self.chanlist=config_chanlist
116 self.stay_channels=config_stay_channels
117 self.play_channels=config_play_channels
118 self.play_status={}
119 self.quiet_channels=config_quiet_channels
120
121
122 def give_me_my_pseudo(self,serv):
123 serv.privmsg("NickServ","RECOVER %s %s"%(config_irc_pseudo,config_irc_password))
124 serv.privmsg("NickServ","RELEASE %s %s"%(config_irc_pseudo,config_irc_password))
125 time.sleep(0.3)
126 serv.nick(config_irc_pseudo)
127
128 def on_welcome(self, serv, ev):
129 self.give_me_my_pseudo(serv)
130 serv.privmsg("NickServ","IDENTIFY %s"%(config_irc_password))
131 log(self.serveur,"Connected")
132 if self.debug:
133 self.chanlist=["#bot"]
134 self.play_channels=["#bot"]
135 for c in self.chanlist:
136 log(self.serveur,"JOIN %s"%(c))
137 serv.join(c)
138
139 def pourmoi(self, serv, message):
140 """renvoie (False,lemessage) ou (True, le message amputé de "pseudo: ")"""
141 pseudo=serv.get_nickname()
142 size=len(pseudo)
143 if message[:size]==pseudo and len(message)>size and message[size]==":":
144 return (True,message[size+1:].lstrip(" "))
145 else:
146 return (False,message)
147
148 def on_privmsg(self, serv, ev):
149 message=ev.arguments()[0]
150 auteur = irclib.nm_to_n(ev.source())
151 try:
152 test=bot_unicode(message)
153 except UnicodeBotError:
154 serv.privmsg(auteur,
155 "Euh, tu fais de la merde avec ton encodage là, j'ai failli crasher…")
156 return
157 message=message.split()
158 cmd=message[0].lower()
159 notunderstood=False
160 if cmd=="join":
161 if auteur in self.ops:
162 if len(message)>1:
163 if message[1] in self.chanlist:
164 serv.privmsg(auteur,"Je suis déjà sur %s"%(message[1]))
165 else:
166 serv.join(message[1])
167 self.chanlist.append(message[1])
168 serv.privmsg(auteur,"Channels : "+" ".join(self.chanlist))
169 log(self.serveur,"priv",auteur," ".join(message))
170 else:
171 serv.privmsg(auteur,"Channels : "+" ".join(self.chanlist))
172 else:
173 notunderstood=True
174 elif cmd=="leave":
175 if auteur in self.ops and len(message)>1:
176 if message[1] in self.chanlist:
177 if not (message[1] in self.stay_channels) or auteur in self.overops:
178 serv.part(message[1])
179 self.chanlist.remove(message[1])
180 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
181 else:
182 serv.privmsg(auteur,"Non, je reste !")
183 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
184 else:
185 serv.privmsg(auteur,"Je ne suis pas sur %s"%(message[1]))
186 else:
187 notunderstood=True
188 elif cmd=="play":
189 if auteur in self.ops:
190 if len(message)>1:
191 if message[1] in self.play_channels:
192 serv.privmsg(auteur,"Je play déjà sur %s."%(message[1]))
193 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
194 else:
195 self.play_channels.append(message[1])
196 self.play_status[message[1]]=[None,None,None]
197 serv.privmsg(auteur,"Play channels : "+" ".join(self.play_channels))
198 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
199 else:
200 serv.privmsg(auteur,"Play channels : "+" ".join(self.play_channels))
201 else:
202 notunderstood=True
203 elif cmd=="noplay":
204 if auteur in self.ops:
205 if len(message)>1:
206 if message[1] in self.play_channels:
207 self.play_channels.remove(message[1])
208 serv.privmsg(auteur,"Play channels : "+" ".join(self.play_channels))
209 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
210 else:
211 serv.privmsg(auteur,"Je ne play pas sur %s."%(message[1]))
212 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
213 else:
214 notunderstood=True
215 elif cmd=="stay":
216 if auteur in self.overops:
217 if len(message)>1:
218 if message[1] in self.stay_channels:
219 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
220 serv.privmsg(auteur,"Je stay déjà sur %s."%(message[1]))
221 else:
222 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
223 self.stay_channels.append(message[1])
224 serv.privmsg(auteur,"Stay channels : "+" ".join(self.stay_channels))
225 else:
226 serv.privmsg(auteur,"Stay channels : "+" ".join(self.stay_channels))
227 else:
228 notunderstood=True
229 elif cmd=="nostay":
230 if auteur in self.overops:
231 if len(message)>1:
232 if message[1] in self.stay_channels:
233 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
234 self.stay_channels.remove(message[1])
235 serv.privmsg(auteur,"Stay channels : "+" ".join(self.stay_channels))
236 else:
237 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
238 serv.privmsg(auteur,"Je ne stay pas sur %s."%(message[1]))
239
240 else:
241 notunderstood=True
242 elif cmd in ["states","status"]:
243 if auteur in self.overops:
244 for k in self.play_status.keys():
245 if self.play_status[k]==[None,None,None]:
246 serv.privmsg(auteur,"None")
247 else:
248 serv.privmsg(auteur,"%s : %s (%s) [%s]"%(k,"".join([str(i[0]) for i in self.play_status[k][0]])
249 ,self.play_status[k][1], self.play_status[k][2]))
250 elif cmd=="die":
251 if auteur in self.overops:
252 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
253 self.mourir()
254 else:
255 notunderstood=True
256 elif cmd=="quiet":
257 if auteur in self.ops:
258 if len(message)>1:
259 if message[1] in self.quiet_channels:
260 serv.privmsg(auteur,"Je me la ferme déjà sur %s"%(message[1]))
261 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
262 else:
263 self.quiet_channels.append(message[1])
264 serv.privmsg(auteur,"Quiet channels : "+" ".join(self.quiet_channels))
265 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
266 else:
267 serv.privmsg(auteur,"Quiet channels : "+" ".join(self.quiet_channels))
268 else:
269 notunderstood=True
270 elif cmd=="noquiet":
271 if auteur in self.ops:
272 if len(message)>1:
273 if message[1] in self.quiet_channels:
274 self.quiet_channels.remove(message[1])
275 serv.privmsg(auteur,"Quiet channels : "+" ".join(self.quiet_channels))
276 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
277 else:
278 serv.privmsg(auteur,"Je ne me la ferme pas sur %s."%(message[1]))
279 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
280 else:
281 notunderstood=True
282 elif cmd=="say":
283 if auteur in self.overops and len(message)>2:
284 serv.privmsg(message[1]," ".join(message[2:]))
285 log(self.serveur,"priv",auteur," ".join(message))
286 elif len(message)<=2:
287 serv.privmsg(auteur,"Syntaxe : SAY <channel> <message>")
288 else:
289 notunderstood=True
290 elif cmd=="do":
291 if auteur in self.overops and len(message)>2:
292 serv.action(message[1]," ".join(message[2:]))
293 log(self.serveur,"priv",auteur," ".join(message))
294 elif len(message)<=2:
295 serv.privmsg(auteur,"Syntaxe : DO <channel> <action>")
296 else:
297 notunderstood=True
298 elif cmd in ["score","scores"]:
299 self.send_scores(serv,auteur)
300 else:
301 notunderstood=True
302 if notunderstood:
303 serv.privmsg(auteur,"Je n'ai pas compris. Essaye HELP…")
304
305 def affiche_mot(self, serv, canal, begin="Mot courant"):
306 if self.play_status.has_key(canal):
307 mot = self.play_status[canal][0]
308 obfuskated=" ".join([lettre[0] if lettre[1] else "_" for lettre in mot])
309 serv.privmsg(canal,"%s : %s"%(begin,obfuskated))
310
311 def start_partie(self, serv, canal):
312 mots=[mot.strip() for mot in open(config_dico_mots).readlines()]
313 defs=[defi.strip() for defi in open(config_dico_defs).readlines()]
314 indice = random.randrange(0,len(mots))
315 mot,definition=mots[indice],defs[indice]
316 # ' et - sont considérés comme déjà devinés
317 mot = [(lettre,lettre in "'-()") for lettre in list(mot)]
318 self.play_status[canal]=[mot,definition,{}]
319 self.affiche_mot(serv, canal, begin="Devinez")
320
321 def on_pubmsg(self, serv, ev):
322 auteur = irclib.nm_to_n(ev.source())
323 canal = ev.target()
324 message = ev.arguments()[0]
325 try:
326 test=bot_unicode(message)
327 except UnicodeBotError:
328 if not canal in self.quiet_channels:
329 serv.privmsg(canal,
330 "%s: Euh, tu fais de la merde avec ton encodage là, j'ai failli crasher…"%(auteur))
331 return
332 pour_moi,message=self.pourmoi(serv,message)
333 if pour_moi and message.split()!=[]:
334 cmd=message.split()[0].lower()
335 try:
336 args=" ".join(message.split()[1:])
337 except:
338 args=""
339 if cmd in ["meurs","die","crève"]:
340 if auteur in self.overops:
341 log(self.serveur,canal,auteur,message+"[successful]")
342 self.mourir()
343 else:
344 serv.privmsg(canal,"%s: crève !"%(auteur))
345 log(self.serveur,canal,auteur,message+"[failed]")
346 elif cmd in ["part","leave","dégage"]:
347 if auteur in self.ops and (not (canal in self.stay_channels)
348 or auteur in self.overops):
349 serv.part(canal,message="Éjecté par %s"%(auteur))
350 log(self.serveur,canal,auteur,message+"[successful]")
351 if canal in self.chanlist:
352 self.chanlist.remove(canal)
353 else:
354 serv.privmsg(canal,"%s: Non, je reste !"%(auteur))
355 log(self.serveur,canal,auteur,message+"[failed]")
356 elif cmd in ["play","jeu","encore","again","partie","pendu","game","mot","go","allez"]:
357 if not canal in self.quiet_channels and canal in self.play_channels:
358 if self.play_status.has_key(canal):
359 if self.play_status[canal]==[None,None,None]:
360 self.start_partie(serv, canal)
361 else:
362 self.affiche_mot(serv, canal, begin="%s: Rappel"%(auteur))
363 else:
364 self.play_status[canal]=[None,None,None]
365 self.start_partie(serv, canal)
366 elif not canal in self.play_channels:
367 serv.privmsg(canal,"%s: pas ici…"%(auteur))
368 elif (cmd in list("azertyuiopqsdfghjklmwxcvbn") and canal in self.play_channels
369 and self.play_status.has_key(canal) and self.play_status[canal]!=[None,None,None]):
370 giv_let=cmd.upper()
371 liste=self.play_status[canal][0]
372 listeapres=[(lettre[0],lettre[1] or lettre[0]==giv_let) for lettre in liste]
373 if liste!=listeapres:
374 nbtrouvees=(sum([lettre[1] for lettre in listeapres if not lettre[0] in "'-()"])
375 - sum([lettre[1] for lettre in liste if not lettre[0] in "'-()"]))
376 if self.play_status[canal][2].has_key(auteur):
377 self.play_status[canal][2][auteur]+= nbtrouvees
378 else:
379 self.play_status[canal][2][auteur] = nbtrouvees
380 self.play_status[canal][0]=listeapres
381 self.affiche_mot(serv, canal, begin="%s placé"%(giv_let))
382 if all([lettre[1] for lettre in listeapres]):
383 self.gagne(serv, canal)
384
385 elif cmd in ["score","scores","!score","!scores"]:
386 self.send_scores(serv,auteur)
387 if cmd in ["meur", "meurt","meurre","meurres"] and not canal in self.quiet_channels:
388 serv.privmsg(canal,'%s: Mourir, impératif, 2ème personne du singulier : "meurs" (de rien)'%(auteur))
389 if is_tag(message) and not canal in self.quiet_channels:
390 if auteur in self.ops:
391 action=random.choice(config_tag_actions)
392 serv.action(canal,action.encode("utf8"))
393 self.quiet_channels.append(canal)
394 else:
395 answer=random.choice(config_tag_answers)
396 for ligne in answer.split("\n"):
397 serv.privmsg(canal,"%s: %s"%(auteur,ligne.encode("utf8")))
398 # on essaye de voir si le mot fourni matche la partie en cours
399 mot = cmd
400 # bon, ce teste merde et j'arrive pas à trouver pourquoi, alors j'ai craqué, je l'ai mis dans un try
401 # bouh ! beurk ! pas bien ! promis, j'irai me flageller…
402 try:
403 if remplace_accents(mot)==("".join([i[0] for i in self.play_status[canal][0]])).lower():
404 # on a trouvé le mot
405 # on regarde combien de lettre il manquait
406 manquait = sum([not lettre[1] for lettre in self.play_status[canal][0]])
407 self.add_score({auteur: manquait})
408 if self.play_status[canal][2].has_key(auteur):
409 self.play_status[canal][2][auteur]+=manquait
410 else:
411 self.play_status[canal][2][auteur]=manquait
412 self.gagne(serv, canal, bonus=auteur, bonusvalue=manquait)
413 except:
414 pass
415 else:
416 pass
417
418
419 def on_action(self, serv, ev):
420 action = ev.arguments()[0]
421 auteur = irclib.nm_to_n(ev.source())
422 channel = ev.target()
423 try:
424 test=bot_unicode(action)
425 except UnicodeBotError:
426 serv.privmsg(channel,
427 "%s : Euh, tu fais de la merde avec ton encodage là, j'ai failli crasher…"%(auteur))
428 return
429 mypseudo=serv.get_nickname()
430
431 def mourir(self):
432 quit_message=random.choice(config_quit_messages)
433 self.die(message=config_quit_message)
434
435 def get_scores(self):
436 f=open(config_scores_file)
437 scores=pickle.load(f)
438 f.close()
439 return scores
440 def save_scores(self,scores):
441 f=open(config_scores_file,'w')
442 pickle.dump(scores,f)
443 f.close()
444 def add_score(self,dico):
445 scores=self.get_scores()
446 for k,v in dico.items():
447 if scores.has_key(k):
448 scores[k]+=v
449 else:
450 scores[k]=v
451 self.save_scores(scores)
452 def send_scores(self, serv, destinataire):
453 scores=self.get_scores()
454 scores=scores.items()
455 scores.sort(lambda x,y:cmp(x[1],y[1]))
456 scores.reverse()
457 serv.privmsg(destinataire,"Scores by score : "+" ; ".join(["%s %s"%(k,v) for (k,v) in scores]) )
458 scores.sort(lambda x,y:cmp(x[0].lower(),y[0].lower()))
459 serv.privmsg(destinataire,"Scores by pseudo : "+" ; ".join(["%s %s"%(k,v) for (k,v) in scores]) )
460
461 def gagne(self, serv, canal, bonus=None, bonusvalue=2):
462 realword="".join([lettre[0] for lettre in self.play_status[canal][0]])
463 definition = self.play_status[canal][1]
464 serv.privmsg(canal,"Bravo ! C'était %s"%(realword))
465 serv.privmsg(canal,definition)
466 nlettre=float(len(realword.replace("'","").replace("-","")))
467 contribs=["%s:%s%%%s"%(pseudo,str(int(100*contrib/nlettre)),("+bonus(%s)"%(bonusvalue))*(bonus==pseudo)) for pseudo,contrib in self.play_status[canal][2].items()]
468 contribs_score={pseudo:int(10*contrib/nlettre) for pseudo,contrib in self.play_status[canal][2].items()}
469 self.add_score(contribs_score)
470 serv.privmsg(canal,"Contributions : %s"%(" ".join(contribs)) )
471 self.play_status[canal]=[None,None,None]
472
473
474 if __name__=="__main__":
475 import sys
476 if len(sys.argv)==1:
477 print "Usage : hung.py <serveur> [--debug]"
478 exit(1)
479 serveur=sys.argv[1]
480 if "debug" in sys.argv or "--debug" in sys.argv:
481 debug=True
482 else:
483 debug=False
484 serveurs={"a♡":"acoeur.crans.org","acoeur":"acoeur.crans.org","acoeur.crans.org":"acoeur.crans.org",
485 "irc":"irc.crans.org","crans":"irc.crans.org","irc.crans.org":"irc.crans.org"}
486 try:
487 serveur=serveurs[serveur]
488 except KeyError:
489 print "Server Unknown : %s"%(serveur)
490 exit(404)
491 hung=Hung(serveur,debug)
492 hung.start()