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