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
, urllib
13 from xml
.dom
import minidom
15 DEMLOG_URL
="http://bacasable.auf/rest/demlog/%s"
16 BUDGET_URL
="http://intranet.auf/reflets-web/budget/?pp=%s"
17 PHONE_URL
="http://intranet.auf/intranet_annuaire_telephonique.php3"
20 xmlfile
= urllib
.urlopen(url
)
22 if not xml
.startswith('<?xml '):
23 typeheader
= xmlfile
.headers
.typeheader
24 if typeheader
and (typeheader
.find('=') >= 0):
25 (junk
,charset
) = typeheader
.rsplit('=',1)
28 xml
= ('<?xml version="1.0" encoding="%s"?>\n' % charset
) + xml
32 def _xmltojab_r(node
, prefix
= ''):
33 if node
.nodeType
== node
.TEXT_NODE
:
34 result
= node
.nodeValue
.strip().replace('\n', '\\n')
35 if result
<> '': result
= prefix
+ result
+ '\n'
37 elif node
.nodeType
== node
.ELEMENT_NODE
:
38 prefix
+= node
.nodeName
+ ' : '
39 if node
.nodeValue
<> None:
40 value
= node
.nodeValue
.strip().replace('\n', '\\n')
41 if value
<> '': value
= prefix
+ value
+ '\n'
45 for child
in node
.childNodes
:
46 result
+= _xmltojab_r(child
, prefix
)
49 return prefix
+ u
'Type de noeud inconnu...\n'
51 def xmltojab(xml
, startwith
=''):
53 doc
= minidom
.parseString(xml
)
58 for node
in doc
.getElementsByTagName(startwith
):
59 nodes
+= node
.childNodes
61 nodes
= doc
.documentElement
.childNodes
64 result
+= _xmltojab_r(node
)
67 def _phonefind(xhtml
, nom
):
69 xmldoc
= minidom
.parseString(xhtml
)
72 # recherche de la division contenant les infos
74 for div
in xmldoc
.getElementsByTagName('div'):
75 if div
.hasAttribute('id') and div
.getAttribute('id') == 'main_content':
78 if main_content
== None: return ''
79 # parcours des infos pour trouver le nom demandé
82 for tr
in main_content
.getElementsByTagName('tr'):
83 tds
= tr
.getElementsByTagName('td')
85 # FIXME: gérer les futurs changements de structure !!
86 nom_complet
= tds
[0].firstChild
.firstChild
.nodeValue
87 if nom_complet
.lower().find(nom
) >= 0:
88 result
+= nom_complet
+ ':'
89 telpub
= tds
[1].firstChild
91 result
+= u
" %s (public)" % telpub
.nodeValue
92 telip
= tds
[2].firstChild
94 result
+= u
" %s (VoIP)" % telip
.nodeValue
100 _admins
= ['jean-christophe.andre@auf.org', 'doan.manh.ha@auf.org', 'thomas.noel@auf.org']
103 _quitter_message
= 'Au revoir !'
105 def __init__(self
, jid
, password
):
106 self
._jid
= xmpp
.JID(jid
)
107 self
._password
= password
108 if self
._jid
.getResource() == '':
109 self
._jid
.setResource('jabbot')
111 self
._client
= xmpp
.Client(self
._jid
.getDomain(), debug
=[])
113 if not self
._client
.connect():
114 raise IOError, u
"can't connect to '%s'" % self
._jid
.getDomain()
116 raise IOError, u
"can't connect to '%s'" % self
._jid
.getDomain()
117 self
._client
.RegisterHandler('presence', self
._presenceHandler
)
118 self
._client
.RegisterHandler('message', self
._messageHandler
)
119 self
._client
.RegisterHandler('iq', self
._iqHandler
)
120 # self._client.UnregisterDisconnectHandler(self._client.DisconnectHandler)
122 auth
= self
._client
.auth(self
._jid
.getNode(), self
._password
, self
._jid
.getResource())
124 raise IOError, u
"unable to authenticate '%s'" % self
._jid
.getNode()
125 self
._client
.sendPresence(jid
=self
._jid
.getDomain())
126 if self
._jid
.getDomain() == 'auf.org':
127 self
.cmd_join('test@reunion.auf.org/' + self
._jid
.getResource())
128 #self.cmd_join('tap@reunion.auf.org/' + self._jid.getResource())
129 if self
._jid
.getDomain() == 'net127':
130 self
.cmd_join('test@conf.net127/' + self
._jid
.getResource())
133 if not self
._client
.isConnected(): return
134 if self
._jid
.getDomain() == 'auf.org':
135 self
.cmd_part('tap@reunion.auf.org/' + self
._jid
.getResource())
136 self
.cmd_part('test@reunion.auf.org/' + self
._jid
.getResource())
137 if self
._jid
.getDomain() == 'net127':
138 self
.cmd_part('test@conf.net127/' + self
._jid
.getResource())
139 self
._client
.sendPresence(typ
='unavailable')
140 self
._client
.disconnect()
142 def _publicReply(self
, message
, text
):
143 reply
= message
.buildReply(text
)
144 if message
.getType() == 'groupchat':
145 reply
.setType('groupchat')
146 reply
.getTo().setResource('')
147 self
._client
.send(reply
)
148 print u
"--> reply=[%s]" % reply
150 def _presenceHandler(self
, client
, presence
):
151 print u
"<-- presence=[%s]" % presence
152 # gestion de notre propre présence
153 if presence
.getTo() <> self
._jid
: return
154 # confirmation de l'entrée dans un salon
155 if presence
.getFrom() in self
._salons
:
156 self
._salons
[presence
.getFrom()]['joined'] = True
157 self
._client
.send(xmpp
.Message(to
=presence
.getFrom().getStripped(),typ
='groupchat',body
='Bonjour tout le monde !'))
159 def _messageHandler(self
, client
, message
):
160 print u
"<-- message=[%s]" % message
161 jid
= message
.getFrom()
162 text
= message
.getBody()
163 # FIXME: on ne traite pas les messages d'erreur (pour le moment)
164 if message
.getType() == 'error': return
165 # FIXME: on ne traite pas les messages vides de texte (pour le moment)
166 # FIXME: du coup on rate les invitations, au moins...
167 if text
== None: return
168 # on ne traite pas les messages qui viennent de nous-même (sinon boucle)
169 if (jid
== self
._jid
) or (jid
in self
._salons
): return
170 # on ne traite pas les messages des salons hormis les commandes explicites
171 if (message
.getType() == 'groupchat') and not text
.startswith('!'): return
172 print u
"... on traite..."
173 if text
.startswith('!'): text
= text
[1:]
174 if text
.find(' ') >= 0:
175 command
,args
= text
.split(' ',1)
177 command
,args
= text
,''
178 if command
== 'aide':
179 reply
= """Aide de %s :
181 dire <texte> répète ce texte en public
182 inviter <jid> invite ce contact dans le salon en cours (cassé)
183 entrer <salon> entre dans un salon
184 sortir <salon> sort du salon en cours
185 demlog <numéro> affiche les données de cette DEMLOG
186 tel <nom> affiche le(s) numéro(s) de téléphone""" % self
._jid
187 self
._publicReply(message
, reply
)
188 elif command
== 'dire':
190 self
._publicReply(message
, "Dire quoi ?")
192 # est-ce une commande publique ?
193 if message
.getType() == 'groupchat':
194 reply
= message
.buildReply(args
)
195 reply
.setType('groupchat')
196 reply
.getTo().setResource('')
197 self
._client
.send(reply
)
198 # est-ce qu'on parle au bot via le contact d'un salon ?
199 elif message
.getTo() in self
._salons
:
200 reply
= message
.buildReply(args
)
201 reply
.setTo(message
.getTo().getStripped())
202 self
._client
.send(reply
)
203 # envoyer sur tous les salons... gasp!
205 reply
= message
.buildReply(args
)
206 reply
.setType('groupchat')
207 for salon
in self
._salons
.keys():
208 reply
.setTo(xmpp
.JID(salon
).getStripped())
209 self
._client
.send(reply
)
210 # envoi à une destination précise : trouver à qui on veut parler
211 #if (args.find('@') >= 0) and (args.find('@') < args.find(' ')):
212 # say_jid,args = args.split(' ',1)
213 # reply = message.buildReply(args)
214 # reply.setTo(say_jid)
215 elif command
== 'inviter':
217 self
._publicReply(message
, "Inviter qui ?")
219 for un_jid
in args
.split(' '):
220 self
.cmd_invite(un_jid
, jid
.getStripped())
221 elif command
== 'entrer':
223 self
._publicReply(message
, "Entrer dans quel salon ?")
225 self
.cmd_join(args
+ '/' + self
._jid
.getResource())
226 elif command
== 'sortir':
227 if args
== '' and (message
.getType() == "groupchat") :
228 args
= jid
.getStripped()
230 self
._publicReply(message
, "Sortir de quel salon ?")
232 self
.cmd_part(args
+ '/' + self
._jid
.getResource())
233 elif command
== u
'numéro':
234 self
._publicReply(message
, """C'est une référence à la série culte « Le Prisonnier ».\nvoir http://fr.wikipedia.org/wiki/Le_Prisonnier""")
235 elif command
== 'quitter':
236 if jid
.getStripped() == self
._jid
.getStripped():
237 self
._publicReply(message
, "D'accord, au revoir !")
238 if args
<> '': self
._quitter_message
= args
241 self
._publicReply(message
, u
"Commande '%s' non autorisée." % command
)
242 #elif command == 'budget':
244 # xml = urllib.urlopen(BUDGET_URL % args).read()
245 # doc = minidom.parseString(xml).documentElement
246 # reply = u"Budget pour %s :" % args
247 # for node in doc.childNodes:
248 # if node.nodeType == node.ELEMENT_NODE:
249 # for value in [n.wholeText for n in node.childNodes]:
250 # reply += '\n%s : %s' % (node.nodeName, value)
252 # reply = u"Erreur pour %s." % args
253 # self._publicReply(message, reply)
254 elif command
== 'demlog':
256 reply
= u
"Quelle numéro de DEMLOG ?"
258 demlog
= xmlurlopen(DEMLOG_URL
% args
)
259 reply
= xmltojab(demlog
, 'demlog').rstrip()
261 reply
= (u
"Données pour la DEMLOG %s :\n" % args
) + reply
263 reply
= u
"Erreur pour la DEMLOG %s." % args
264 self
._publicReply(message
, reply
)
265 elif command
== 'tel':
267 reply
= u
"Téléphone de qui ?"
269 reply
= _phonefind(xmlurlopen(PHONE_URL
), args
).rstrip()
271 reply
= u
"Recherche de téléphone pour '%s' :\n%s" % (args
, reply
)
273 reply
= u
"Pas de téléphone trouvé pour '%s'." % args
274 self
._publicReply(message
, reply
)
276 self
._publicReply(message
, "Commande '%s' inconnue." % command
)
278 def _iqHandler(self
, client
, iq
):
279 print u
"<-- iq=[%s]" % iq
281 def cmd_join(self
, room
, password
=''):
282 #if self._salons.has_key(room) and self._salons[room]['joined']: return
283 self
._salons
[room
] = { 'joined': False, 'password': password
}
284 p
= xmpp
.Presence(to
=room
, priority
='0', show
='available', status
="Je ne suis pas un numéro, je suis un bot libre !")
285 x
= p
.setTag('x', namespace
=xmpp
.NS_MUC
)
286 if password
<> '': x
.setTagData('password', password
)
287 x
.addChild('history', {'maxchars':'0','maxstanzas':'0'})
290 def cmd_part(self
, room
):
291 p
= xmpp
.Presence(to
=room
, typ
='unavailable')
292 p
.setTag('x', namespace
=xmpp
.NS_MUC
)
295 def cmd_invite(self
, jid
, room
):
296 m
= xmpp
.Message(to
=jid
,typ
='normal',frm
=room
)
297 x
= m
.setTag('x', namespace
=xmpp
.NS_MUC_USER
)
298 invite
= x
.addChild('invite', {'from':self
._jid
})
299 reason
= invite
.addChild('reason')
300 reason
.addData(u
"Vous êtes invité sur '%s'." % room
)
301 m
.setTag('x', {'jid':room
}, 'jabber:x:conference')
302 print "DEBUG: m=[%s]" % m
305 def admins(self
, new_admins
=False):
306 old_admins
= self
._admins
307 if new_admins
: self
.admins
= new_admins
311 while (not self
._quitter
):
313 self
._client
.Process(1)
314 except KeyboardInterrupt:
316 self
._client
.send(xmpp
.Message(to
='test@reunion.auf.org', body
=self
._quitter_message
, typ
='groupchat'))
318 if __name__
== '__main__':
319 if len(sys
.argv
) != 3:
320 print "Usage: %s jid password" % __file__
323 bot
= Bot(jid
=sys
.argv
[1], password
=sys
.argv
[2])
325 print "ERROR: %s" % msg