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