]> gitweb.pimeys.fr Git - bots/hung.git/blob - hung.py
Des quit_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
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 quit_message=random.choice(config_quit_messages)
254 self.die(message=config_quit_message)
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.die()
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 serv.part(canal,message="Éjecté par %s"%(auteur))
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 get_scores(self):
433 f=open(config_scores_file)
434 scores=pickle.load(f)
435 f.close()
436 return scores
437 def save_scores(self,scores):
438 f=open(config_scores_file,'w')
439 pickle.dump(scores,f)
440 f.close()
441 def add_score(self,dico):
442 scores=self.get_scores()
443 for k,v in dico.items():
444 if scores.has_key(k):
445 scores[k]+=v
446 else:
447 scores[k]=v
448 self.save_scores(scores)
449 def send_scores(self, serv, destinataire):
450 scores=self.get_scores()
451 scores=scores.items()
452 scores.sort(lambda x,y:cmp(x[1],y[1]))
453 scores.reverse()
454 serv.privmsg(destinataire,"Scores by score : "+" ; ".join(["%s %s"%(k,v) for (k,v) in scores]) )
455 scores.sort(lambda x,y:cmp(x[0].lower(),y[0].lower()))
456 serv.privmsg(destinataire,"Scores by pseudo : "+" ; ".join(["%s %s"%(k,v) for (k,v) in scores]) )
457
458 def gagne(self, serv, canal, bonus=None, bonusvalue=2):
459 realword="".join([lettre[0] for lettre in self.play_status[canal][0]])
460 definition = self.play_status[canal][1]
461 serv.privmsg(canal,"Bravo ! C'était %s"%(realword))
462 serv.privmsg(canal,definition)
463 nlettre=float(len(realword.replace("'","").replace("-","")))
464 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()]
465 contribs_score={pseudo:int(10*contrib/nlettre) for pseudo,contrib in self.play_status[canal][2].items()}
466 self.add_score(contribs_score)
467 serv.privmsg(canal,"Contributions : %s"%(" ".join(contribs)) )
468 self.play_status[canal]=[None,None,None]
469
470
471 if __name__=="__main__":
472 import sys
473 if len(sys.argv)==1:
474 print "Usage : hung.py <serveur> [--debug]"
475 exit(1)
476 serveur=sys.argv[1]
477 if "debug" in sys.argv or "--debug" in sys.argv:
478 debug=True
479 else:
480 debug=False
481 serveurs={"a♡":"acoeur.crans.org","acoeur":"acoeur.crans.org","acoeur.crans.org":"acoeur.crans.org",
482 "irc":"irc.crans.org","crans":"irc.crans.org","irc.crans.org":"irc.crans.org"}
483 try:
484 serveur=serveurs[serveur]
485 except KeyError:
486 print "Server Unknown : %s"%(serveur)
487 exit(404)
488 hung=Hung(serveur,debug)
489 hung.start()