]> code.delx.au - pymsnt/blob - src/legacy/msnw.py
Reimport and tags (0.10.1)
[pymsnt] / src / legacy / msnw.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 from twisted.internet import reactor
5 from twisted.internet.protocol import ClientFactory
6 from twisted.python import log
7 from debug import LogEvent, INFO, WARN, ERROR
8 from tlib import msn
9 import math
10 import base64
11 import binascii
12 import math
13 import config
14 import utils
15 import lang
16
17 MAXMESSAGESIZE = 1400
18
19
20 class MSNConnection:
21 """ Manages all the Twisted factories, etc """
22 def __init__(self, username, password):
23 self.username = username
24 self.password = password
25 self.inited = False
26 self.tries = 0
27 self.initMe()
28 LogEvent(INFO, self.session.jabberID)
29
30 def initMe(self):
31 if self.inited:
32 MSNConnection.removeMe(self)
33
34 self.switchboardSessions = {}
35 dispatchFactory = DispatchFactory(self)
36 reactor.connectTCP('messenger.hotmail.com', 1863, dispatchFactory)
37 self.notificationFactory = msn.NotificationFactory()
38 self.notificationFactory.userHandle = self.username
39 self.notificationFactory.password = self.password
40 self.notificationFactory.msncon = self
41 self.notificationFactory.protocol = Notification
42 self.notificationFactory.initialListVersion = self.initialListVersion
43 self.notificationProtocol = None
44
45 self.savedEvents = SavedEvents()
46
47 self.inited = True
48
49 LogEvent(INFO, self.session.jabberID)
50
51 def removeMe(self):
52 LogEvent(INFO, self.session.jabberID)
53 if self.notificationProtocol:
54 self.notificationProtocol.removeMe()
55 if self.notificationFactory:
56 self.notificationFactory.msncon = None
57 self.notificationFactory = None
58 self.notificationProtocol = None
59 self.savedEvents = SavedEvents()
60 for userHandle in self.switchboardSessions.copy():
61 self.switchboardSessions[userHandle].removeMe()
62 self.switchboardSessions = {}
63
64 def resourceOffline(self, offlineResource):
65 for contact in self.switchboardSessions.keys():
66 if self.switchboardSessions[contact].resource == offlineResource:
67 self.switchboardSessions[contact].resource = self.highestResource()
68
69 def getContacts(self):
70 if self.notificationFactory:
71 return self.notificationFactory.contacts
72 else:
73 return None
74
75
76 def sendMessage(self, remoteUser, resource, text, noerror):
77 LogEvent(INFO, self.session.jabberID)
78 if self.notificationProtocol:
79 if not self.switchboardSessions.has_key(remoteUser):
80 self.switchboardSessions[remoteUser] = SwitchboardSession(self, remoteUser, resource)
81 self.switchboardSessions[remoteUser].resource = resource
82 self.switchboardSessions[remoteUser].sendMessage(text.replace("\n", "\r\n"), noerror)
83 elif not noerror:
84 self.failedMessage(remoteUser, text)
85
86 def requestAvatar(self, userHandle):
87 LogEvent(INFO, self.session.jabberID)
88 resource = self.session.highestResource()
89 if config.getAllAvatars:
90 if not self.switchboardSessions.has_key(userHandle):
91 self.switchboardSessions[userHandle] = SwitchboardSession(self, userHandle, resource)
92 else:
93 self.switchboardSessions[userHandle].requestAvatar()
94 else:
95 if self.switchboardSessions.has_key(userHandle): # Only request avatars for open switchboards
96 self.switchboardSessions[userHandle].requestAvatar()
97
98 def sendTypingToContact(self, remoteUser):
99 if self.switchboardSessions.has_key(remoteUser):
100 self.switchboardSessions[remoteUser].sendTypingNofication()
101
102 def notificationProtocolReady(self, notificationProtocol):
103 self.notificationProtocol = notificationProtocol
104 self.loggedIn()
105 self.tries = 0
106
107 def sendSavedEvents(self):
108 # Hack for events sent before we're logged in
109 self.savedEvents.send(self)
110 self.savedEvents = None
111
112 def changeAvatar(self, imageData):
113 if self.notificationProtocol:
114 self.notificationProtocol.changeAvatar(imageData, push=True)
115 else:
116 self.savedEvents.avatarImageData = imageData
117
118 def changeStatus(self, statusCode, screenName, personal):
119 if self.notificationProtocol:
120 def cb1(arg):
121 self.ourStatusChanged(arg[0])
122 def cb2(arg):
123 self.ourNickChanged(arg[0])
124 def cb3(arg):
125 self.ourPersonalChanged(personal)
126 LogEvent(INFO, self.session.jabberID)
127 if statusCode:
128 statusCode = str(statusCode.encode("utf-8"))
129 self.notificationProtocol.changeStatus(statusCode).addCallback(cb1)
130 if screenName:
131 screenName = str(screenName.encode("utf-8"))
132 self.notificationProtocol.changeScreenName(screenName).addCallback(cb2)
133 if personal:
134 personal = str(personal.encode("utf-8"))
135 else:
136 personal = ""
137 self.notificationProtocol.changePersonalMessage(personal).addCallback(cb3)
138 else:
139 self.savedEvents.statusCode = statusCode
140 self.savedEvents.screenName = screenName
141 self.savedEvents.personal = personal
142
143 def connectionLostBase(self, reason):
144 # Attempts to reconnect
145 if self.tries < 5 and self.session:
146 reactor.callLater(2 ** self.tries, self.initMe)
147 self.tries += 1
148 else:
149 self.connectionLost(self)
150
151 def addContact(self, listType, userHandle):
152 if self.notificationProtocol:
153 return self.notificationProtocol.addContact(listType, str(userHandle))
154 else:
155 self.savedEvents.addContacts.append((listType, str(userHandle)))
156
157 def remContact(self, listType, userHandle, groupID=0):
158 if self.notificationProtocol:
159 return self.notificationProtocol.remContact(listType, str(userHandle))
160 else:
161 self.savedEvents.remContacts.append((listType, str(userHandle)))
162
163
164
165 def initialEmailNotification(self, inboxunread, foldersunread):
166 pass
167
168 def realtimeEmailNotification(self, mailfrom, fromaddr, subject):
169 pass
170
171 def loggedIn(self):
172 pass
173
174 def loginFailure(self, message):
175 pass
176
177 def multipleLogin(self):
178 pass
179
180 def gotMessage(self, remoteUser, resource, text):
181 pass
182
183 def avatarHashChanged(self, userHandle, hash):
184 pass
185
186 def gotAvatarImage(self, to, image):
187 pass
188
189 def listSynchronized(self):
190 pass
191
192 def contactStatusChanged(self, remoteUser):
193 pass
194
195 def ourStatusChanged(self, statusCode):
196 pass
197
198 def ourNickChanged(self, nick):
199 pass
200
201 def ourPersonalChanged(self, personal):
202 pass
203
204 def userMapping(self, passport, jid):
205 pass
206
207 def gotContactTyping(self, remoteUser, resource):
208 pass
209
210 def serverGoingDown(self):
211 pass
212
213 def accountNotVerified(self):
214 pass
215
216 def userAddedMe(self, userHandle):
217 pass
218
219 def userRemovedMe(self, userHandle):
220 pass
221
222 def failedMessage(self, remoteUser, message):
223 pass
224
225 def connectionLost(self):
226 pass
227
228
229
230 class SavedEvents:
231 def __init__(self):
232 self.nickname = ""
233 self.statusCode = ""
234 self.personal = ""
235 self.avatarImageData = ""
236 self.addContacts = []
237 self.remContacts = []
238
239 def send(self, msncon):
240 if self.avatarImageData:
241 msncon.notificationProtocol.changeAvatar(self.avatarImageData, push=False)
242 if self.nickname or self.statusCode or self.personal:
243 msncon.changeStatus(self.statusCode, self.nickname, self.personal)
244 for listType, userHandle in self.addContacts:
245 msncon.addContact(listType, userHandle)
246 for listType, userHandle in self.remContacts:
247 msncon.remContact(listType, userHandle)
248
249
250
251 def switchToGroupchat(switchboardSession, user1, user2):
252 gcsbs = GroupchatSwitchboardSession()
253 from glue import LegacyGroupchat, msn2jid
254 groupchat = LegacyGroupchat(session=switchboardSession.msncon.session, resource=None, existing=True, switchboardSession=gcsbs)
255 gcsbs.groupchat = groupchat
256 gcsbs.msncon = switchboardSession.msncon
257 gcsbs.switchboard = switchboardSession.switchboard
258 gcsbs.switchboard.switchboardSession = gcsbs
259 gcsbs.ready = True
260 gcsbs.userJoined(user1)
261 gcsbs.userJoined(user2)
262 groupchat.sendUserInvite(msn2jid(switchboardSession.remoteUser))
263 switchboardSession.removeMe(False)
264 LogEvent(INFO, gcsbs.msncon.session.jabberID)
265 return gcsbs
266
267
268 class SwitchboardSessionBase:
269 def sendMessage(self, message, noerror):
270 if self.ready:
271 def failedMessage(ignored):
272 if self.__class__ == GroupchatSwitchboardSession:
273 tempmsncon.failedMessage(self.groupchat.roomJID(), message)
274 else:
275 tempmsncon.failedMessage(self.remoteUser, message)
276
277 tempmsncon = self.msncon # In case MSN tells us the message failed after removeMe()
278
279 LogEvent(INFO, self.ident)
280 message = str(message.encode("utf-8"))
281
282 if len(message) < MAXMESSAGESIZE:
283 msnmessage = msn.MSNMessage(message=message)
284 msnmessage.setHeader("Content-Type", "text/plain; charset=UTF-8")
285 msnmessage.ack = msn.MSNMessage.MESSAGE_NACK
286
287 d = self.switchboard.sendMessage(msnmessage)
288 if not noerror:
289 d.addCallback(failedMessage)
290 else:
291 chunks = int(math.ceil(len(message) / float(MAXMESSAGESIZE)))
292 chunk = 0
293 guid = utils.random_guid()
294 while chunk < chunks:
295 offset = chunk * MAXMESSAGESIZE
296 text = message[offset : offset + MAXMESSAGESIZE]
297
298 msnmessage = msn.MSNMessage(message=text)
299 msnmessage.setHeader("Message-ID", guid)
300 if chunk == 0:
301 msnmessage.setHeader("Content-Type", "text/plain; charset=UTF-8")
302 msnmessage.setHeader("Chunks", str(chunks))
303 else:
304 msnmessage.setHeader("Chunk", str(chunk))
305 msnmessage.ack = msn.MSNMessage.MESSAGE_NACK
306
307 d = self.switchboard.sendMessage(msnmessage)
308 if not noerror:
309 d.addCallback(failedMessage)
310 chunk += 1
311
312 self.resetTimer()
313 else:
314 self.messageBuffer.append((message, noerror))
315
316 def gotAvatarImage(self, to, image):
317 self.msncon.gotAvatarImage(to, image)
318
319 def switchboardReady(self, switchboard):
320 LogEvent(INFO, self.ident)
321 self.ready = True
322 self.switchboard = switchboard
323 self.flushBuffer()
324
325 def resetTimer(self):
326 pass
327
328
329 class GroupchatSwitchboardSession(SwitchboardSessionBase):
330 def __init__(self, groupchat=None, makeSwitchboard=False):
331 self.removed = False
332
333 self.msncon = None
334 if groupchat:
335 self.ident = groupchat.roomJID()
336 self.groupchat = groupchat
337 self.msncon = self.groupchat.session.legacycon
338 else:
339 self.ident = str(self)
340 self.groupchat = None
341 self.switchboard = None
342 self.ready = False
343 self.messageBuffer = []
344 self.invitedUsers = []
345 self.oneUserHasJoined = False
346
347 if makeSwitchboard and groupchat:
348 LogEvent(INFO, self.ident, "Requesting switchboard.")
349 d = self.msncon.notificationProtocol.requestSwitchboardServer()
350 d.addCallback(self.sbRequestAccepted)
351 d.addErrback(self.removeMe)
352
353 if self.msncon:
354 LogEvent(INFO, self.ident, "Created groupchat for " + self.msncon.username)
355
356 def removeMe(self):
357 if self.removed:
358 log.err("removeMe called more than once!")
359 return
360 self.removed = True
361
362 LogEvent(INFO, self.ident)
363 self.msncon = None
364 if self.switchboard:
365 self.switchboard.removeMe()
366 self.switchboard = None
367 self.groupchat = None
368 self.ready = False
369
370 utils.mutilateMe(self)
371
372 def sbRequestAccepted(self, (host, port, key)):
373 # Connect to the switchboard server
374 LogEvent(INFO, self.ident)
375 reactor.connectTCP(host, port, SwitchboardFactory(self, key))
376
377 def sendMessage(self, message, noerror):
378 if self.oneUserHasJoined:
379 SwitchboardSessionBase.sendMessage(self, message, noerror)
380 else:
381 self.messageBuffer.append((message, noerror))
382
383 def inviteUser(self, userHandle):
384 userHandle = str(userHandle)
385 if self.ready:
386 LogEvent(INFO, self.ident)
387 self.switchboard.inviteUser(userHandle)
388 else:
389 self.invitedUsers.append(userHandle)
390
391 def gotMessage(self, message):
392 self.groupchat.messageReceived(message.userHandle, message.getMessage())
393
394 def flushBuffer(self):
395 for m, noerror in self.messageBuffer[:]:
396 self.messageBuffer.remove((m, noerror))
397 self.sendMessage(m, noerror)
398
399 for i in self.invitedUsers[:]:
400 self.invitedUsers.remove(i)
401 self.inviteUser(i)
402
403 def userJoined(self, userHandle):
404 LogEvent(INFO, self.ident)
405 self.oneUserHasJoined = True
406 self.flushBuffer()
407 self.groupchat.contactJoined(userHandle)
408
409 def userLeft(self, userHandle):
410 LogEvent(INFO, self.ident)
411 self.groupchat.contactLeft(userHandle)
412
413
414
415 class SwitchboardSession(SwitchboardSessionBase):
416 def __init__(self, msncon, remoteUser, resource, reply=False, host=None, port=None, key=None, sessionID=None):
417 self.removed = False
418
419 self.ident = (msncon.session.jabberID, remoteUser)
420 self.msncon = msncon
421 self.remoteUser = str(remoteUser)
422 self.resource = str(resource)
423
424 self.killTimer = reactor.callLater(30.0*60.0, self.removeMe)
425
426 self.switchboard = None # The SwitchboardClient class
427 self.messageBuffer = [] # Any messages sent before the switchboard is ready are buffered
428 self.ready = False # Is True when we are connected to the switchboard, and the remote user has accepted our invite
429
430 if not reply:
431 # Request a switchboard
432 d = self.msncon.notificationProtocol.requestSwitchboardServer()
433 d.addCallback(self.sbRequestAccepted)
434 d.addErrback(self.removeMe)
435 else:
436 reactor.connectTCP(host, port, SwitchboardFactory(self, key, sessionID, reply))
437
438 LogEvent(INFO, self.ident)
439
440 def removeMe(self, sbflag=True):
441 if self.removed:
442 log.err("removeMe called more than once!")
443 return
444 self.removed = True
445
446 LogEvent(INFO, self.ident)
447 for message, noerror in self.messageBuffer:
448 if not noerror:
449 self.msncon.failedMessage(self.remoteUser, message)
450 self.messageBuffer = []
451
452 del self.msncon.switchboardSessions[self.remoteUser]
453 self.msncon = None
454 if sbflag and self.switchboard:
455 self.switchboard.removeMe()
456 self.switchboard = None
457 self.ready = False
458 if self.killTimer and not self.killTimer.called:
459 self.killTimer.cancel()
460 self.killTimer = None
461
462 utils.mutilateMe(self)
463
464 def resetTimer(self):
465 # Sets a count down timer to kill this switchboard session in 30 minutes
466 self.killTimer.cancel()
467 self.killTimer = reactor.callLater(30.0*60.0, self.removeMe)
468
469 def sbRequestAccepted(self, (host, port, key)):
470 # Connect to the switchboard server
471 reactor.connectTCP(host, port, SwitchboardFactory(self, key))
472
473 def sendTypingNofication(self):
474 if self.ready:
475 self.switchboard.sendTypingNotification()
476
477 def contactTyping(self):
478 self.msncon.gotContactTyping(self.remoteUser, self.resource)
479
480 def flushBuffer(self):
481 for m, noerror in self.messageBuffer[:]:
482 self.messageBuffer.remove((m, noerror))
483 self.sendMessage(m, noerror)
484
485 def gotMessage(self, message):
486 self.msncon.gotMessage(self.remoteUser, self.resource, message.getMessage())
487 self.resetTimer()
488
489 CAPS = msn.MSNContact.MSNC1 | msn.MSNContact.MSNC2 | msn.MSNContact.MSNC3 | msn.MSNContact.MSNC4
490 def requestAvatar(self):
491 if not self.switchboard: return
492 msnContacts = self.msncon.getContacts()
493 if not msnContacts: return
494 msnContact = msnContacts.getContact(self.remoteUser)
495 if not (msnContact and msnContact.caps & self.CAPS and msnContact.msnobj): return
496 if msnContact.msnobjGot: return
497 msnContact.msnobjGot = True # This is deliberately set before we get the avatar. So that we don't try to reget failed avatars over & over
498 self.switchboard.sendAvatarRequest(self.remoteUser, msnContact.msnobj)
499
500 def userJoined(self, userHandle):
501 if userHandle != self.remoteUser:
502 # Another user has joined, so we now have three participants (these two and ourself)
503 switchToGroupchat(self, self.remoteUser, userHandle)
504 else:
505 self.requestAvatar()
506
507 def userLeft(self, userHandle):
508 if userHandle == self.remoteUser:
509 self.removeMe()
510
511
512
513 class DispatchFactory(ClientFactory):
514 def __init__(self, msncon):
515 self.msncon = msncon
516
517 def buildProtocol(self, addr):
518 p = Dispatch(self.msncon)
519 del self.msncon # No longer needed
520 return p
521
522 def clientConnectionFailed(self, connector, reason):
523 self.msncon.connectionLostBase(reason)
524
525
526 class Dispatch(msn.DispatchClient):
527 def __init__(self, msncon):
528 msn.DispatchClient.__init__(self)
529 self.msncon = msncon
530 self.userHandle = self.msncon.username
531
532 def __del__(self):
533 self.factory = None
534 self.msncon = None
535
536 def gotNotificationReferral(self, host, port):
537 self.transport.loseConnection()
538 if self.msncon and self.msncon.session and self.msncon.session.alive:
539 reactor.connectTCP(host, port, self.msncon.notificationFactory)
540
541
542
543 class Notification(msn.NotificationClient):
544 def __init__(self):
545 self.removed = False
546
547 msn.NotificationClient.__init__(self)
548
549 def removeMe(self):
550 if self.removed:
551 log.err("removeMe called more than once!")
552 return
553 self.removed = True
554
555 self.logOut()
556 self.transport.loseConnection()
557 if self.factory.msncon:
558 self.factory.msncon.notificationProtocol = None
559 self.factory.msncon = None
560 self.factory = None
561
562 utils.mutilateMe(self)
563
564 def badConditions(self):
565 if not (self.factory and self.factory.msncon and self.factory.msncon.session and self.factory.msncon.session.alive):
566 if not self.removed:
567 self.removeMe()
568 return True
569 return False
570
571
572 def loginFailure(self, message):
573 if self.badConditions(): return
574 self.factory.msncon.loginFailure(message)
575
576 def loggedIn(self, userHandle, verified):
577 if self.badConditions(): return
578
579 self.factory.msncon.notificationProtocolReady(self)
580 if not verified:
581 self.factory.msncon.accountNotVerified()
582
583 msn.NotificationClient.loggedIn(self, userHandle, verified)
584
585 LogEvent(INFO, self.factory.msncon.session.jabberID)
586
587 def msnAlertReceived(self, body, action, subscr):
588 if self.badConditions(): return
589 self.factory.msncon.msnAlert(body, action, subscr)
590
591 def initialEmailNotification(self, inboxunread, foldersunread):
592 if self.badConditions() or not config.mailNotifications: return
593 self.factory.msncon.initialEmailNotification(inboxunread, foldersunread)
594
595 def realtimeEmailNotification(self, mailfrom, fromaddr, subject):
596 if self.badConditions() or not config.mailNotifications: return
597 self.factory.msncon.realtimeEmailNotification(mailfrom, fromaddr, subject)
598
599 def connectionLost(self, reason):
600 if self.badConditions(): return
601 def wait():
602 LogEvent(INFO, self.factory.msncon.session.jabberID)
603 msn.NotificationClient.connectionLost(self, reason)
604 self.factory.msncon.connectionLostBase(reason)
605 # Make sure this event is handled after any others
606 reactor.callLater(0, wait)
607
608 def listSynchronized(self, *args):
609 if self.badConditions(): return
610 LogEvent(INFO, self.factory.msncon.session.jabberID)
611 self.factory.msncon.listSynchronized()
612 if self.badConditions(): return # Just in case the session is deregistered
613 self.factory.msncon.sendSavedEvents()
614 self.setPrivacyMode(False)
615
616 def gotSwitchboardInvitation(self, sessionID, host, port, key, remoteUser, screenName):
617 if self.badConditions(): return
618 LogEvent(INFO, self.factory.msncon.session.jabberID)
619 sbs = SwitchboardSession(self.factory.msncon, remoteUser, self.factory.msncon.session.highestResource(), True, host, port, key, sessionID)
620 if self.factory.msncon.switchboardSessions.has_key(remoteUser):
621 self.factory.msncon.switchboardSessions[remoteUser].removeMe()
622 self.factory.msncon.switchboardSessions[remoteUser] = sbs
623
624 def avatarHashChanged(self, userHandle, hash):
625 if self.badConditions(): return
626 LogEvent(INFO, self.factory.msncon.session.jabberID)
627 hash = base64.decodestring(hash)
628 hash = binascii.hexlify(hash)
629 self.factory.msncon.avatarHashChanged(userHandle, hash)
630
631 def contactStatusChanged(self, statusCode, userHandle, screenName):
632 if self.badConditions(): return
633 LogEvent(INFO, self.factory.msncon.session.jabberID)
634
635 self.factory.msncon.contactStatusChanged(userHandle)
636
637 def contactPersonalChanged(self, userHandle, personal):
638 if self.badConditions(): return
639 msn.NotificationClient.contactPersonalChanged(self, userHandle, personal)
640 self.factory.msncon.contactStatusChanged(userHandle)
641
642 def contactOffline(self, userHandle):
643 if self.badConditions(): return
644 LogEvent(INFO, self.factory.msncon.session.jabberID)
645 msn.NotificationClient.contactOffline(self, userHandle)
646
647 self.factory.msncon.contactStatusChanged(userHandle)
648
649 def userAddedMe(self, userGuid, userHandle, screenName):
650 if self.badConditions(): return
651 LogEvent(INFO, self.factory.msncon.session.jabberID)
652 msn.NotificationClient.userAddedMe(self, userGuid, userHandle, screenName)
653 self.factory.msncon.userAddedMe(userHandle)
654
655 def userRemovedMe(self, userGuid, userHandle):
656 if self.badConditions(): return
657 LogEvent(INFO, self.factory.msncon.session.jabberID)
658 msn.NotificationClient.userRemovedMe(self, userGuid, userHandle)
659 self.factory.msncon.userRemovedMe(userHandle)
660
661 def multipleLogin(self):
662 if self.badConditions(): return
663 LogEvent(INFO, self.factory.msncon.session.jabberID)
664 self.factory.msncon.multipleLogin()
665
666
667
668 class SwitchboardFactory(ClientFactory):
669 def __init__(self, switchboardSession, key, sessionID=None, reply=False):
670 self.switchboardSession = switchboardSession
671 self.key = key
672 self.sessionID = sessionID
673 self.reply = reply
674
675 def buildProtocol(self, addr):
676 p = Switchboard(self.switchboardSession)
677 if p.badConditions(): return p
678 p.key = self.key
679 p.sessionID = self.sessionID
680 p.reply = self.reply
681 p.userHandle = self.switchboardSession.msncon.username
682 p.factory = self
683 return p
684
685 class Switchboard(msn.SwitchboardClient):
686 def __init__(self, switchboardSession):
687 self.removed = False
688
689 self.switchboardSession = switchboardSession
690 self.chattingUsers = []
691 self.callid = None
692 msn.SwitchboardClient.__init__(self)
693 if self.badConditions(): return
694 self.msnobj = self.switchboardSession.msncon.notificationProtocol.msnobj
695 LogEvent(INFO, self.switchboardSession.ident)
696
697 def removeMe(self):
698 if self.removed:
699 log.err("removeMe called more than once!")
700 return
701 self.removed = True
702
703 self.transport.loseConnection()
704 LogEvent(INFO, self.switchboardSession.ident)
705 self.switchboardSession = None
706 self.factory.switchboardSession = None
707 self.factory = None
708
709 if self.callid and not self.callid.called:
710 self.callid.cancel() # Cancel the invite fail message
711 self.callid = None
712
713 utils.mutilateMe(self)
714
715 def badConditions(self):
716 if not (self.switchboardSession and self.switchboardSession.msncon and self.switchboardSession.msncon.session and self.switchboardSession.msncon.session.alive):
717 if self.switchboardSession:
718 if not self.switchboardSession.removed:
719 self.switchboardSession.removeMe()
720 elif not self.removed:
721 self.removeMe()
722 return True
723 return False
724
725 def loggedIn(self):
726 if self.badConditions(): return
727 if (not self.reply) and self.switchboardSession.__class__ == SwitchboardSession:
728 def failCB(arg=None):
729 LogEvent(INFO, ident, "User has not joined after 30 seconds.")
730 self.switchboardSession.removeMe()
731 d = self.inviteUser(self.switchboardSession.remoteUser)
732 d.addErrback(failCB)
733 ident = self.switchboardSession.ident
734 # If the user doesn't join then we want to tear down the SwitchboardSession
735 self.callid = reactor.callLater(30.0, failCB)
736
737 else:
738 self.readySwitchboardSession()
739
740 def readySwitchboardSession(self):
741 self.switchboardSession.switchboardReady(self)
742 for user in self.chattingUsers:
743 self.switchboardSession.userJoined(user)
744 if self.callid and not self.callid.called:
745 self.callid.cancel() # Cancel the invite fail message (only applies if we needed to invite the user)
746 self.callid = None
747
748 def gotChattingUsers(self, users):
749 for user in users:
750 self.chattingUsers.append(user)
751
752 def userJoined(self, userHandle, screenName):
753 if self.badConditions(): return
754 # FIXME - check this is correct
755 if (not self.reply) and isinstance(self.switchboardSession, SwitchboardSession):
756 self.readySwitchboardSession()
757 LogEvent(INFO, self.switchboardSession.ident)
758 self.switchboardSession.userJoined(userHandle)
759
760 def userLeft(self, userHandle):
761 if self.badConditions(): return
762 LogEvent(INFO, self.switchboardSession.ident)
763 def wait():
764 if self.badConditions(): return
765 self.switchboardSession.userLeft(userHandle)
766 # Make sure this event is handled after any others (eg, gotMessage)
767 reactor.callLater(0, wait)
768
769 def gotMessage(self, message):
770 if self.badConditions():
771 LogEvent(WARN, self.switchboardSession.ident, "gotMessage() called too late. Dropped a message!")
772 return
773
774 LogEvent(INFO, self.switchboardSession.ident)
775 cTypes = [s.lstrip() for s in message.getHeader("Content-Type").split(';')]
776 if "text/plain" in cTypes:
777 try:
778 if len(cTypes) > 1 and cTypes[1].find("UTF-8") >= 0:
779 message.message = message.message.decode("utf-8")
780 self.switchboardSession.gotMessage(message)
781 except:
782 self.switchboardSession.gotMessage(lang.get(self.switchboardSession.msncon.session.lang).msnDroppedMessage) # FIXME, this is a little deep
783 raise
784 return
785 if "text/x-clientcaps" in cTypes:
786 if message.hasHeader("JabberID"):
787 jid = message.getHeader("JabberID")
788 self.switchboardSession.msncon.userMapping(message.userHandle, jid)
789 return
790 LogEvent(INFO, self.switchboardSession.ident, "Discarding unknown message type.")
791
792 def userTyping(self, message):
793 if self.badConditions(): return
794 if self.switchboardSession.__class__ == SwitchboardSession: # Ignore typing in groupchats
795 if message.userHandle == self.switchboardSession.remoteUser:
796 self.switchboardSession.contactTyping()
797
798 def sendClientCaps(self):
799 message = msn.MSNMessage()
800 message.setHeader("Content-Type", "text/x-clientcaps")
801 message.setHeader("Client-Name", "PyMSNt")
802 message.setHeader("JabberID", str(self.switchboardSession.msncon.session.jabberID)) # FIXME, this is a little deep
803 self.sendMessage(message)
804
805 def sendMessage(self, message):
806 # A little bit of fancyness to make sure that clientcaps
807 # only gets sent after the first text message.
808 if message.getHeader("Content-Type").startswith("text"):
809 self.sendMessage = type(self.sendMessage)(msn.SwitchboardClient.sendMessage, self, Switchboard)
810 self.sendClientCaps()
811 return self.sendMessage(message)
812 else:
813 return msn.SwitchboardClient.sendMessage(self, message)
814
815 def gotAvatarImage(self, to, image):
816 if self.badConditions(): return
817 self.switchboardSession.gotAvatarImage(to, image)
818
819