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