]> code.delx.au - pymsnt/blob - src/jabw.py
The <host/> config option now sets the bind address for outgoing connections
[pymsnt] / src / jabw.py
1 # Copyright 2004-2005 James Bunton <james@delx.cjb.net>
2 # Licensed for distribution under the GPL version 2, check COPYING for details
3
4 import utils
5 from twisted.words.xish.domish import Element
6 from twisted.words.protocols.jabber.jid import internJID
7 from debug import LogEvent, INFO, WARN, ERROR
8 import disco
9
10
11 def sendMessage(pytrans, to, fro, body, mtype=None, delay=None):
12 """ Sends a Jabber message """
13 LogEvent(INFO)
14 el = Element((None, "message"))
15 el.attributes["to"] = to
16 el.attributes["from"] = fro
17 el.attributes["id"] = pytrans.makeMessageID()
18 if(mtype):
19 el.attributes["type"] = mtype
20
21 if(delay):
22 x = el.addElement("x")
23 x.attributes["xmlns"] = disco.XDELAY
24 x.attributes["from"] = fro
25 x.attributes["stamp"] = delay
26
27 b = el.addElement("body")
28 b.addContent(body)
29 x = el.addElement("x")
30 x.attributes["xmlns"] = disco.XEVENT
31 composing = x.addElement("composing")
32 pytrans.send(el)
33
34 def sendPresence(pytrans, to, fro, show=None, status=None, priority=None, ptype=None, avatarHash=None, nickname=None, payload=[]):
35 # Strip the resource off any presence subscribes (as per XMPP RFC 3921 Section 5.1.6)
36 # Makes eJabberd behave :)
37 if ptype in ("subscribe", "subscribed", "unsubscribe", "unsubscribed"):
38 to = internJID(to).userhost()
39 fro = internJID(fro).userhost()
40
41 el = Element((None, "presence"))
42 el.attributes["to"] = to
43 el.attributes["from"] = fro
44 if(ptype):
45 el.attributes["type"] = ptype
46 if(show):
47 s = el.addElement("show")
48 s.addContent(show)
49 if(status):
50 s = el.addElement("status")
51 s.addContent(status)
52 if(priority):
53 s = el.addElement("priority")
54 s.addContent(priority)
55
56 if(not ptype):
57 x = el.addElement("x")
58 x.attributes["xmlns"] = disco.XVCARDUPDATE
59 if(avatarHash):
60 xx = el.addElement("x")
61 xx.attributes["xmlns"] = disco.XAVATAR
62 h = xx.addElement("hash")
63 h.addContent(avatarHash)
64 h = x.addElement("photo")
65 h.addContent(avatarHash)
66 if(nickname):
67 n = x.addElement("nickname")
68 n.addContent(nickname)
69
70 if(payload):
71 for p in payload:
72 el.addChild(p)
73
74 pytrans.send(el)
75
76
77 def sendErrorMessage(pytrans, to, fro, etype, condition, explanation, body=None):
78 el = Element((None, "message"))
79 el.attributes["to"] = to
80 el.attributes["from"] = fro
81 el.attributes["type"] = "error"
82 error = el.addElement("error")
83 error.attributes["type"] = etype
84 error.attributes["code"] = str(utils.errorCodeMap[condition])
85 desc = error.addElement(condition)
86 desc.attributes["xmlns"] = disco.XMPP_STANZAS
87 text = error.addElement("text")
88 text.attributes["xmlns"] = disco.XMPP_STANZAS
89 text.addContent(explanation)
90 if(body and len(body) > 0):
91 b = el.addElement("body")
92 b.addContent(body)
93 pytrans.send(el)
94
95
96
97
98 class JabberConnection:
99 """ A class to handle a Jabber "Connection", ie, the Jabber side of the gateway.
100 If you want to send a Jabber event, this is the place, and this is where incoming
101 Jabber events for a session come to. """
102
103 def __init__(self, pytrans, jabberID):
104 self.pytrans = pytrans
105 self.jabberID = jabberID
106
107 self.typingUser = False # Whether this user can accept typing notifications
108 self.messageIDs = dict() # The ID of the last message the user sent to a particular contact. Indexed by contact JID
109
110 LogEvent(INFO, self.jabberID)
111
112 def removeMe(self):
113 """ Cleanly deletes the object """
114 LogEvent(INFO, self.jabberID)
115
116 def sendMessage(self, to, fro, body, mtype=None, delay=None):
117 """ Sends a Jabber message
118 For this message to have a <x xmlns="jabber:x:delay"/> you must pass a correctly formatted timestamp (See JEP0091)
119 """
120 LogEvent(INFO, self.jabberID)
121 sendMessage(self.pytrans, to, fro, body, mtype, delay)
122
123 def sendTypingNotification(self, to, fro, typing):
124 """ Sends the user the contact's current typing notification status """
125 if(self.typingUser):
126 LogEvent(INFO, self.jabberID)
127 el = Element((None, "message"))
128 el.attributes["to"] = to
129 el.attributes["from"] = fro
130 x = el.addElement("x")
131 x.attributes["xmlns"] = disco.XEVENT
132 if(typing):
133 composing = x.addElement("composing")
134 id = x.addElement("id")
135 if(self.messageIDs.has_key(fro) and self.messageIDs[fro]):
136 id.addContent(self.messageIDs[fro])
137 self.pytrans.send(el)
138
139 def sendVCardRequest(self, to, fro):
140 """ Requests the the vCard of 'to'
141 Returns a Deferred which fires when the vCard has been received.
142 First argument an Element object of the vCard
143 """
144 el = Element((None, "iq"))
145 el.attributes["to"] = to
146 el.attributes["from"] = fro
147 el.attributes["type"] = "get"
148 el.attributes["id"] = self.pytrans.makeMessageID()
149 vCard = el.addElement("vCard")
150 vCard.attributes["xmlns"] = "vcard-temp"
151 return self.pytrans.discovery.sendIq(el)
152
153 def sendErrorMessage(self, to, fro, etype, condition, explanation, body=None):
154 LogEvent(INFO, self.jabberID)
155 sendErrorMessage(self.pytrans, to, fro, etype, condition, explanation, body)
156
157 def sendPresence(self, to, fro, show=None, status=None, priority=None, ptype=None, avatarHash=None, nickname=None, payload=[]):
158 """ Sends a Jabber presence packet """
159 LogEvent(INFO, self.jabberID)
160 sendPresence(self.pytrans, to, fro, show, status, priority, ptype, avatarHash, nickname, payload)
161
162 def sendRosterImport(self, jid, ptype, sub, name="", groups=[]):
163 """ Sends a special presence packet. This will work with all clients, but clients that support roster-import will give a better user experience
164 IMPORTANT - Only ever use this for contacts that have already been authorised on the legacy service """
165 x = Element((None, "x"))
166 x.attributes["xmlns"] = disco.SUBSYNC
167 item = x.addElement("item")
168 item.attributes["subscription"] = sub
169 if name:
170 item.attributes["name"] = unicode(name)
171 for group in groups:
172 g = item.addElement("group")
173 g.addContent(group)
174
175 self.sendPresence(to=self.jabberID, fro=jid, ptype=ptype, payload=[x])
176
177 def onMessage(self, el):
178 """ Handles incoming message packets """
179 #LogEvent(INFO, self.jabberID)
180 fro = el.getAttribute("from")
181 to = el.getAttribute("to")
182 try:
183 froj = internJID(fro)
184 toj = internJID(to)
185 except Exception, e:
186 LogEvent(WARN, self.jabberID)
187 return
188
189 mID = el.getAttribute("id")
190 mtype = el.getAttribute("type")
191 body = ""
192 inviteTo = ""
193 inviteRoom = ""
194 messageEvent = False
195 noerror = False
196 composing = None
197 for child in el.elements():
198 if(child.name == "body"):
199 body = child.__str__()
200 elif(child.name == "noerror" and child.uri == "sapo:noerror"):
201 noerror = True
202 elif(child.name == "x"):
203 if(child.uri == disco.XCONFERENCE):
204 inviteTo = to
205 inviteRoom = child.getAttribute("jid") # The room the contact is being invited to
206 elif(child.uri == disco.MUC_USER):
207 for child2 in child.elements():
208 if(child2.name == "invite"):
209 inviteTo = child2.getAttribute("to")
210 break
211 inviteRoom = to
212 elif(child.uri == disco.XEVENT):
213 messageEvent = True
214 composing = False
215 for child2 in child.elements():
216 if(child2.name == "composing"):
217 composing = True
218 break
219
220 if(inviteTo and inviteRoom):
221 LogEvent(INFO, self.jabberID, "Message groupchat invite packet")
222 self.inviteReceived(source=froj.userhost(), resource=froj.resource, dest=inviteTo, destr="", roomjid=inviteRoom)
223 return
224
225 # Check message event stuff
226 if(body and messageEvent):
227 self.typingUser = True
228 elif(body and not messageEvent):
229 self.typingUser = False
230 elif(not body and messageEvent):
231 LogEvent(INFO, self.jabberID, "Message typing notification packet")
232 self.typingNotificationReceived(toj.userhost(), toj.resource, composing)
233
234
235 if(body):
236 # Save the message ID for later
237 self.messageIDs[to] = mID
238 LogEvent(INFO, self.jabberID, "Message packet")
239 self.messageReceived(froj.userhost(), froj.resource, toj.userhost(), toj.resource, mtype, body, noerror)
240
241 def onPresence(self, el):
242 """ Handles incoming presence packets """
243 #LogEvent(INFO, self.jabberID)
244 fro = el.getAttribute("from")
245 froj = internJID(fro)
246 to = el.getAttribute("to")
247 toj = internJID(to)
248
249 # Grab the contents of the <presence/> packet
250 ptype = el.getAttribute("type")
251 if ptype and (ptype.startswith("subscribe") or ptype.startswith("unsubscribe")):
252 LogEvent(INFO, self.jabberID, "Parsed subscription presence packet")
253 self.subscriptionReceived(fro, toj.userhost(), ptype)
254 else:
255 status = None
256 show = None
257 priority = None
258 avatarHash = ""
259 nickname = ""
260 for child in el.elements():
261 if(child.name == "status"):
262 status = child.__str__()
263 elif(child.name == "show"):
264 show = child.__str__()
265 elif(child.name == "priority"):
266 priority = child.__str__()
267 elif(child.uri == disco.XVCARDUPDATE):
268 avatarHash = " "
269 for child2 in child.elements():
270 if(child2.name == "photo"):
271 avatarHash = child2.__str__()
272 elif(child2.name == "nickname"):
273 nickname = child2.__str__()
274
275 if not ptype:
276 # available presence
277 if(avatarHash):
278 self.avatarHashReceived(froj.userhost(), toj.userhost(), avatarHash)
279 if(nickname):
280 self.nicknameReceived(froj.userhost(), toj.userhost(), nickname)
281
282 LogEvent(INFO, self.jabberID, "Parsed presence packet")
283 self.presenceReceived(froj.userhost(), froj.resource, toj.userhost(), toj.resource, priority, ptype, show, status)
284
285
286
287 def messageReceived(self, source, resource, dest, destr, mtype, body, noerror):
288 """ Override this method to be notified when a message is received """
289 pass
290
291 def inviteReceived(self, source, resource, dest, destr, roomjid):
292 """ Override this method to be notified when an invitation is received """
293 pass
294
295 def presenceReceived(self, source, resource, to, tor, priority, ptype, show, status):
296 """ Override this method to be notified when presence is received """
297 pass
298
299 def subscriptionReceived(self, source, dest, subtype):
300 """ Override this method to be notified when a subscription packet is received """
301 pass
302
303 def nicknameReceived(self, source, dest, nickname):
304 """ Override this method to be notified when a nickname has been received """
305 pass
306
307 def avatarHashReceieved(self, source, dest, avatarHash):
308 """ Override this method to be notified when an avatar hash is received """
309 pass
310
311