2 # -*- coding: utf-8 -*-
4 Robot pour offrir des services via jabber.
6 Copyright : Agence universitaire de la Francophonie
7 Licence : GNU General Public Licence, version 2
8 Auteur : Jean Christophe André
9 Date de création : 22 juin 2008
12 import sys
, getopt
, xmpp
14 from xml
.dom
import minidom
16 DEMLOG_URL
="http://bacasable.auf/rest/demlog/%s"
17 BUDGET_URL
="http://intranet.auf/reflets-web/budget/?pp=%s"
18 PHONE_URL
="http://intranet.auf/intranet_annuaire_telephonique.php3"
21 xmlfile
= urllib
.urlopen(url
)
23 if not xml
.startswith('<?xml '):
24 typeheader
= xmlfile
.headers
.typeheader
25 if typeheader
and (typeheader
.find('=') >= 0):
26 (junk
,charset
) = typeheader
.rsplit('=',1)
29 xml
= ('<?xml version="1.0" encoding="%s"?>\n' % charset
) + xml
33 def _xmltojab_r(node
, prefix
= ''):
34 if node
.nodeType
== node
.TEXT_NODE
:
35 result
= node
.nodeValue
.strip().replace('\n', '\\n')
36 if result
<> '': result
= prefix
+ result
+ '\n'
38 elif node
.nodeType
== node
.ELEMENT_NODE
:
39 prefix
+= node
.nodeName
+ ' : '
40 if node
.nodeValue
<> None:
41 value
= node
.nodeValue
.strip().replace('\n', '\\n')
42 if value
<> '': value
= prefix
+ value
+ '\n'
46 for child
in node
.childNodes
:
47 result
+= _xmltojab_r(child
, prefix
)
50 return prefix
+ u
'Type de noeud inconnu...\n'
52 def xmltojab(xml
, startwith
=''):
54 doc
= minidom
.parseString(xml
)
59 for node
in doc
.getElementsByTagName(startwith
):
60 nodes
+= node
.childNodes
62 nodes
= doc
.documentElement
.childNodes
65 result
+= _xmltojab_r(node
)
68 def _phonefind(xhtml
, nom
):
70 xmldoc
= minidom
.parseString(xhtml
)
73 # recherche de la division contenant les infos
75 for div
in xmldoc
.getElementsByTagName('div'):
76 if div
.hasAttribute('id') and div
.getAttribute('id') == 'main_content':
79 if main_content
== None: return ''
80 # parcours des infos pour trouver le nom demandé
83 for tr
in main_content
.getElementsByTagName('tr'):
84 tds
= tr
.getElementsByTagName('td')
86 # FIXME: gérer les futurs changements de structure !!
87 nom_complet
= tds
[0].firstChild
.firstChild
.nodeValue
88 if nom_complet
.lower().find(nom
) >= 0:
89 result
+= nom_complet
+ ':'
90 telpub
= tds
[1].firstChild
92 result
+= u
" %s (public)" % telpub
.nodeValue
93 telip
= tds
[2].firstChild
95 result
+= u
" %s (VoIP)" % telip
.nodeValue
100 _jid
= _password
= ''
101 _admins
= ['jean-christophe.andre@auf.org', 'doan.manh.ha@auf.org', 'thomas.noel@auf.org']
104 _quitter_message
= 'Au revoir !'
106 def __init__(self
, jid
, password
):
107 self
._jid
= xmpp
.JID(jid
)
108 self
._password
= password
109 if self
._jid
.getResource() == '':
110 self
._jid
.setResource('botap')
112 self
._client
= xmpp
.Client(self
._jid
.getDomain(), debug
=[])
114 if not self
._client
.connect():
115 raise IOError, u
"can't connect to '%s'" % self
._jid
.getDomain()
117 raise IOError, u
"can't connect to '%s'" % self
._jid
.getDomain()
118 self
._client
.RegisterHandler('presence', self
._presenceHandler
)
119 self
._client
.RegisterHandler('message', self
._messageHandler
)
120 self
._client
.RegisterHandler('iq', self
._iqHandler
)
121 # self._client.UnregisterDisconnectHandler(self._client.DisconnectHandler)
123 auth
= self
._client
.auth(self
._jid
.getNode(), self
._password
, self
._jid
.getResource())
125 raise IOError, u
"unable to authenticate '%s'" % self
._jid
.getNode()
126 self
._client
.sendPresence(jid
=self
._jid
.getDomain())
127 if self
._jid
.getDomain() == 'auf.org':
128 self
.cmd_join('test@reunion.auf.org/' + self
._jid
.getResource())
129 #self.cmd_join('tap@reunion.auf.org/' + self._jid.getResource())
130 if self
._jid
.getDomain() == 'net127':
131 self
.cmd_join('test@conf.net127/' + self
._jid
.getResource())
134 if not self
._client
.isConnected(): return
135 if self
._jid
.getDomain() == 'auf.org':
136 self
.cmd_part('tap@reunion.auf.org/' + self
._jid
.getResource())
137 self
.cmd_part('test@reunion.auf.org/' + self
._jid
.getResource())
138 if self
._jid
.getDomain() == 'net127':
139 self
.cmd_part('test@conf.net127/' + self
._jid
.getResource())
140 self
._client
.sendPresence(typ
='unavailable')
141 self
._client
.disconnect()
143 def _publicReply(self
, message
, text
):
144 reply
= message
.buildReply(text
)
145 if message
.getType() == 'groupchat':
146 reply
.setType('groupchat')
147 reply
.getTo().setResource('')
148 self
._client
.send(reply
)
149 print u
"--> reply=[%s]" % reply
151 def _presenceHandler(self
, client
, presence
):
152 print u
"<-- presence=[%s]" % presence
153 # gestion de notre propre présence
154 if presence
.getTo() <> self
._jid
: return
155 # confirmation de l'entrée dans un salon
156 if presence
.getFrom() in self
._salons
:
157 self
._salons
[presence
.getFrom()]['joined'] = True
158 self
._client
.send(xmpp
.Message(to
=presence
.getFrom().getStripped(),typ
='groupchat',body
='Bonjour tout le monde !'))
160 def _messageHandler(self
, client
, message
):
161 print u
"<-- message=[%s]" % message
162 jid
= message
.getFrom()
163 text
= message
.getBody()
164 # FIXME: on ne traite pas les messages d'erreur (pour le moment)
165 if message
.getType() == 'error': return
166 # FIXME: on ne traite pas les messages vides de texte (pour le moment)
167 # FIXME: du coup on rate les invitations, au moins...
168 if text
== None: return
169 # on ne traite pas les messages qui viennent de nous-même (sinon boucle)
170 if (jid
== self
._jid
) or (jid
in self
._salons
): return
171 # on ne traite pas les messages des salons hormis les commandes explicites
172 if (message
.getType() == 'groupchat') and not text
.startswith('!'): return
173 print u
"... on traite..."
174 if text
.startswith('!'): text
= text
[1:]
175 if text
.find(' ') >= 0:
176 command
,args
= text
.split(' ',1)
178 command
,args
= text
,''
179 if command
== 'aide':
180 reply
= """Aide de %s :
182 dire <texte> répète ce texte en public
183 inviter <jid> invite ce contact dans le salon en cours (cassé)
184 entrer <salon> entre dans un salon
185 sortir <salon> sort du salon en cours
186 demlog <numéro> affiche les données de cette DEMLOG
187 tel <nom> affiche le(s) numéro(s) de téléphone""" % self
._jid
188 self
._publicReply(message
, reply
)
189 elif command
== 'dire':
191 self
._publicReply(message
, "Dire quoi ?")
193 # est-ce une commande publique ?
194 if message
.getType() == 'groupchat':
195 reply
= message
.buildReply(args
)
196 reply
.setType('groupchat')
197 reply
.getTo().setResource('')
198 self
._client
.send(reply
)
199 # est-ce qu'on parle au bot via le contact d'un salon ?
200 elif message
.getTo() in self
._salons
:
201 reply
= message
.buildReply(args
)
202 reply
.setTo(message
.getTo().getStripped())
203 self
._client
.send(reply
)
204 # envoyer sur tous les salons... gasp!
206 reply
= message
.buildReply(args
)
207 reply
.setType('groupchat')
208 for salon
in self
._salons
.keys():
209 reply
.setTo(xmpp
.JID(salon
).getStripped())
210 self
._client
.send(reply
)
211 # envoi à une destination précise : trouver à qui on veut parler
212 #if (args.find('@') >= 0) and (args.find('@') < args.find(' ')):
213 # say_jid,args = args.split(' ',1)
214 # reply = message.buildReply(args)
215 # reply.setTo(say_jid)
216 elif command
== 'inviter':
218 self
._publicReply(message
, "Inviter qui ?")
220 for un_jid
in args
.split(' '):
221 self
.cmd_invite(un_jid
, jid
.getStripped())
222 elif command
== 'entrer':
224 self
._publicReply(message
, "Entrer dans quel salon ?")
226 self
.cmd_join(args
+ '/' + self
._jid
.getResource())
227 elif command
== 'sortir':
228 if args
== '' and (message
.getType() == "groupchat") :
229 args
= jid
.getStripped()
231 self
._publicReply(message
, "Sortir de quel salon ?")
233 self
.cmd_part(args
+ '/' + self
._jid
.getResource())
234 elif command
== u
'numéro':
235 self
._publicReply(message
, """C'est une référence à la série culte « Le Prisonnier ».\nvoir http://fr.wikipedia.org/wiki/Le_Prisonnier""")
236 elif command
== 'quitter':
237 if jid
.getStripped() == self
._jid
.getStripped():
238 self
._publicReply(message
, "D'accord, au revoir !")
239 if args
<> '': self
._quitter_message
= args
242 self
._publicReply(message
, u
"Commande '%s' non autorisée." % command
)
243 #elif command == 'budget':
245 # xml = urllib.urlopen(BUDGET_URL % args).read()
246 # doc = minidom.parseString(xml).documentElement
247 # reply = u"Budget pour %s :" % args
248 # for node in doc.childNodes:
249 # if node.nodeType == node.ELEMENT_NODE:
250 # for value in [n.wholeText for n in node.childNodes]:
251 # reply += '\n%s : %s' % (node.nodeName, value)
253 # reply = u"Erreur pour %s." % args
254 # self._publicReply(message, reply)
255 elif command
== 'demlog':
257 reply
= u
"Quelle numéro de DEMLOG ?"
259 demlog
= xmlurlopen(DEMLOG_URL
% args
)
260 reply
= xmltojab(demlog
, 'demlog').rstrip()
262 reply
= (u
"Données pour la DEMLOG %s :\n" % args
) + reply
264 reply
= u
"Erreur pour la DEMLOG %s." % args
265 self
._publicReply(message
, reply
)
266 elif command
== 'tel':
268 reply
= u
"Téléphone de qui ?"
270 reply
= _phonefind(xmlurlopen(PHONE_URL
), args
).rstrip()
272 reply
= u
"Recherche de téléphone pour '%s' :\n%s" % (args
, reply
)
274 reply
= u
"Pas de téléphone trouvé pour '%s'." % args
275 self
._publicReply(message
, reply
)
277 self
._publicReply(message
, "Commande '%s' inconnue." % command
)
279 def _iqHandler(self
, client
, iq
):
280 print u
"<-- iq=[%s]" % iq
282 def cmd_join(self
, room
, password
=''):
283 #if self._salons.has_key(room) and self._salons[room]['joined']: return
284 self
._salons
[room
] = { 'joined': False, 'password': password
}
285 p
= xmpp
.Presence(to
=room
, priority
='0', show
='available', status
="Je ne suis pas un numéro, je suis un bot libre !")
286 x
= p
.setTag('x', namespace
=xmpp
.NS_MUC
)
287 if password
<> '': x
.setTagData('password', password
)
288 x
.addChild('history', {'maxchars':'0','maxstanzas':'0'})
291 def cmd_part(self
, room
):
292 p
= xmpp
.Presence(to
=room
, typ
='unavailable')
293 p
.setTag('x', namespace
=xmpp
.NS_MUC
)
296 def cmd_invite(self
, jid
, room
):
297 m
= xmpp
.Message(to
=jid
,typ
='normal',frm
=room
)
298 x
= m
.setTag('x', namespace
=xmpp
.NS_MUC_USER
)
299 invite
= x
.addChild('invite', {'from':self
._jid
})
300 reason
= invite
.addChild('reason')
301 reason
.addData(u
"Vous êtes invité sur '%s'." % room
)
302 m
.setTag('x', {'jid':room
}, 'jabber:x:conference')
303 print "DEBUG: m=[%s]" % m
306 def admins(self
, new_admins
=False):
307 old_admins
= self
._admins
308 if new_admins
: self
.admins
= new_admins
312 while (not self
._quitter
):
314 self
._client
.Process(1)
315 except KeyboardInterrupt:
317 self
._client
.send(xmpp
.Message(to
='test@reunion.auf.org', body
=self
._quitter_message
, typ
='groupchat'))
319 if __name__
== "__main__":
321 print "Usage: %s username@domain.tld password" % __file__
324 bot
= Bot(jid
=sys
.argv
[1], password
=sys
.argv
[2])
326 print "ERROR: %s" % msg