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