]> code.delx.au - pymsnt/blob - src/legacy/msn/msnw.py
Should fix PyMSNt not responding error.
[pymsnt] / src / legacy / msn / msnw.py
1 # Copyright 2004-2006 James Bunton <james@delx.cjb.net>
2 # Licensed for distribution under the GPL version 2, check COPYING for details
3
4 # Twisted imports
5 from twisted.internet import reactor
6 from twisted.internet.defer import Deferred
7 from twisted.internet.protocol import ClientFactory
8
9 # System imports
10 import math, base64, binascii
11
12 # Local imports
13 from debug import LogEvent, INFO, WARN, ERROR
14 import msn
15
16
17
18 """
19 All interaction should be with the MSNConnection and MultiSwitchboardSession classes.
20 You should not directly instantiate any objects of other classes.
21 """
22
23 class MSNConnection:
24 """ Manages all the Twisted factories, etc """
25 MAXMESSAGESIZE = 1400
26 SWITCHBOARDTIMEOUT = 30.0*60.0
27 GETALLAVATARS = False
28
29 def __init__(self, username, password, ident):
30 """ Connects to the MSN servers.
31 @param username: the MSN passport to connect with.
32 @param password: the password for this account.
33 @param ident: a unique identifier to use in logging.
34 """
35 self.username = username
36 self.password = password
37 self.ident = ident
38 self.timeout = None
39 self.notificationFactory = None
40 self.notificationClient = None
41 self.connect()
42 LogEvent(INFO, self.ident)
43
44 def connect(self):
45 """ Automatically called by the constructor """
46 self.connectors = []
47 self.switchboardSessions = {}
48 self.savedEvents = SavedEvents() # Save any events that occur before connect
49 self._getNotificationReferral()
50
51 def _getNotificationReferral(self):
52 def timeout():
53 self.timeout = None
54 dispatchFactory.d = None
55 if not d.called:
56 d.errback(Exception("Timeout"))
57 self.logOut() # Clean up everything
58 self.timeout = reactor.callLater(30, timeout)
59 dispatchFactory = msn.DispatchFactory()
60 dispatchFactory.userHandle = self.username
61 dispatchFactory.protocol = DispatchClient
62 d = Deferred()
63 dispatchFactory.d = d
64 d.addCallbacks(self._gotNotificationReferral, self.connectionFailed)
65 self.connectors.append(reactor.connectTCP("messenger.hotmail.com", 1863, dispatchFactory))
66 LogEvent(INFO, self.ident)
67
68 def _gotNotificationReferral(self, (host, port)):
69 self.timeout.cancel()
70 self.timeout = None
71 # Create the NotificationClient
72 self.notificationFactory = msn.NotificationFactory()
73 self.notificationFactory.userHandle = self.username
74 self.notificationFactory.password = self.password
75 self.notificationFactory.msncon = self
76 self.notificationFactory.protocol = NotificationClient
77 self.connectors.append(reactor.connectTCP(host, port, self.notificationFactory))
78 LogEvent(INFO, self.ident)
79
80 def _sendSavedEvents(self):
81 self.savedEvents.send(self)
82
83 def _notificationClientReady(self, notificationClient):
84 self.notificationClient = notificationClient
85
86 def _ensureSwitchboardSession(self, userHandle):
87 if not self.switchboardSessions.has_key(userHandle):
88 sb = OneSwitchboardSession(self, userHandle)
89 sb.connect()
90 self.switchboardSessions[userHandle] = sb
91
92
93
94 # API calls
95 def getContacts(self):
96 """ Gets the contact list.
97
98 @return an instance of MSNContactList (do not modify) if connected,
99 or None if not.
100 """
101 if self.notificationFactory:
102 return self.notificationFactory.contacts
103 else:
104 return None
105
106 def sendMessage(self, userHandle, text, noerror=False):
107 """
108 Sends a message to a contact. Can only be called after listSynchronized().
109
110 @param userHandle: the contact's MSN passport.
111 @param text: the text to send.
112 @param noerror: Set this to True if you don't want failed messages to bounce.
113 """
114 LogEvent(INFO, self.ident)
115 if self.notificationClient:
116 self._ensureSwitchboardSession(userHandle)
117 self.switchboardSessions[userHandle].sendMessage(text, noerror)
118 elif not noerror:
119 self.failedMessage(userHandle, text)
120
121 def sendAvatarRequest(self, userHandle):
122 """
123 Requests the avatar of a contact.
124
125 @param userHandle: the contact to request an avatar from.
126 @return: a Deferred() if the avatar can be fetched at this time.
127 This will fire with an argument of a tuple with the PNG
128 image data as the only element.
129 Otherwise returns None
130 """
131
132 LogEvent(INFO, self.ident)
133 if not self.notificationClient: return
134 if MSNConnection.GETALLAVATARS:
135 self._ensureSwitchboardSession(userHandle)
136 sb = self.switchboardSessions.get(userHandle)
137 if sb: return sb.sendAvatarRequest()
138
139 def sendFile(self, userHandle, filename, filesize):
140 """
141 Used to send a file to a contact.
142
143 @param username: the passport of the contact to send a file to.
144 @param filename: the name of the file to send.
145 @param filesize: the size of the file to send.
146
147 @return: A Deferred, which will fire with an argument of:
148 (fileSend, d) A FileSend object and a Deferred.
149 The new Deferred will pass one argument in a tuple,
150 whether or not the transfer is accepted. If you
151 receive a True, then you can call write() on the
152 fileSend object to send your file. Call close()
153 when the file is done.
154 NOTE: You MUST write() exactly as much as you
155 declare in filesize.
156 """
157 msnContact = self.getContacts().getContact(userHandle)
158 if not msnContact:
159 raise ValueError, "Contact not found"
160 self._ensureSwitchboardSession(userHandle)
161 return self.switchboardSessions[userHandle].sendFile(msnContact, filename, filesize)
162
163 def sendTypingToContact(self, userHandle):
164 """
165 Sends typing notification to a contact. Should send every 5secs.
166 @param userHandle: the contact to notify of our typing.
167 """
168
169 sb = self.switchboardSessions.get(userHandle)
170 if sb: return sb.sendTypingNotification()
171
172 def changeAvatar(self, imageData):
173 """
174 Changes the user's avatar.
175 @param imageData: the new PNG avatar image data.
176 """
177 if self.notificationClient:
178 LogEvent(INFO, self.ident)
179 self.notificationClient.changeAvatar(imageData, push=True)
180 # Save the avatar for reuse on disconnection
181 self.savedEvents.avatarImageData = imageData
182
183 def changeStatus(self, statusCode, screenName, personal):
184 """
185 Changes your status details. All details must be given with
186 each call. This can be called before connection if you wish.
187
188 @param statusCode: the user's new status (look in msn.statusCodes).
189 @param screenName: the user's new screenName (up to 127 characters).
190 @param personal: the user's new personal message.
191 """
192
193 if not screenName: screenName = self.username
194 if not statusCode: statusCode = msn.STATUS_ONLINE
195 if not personal: personal = ""
196 if self.notificationClient:
197 changeCount = [0] # Hack for Python's limited scope :(
198 def cb(ignored=None):
199 changeCount[0] += 1
200 if changeCount[0] == 3:
201 self.ourStatusChanged(statusCode, screenName, personal)
202 def errcb(ignored=None):
203 pass # FIXME, should we do something here?
204 LogEvent(INFO, self.ident)
205 self.notificationClient.changeStatus(statusCode.encode("utf-8")).addCallbacks(cb, errcb)
206 self.notificationClient.changeScreenName(screenName.encode("utf-8")).addCallbacks(cb, errcb)
207 self.notificationClient.changePersonalMessage(personal.encode("utf-8")).addCallbacks(cb, errcb)
208 # Remember the saved status
209 self.savedEvents.statusCode = statusCode
210 self.savedEvents.screenName = screenName
211 self.savedEvents.personal = personal
212
213 def addContact(self, listType, userHandle):
214 """ See msn.NotificationClient.addContact """
215 if self.notificationClient:
216 return self.notificationClient.addContact(listType, str(userHandle))
217 else:
218 self.savedEvents.addContacts.append((listType, str(userHandle)))
219
220 def remContact(self, listType, userHandle):
221 """ See msn.NotificationClient.remContact """
222 if self.notificationClient:
223 return self.notificationClient.remContact(listType, str(userHandle))
224 else:
225 self.savedEvents.remContacts.append((listType, str(userHandle)))
226
227 def logOut(self):
228 """ Shuts down the whole connection. Don't try to call any
229 other methods after this one. Except maybe connect() """
230 if self.notificationClient:
231 self.notificationClient.logOut()
232 for c in self.connectors:
233 c.disconnect()
234 self.connectors = []
235 if self.notificationFactory:
236 self.notificationFactory.stopTrying()
237 self.notificationFactory.msncon = None
238 self.notificationFactory = None
239 for sbs in self.switchboardSessions.values():
240 if hasattr(sbs, "transport") and sbs.transport:
241 sbs.transport.loseConnection()
242 self.switchboardSessions = {}
243 if self.timeout:
244 self.timeout.cancel()
245 self.timeout = None
246 LogEvent(INFO, self.ident)
247
248
249 # Reimplement these!
250 def connectionFailed(self, reason=''):
251 """ Called when the connection to the server failed. """
252
253 def loginFailed(self, reason=''):
254 """ Called when the account could not be logged in. """
255
256 def connectionLost(self, reason=''):
257 """ Called when we are disconnected. """
258
259 def multipleLogin(self):
260 """ Called when the server says there has been another login
261 for this account. """
262
263 def serverGoingDown(self):
264 """ Called when the server says that it will be going down. """
265
266 def accountNotVerified(self):
267 """ Called if this passport has not been verified. Certain
268 functions are not available. """
269
270 def userMapping(self, passport, jid):
271 """ Called when it is brought to our attention that one of the
272 MSN contacts has a Jabber ID. You should communicate with Jabber. """
273
274 def loggedIn(self):
275 """ Called when we have authenticated, but before we receive
276 the contact list. """
277
278 def listSynchronized(self):
279 """ Called when we have received the contact list. All methods
280 in this class are now valid. """
281
282 def ourStatusChanged(self, statusCode, screenName, personal):
283 """ Called when the user's status has changed. """
284
285 def gotMessage(self, userHandle, text):
286 """ Called when a contact sends us a message """
287
288 def gotGroupchat(self, msnGroupchat, userHandle):
289 """ Called when a conversation with more than one contact begins.
290 userHandle is the person who invited us.
291 The overriding method is expected to set msnGroupchat.groupchat to an object
292 that implements the following methods:
293 contactJoined(userHandle)
294 contactLeft(userHandle)
295 gotMessage(userHandle, text)
296
297 The object received as 'msnGroupchat' is an instance of MultiSwitchboardSession.
298 """
299
300 def gotContactTyping(self, userHandle):
301 """ Called when a contact sends typing notification.
302 Will be called once every 5 seconds. """
303
304 def failedMessage(self, userHandle, text):
305 """ Called when a message we sent has been bounced back. """
306
307 def contactAvatarChanged(self, userHandle, hash):
308 """ Called when we receive a changed avatar hash for a contact.
309 You should call sendAvatarRequest(). """
310
311 def contactStatusChanged(self, userHandle):
312 """ Called when we receive status information for a contact. """
313
314 def gotFileReceive(self, fileReceive):
315 """ Called when a contact sends the user a file.
316 Call accept(fileHandle) or reject() on the object. """
317
318 def contactAddedMe(self, userHandle):
319 """ Called when a contact adds the user to their list. """
320
321 def contactRemovedMe(self, userHandle):
322 """ Called when a contact removes the user from their list. """
323
324 def gotInitialEmailNotification(self, inboxunread, foldersunread):
325 """ Received at login to tell about the user's Hotmail status """
326
327 def gotRealtimeEmailNotification(self, mailfrom, fromaddr, subject):
328 """ Received in realtime whenever an email comes into the hotmail account """
329
330 def gotMSNAlert(self, body, action, subscr):
331 """ An MSN Alert (http://alerts.msn.com) was received. Body is the
332 text of the alert. 'action' is a url for more information,
333 'subscr' is a url to modify your your alerts subscriptions. """
334
335 def gotAvatarImageData(self, userHandle, imageData):
336 """ An contact's avatar has been received because a switchboard
337 session with them was started. """
338
339
340 class SavedEvents:
341 def __init__(self):
342 self.screenName = ""
343 self.statusCode = ""
344 self.personal = ""
345 self.avatarImageData = ""
346 self.addContacts = []
347 self.remContacts = []
348
349 def send(self, msncon):
350 if self.avatarImageData:
351 msncon.notificationClient.changeAvatar(self.avatarImageData, push=False)
352 if self.screenName or self.statusCode or self.personal:
353 msncon.changeStatus(self.statusCode, self.screenName, self.personal)
354 for listType, userHandle in self.addContacts:
355 msncon.addContact(listType, userHandle)
356 for listType, userHandle in self.remContacts:
357 msncon.remContact(listType, userHandle)
358
359
360
361 class DispatchClient(msn.DispatchClient):
362 def gotNotificationReferral(self, host, port):
363 d = self.factory.d
364 self.factory.d = None
365 if not d or d.called:
366 return # Too slow! We've already timed out
367 d.callback((host, port))
368
369
370 class NotificationClient(msn.NotificationClient):
371 def doDisconnect(self, *args):
372 if hasattr(self, "transport") and self.transport:
373 self.transport.loseConnection()
374
375 def loginFailure(self, message):
376 self.factory.msncon.loginFailed(message)
377
378 def loggedIn(self, userHandle, verified):
379 LogEvent(INFO, self.factory.msncon.ident)
380 msn.NotificationClient.loggedIn(self, userHandle, verified)
381 self.factory.msncon._notificationClientReady(self)
382 self.factory.msncon.loggedIn()
383 if not verified:
384 self.factory.msncon.accountNotVerified()
385
386 def logOut(self):
387 msn.NotificationClient.logOut(self)
388 # If we explicitly log out, then all of these events
389 # are now redundant
390 self.loginFailure = self.doDisconnect
391 self.loggedIn = self.doDisconnect
392 self.connectionLost = lambda reason: msn.NotificationClient.connectionLost(self, reason)
393
394 def connectionLost(self, reason):
395 if not self.factory.msncon:
396 # If MSNConnection.logOut is called before _notificationClientReady
397 return
398
399 def wait():
400 LogEvent(INFO, self.factory.msncon.ident)
401 msn.NotificationClient.connectionLost(self, reason)
402 if self.factory.maxRetries > self.factory.retries:
403 self.factory.stopTrying()
404 self.factory.msncon.connectionLost(reason)
405 # Make sure this event is handled after any others
406 reactor.callLater(0, wait)
407
408 def gotMSNAlert(self, body, action, subscr):
409 LogEvent(INFO, self.factory.msncon.ident)
410 self.factory.msncon.gotMSNAlert(body, action, subscr)
411
412 def gotInitialEmailNotification(self, inboxunread, foldersunread):
413 LogEvent(INFO, self.factory.msncon.ident)
414 self.factory.msncon.gotInitialEmailNotification(inboxunread, foldersunread)
415
416 def gotRealtimeEmailNotification(self, mailfrom, fromaddr, subject):
417 LogEvent(INFO, self.factory.msncon.ident)
418 self.factory.msncon.gotRealtimeEmailNotification(mailfrom, fromaddr, subject)
419
420 def userAddedMe(self, userGuid, userHandle, screenName):
421 LogEvent(INFO, self.factory.msncon.ident)
422 self.factory.msncon.contactAddedMe(userHandle)
423
424 def userRemovedMe(self, userHandle):
425 LogEvent(INFO, self.factory.msncon.ident)
426 self.factory.msncon.contactRemovedMe(userHandle)
427
428 def listSynchronized(self, *args):
429 LogEvent(INFO, self.factory.msncon.ident)
430 self.factory.msncon._sendSavedEvents()
431 self.factory.msncon.listSynchronized()
432
433 def contactAvatarChanged(self, userHandle, hash):
434 LogEvent(INFO, self.factory.msncon.ident)
435 self.factory.msncon.contactAvatarChanged(userHandle, hash)
436
437 def gotContactStatus(self, userHandle, statusCode, screenName):
438 LogEvent(INFO, self.factory.msncon.ident)
439 self.factory.msncon.contactStatusChanged(userHandle)
440
441 def contactStatusChanged(self, userHandle, statusCode, screenName):
442 LogEvent(INFO, self.factory.msncon.ident)
443 self.factory.msncon.contactStatusChanged(userHandle)
444
445 def contactPersonalChanged(self, userHandle, personal):
446 LogEvent(INFO, self.factory.msncon.ident)
447 self.factory.msncon.contactStatusChanged(userHandle)
448
449 def contactOffline(self, userHandle):
450 LogEvent(INFO, self.factory.msncon.ident)
451 self.factory.msncon.contactStatusChanged(userHandle)
452
453 def gotSwitchboardInvitation(self, sessionID, host, port, key, userHandle, screenName):
454 LogEvent(INFO, self.factory.msncon.ident)
455 sb = self.factory.msncon.switchboardSessions.get(userHandle)
456 if sb and sb.transport:
457 sb.transport.loseConnection()
458 sb = OneSwitchboardSession(self.factory.msncon, userHandle)
459 self.factory.msncon.switchboardSessions[userHandle] = sb
460 sb.connectReply(host, port, key, sessionID)
461
462 def multipleLogin(self):
463 LogEvent(INFO, self.factory.msncon.ident)
464 self.factory.msncon.multipleLogin()
465
466 def serverGoingDown(self):
467 LogEvent(INFO, self.factory.msncon.ident)
468 self.factory.msncon.serverGoingDown()
469
470
471
472 class SwitchboardSessionBase(msn.SwitchboardClient):
473 def __init__(self, msncon):
474 msn.SwitchboardClient.__init__(self)
475 self.msncon = msncon
476 self.msnobj = msncon.notificationClient.msnobj
477 self.userHandle = msncon.username
478 self.ident = (msncon.ident, "INVALID!!")
479 self.messageBuffer = []
480 self.funcBuffer = []
481 self.ready = False
482
483 def connectionLost(self, reason):
484 msn.SwitchboardClient.connectionLost(self, reason)
485 LogEvent(INFO, self.ident)
486 self.ready = False
487 self.msncon = None
488 self.msnobj = None
489 self.ident = (self.ident[0], self.ident[1], "Disconnected!")
490
491 def loggedIn(self):
492 LogEvent(INFO, self.ident)
493 self.ready = True
494 self.flushBuffer()
495
496 def connect(self):
497 LogEvent(INFO, self.ident)
498 self.ready = False
499 def sbRequestAccepted((host, port, key)):
500 LogEvent(INFO, self.ident)
501 self.key = key
502 self.reply = 0
503 factory = ClientFactory()
504 factory.buildProtocol = lambda addr: self
505 reactor.connectTCP(host, port, factory)
506 def sbRequestFailed(ignored=None):
507 LogEvent(INFO, self.ident)
508 del self.msncon.switchboardSessions[self.remoteUser]
509 d = self.msncon.notificationClient.requestSwitchboardServer()
510 d.addCallbacks(sbRequestAccepted, sbRequestFailed)
511
512 def connectReply(self, host, port, key, sessionID):
513 LogEvent(INFO, self.ident)
514 self.ready = False
515 self.key = key
516 self.sessionID = sessionID
517 self.reply = 1
518 factory = ClientFactory()
519 factory.buildProtocol = lambda addr: self
520 reactor.connectTCP(host, port, factory)
521
522 def flushBuffer(self):
523 for message, noerror in self.messageBuffer[:]:
524 self.messageBuffer.remove((message, noerror))
525 self.sendMessage(message, noerror)
526 for f in self.funcBuffer[:]:
527 self.funcBuffer.remove(f)
528 f()
529
530 def failedMessage(self, *ignored):
531 raise NotImplementedError
532
533 def sendClientCaps(self):
534 message = msn.MSNMessage()
535 message.setHeader("Content-Type", "text/x-clientcaps")
536 message.setHeader("Client-Name", "PyMSNt")
537 if hasattr(self.msncon, "jabberID"):
538 message.setHeader("JabberID", str(self.msncon.jabberID))
539 self.sendMessage(message)
540
541 def sendMessage(self, message, noerror=False):
542 # Check to make sure that clientcaps only gets sent after
543 # the first text type message.
544 if isinstance(message, msn.MSNMessage) and message.getHeader("Content-Type").startswith("text"):
545 self.sendMessage = self.sendMessageReal
546 self.sendClientCaps()
547 return self.sendMessage(message, noerror)
548 else:
549 return self.sendMessageReal(message, noerror)
550
551 def sendMessageReal(self, text, noerror=False):
552 if not isinstance(text, basestring):
553 msn.SwitchboardClient.sendMessage(self, text)
554 return
555 if not self.ready:
556 self.messageBuffer.append((text, noerror))
557 else:
558 LogEvent(INFO, self.ident)
559 text = str(text.replace("\n", "\r\n").encode("utf-8"))
560 def failedMessage(ignored):
561 if not noerror:
562 self.failedMessage(text)
563
564 if len(text) < MSNConnection.MAXMESSAGESIZE:
565 message = msn.MSNMessage(message=text)
566 message.ack = msn.MSNMessage.MESSAGE_NACK
567
568 d = msn.SwitchboardClient.sendMessage(self, message)
569 if not noerror:
570 d.addCallbacks(failedMessage, failedMessage)
571
572 else:
573 chunks = int(math.ceil(len(text) / float(MSNConnection.MAXMESSAGESIZE)))
574 chunk = 0
575 guid = msn.random_guid()
576 while chunk < chunks:
577 offset = chunk * MSNConnection.MAXMESSAGESIZE
578 message = msn.MSNMessage(message=text[offset : offset + MSNConnection.MAXMESSAGESIZE])
579 message.ack = msn.MSNMessage.MESSAGE_NACK
580 message.setHeader("Message-ID", guid)
581 if chunk == 0:
582 message.setHeader("Chunks", str(chunks))
583 else:
584 message.delHeader("MIME-Version")
585 message.delHeader("Content-Type")
586 message.setHeader("Chunk", str(chunk))
587
588 d = msn.SwitchboardClient.sendMessage(self, message)
589 if not noerror:
590 d.addCallbacks(failedMessage, failedMessage)
591
592 chunk += 1
593
594
595 class MultiSwitchboardSession(SwitchboardSessionBase):
596 """ Create one of me to chat to multiple contacts """
597
598 def __init__(self, msncon):
599 """ Automatically creates a new switchboard connection to the server """
600 SwitchboardSessionBase.__init__(self, msncon)
601 self.ident = (self.msncon.ident, repr(self))
602 self.contactCount = 0
603 self.groupchat = None
604 self.connect()
605
606 def failedMessage(self, text):
607 self.groupchat.gotMessage("BOUNCE", text)
608
609 def sendMessage(self, text, noerror=False):
610 """ Used to send a mesage to the groupchat. Can be called immediately
611 after instantiation. """
612 if self.contactCount > 0:
613 SwitchboardSessionBase.sendMessage(self, text, noerror)
614 else:
615 #self.messageBuffer.append((message, noerror))
616 pass # They're sending messages to an empty room. Ignore.
617
618 def inviteUser(self, userHandle):
619 """ Used to invite a contact to the groupchat. Can be called immediately
620 after instantiation. """
621 userHandle = str(userHandle)
622 if self.ready:
623 LogEvent(INFO, self.ident, "immediate")
624 msn.SwitchboardClient.inviteUser(self, userHandle)
625 else:
626 LogEvent(INFO, self.ident, "pending")
627 self.funcBuffer.append(lambda: msn.SwitchboardClient.inviteUser(self, userHandle))
628
629 def gotMessage(self, message):
630 self.groupchat.gotMessage(message.userHandle, message.getMessage())
631
632 def userJoined(self, userHandle, screenName=''):
633 LogEvent(INFO, self.ident)
634 self.contactCount += 1
635 self.groupchat.contactJoined(userHandle)
636
637 def userLeft(self, userHandle):
638 LogEvent(INFO, self.ident)
639 self.contactCount -= 1
640 self.groupchat.contactLeft(userHandle)
641
642
643
644 class OneSwitchboardSession(SwitchboardSessionBase):
645 def __init__(self, msncon, remoteUser):
646 SwitchboardSessionBase.__init__(self, msncon)
647 self.remoteUser = str(remoteUser)
648 self.ident = (self.msncon.ident, self.remoteUser)
649 self.chattingUsers = []
650 self.timeout = None
651
652 def connectionLost(self, reason):
653 if self.timeout:
654 self.timeout.cancel()
655 self.timeout = None
656 for message, noerror in self.messageBuffer:
657 if not noerror:
658 self.failedMessage(message)
659 self.messageBuffer = []
660
661 if self.msncon and self.msncon.switchboardSessions.has_key(self.remoteUser):
662 # Unexpected disconnection. Must remove us from msncon
663 self.msncon.switchboardSessions.pop(self.remoteUser)
664
665 SwitchboardSessionBase.connectionLost(self, reason)
666
667 def _ready(self):
668 LogEvent(INFO, self.ident)
669 self.ready = True
670 for user in self.chattingUsers:
671 self.userJoined(user)
672 if self.timeout:
673 self.timeout.cancel()
674 self.timeout = None
675 self.flushBuffer()
676
677 def _switchToMulti(self, userHandle):
678 LogEvent(INFO, self.ident)
679 del self.msncon.switchboardSessions[self.remoteUser]
680 self.__class__ = MultiSwitchboardSession
681 del self.remoteUser
682 self.contactCount = 0
683 self.msncon.gotGroupchat(self, userHandle)
684 assert self.groupchat
685
686 def failedMessage(self, text):
687 self.msncon.failedMessage(self.remoteUser, text)
688
689 # Callbacks
690 def loggedIn(self):
691 LogEvent(INFO, self.ident)
692 if not self.reply:
693 def failCB(arg=None):
694 self.timeout = None
695 self.transport.loseConnection()
696 if not (self.msncon and self.msncon.switchboardSessions.has_key(self.remoteUser)):
697 return
698 LogEvent(INFO, self.ident, "User has not joined after 30 seconds.")
699 del self.msncon.switchboardSessions[self.remoteUser]
700 d = self.inviteUser(self.remoteUser)
701 d.addErrback(failCB)
702 self.timeout = reactor.callLater(30.0, failCB)
703 else:
704 self._ready()
705
706 def gotChattingUsers(self, users):
707 for userHandle in users.keys():
708 self.chattingUsers.append(userHandle)
709
710 def userJoined(self, userHandle, screenName=''):
711 LogEvent(INFO, self.ident)
712 if not self.reply and not self.ready:
713 self._ready()
714 if userHandle != self.remoteUser:
715 # Another user has joined, so we now have three participants.
716 remoteUser = self.remoteUser
717 self._switchToMulti(remoteUser)
718 self.userJoined(remoteUser)
719 self.userJoined(userHandle)
720 else:
721 def updateAvatarCB((imageData, )):
722 if self.msncon:
723 self.msncon.gotAvatarImageData(self.remoteUser, imageData)
724 d = self.sendAvatarRequest()
725 if d:
726 d.addCallback(updateAvatarCB)
727
728 def userLeft(self, userHandle):
729 def wait():
730 if userHandle == self.remoteUser:
731 if self.msncon and self.msncon.switchboardSessions.has_key(self.remoteUser):
732 del self.msncon.switchboardSessions[self.remoteUser]
733 reactor.callLater(0, wait) # Make sure this is handled after everything else
734
735 def gotMessage(self, message):
736 LogEvent(INFO, self.ident)
737 cTypes = [s.strip() for s in message.getHeader("Content-Type").split(';')]
738 if "text/plain" == cTypes[0]:
739 try:
740 if len(cTypes) > 1 and cTypes[1].lower().find("utf-8") >= 0:
741 text = message.getMessage().decode("utf-8")
742 else:
743 text = message.getMessage()
744 self.msncon.gotMessage(self.remoteUser, text)
745 except UnicodeDecodeError:
746 LogEvent(WARN, self.ident, "Message lost!")
747 self.msncon.gotMessage(self.remoteUser, "A message was lost.")
748 raise
749 elif "text/x-clientcaps" == cTypes[0]:
750 if message.hasHeader("JabberID"):
751 jid = message.getHeader("JabberID")
752 self.msncon.userMapping(message.userHandle, jid)
753 else:
754 LogEvent(INFO, self.ident, "Discarding unknown message type.")
755
756 def gotFileReceive(self, fileReceive):
757 LogEvent(INFO, self.ident)
758 self.msncon.gotFileReceive(fileReceive)
759
760 def gotContactTyping(self, message):
761 LogEvent(INFO, self.ident)
762 self.msncon.gotContactTyping(message.userHandle)
763
764 def sendTypingNotification(self):
765 LogEvent(INFO, self.ident)
766 if self.ready:
767 msn.SwitchboardClient.sendTypingNotification(self)
768
769 CAPS = msn.MSNContact.MSNC1 | msn.MSNContact.MSNC2 | msn.MSNContact.MSNC3 | msn.MSNContact.MSNC4
770 def sendAvatarRequest(self):
771 if not self.ready: return
772 msnContacts = self.msncon.getContacts()
773 if not msnContacts: return
774 msnContact = msnContacts.getContact(self.remoteUser)
775 if not (msnContact and msnContact.caps & self.CAPS and msnContact.msnobj): return
776 if msnContact.msnobjGot: return
777 msnContact.msnobjGot = True # This is deliberately set before we get the avatar. So that we don't try to reget failed avatars over & over
778 return msn.SwitchboardClient.sendAvatarRequest(self, msnContact)
779
780 def sendFile(self, msnContact, filename, filesize):
781 def doSendFile(ignored=None):
782 d.callback(msn.SwitchboardClient.sendFile(self, msnContact, filename, filesize))
783 d = Deferred()
784 if self.ready:
785 reactor.callLater(0, doSendFile)
786 else:
787 self.funcBuffer.append(doSendFile)
788 return d
789