]> code.delx.au - pymsnt/blob - src/legacy/msnw.py
85b9256f4c45ca980b1cb891c4e9384cb49f5db5
[pymsnt] / src / legacy / msnw.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 from twisted.internet import reactor
5 from twisted.internet.protocol import ClientFactory
6 from tlib import msn
7 import config
8 import utils
9 import debug
10
11
12
13
14 class MSNConnection:
15 """ Manages all the Twisted factories, etc """
16 def __init__(self, username, password):
17 self.username = username
18 self.password = password
19 self.inited = False
20 self.tries = 0
21 self.initMe()
22 debug.log("MSNConnection: \"%s\" created" % (self.username))
23
24 def initMe(self):
25 if(self.inited):
26 MSNConnection.removeMe(self)
27
28 self.switchboardSessions = {}
29 dispatchFactory = DispatchFactory(self)
30 reactor.connectTCP('messenger.hotmail.com', 1863, dispatchFactory)
31 self.notificationFactory = msn.NotificationFactory()
32 self.notificationFactory.userHandle = self.username
33 self.notificationFactory.password = self.password
34 self.notificationFactory.msncon = self
35 self.notificationFactory.protocol = Notification
36 self.notificationFactory.initialListVersion = self.initialListVersion
37 self.notificationProtocol = None
38
39 self.savedStatus = None
40
41 self.inited = True
42
43 debug.log("MSNConnection: \"%s\" initialised" % (self.username))
44
45 def removeMe(self):
46 debug.log("MSNConnection: \"%s\" destroyed" % (self.username))
47 if(self.notificationProtocol):
48 self.notificationProtocol.removeMe()
49 if(self.notificationFactory):
50 self.notificationFactory.msncon = None
51 self.notificationFactory = None
52 self.notificationProtocol = None
53 for userHandle in utils.copyDict(self.switchboardSessions):
54 self.switchboardSessions[userHandle].removeMe()
55 self.switchboardSessions = {}
56
57 def resourceOffline(self, offlineResource):
58 for contact in self.switchboardSessions.keys():
59 if(self.switchboardSessions[contact].resource == offlineResource):
60 self.switchboardSessions[contact].resource = self.highestResource()
61
62 def getContacts(self):
63 if(self.notificationFactory):
64 return self.notificationFactory.contacts
65 else:
66 return None
67
68
69 def sendMessage(self, remoteUser, resource, text, noerror):
70 debug.log("MSNConnection: \"%s\" sendMessage(\"%s\", \"%s\")" % (self.username, remoteUser, text))
71 if(self.notificationProtocol):
72 if(not self.switchboardSessions.has_key(remoteUser)):
73 self.switchboardSessions[remoteUser] = SwitchboardSession(self, remoteUser, resource)
74 self.switchboardSessions[remoteUser].resource = resource
75 self.switchboardSessions[remoteUser].sendMessage(text.replace("\n", "\r\n"), noerror)
76 elif(not noerror):
77 self.failedMessage(remoteUser, text)
78
79 def sendTypingToContact(self, remoteUser):
80 if(self.switchboardSessions.has_key(remoteUser)):
81 self.switchboardSessions[remoteUser].sendTypingNofication()
82
83 def notificationProtocolReady(self, notificationProtocol):
84 self.notificationProtocol = notificationProtocol
85 self.loggedIn()
86 self.tries = 0
87
88 def sendSavedStatus(self):
89 # Hack for initial status
90 if(self.savedStatus):
91 statusCode, screenName = self.savedStatus
92 self.savedStatus = None
93 self.changeStatus(statusCode, screenName)
94
95 def changeStatus(self, statusCode, screenName):
96 if(self.notificationProtocol):
97 def cb1(arg):
98 self.ourStatusChanged(arg[0])
99 def cb2(arg):
100 self.ourNickChanged(arg[0])
101 debug.log("MSNConnection: \"%s\" - changing status and screenName (\"%s\", \"%s\")" % (self.username, statusCode, screenName))
102 if(statusCode):
103 statusCode = str(statusCode.encode("utf-8"))
104 self.notificationProtocol.changeStatus(statusCode).addCallback(cb1)
105 if(screenName):
106 screenName = str(screenName.encode("utf-8"))
107 self.notificationProtocol.changeScreenName(screenName).addCallback(cb2)
108 else:
109 self.savedStatus = (statusCode, screenName)
110
111 def connectionLostBase(self, reason):
112 # Attempts to reconnect
113 if(self.tries < 5 and self.session):
114 reactor.callLater(2 ** self.tries, self.initMe)
115 self.tries += 1
116 else:
117 self.connectionLost(self)
118
119 def addContact(self, listType, userHandle):
120 return self.notificationProtocol.addContact(listType, str(userHandle))
121
122 def remContact(self, listType, userHandle, groupID=0):
123 return self.notificationProtocol.remContact(listType, str(userHandle))
124
125
126
127 def initialEmailNotification(self, inboxunread, foldersunread):
128 pass
129
130 def realtimeEmailNotification(self, mailfrom, fromaddr, subject):
131 pass
132
133 def loggedIn(self):
134 pass
135
136 def loginFailure(self, message):
137 pass
138
139 def multipleLogin(self):
140 pass
141
142 def gotMessage(self, remoteUser, resource, text):
143 pass
144
145 def listSynchronized(self):
146 pass
147
148 def contactStatusChanged(self, remoteUser):
149 pass
150
151 def ourStatusChanged(self, statusCode):
152 pass
153
154 def userMapping(self, passport, jid):
155 pass
156
157 def gotContactTyping(self, remoteUser, resource):
158 pass
159
160 def ourNickChanged(self, arg):
161 pass
162
163 def serverGoingDown(self):
164 pass
165
166 def accountNotVerified(self):
167 pass
168
169 def userAddedMe(self, userHandle):
170 pass
171
172 def userRemovedMe(self, userHandle):
173 pass
174
175 def failedMessage(self, remoteUser, message):
176 pass
177
178 def connectionLost(self):
179 pass
180
181
182
183
184 def switchToGroupchat(switchboardSession, user1, user2):
185 gcsbs = GroupchatSwitchboardSession()
186 from glue import LegacyGroupchat, msn2jid
187 groupchat = LegacyGroupchat(session=switchboardSession.msncon.session, resource=None, existing=True, switchboardSession=gcsbs)
188 gcsbs.groupchat = groupchat
189 gcsbs.msncon = switchboardSession.msncon
190 gcsbs.switchboard = switchboardSession.switchboard
191 gcsbs.switchboard.switchboardSession = gcsbs
192 gcsbs.ready = True
193 gcsbs.userJoined(user1)
194 gcsbs.userJoined(user2)
195 groupchat.sendUserInvite(msn2jid(switchboardSession.remoteUser))
196 switchboardSession.removeMe(False)
197 debug.log("GroupchatSwitchboardSession: \"%s\" \"%s\" created by conversion" % (gcsbs.groupchat.roomJID(), gcsbs))
198 return gcsbs
199
200
201 class GroupchatSwitchboardSession:
202 def __init__(self, groupchat=None, makeSwitchboard=False):
203 self.removed = False
204
205 self.msncon = None
206 self.groupchat = None
207 if(groupchat):
208 self.groupchat = groupchat
209 self.msncon = self.groupchat.session.legacycon
210 self.switchboard = None
211 self.ready = False
212 self.messageBuffer = []
213 self.invitedUsers = []
214 self.oneUserHasJoined = False
215
216 if(makeSwitchboard and groupchat):
217 debug.log("GroupchatSwitchboardSession: \"%s\" \"%s\" requesting a switchboard session" % (self.groupchat.roomJID(), self))
218 d = self.msncon.notificationProtocol.requestSwitchboardServer()
219 d.addCallback(self.sbRequestAccepted)
220 d.addErrback(self.removeMe)
221
222 if(self.msncon):
223 debug.log("GroupchatSwitchboardSession: \"%s\" \"%s\" created" % (self.msncon.username, self))
224
225 def removeMe(self):
226 if(self.removed):
227 debug.log("GroupchatSwitchboardSession: removeMe called more than once! Traceback!")
228 return
229 self.removed = True
230
231 debug.log("GroupchatSwitchboardSession: \"%s\" \"%s\" destroyed" % (self.groupchat.roomJID(), self))
232 self.msncon = None
233 if(self.switchboard):
234 self.switchboard.removeMe()
235 self.switchboard = None
236 self.groupchat = None
237 self.ready = False
238
239 utils.mutilateMe(self)
240
241 def sbRequestAccepted(self, (host, port, key)):
242 # Connect to the switchboard server
243 debug.log("GroupchatSwitchboardSession: \"%s\" \"%s\" sbRequestAccepted()" % (self.msncon.username, self))
244 reactor.connectTCP(host, port, SwitchboardFactory(self, key))
245
246 def sendMessage(self, message, noerror):
247 if(self.ready and self.oneUserHasJoined):
248 def failedMessage(ignored=None):
249 tempmsncon.failedMessage(self.groupchat.roomJID(), message)
250 message = str(message.encode("utf-8"))
251 msnmessage = msn.MSNMessage(message=message)
252 msnmessage.setHeader("Content-Type", "text/plain; charset=UTF-8")
253 msnmessage.ack = msn.MSNMessage.MESSAGE_NACK
254 tempmsncon = self.msncon # In case MSN tells us the message failed after removeMe()
255 d = self.switchboard.sendMessage(msnmessage)
256 if(not noerror):
257 d.addCallback(failedMessage)
258 else:
259 self.messageBuffer.append(message)
260
261 def inviteUser(self, userHandle):
262 userHandle = str(userHandle)
263 if(self.ready):
264 debug.log("GroupchatSwitchboardSession: \"%s\" \"%s\" inviting %s" % (self.msncon.username, self, userHandle))
265 self.switchboard.inviteUser(userHandle)
266 else:
267 self.invitedUsers.append(userHandle)
268
269 def gotMessage(self, message):
270 self.groupchat.messageReceived(message.userHandle, message.getMessage())
271
272 def flushBuffer(self):
273 for m in utils.copyList(self.messageBuffer):
274 self.messageBuffer.remove(m)
275 self.sendMessage(m, True)
276
277 for i in utils.copyList(self.invitedUsers):
278 self.invitedUsers.remove(i)
279 self.inviteUser(i)
280
281 def userJoined(self, userHandle):
282 debug.log("GroupchatSwitchboardSession: \"%s\" \"%s\" userJoined(\"%s\")" % (self.msncon.username, self, userHandle))
283 self.oneUserHasJoined = True
284 self.flushBuffer()
285 self.groupchat.contactJoined(userHandle)
286
287 def userLeft(self, userHandle):
288 debug.log("GroupchatSwitchboardSession: \"%s\" \"%s\" userLeft(\"%s\")" % (self.msncon.username, self, userHandle))
289 self.groupchat.contactLeft(userHandle)
290
291
292
293 class SwitchboardSession:
294 def __init__(self, msncon, remoteUser, resource, reply=False, host=None, port=None, key=None, sessionID=None):
295 self.removed = False
296
297 self.msncon = msncon
298 self.remoteUser = str(remoteUser)
299 self.resource = str(resource)
300
301 self.killTimer = reactor.callLater(30.0*60.0, self.removeMe)
302
303 self.switchboard = None # The SwitchboardClient class
304 self.messageBuffer = [] # Any messages sent before the switchboard is ready are buffered
305 self.ready = False # Is True when we are connected to the switchboard, and the remote user has accepted our invite
306
307 if(not reply):
308 # Request a switchboard
309 d = self.msncon.notificationProtocol.requestSwitchboardServer()
310 d.addCallback(self.sbRequestAccepted)
311 d.addErrback(self.removeMe)
312 else:
313 reactor.connectTCP(host, port, SwitchboardFactory(self, key, sessionID, reply))
314
315 debug.log("SwitchboardSession: \"%s\" \"%s\" \"%s\" created" % (self.msncon.username, self.remoteUser, self.resource))
316
317 def removeMe(self, sbflag=True):
318 if(self.removed):
319 debug.log("SwitchboardSession: removeMe called more than once! Traceback!")
320 return
321 self.removed = True
322
323 debug.log("SwitchboardSession: \"%s\" \"%s\" \"%s\" destroyed" % (self.msncon.username, self.remoteUser, self.resource))
324 for message, noerror in self.messageBuffer:
325 if(not noerror):
326 self.msncon.failedMessage(self.remoteUser, message)
327 self.messageBuffer = []
328
329 del self.msncon.switchboardSessions[self.remoteUser]
330 self.msncon = None
331 if(sbflag and self.switchboard):
332 self.switchboard.removeMe()
333 self.switchboard = None
334 self.ready = False
335 if(self.killTimer and not self.killTimer.called):
336 self.killTimer.cancel()
337 self.killTimer = None
338
339 utils.mutilateMe(self)
340
341 def resetTimer(self):
342 # Sets a count down timer to kill this switchboard session in 30 minutes
343 self.killTimer.cancel()
344 self.killTimer = reactor.callLater(30.0*60.0, self.removeMe)
345
346 def sbRequestAccepted(self, (host, port, key)):
347 # Connect to the switchboard server
348 reactor.connectTCP(host, port, SwitchboardFactory(self, key))
349
350 def sendMessage(self, message, noerror):
351 if(self.ready):
352 debug.log("SwitchboardSession: \"%s\" \"%s\" sending message \"%s\"" % (self.msncon.username, self.remoteUser, message))
353 message = str(message.encode("utf-8"))
354 msnmessage = msn.MSNMessage(message=message)
355 msnmessage.setHeader("Content-Type", "text/plain; charset=UTF-8")
356 msnmessage.ack = msn.MSNMessage.MESSAGE_NACK
357 def failedMessage(ignored):
358 tempmsncon.failedMessage(self.remoteUser, message)
359 d = self.switchboard.sendMessage(msnmessage)
360 tempmsncon = self.msncon # In case MSN tells us the message failed after removeMe()
361 if(not noerror):
362 d.addCallback(failedMessage)
363 self.resetTimer()
364 else:
365 self.messageBuffer.append((message, noerror))
366
367 def sendTypingNofication(self):
368 if(self.ready):
369 self.switchboard.sendTypingNotification()
370
371 def contactTyping(self):
372 self.msncon.gotContactTyping(self.remoteUser, self.resource)
373
374 def flushBuffer(self):
375 for m, noerror in utils.copyList(self.messageBuffer):
376 self.messageBuffer.remove((m, noerror))
377 self.sendMessage(m, noerror)
378
379 def gotMessage(self, message):
380 self.msncon.gotMessage(self.remoteUser, self.resource, message.getMessage())
381 self.resetTimer()
382
383 def userJoined(self, userHandle):
384 if(userHandle != self.remoteUser):
385 # Another user has joined, so we now have three participants (these two and ourself)
386 switchToGroupchat(self, self.remoteUser, userHandle)
387
388 def userLeft(self, userHandle):
389 if(userHandle == self.remoteUser):
390 self.removeMe()
391
392
393
394
395
396
397 class DispatchFactory(ClientFactory):
398 def __init__(self, msncon):
399 self.msncon = msncon
400
401 def buildProtocol(self, addr):
402 p = Dispatch(self.msncon)
403 del self.msncon # No longer needed
404 return p
405
406
407 class Dispatch(msn.DispatchClient):
408 def __init__(self, msncon):
409 msn.DispatchClient.__init__(self)
410 self.msncon = msncon
411 self.userHandle = self.msncon.username
412
413 def __del__(self):
414 self.factory = None
415 self.msncon = None
416
417 def gotNotificationReferral(self, host, port):
418 self.transport.loseConnection()
419 if(self.msncon and self.msncon.session and self.msncon.session.alive):
420 reactor.connectTCP(host, port, self.msncon.notificationFactory)
421
422
423
424 class Notification(msn.NotificationClient):
425 def __init__(self):
426 self.removed = False
427
428 msn.NotificationClient.__init__(self, proxy=config.proxyServer, proxyport=config.proxyPort)
429
430 def removeMe(self):
431 if(self.removed):
432 debug.log("Notification: removeMe called more than once! Traceback!")
433 return
434 self.removed = True
435
436 self.logOut()
437 self.transport.loseConnection()
438 if(self.factory.msncon):
439 self.factory.msncon.notificationProtocol = None
440 self.factory.msncon = None
441 self.factory = None
442
443 utils.mutilateMe(self)
444
445 def badConditions(self):
446 if(not (self.factory and self.factory.msncon and self.factory.msncon.session and self.factory.msncon.session.alive)):
447 if(not self.removed):
448 self.removeMe()
449 return True
450 return False
451
452
453 def loginFailure(self, message):
454 if(self.badConditions()): return
455 self.factory.msncon.loginFailure(message)
456
457 def loggedIn(self, userHandle, screenName, verified):
458 if(self.badConditions()): return
459
460 self.factory.msncon.notificationProtocolReady(self)
461 if(not verified):
462 self.factory.msncon.accountNotVerified()
463
464 msn.NotificationClient.loggedIn(self, userHandle, screenName, verified)
465
466 debug.log("NotificationClient: \"%s\" authenticated with MSN servers" % (self.factory.msncon.username))
467
468 def gotMessage(self, msnmessage):
469 if(self.badConditions()): return
470 debug.log("NotificationClient: \"%s\" gotMessage()" % (self.factory.msncon.username))
471
472 cTypes = [s.lstrip() for s in msnmessage.getHeader("Content-Type").split(';')]
473 def getFields():
474 fields = msnmessage.getMessage().strip().split('\n')
475 values = {}
476 for i in fields:
477 a = i.split(':')
478 if(len(a) != 2): continue
479 f, v = a
480 f = f.strip()
481 v = v.strip()
482 values[f] = v
483 return values
484
485 if("text/x-msmsgsinitialemailnotification" in cTypes and config.mailNotifications):
486 values = getFields()
487 try:
488 inboxunread = int(values["Inbox-Unread"])
489 foldersunread = int(values["Folders-Unread"])
490 except KeyError:
491 return
492 if(foldersunread + inboxunread == 0): return # For some reason MSN sends notifications about empty inboxes sometimes?
493 debug.log("NotificationClient: \"%s\" Initial hotmail notification" % (self.factory.msncon.username))
494 self.factory.msncon.initialEmailNotification(inboxunread, foldersunread)
495
496 elif("text/x-msmsgsemailnotification" in cTypes and config.mailNotifications):
497 values = getFields()
498 try:
499 mailfrom = values["From"]
500 fromaddr = values["From-Addr"]
501 subject = values["Subject"]
502 junkbeginning = "=?\"us-ascii\"?Q?"
503 junkend = "?="
504 subject = subject.replace(junkbeginning, "").replace(junkend, "").replace("_", " ")
505 except KeyError:
506 # If any of the fields weren't found then it's not a big problem. We just ignore the message
507 return
508 debug.log("NotificationClient: \"%s\" Live hotmail notification" % (self.factory.msncon.username))
509 self.factory.msncon.realtimeEmailNotification(mailfrom, fromaddr, subject)
510
511 elif("NOTIFICATION" == msnmessage.userHandle):
512 notification = utils.parseText(msnmessage.message)
513 siteurl = notification.getAttribute("siteurl")
514 notid = notification.getAttribute("id")
515
516 msg = None
517 for e in notification.elements():
518 if(e.name == "MSG"):
519 msg = e
520 break
521 else: return
522
523 msgid = msg.getAttribute("id")
524
525 action = None
526 subscr = None
527 bodytext = None
528 for e in msg.elements():
529 if(e.name == "ACTION"):
530 action = e.getAttribute("url")
531 if(e.name == "SUBSCR"):
532 subscr = e.getAttribute("url")
533 if(e.name == "BODY"):
534 for e2 in e.elements():
535 if(e2.name == "TEXT"):
536 bodytext = e2.__str__()
537 if(not (action and subscr and bodytext)): return
538
539
540 actionurl = "%s&notification_id=%s&message_id=%s&agent=messenger" % (action, notid, msgid)
541 subscrurl = "%s&notification_id=%s&message_id=%s&agent=messenger" % (subscr, notid, msgid)
542
543 self.factory.msncon.msnAlert(bodytext, actionurl, subscrurl)
544
545
546
547 def connectionLost(self, reason):
548 if(self.badConditions()): return
549 def wait():
550 debug.log("NotificationClient: \"%s\" lost connection with MSN servers" % (self.factory.userHandle))
551 msn.NotificationClient.connectionLost(self, reason)
552 self.factory.msncon.connectionLostBase(reason)
553 # Make sure this event is handled after any others
554 reactor.callLater(0, wait)
555
556 def listSynchronized(self, *args):
557 if(self.badConditions()): return
558 debug.log("NotificationClient: \"%s\" MSN contact lists synchronised" % (self.factory.userHandle))
559 self.factory.msncon.listSynchronized()
560 if(self.badConditions()): return # Just in case the session is deregistered
561 self.factory.msncon.sendSavedStatus()
562
563 def gotSwitchboardInvitation(self, sessionID, host, port, key, remoteUser, screenName):
564 if(self.badConditions()): return
565 debug.log("NotificationClient: \"%s\" gotSwitchboardInvitation(\"%s\")" % (self.factory.userHandle, remoteUser))
566 sbs = SwitchboardSession(self.factory.msncon, remoteUser, self.factory.msncon.session.highestResource(), True, host, port, key, sessionID)
567 if(self.factory.msncon.switchboardSessions.has_key(remoteUser)):
568 self.factory.msncon.switchboardSessions[remoteUser].removeMe()
569 self.factory.msncon.switchboardSessions[remoteUser] = sbs
570
571 def contactStatusChanged(self, statusCode, userHandle, screenName):
572 if(self.badConditions()): return
573 debug.log("NotificationClient: \"%s\" contactStatusChanged(\"%s\", \"%s\")" % (self.factory.userHandle, statusCode, userHandle))
574 msn.NotificationClient.contactStatusChanged(self, statusCode, userHandle, screenName)
575
576 self.factory.msncon.contactStatusChanged(userHandle)
577
578 def gotContactStatus(self, statusCode, userHandle, screenName):
579 if(self.badConditions()): return
580 msn.NotificationClient.gotContactStatus(self, statusCode, userHandle, screenName)
581 debug.log("NotificationClient: \"%s\" gotContactStatus(\"%s\", \"%s\")" % (self.factory.userHandle, statusCode, userHandle))
582
583 self.factory.msncon.contactStatusChanged(userHandle)
584
585 def contactOffline(self, userHandle):
586 if(self.badConditions()): return
587 debug.log("NotificationClient: \"%s\" contactOffline(\"%s\")" % (self.factory.userHandle, userHandle))
588 msn.NotificationClient.contactOffline(self, userHandle)
589
590 self.factory.msncon.contactStatusChanged(userHandle)
591
592 def userAddedMe(self, userHandle, screenName, listVersion):
593 if(self.badConditions()): return
594 debug.log("NotificationClient: \"%s\" userAddedMe(\"%s\", \"%s\")" % (self.factory.userHandle, userHandle, listVersion))
595 msn.NotificationClient.userAddedMe(self, userHandle, screenName, listVersion)
596 self.factory.msncon.userAddedMe(userHandle)
597
598 def userRemovedMe(self, userHandle, listVersion):
599 if(self.badConditions()): return
600 debug.log("NotificationClient: \"%s\" userRemovedMe(\"%s\", \"%s\")" % (self.factory.userHandle, userHandle, listVersion))
601 msn.NotificationClient.userRemovedMe(self, userHandle, listVersion)
602 self.factory.msncon.userRemovedMe(userHandle)
603
604 def multipleLogin(self):
605 if(self.badConditions()): return
606 debug.log("NotificationClient: \"%s\" multiple logins" % (self.factory.msncon.username))
607 self.factory.msncon.multipleLogin()
608
609
610
611 class SwitchboardFactory(ClientFactory):
612 def __init__(self, switchboardSession, key, sessionID=None, reply=False):
613 self.switchboardSession = switchboardSession
614 self.key = key
615 self.sessionID = sessionID
616 self.reply = reply
617
618 def buildProtocol(self, addr):
619 p = Switchboard(self.switchboardSession)
620 if(p.badConditions()): return p
621 p.key = self.key
622 p.sessionID = self.sessionID
623 p.reply = self.reply
624 p.userHandle = self.switchboardSession.msncon.username
625 p.factory = self
626 return p
627
628 class Switchboard(msn.SwitchboardClient):
629 def __init__(self, switchboardSession):
630 self.removed = False
631
632 msn.SwitchboardClient.__init__(self)
633 self.switchboardSession = switchboardSession
634 self.chattingUsers = []
635 self.callid = None
636 if(self.badConditions()): return
637 debug.log("SwitchboardClient: \"%s\" \"%s\" - created" % (self.switchboardSession.msncon.username, self.switchboardSession))
638
639 def removeMe(self):
640 if(self.removed):
641 debug.log("Switchboard: removeMe called more than once! Traceback!")
642 return
643 self.removed = True
644
645 self.transport.loseConnection()
646 debug.log("SwitchboardClient: \"%s\" - destroyed" % (self.switchboardSession))
647 self.switchboardSession = None
648 self.factory.switchboardSession = None
649 self.factory = None
650
651 if(self.callid and not self.callid.called):
652 self.callid.cancel() # Cancel the invite fail message
653 self.callid = None
654
655 utils.mutilateMe(self)
656
657 def badConditions(self):
658 if(not (self.switchboardSession and self.switchboardSession.msncon and self.switchboardSession.msncon.session and self.switchboardSession.msncon.session.alive)):
659 if(self.switchboardSession):
660 if(not self.switchboardSession.removed):
661 self.switchboardSession.removeMe()
662 elif(not self.removed):
663 self.removeMe()
664 return True
665 return False
666
667 def loggedIn(self):
668 if(self.badConditions()): return
669 if((not self.reply) and self.switchboardSession.__class__ == SwitchboardSession):
670 def failCB(arg=None):
671 debug.log(templogmessage)
672 self.switchboardSession.removeMe()
673 d = self.inviteUser(self.switchboardSession.remoteUser)
674 d.addErrback(failCB)
675 templogmessage = "SwitchboardClient: \"%s\" \"%s\" - user has NOT joined after 30 seconds" % (self.switchboardSession.msncon.username, self.switchboardSession.remoteUser)
676 # If the user doesn't join then we want to tear down the SwitchboardSession
677 self.callid = reactor.callLater(30.0, failCB)
678
679 else:
680 self.readySwitchboardSession()
681
682 def readySwitchboardSession(self, ignored=None):
683 if(self.badConditions()): return
684 debug.log("SwitchboardClient: \"%s\" \"%s\" - ready for use" % (self.switchboardSession.msncon.username, self.switchboardSession))
685 self.switchboardSession.ready = True
686 self.switchboardSession.switchboard = self
687 self.switchboardSession.flushBuffer()
688 for user in self.chattingUsers:
689 self.switchboardSession.userJoined(user)
690 if(self.callid and not self.callid.called):
691 self.callid.cancel() # Cancel the invite fail message (only applies if we needed to invite the user)
692 self.callid = None
693
694 def gotChattingUsers(self, users):
695 for user in users:
696 self.chattingUsers.append(user)
697
698 def userJoined(self, userHandle, screenName):
699 if(self.badConditions()): return
700 if((not self.reply) and self.switchboardSession.__class__ == SwitchboardSession):
701 self.readySwitchboardSession()
702 debug.log("SwitchboardClient: \"%s\" \"%s\" - userJoined(\"%s\")" % (self.switchboardSession.msncon.username, self.switchboardSession, userHandle))
703 self.switchboardSession.userJoined(userHandle)
704 self.sendClientCaps()
705
706 def userLeft(self, userHandle):
707 if(self.badConditions()): return
708 debug.log("SwitchboardClient: \"%s\" \"%s\" - userLeft(\"%s\")" % (self.switchboardSession.msncon.username, self.switchboardSession, userHandle))
709 def wait():
710 self.switchboardSession.userLeft(userHandle)
711 # Make sure this event is handled after any others (eg, gotMessage)
712 reactor.callLater(0, wait)
713
714 def gotMessage(self, message):
715 if(self.badConditions()):
716 debug.log("SwitchboardClient: gotMessage called too late! Traceback!")
717 return
718 debug.log("SwitchboardClient: \"%s\" \"%s\" gotMessage(\"%s\")" % (self.switchboardSession.msncon.username, message.userHandle, message.getMessage()))
719 cTypes = [s.lstrip() for s in message.getHeader("Content-Type").split(';')]
720 if("text/plain" in cTypes):
721 if(len(cTypes) > 1 and cTypes[1].find("UTF-8") >= 0):
722 message.message = message.message.decode("utf-8")
723 self.switchboardSession.gotMessage(message)
724 return
725 if("text/x-clientcaps" in cTypes):
726 if(message.hasHeader("JabberID")):
727 jid = message.getHeader("JabberID")
728 self.switchboardSession.msncon.userMapping(message.userHandle, jid)
729 return
730 debug.log("Discarding unknown message type: %s" % (message.getMessage()))
731
732 def userTyping(self, message):
733 if(self.badConditions()): return
734 if(self.switchboardSession.__class__ == SwitchboardSession): # Ignore typing in groupchats
735 if(message.userHandle == self.switchboardSession.remoteUser):
736 self.switchboardSession.contactTyping()
737
738 def sendClientCaps(self):
739 message = msn.MSNMessage()
740 message.setHeader("Content-Type", "text/x-clientcaps")
741 message.setHeader("Client-Name", "PyMSNt")
742 message.setHeader("JabberID", str(self.switchboardSession.msncon.session.jabberID)) # FIXME, this is a little deep
743 self.sendMessage(message)
744
745