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