]> code.delx.au - pymsnt/blob - src/legacy/glue.py
Initial file transfer work
[pymsnt] / src / legacy / glue.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.internet import task
6 if utils.checkTwisted():
7 from twisted.xish.domish import Element
8 else:
9 from tlib.domish import Element
10 from tlib import msn, msnp2p
11 from debug import LogEvent, INFO, WARN, ERROR
12 import sha
13 import groupchat
14 import ft
15 import avatar
16 import msnw
17 import config
18 import lang
19
20
21
22
23 name = "MSN Transport" # The name of the transport
24 url = "http://msn-transport.jabberstudio.org"
25 version = "0.11-dev" # The transport version
26 mangle = True # XDB '@' -> '%' mangling
27 id = "msn" # The transport identifier
28
29
30 # Load the default avatar
31 f = open("src/legacy/defaultJabberAvatar.png")
32 defaultJabberAvatarData = f.read()
33 f.close()
34
35
36 def isGroupJID(jid):
37 """ Returns True if the JID passed is a valid groupchat JID (for MSN, does not contain '%') """
38 return (jid.find('%') == -1)
39
40
41
42 # This should be set to the name space the registration entries are in, in the xdb spool
43 namespace = "jabber:iq:register"
44
45
46 def formRegEntry(username, password):
47 """ Returns a domish.Element representation of the data passed. This element will be written to the XDB spool file """
48 reginfo = Element((None, "query"))
49 reginfo.attributes["xmlns"] = "jabber:iq:register"
50
51 userEl = reginfo.addElement("username")
52 userEl.addContent(username)
53
54 passEl = reginfo.addElement("password")
55 passEl.addContent(password)
56
57 return reginfo
58
59
60
61
62 def getAttributes(base):
63 """ This function should, given a spool domish.Element, pull the username, password,
64 and out of it and return them """
65 username = ""
66 password = ""
67 for child in base.elements():
68 try:
69 if child.name == "username":
70 username = child.__str__()
71 elif child.name == "password":
72 password = child.__str__()
73 except AttributeError:
74 continue
75
76 return username, password
77
78
79 def startStats(statistics):
80 stats = statistics.stats
81 stats["MessageCount"] = 0
82 stats["FailedMessageCount"] = 0
83 stats["AvatarCount"] = 0
84 stats["FailedAvatarCount"] = 0
85
86 def updateStats(statistics):
87 stats = statistics.stats
88 stats["AvatarCount"] = msnp2p.MSNP2P_Avatar.TRANSFER_COUNT
89 stats["FailedAvatarCount"] = msnp2p.MSNP2P_Avatar.ERROR_COUNT
90
91
92 def msn2jid(msnid):
93 """ Converts a MSN passport into a JID representation to be used with the transport """
94 return msnid.replace('@', '%') + "@" + config.jid
95
96 translateAccount = msn2jid # Marks this as the function to be used in jabber:iq:gateway (Service ID Translation)
97
98 def jid2msn(jid):
99 """ Converts a JID representation of a MSN passport into the original MSN passport """
100 return unicode(jid[:jid.find('@')].replace('%', '@'))
101
102
103 def presence2state(show, ptype):
104 """ Converts a Jabber presence into an MSN status code """
105 if ptype == "unavailable":
106 return msn.STATUS_OFFLINE
107 elif not show or show == "online" or show == "chat":
108 return msn.STATUS_ONLINE
109 elif show == "dnd":
110 return msn.STATUS_BUSY
111 elif show == "away" or show == "xa":
112 return msn.STATUS_AWAY
113
114
115 def state2presence(state):
116 """ Converts a MSN status code into a Jabber presence """
117 if state == msn.STATUS_ONLINE:
118 return (None, None)
119 elif state == msn.STATUS_BUSY:
120 return ("dnd", None)
121 elif state == msn.STATUS_AWAY:
122 return ("away", None)
123 elif state == msn.STATUS_IDLE:
124 return ("away", None)
125 elif state == msn.STATUS_BRB:
126 return ("away", None)
127 elif state == msn.STATUS_PHONE:
128 return ("dnd", None)
129 elif state == msn.STATUS_LUNCH:
130 return ("away", None)
131 else:
132 return (None, "unavailable")
133
134
135
136
137 # This class handles groupchats with the legacy protocol
138 class LegacyGroupchat(groupchat.BaseGroupchat):
139 def __init__(self, session, resource, ID=None, existing=False, switchboardSession=None):
140 """ Possible entry points for groupchat
141 - User starts an empty switchboard session by sending presence to a blank room
142 - An existing switchboard session is joined by another MSN user
143 - User invited to an existing switchboard session with more than one user
144 """
145 groupchat.BaseGroupchat.__init__(self, session, resource, ID)
146 if not existing:
147 self.switchboardSession = msnw.GroupchatSwitchboardSession(self, makeSwitchboard=True)
148 else:
149 self.switchboardSession = switchboardSession
150
151 assert(self.switchboardSession != None)
152
153 LogEvent(INFO, self.roomJID())
154
155 def removeMe(self):
156 self.switchboardSession.removeMe()
157 self.switchboardSession = None
158 groupchat.BaseGroupchat.removeMe(self)
159 LogEvent(INFO, self.roomJID())
160 utils.mutilateMe(self)
161
162 def sendLegacyMessage(self, message, noerror):
163 LogEvent(INFO, self.roomJID())
164 self.switchboardSession.sendMessage(message.replace("\n", "\r\n"), noerror)
165
166 def sendContactInvite(self, contactJID):
167 LogEvent(INFO, self.roomJID())
168 userHandle = jid2msn(contactJID)
169 self.switchboardSession.inviteUser(userHandle)
170
171
172
173 # This class handles most interaction with the legacy protocol
174 class LegacyConnection(msnw.MSNConnection):
175 """ A glue class that connects to the legacy network """
176 def __init__(self, username, password, session):
177 self.session = session
178 self.listSynced = False
179 self.initialListVersion = 0
180
181 self.remoteShow = ""
182 self.remoteStatus = ""
183 self.remoteNick = ""
184
185 # Init the MSN bits
186 msnw.MSNConnection.__init__(self, username, password)
187
188 # User typing notification stuff
189 self.userTyping = dict() # Indexed by contact MSN ID, stores whether the user is typing to this contact
190 # Contact typing notification stuff
191 self.contactTyping = dict() # Indexed by contact MSN ID, stores an integer that is incremented at 5 second intervals. If it reaches 3 then the contact has stopped typing. It is set to zero whenever MSN typing notification messages are received
192 # Looping function
193 self.userTypingSend = task.LoopingCall(self.sendTypingNotifications)
194 self.userTypingSend.start(5.0)
195
196 import legacylist # Is in here to prevent an ImportError loop
197 self.legacyList = legacylist.LegacyList(self.session)
198
199 LogEvent(INFO, self.session.jabberID)
200
201 def removeMe(self):
202 LogEvent(INFO, self.session.jabberID)
203
204 self.userTypingSend.stop()
205
206 msnw.MSNConnection.removeMe(self)
207 self.legacyList.removeMe()
208 self.legacyList = None
209 self.session = None
210
211 utils.mutilateMe(self)
212
213 def jidRes(self, resource):
214 to = self.session.jabberID
215 if resource:
216 to += "/" + resource
217
218 return to
219
220 def highestResource(self):
221 """ Returns highest priority resource """
222 return self.session.highestResource()
223
224 def sendMessage(self, dest, resource, body, noerror):
225 dest = jid2msn(dest)
226 if self.userTyping.has_key(dest):
227 del self.userTyping[dest]
228 try:
229 msnw.MSNConnection.sendMessage(self, dest, resource, body, noerror)
230 self.session.pytrans.statistics.stats["MessageCount"] += 1
231 except:
232 self.failedMessage(dest, body)
233 raise
234
235 def msnAlert(self, text, actionurl, subscrurl):
236 if not self.session: return
237
238 el = Element((None, "message"))
239 el.attributes["to"] = self.session.jabberID
240 el.attributes["from"] = config.jid
241 el.attributes["type"] = "headline"
242 body = el.addElement("body")
243 body.addContent(text)
244
245 x = el.addElement("x")
246 x.attributes["xmlns"] = "jabber:x:oob"
247 x.addElement("desc").addContent("More information on this notice.")
248 x.addElement("url").addContent(actionurl)
249
250 x = el.addElement("x")
251 x.attributes["xmlns"] = "jabber:x:oob"
252 x.addElement("desc").addContent("Manage subscriptions to alerts.")
253 x.addElement("url").addContent(subscrurl)
254
255 self.session.pytrans.send(el)
256
257 def setStatus(self, nickname, show, status):
258 statusCode = presence2state(show, None)
259 msnw.MSNConnection.changeStatus(self, statusCode, nickname, status)
260
261 def updateAvatar(self, av=None):
262 global defaultJabberAvatarData
263
264 if av:
265 msnw.MSNConnection.changeAvatar(self, av.getImageData())
266 else:
267 msnw.MSNConnection.changeAvatar(self, defaultJabberAvatarData)
268
269 def sendTypingNotifications(self):
270 if not self.session: return
271
272 # Send any typing notification messages to the user's contacts
273 for contact in self.userTyping.keys():
274 if self.userTyping[contact]:
275 self.sendTypingToContact(contact)
276
277 # Send any typing notification messages from contacts to the user
278 for contact, resource in self.contactTyping.keys():
279 self.contactTyping[(contact, resource)] += 1
280 if self.contactTyping[(contact, resource)] >= 3:
281 self.session.sendTypingNotification(self.jidRes(resource), msn2jid(contact), False)
282 del self.contactTyping[(contact, resource)]
283
284 def gotContactTyping(self, contact, resource):
285 if not self.session: return
286 # Check if the contact has only just started typing
287 if not self.contactTyping.has_key((contact, resource)):
288 self.session.sendTypingNotification(self.jidRes(resource), msn2jid(contact), True)
289
290 # Reset the counter
291 self.contactTyping[(contact, resource)] = 0
292
293 def userTypingNotification(self, dest, resource, composing):
294 if not self.session: return
295 dest = jid2msn(dest)
296 self.userTyping[dest] = composing
297 if composing: # Make it instant
298 self.sendTypingToContact(dest)
299
300 def listSynchronized(self):
301 if not self.session: return
302 self.session.sendPresence(to=self.session.jabberID, fro=config.jid)
303 self.legacyList.syncJabberLegacyLists()
304 self.listSynced = True
305 #self.legacyList.flushSubscriptionBuffer()
306
307 def gotMessage(self, remoteUser, resource, text):
308 if not self.session: return
309 source = msn2jid(remoteUser)
310 self.session.sendMessage(self.jidRes(resource), fro=source, body=text, mtype="chat")
311 self.session.pytrans.statistics.stats["MessageCount"] += 1
312
313 def avatarHashChanged(self, userHandle, hash):
314 if not self.session: return
315 av = self.session.pytrans.avatarCache.getAvatar(hash)
316 if av:
317 msnContact = self.getContacts().getContact(userHandle)
318 msnContact.msnobjGot = True
319 jid = msn2jid(userHandle)
320 c = self.session.contactList.findContact(jid)
321 if not c: return
322 c.updateAvatar(av)
323 else:
324 self.requestAvatar(userHandle)
325
326 def gotAvatarImage(self, userHandle, imageData):
327 if not self.session: return
328 jid = msn2jid(userHandle)
329 c = self.session.contactList.findContact(jid)
330 if not c: return
331 av = self.session.pytrans.avatarCache.setAvatar(imageData)
332 c.updateAvatar(av)
333
334 def gotSendRequest(self, fileReceive):
335 if not self.session: return
336 LogEvent(INFO, self.session.jabberID)
337 ft.FTReceive(self.session, msn2jid(fileReceive).userHandle, fileReceive)
338
339 def loggedIn(self):
340 if not self.session: return
341 LogEvent(INFO, self.session.jabberID)
342 self.session.ready = True
343
344 def contactStatusChanged(self, remoteUser):
345 if not (self.session and self.getContacts()): return
346 LogEvent(INFO, self.session.jabberID)
347
348 msnContact = self.getContacts().getContact(remoteUser)
349 c = self.session.contactList.findContact(msn2jid(remoteUser))
350 if not (c and msnContact): return
351
352 show, ptype = state2presence(msnContact.status)
353 status = msnContact.personal.decode("utf-8")
354 screenName = msnContact.screenName.decode("utf-8")
355
356 c.updateNickname(screenName, push=False)
357 c.updatePresence(show, status, ptype, force=True)
358
359 def ourStatusChanged(self, statusCode):
360 # Send out a new presence packet to the Jabber user so that the MSN-t icon changes
361 if not self.session: return
362 LogEvent(INFO, self.session.jabberID)
363 self.remoteShow, ptype = state2presence(statusCode)
364 self.sendShowStatus()
365
366 def ourPersonalChanged(self, statusMessage):
367 if not self.session: return
368 LogEvent(INFO, self.session.jabberID)
369 self.remoteStatus = statusMessage
370 self.sendShowStatus()
371
372 def ourNickChanged(self, nick):
373 if not self.session: return
374 LogEvent(INFO, self.session.jabberID)
375 self.remoteNick = nick
376 self.sendShowStatus()
377
378 def sendShowStatus(self):
379 if not self.session: return
380 source = config.jid
381 to = self.session.jabberID
382 self.session.sendPresence(to=to, fro=source, show=self.remoteShow, status=self.remoteStatus, nickname=self.remoteNick)
383
384 def userMapping(self, passport, jid):
385 if not self.session: return
386 text = lang.get(self.session.lang).userMapping % (passport, jid)
387 self.session.sendMessage(to=self.session.jabberID, fro=msn2jid(passport), body=text)
388
389 def userAddedMe(self, userHandle):
390 if not self.session: return
391 self.session.contactList.getContact(msn2jid(userHandle)).contactRequestsAuth()
392
393 def userRemovedMe(self, userHandle):
394 if not self.session: return
395 c = self.session.contactList.getContact(msn2jid(userHandle))
396 c.contactDerequestsAuth()
397 c.contactRemovesAuth()
398
399 def serverGoingDown(self):
400 if not self.session: return
401 self.session.sendMessage(to=self.session.jabberID, fro=config.jid, body=lang.get(self.session.lang).msnMaintenance)
402
403 def multipleLogin(self):
404 if not self.session: return
405 self.session.sendMessage(to=self.session.jabberID, fro=config.jid, body=lang.get(self.session.lang).msnMultipleLogin)
406 self.session.removeMe()
407
408 def accountNotVerified(self):
409 if not self.session: return
410 text = lang.get(self.session.lang).msnNotVerified % (self.session.username)
411 self.session.sendMessage(to=self.session.jabberID, fro=config.jid, body=text)
412
413 def loginFailure(self, message):
414 if not self.session: return
415 text = lang.get(self.session.lang).msnLoginFailure % (self.session.username)
416 self.session.sendErrorMessage(to=self.session.jabberID, fro=config.jid, etype="auth", condition="not-authorized", explanation=text, body="Login Failure")
417 self.session.removeMe()
418
419 def failedMessage(self, remoteUser, message):
420 if not self.session: return
421 self.session.pytrans.statistics.stats["FailedMessageCount"] += 1
422 fro = msn2jid(remoteUser)
423 self.session.sendErrorMessage(to=self.session.jabberID, fro=fro, etype="wait", condition="recipient-unavailable", explanation=lang.get(self.session.lang).msnFailedMessage, body=message)
424
425 def initialEmailNotification(self, inboxunread, foldersunread):
426 if not self.session: return
427 text = lang.get(self.session.lang).msnInitialMail % (inboxunread, foldersunread)
428 self.session.sendMessage(to=self.session.jabberID, fro=config.jid, body=text, mtype="headline")
429
430 def realtimeEmailNotification(self, mailfrom, fromaddr, subject):
431 if not self.session: return
432 text = lang.get(self.session.lang).msnRealtimeMail % (mailfrom, fromaddr, subject)
433 self.session.sendMessage(to=self.session.jabberID, fro=config.jid, body=text, mtype="headline")
434
435 def connectionLost(self, reason):
436 if not self.session: return
437 LogEvent(INFO, self.jabberID)
438 text = lang.get(self.session.lang).msnDisconnected % ("Error") # FIXME, a better error would be nice =P
439 self.session.sendMessage(to=self.session.jabberID, fro=config.jid, body=text)
440 self.session.removeMe() # Tear down the session
441
442