]> gitweb.pimeys.fr Git - bots/hung.git/blob - hung.py
Plantait si on lui proposait un mot en dehors d'une partie
[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","localhost":"localhost"}
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 config_leave_messages=["On finira la partie plus tard :p"]
64
65 def log(serveur,channel,auteur=None,message=None):
66 f=open(get_config_logfile(serveur),"a")
67 if auteur==message==None:
68 # alors c'est que c'est pas un channel mais juste une ligne de log
69 chain="%s %s"%(time.strftime("%F %T"),channel)
70 else:
71 chain="%s [%s:%s] %s"%(time.strftime("%F %T"),channel,auteur,message)
72 f.write(chain+"\n")
73 if config_debug_stdout:
74 print chain
75 f.close()
76
77
78 class UnicodeBotError(Exception):
79 pass
80 def bot_unicode(chain):
81 try:
82 unicode(chain,"utf8")
83 except UnicodeDecodeError as exc:
84 raise UnicodeBotError
85
86 def remplace_accents(chaine):
87 chaine=chaine.lower()
88 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"}
89 for avant,apres in remplacements.items():
90 chaine=chaine.replace(avant,apres)
91 return chaine
92
93 def is_something(chain,matches,avant=u".*(?:^| )",apres=u"(?:$|\.| |,|;).*",case_sensitive=False,debug=False):
94 if case_sensitive:
95 chain=unicode(chain,"utf8")
96 else:
97 chain=unicode(chain,"utf8").lower()
98 allmatches="("+"|".join(matches)+")"
99 reg=(avant+allmatches+apres).lower()
100 o=re.match(reg,chain)
101 return o
102
103 def is_tag(chain):
104 return is_something(chain,config_tag_triggers)
105
106 def is_mot(mot,liste):
107 real_word = "".join([lettre[0] for lettre in liste])
108 real_word = real_word.decode("utf8").lower()
109 mot=remplace_accents(mot.decode("utf8"))
110 return mot==real_word
111
112 class Hung(ircbot.SingleServerIRCBot):
113 def __init__(self,serveur,debug=False):
114 temporary_pseudo=config_irc_pseudo+str(random.randrange(10000,100000))
115 ircbot.SingleServerIRCBot.__init__(self, [(serveur, 6667)],
116 temporary_pseudo,"Bot irc pour jouer au pendu", 10)
117 self.debug=debug
118 self.serveur=serveur
119 self.overops=config_overops
120 self.ops=self.overops+config_ops
121 self.report_bugs_to=config_report_bugs_to
122 self.chanlist=config_chanlist
123 self.stay_channels=config_stay_channels
124 self.play_channels=config_play_channels
125 self.play_status={i:[None,None,None] for i in self.play_channels}
126 self.quiet_channels=config_quiet_channels
127
128
129 def give_me_my_pseudo(self,serv):
130 serv.privmsg("NickServ","RECOVER %s %s"%(config_irc_pseudo,config_irc_password))
131 serv.privmsg("NickServ","RELEASE %s %s"%(config_irc_pseudo,config_irc_password))
132 time.sleep(0.3)
133 serv.nick(config_irc_pseudo)
134
135 def on_welcome(self, serv, ev):
136 self.serv=serv # ça serv ira :)
137 self.give_me_my_pseudo(serv)
138 serv.privmsg("NickServ","IDENTIFY %s"%(config_irc_password))
139 log(self.serveur,"Connected")
140 if self.debug:
141 self.chanlist=["#bot"]
142 self.play_channels=["#bot"]
143 for c in self.chanlist:
144 log(self.serveur,"JOIN %s"%(c))
145 serv.join(c)
146
147 def pourmoi(self, serv, message):
148 """renvoie (False,lemessage) ou (True, le message amputé de "pseudo: ")"""
149 pseudo=serv.get_nickname()
150 size=len(pseudo)
151 if message[:size]==pseudo and len(message)>size and message[size]==":":
152 return (True,message[size+1:].lstrip(" "))
153 else:
154 return (False,message)
155
156 def on_privmsg(self, serv, ev):
157 message=ev.arguments()[0]
158 auteur = irclib.nm_to_n(ev.source())
159 try:
160 test=bot_unicode(message)
161 except UnicodeBotError:
162 serv.privmsg(auteur,
163 "Euh, tu fais de la merde avec ton encodage là, j'ai failli crasher…")
164 return
165 message=message.split()
166 cmd=message[0].lower()
167 notunderstood=False
168 if cmd=="join":
169 if auteur in self.ops:
170 if len(message)>1:
171 if message[1] in self.chanlist:
172 serv.privmsg(auteur,"Je suis déjà sur %s"%(message[1]))
173 else:
174 serv.join(message[1])
175 self.chanlist.append(message[1])
176 serv.privmsg(auteur,"Channels : "+" ".join(self.chanlist))
177 log(self.serveur,"priv",auteur," ".join(message))
178 else:
179 serv.privmsg(auteur,"Channels : "+" ".join(self.chanlist))
180 else:
181 notunderstood=True
182 elif cmd=="leave":
183 if auteur in self.ops and len(message)>1:
184 if message[1] in self.chanlist:
185 if not (message[1] in self.stay_channels) or auteur in self.overops:
186 self.quitter(message[1]," ".join(message[2:]))
187 self.chanlist.remove(message[1])
188 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
189 else:
190 serv.privmsg(auteur,"Non, je reste !")
191 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
192 else:
193 serv.privmsg(auteur,"Je ne suis pas sur %s"%(message[1]))
194 else:
195 notunderstood=True
196 elif cmd=="play":
197 if auteur in self.ops:
198 if len(message)>1:
199 if message[1] in self.play_channels:
200 serv.privmsg(auteur,"Je play déjà sur %s."%(message[1]))
201 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
202 else:
203 self.play_channels.append(message[1])
204 self.play_status[message[1]]=[None,None,None]
205 serv.privmsg(auteur,"Play channels : "+" ".join(self.play_channels))
206 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
207 else:
208 serv.privmsg(auteur,"Play channels : "+" ".join(self.play_channels))
209 else:
210 notunderstood=True
211 elif cmd=="noplay":
212 if auteur in self.ops:
213 if len(message)>1:
214 if message[1] in self.play_channels:
215 self.play_channels.remove(message[1])
216 serv.privmsg(auteur,"Play channels : "+" ".join(self.play_channels))
217 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
218 else:
219 serv.privmsg(auteur,"Je ne play pas sur %s."%(message[1]))
220 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
221 else:
222 notunderstood=True
223 elif cmd=="stay":
224 if auteur in self.overops:
225 if len(message)>1:
226 if message[1] in self.stay_channels:
227 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
228 serv.privmsg(auteur,"Je stay déjà sur %s."%(message[1]))
229 else:
230 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
231 self.stay_channels.append(message[1])
232 serv.privmsg(auteur,"Stay channels : "+" ".join(self.stay_channels))
233 else:
234 serv.privmsg(auteur,"Stay channels : "+" ".join(self.stay_channels))
235 else:
236 notunderstood=True
237 elif cmd=="nostay":
238 if auteur in self.overops:
239 if len(message)>1:
240 if message[1] in self.stay_channels:
241 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
242 self.stay_channels.remove(message[1])
243 serv.privmsg(auteur,"Stay channels : "+" ".join(self.stay_channels))
244 else:
245 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
246 serv.privmsg(auteur,"Je ne stay pas sur %s."%(message[1]))
247
248 else:
249 notunderstood=True
250 elif cmd in ["states","status"]:
251 if auteur in self.overops:
252 for k in self.play_status.keys():
253 if self.play_status[k]==[None,None,None]:
254 serv.privmsg(auteur,"None")
255 else:
256 serv.privmsg(auteur,"%s : %s (%s) [%s]"%(k,"".join([str(i[0]) for i in self.play_status[k][0]])
257 ,self.play_status[k][1], self.play_status[k][2]))
258 elif cmd=="die":
259 if auteur in self.overops:
260 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
261 self.mourir()
262 else:
263 notunderstood=True
264 elif cmd=="quiet":
265 if auteur in self.ops:
266 if len(message)>1:
267 if message[1] in self.quiet_channels:
268 serv.privmsg(auteur,"Je me la ferme déjà sur %s"%(message[1]))
269 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
270 else:
271 self.quiet_channels.append(message[1])
272 serv.privmsg(auteur,"Quiet channels : "+" ".join(self.quiet_channels))
273 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
274 else:
275 serv.privmsg(auteur,"Quiet channels : "+" ".join(self.quiet_channels))
276 else:
277 notunderstood=True
278 elif cmd=="noquiet":
279 if auteur in self.ops:
280 if len(message)>1:
281 if message[1] in self.quiet_channels:
282 self.quiet_channels.remove(message[1])
283 serv.privmsg(auteur,"Quiet channels : "+" ".join(self.quiet_channels))
284 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
285 else:
286 serv.privmsg(auteur,"Je ne me la ferme pas sur %s."%(message[1]))
287 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
288 else:
289 notunderstood=True
290 elif cmd=="say":
291 if auteur in self.overops and len(message)>2:
292 serv.privmsg(message[1]," ".join(message[2:]))
293 log(self.serveur,"priv",auteur," ".join(message))
294 elif len(message)<=2:
295 serv.privmsg(auteur,"Syntaxe : SAY <channel> <message>")
296 else:
297 notunderstood=True
298 elif cmd=="do":
299 if auteur in self.overops and len(message)>2:
300 serv.action(message[1]," ".join(message[2:]))
301 log(self.serveur,"priv",auteur," ".join(message))
302 elif len(message)<=2:
303 serv.privmsg(auteur,"Syntaxe : DO <channel> <action>")
304 else:
305 notunderstood=True
306 elif cmd in ["score","scores"]:
307 self.send_scores(serv,auteur)
308 else:
309 notunderstood=True
310 if notunderstood:
311 serv.privmsg(auteur,"Je n'ai pas compris. Essaye HELP…")
312
313 def affiche_mot(self, serv, canal, begin="Mot courant"):
314 if self.play_status.has_key(canal):
315 mot = self.play_status[canal][0]
316 obfuskated=" ".join([lettre[0] if lettre[1] else "_" for lettre in mot])
317 serv.privmsg(canal,"%s : %s"%(begin,obfuskated))
318
319 def start_partie(self, serv, canal):
320 mots=[mot.strip() for mot in open(config_dico_mots).readlines()]
321 defs=[defi.strip() for defi in open(config_dico_defs).readlines()]
322 indice = random.randrange(0,len(mots))
323 mot,definition=mots[indice],defs[indice]
324 # ' et - sont considérés comme déjà devinés
325 mot = [(lettre,lettre in "'-()") for lettre in list(mot)]
326 self.play_status[canal]=[mot,definition,{}]
327 self.affiche_mot(serv, canal, begin="Devinez")
328
329 def on_pubmsg(self, serv, ev):
330 auteur = irclib.nm_to_n(ev.source())
331 canal = ev.target()
332 message = ev.arguments()[0]
333 try:
334 test=bot_unicode(message)
335 except UnicodeBotError:
336 if not canal in self.quiet_channels:
337 serv.privmsg(canal,
338 "%s: Euh, tu fais de la merde avec ton encodage là, j'ai failli crasher…"%(auteur))
339 return
340 pour_moi,message=self.pourmoi(serv,message)
341 if pour_moi and message.split()!=[]:
342 cmd=message.split()[0].lower()
343 try:
344 args=" ".join(message.split()[1:])
345 except:
346 args=""
347 if cmd in ["meurs","die","crève"]:
348 if auteur in self.overops:
349 log(self.serveur,canal,auteur,message+"[successful]")
350 self.mourir()
351 else:
352 serv.privmsg(canal,"%s: crève !"%(auteur))
353 log(self.serveur,canal,auteur,message+"[failed]")
354 elif cmd in ["part","leave","dégage"]:
355 if auteur in self.ops and (not (canal in self.stay_channels)
356 or auteur in self.overops):
357 self.quitter(canal)
358 log(self.serveur,canal,auteur,message+"[successful]")
359 if canal in self.chanlist:
360 self.chanlist.remove(canal)
361 else:
362 serv.privmsg(canal,"%s: Non, je reste !"%(auteur))
363 log(self.serveur,canal,auteur,message+"[failed]")
364 elif cmd in ["play","jeu","encore","again","partie","pendu","game","mot","go","allez"]:
365 if not canal in self.quiet_channels and canal in self.play_channels:
366 if self.play_status.has_key(canal):
367 if self.play_status[canal]==[None,None,None]:
368 self.start_partie(serv, canal)
369 else:
370 self.affiche_mot(serv, canal, begin="%s: Rappel"%(auteur))
371 else:
372 self.play_status[canal]=[None,None,None]
373 self.start_partie(serv, canal)
374 elif not canal in self.play_channels:
375 serv.privmsg(canal,"%s: pas ici…"%(auteur))
376 elif (cmd in list("azertyuiopqsdfghjklmwxcvbn") and canal in self.play_channels
377 and self.play_status.has_key(canal) and self.play_status[canal]!=[None,None,None]):
378 giv_let=cmd.upper()
379 liste=self.play_status[canal][0]
380 listeapres=[(lettre[0],lettre[1] or lettre[0]==giv_let) for lettre in liste]
381 if liste!=listeapres:
382 nbtrouvees=(sum([lettre[1] for lettre in listeapres if not lettre[0] in "'-()"])
383 - sum([lettre[1] for lettre in liste if not lettre[0] in "'-()"]))
384 if self.play_status[canal][2].has_key(auteur):
385 self.play_status[canal][2][auteur]+= nbtrouvees
386 else:
387 self.play_status[canal][2][auteur] = nbtrouvees
388 self.play_status[canal][0]=listeapres
389 self.affiche_mot(serv, canal, begin="%s placé"%(giv_let))
390 if all([lettre[1] for lettre in listeapres]):
391 self.gagne(serv, canal)
392
393 elif cmd in ["score","scores","!score","!scores"]:
394 self.send_scores(serv,auteur)
395 if cmd in ["meur", "meurt","meurre","meurres"] and not canal in self.quiet_channels:
396 serv.privmsg(canal,'%s: Mourir, impératif, 2ème personne du singulier : "meurs" (de rien)'%(auteur))
397 if is_tag(message) and not canal in self.quiet_channels:
398 if auteur in self.ops:
399 action=random.choice(config_tag_actions)
400 serv.action(canal,action.encode("utf8"))
401 self.quiet_channels.append(canal)
402 else:
403 answer=random.choice(config_tag_answers)
404 for ligne in answer.split("\n"):
405 serv.privmsg(canal,"%s: %s"%(auteur,ligne.encode("utf8")))
406 # on essaye de voir si le mot fourni matche la partie en cours
407 mot = cmd
408 if self.play_status[canal][0]!=None and is_mot(mot, self.play_status[canal][0]):
409 # on a trouvé le mot
410 # on regarde combien de lettre il manquait
411 manquait = sum([not lettre[1] for lettre in self.play_status[canal][0]])
412 self.add_score({auteur: manquait})
413 if self.play_status[canal][2].has_key(auteur):
414 self.play_status[canal][2][auteur]+=manquait
415 else:
416 self.play_status[canal][2][auteur]=manquait
417 self.gagne(serv, canal, bonus=auteur, bonusvalue=manquait)
418 else:
419 pass
420
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 serv.privmsg(channel,
430 "%s : Euh, tu fais de la merde avec ton encodage là, j'ai failli crasher…"%(auteur))
431 return
432 mypseudo=serv.get_nickname()
433
434 def quitter(self,chan,leave_message=None):
435 if leave_message==None:
436 leave_message=random.choice(config_leave_messages)
437 self.serv.part(chan,message=leave_message.encode("utf8"))
438
439 def mourir(self):
440 quit_message=random.choice(config_quit_messages)
441 self.die(msg=quit_message.encode("utf8"))
442
443 def get_scores(self):
444 f=open(config_scores_file)
445 scores=pickle.load(f)
446 f.close()
447 return scores
448 def save_scores(self,scores):
449 f=open(config_scores_file,'w')
450 pickle.dump(scores,f)
451 f.close()
452 def add_score(self,dico):
453 scores=self.get_scores()
454 for k,v in dico.items():
455 if scores.has_key(k):
456 scores[k]+=v
457 else:
458 scores[k]=v
459 self.save_scores(scores)
460 def send_scores(self, serv, destinataire):
461 scores=self.get_scores()
462 scores=scores.items()
463 scores.sort(lambda x,y:cmp(x[1],y[1]))
464 scores.reverse()
465 serv.privmsg(destinataire,"Scores by score : "+" ; ".join(["%s %s"%(k,v) for (k,v) in scores]) )
466 scores.sort(lambda x,y:cmp(x[0].lower(),y[0].lower()))
467 serv.privmsg(destinataire,"Scores by pseudo : "+" ; ".join(["%s %s"%(k,v) for (k,v) in scores]) )
468
469 def gagne(self, serv, canal, bonus=None, bonusvalue=2):
470 realword="".join([lettre[0] for lettre in self.play_status[canal][0]])
471 definition = self.play_status[canal][1]
472 serv.privmsg(canal,"Bravo ! C'était %s"%(realword))
473 serv.privmsg(canal,definition)
474 nlettre=float(len(realword.replace("'","").replace("-","")))
475 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()]
476 contribs_score={pseudo:int(10*contrib/nlettre) for pseudo,contrib in self.play_status[canal][2].items()}
477 self.add_score(contribs_score)
478 serv.privmsg(canal,"Contributions : %s"%(" ".join(contribs)) )
479 self.play_status[canal]=[None,None,None]
480
481
482 if __name__=="__main__":
483 import sys
484 if len(sys.argv)==1:
485 print "Usage : hung.py <serveur> [--debug]"
486 exit(1)
487 serveur=sys.argv[1]
488 if "debug" in sys.argv or "--debug" in sys.argv:
489 debug=True
490 else:
491 debug=False
492 serveurs={"a♡":"acoeur.crans.org","acoeur":"acoeur.crans.org","acoeur.crans.org":"acoeur.crans.org",
493 "irc":"irc.crans.org","crans":"irc.crans.org","irc.crans.org":"irc.crans.org",
494 "localhost":"localhost"}
495 try:
496 serveur=serveurs[serveur]
497 except KeyError:
498 print "Server Unknown : %s"%(serveur)
499 exit(404)
500 hung=Hung(serveur,debug)
501 hung.start()