]>
code.delx.au - pymsnt/blob - src/tlib/msn/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
5 from twisted
.internet
import reactor
6 from twisted
.internet
.defer
import Deferred
7 from twisted
.internet
.protocol
import ClientFactory
10 import math
, base64
, binascii
, math
13 from debug
import LogEvent
, INFO
, WARN
, ERROR
14 from tlib
.msn
import msn
18 SWITCHBOARDTIMEOUT
= 30.0*60.0
23 All interaction should be with the MSNConnection and MultiSwitchboardSession classes.
24 You should not directly instantiate any objects of other classes.
28 """ Manages all the Twisted factories, etc """
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.
35 self
.username
= username
36 self
.password
= password
39 self
.notificationClient
= None
41 LogEvent(INFO
, self
.ident
)
44 """ Automatically called by the constructor """
46 self
.switchboardSessions
= {}
47 self
.savedEvents
= SavedEvents() # Save any events that occur before connect
48 self
._getNotificationReferral
()
50 def _getNotificationReferral(self
):
52 if not d
.called
: d
.errback()
53 self
.timeout
= reactor
.callLater(30, timeout
)
54 dispatchFactory
= msn
.DispatchFactory()
55 dispatchFactory
.userHandle
= self
.username
56 dispatchFactory
.protocol
= DispatchClient
59 d
.addCallbacks(self
._gotNotificationReferral
, self
.connectionFailed
)
60 self
.connectors
.append(reactor
.connectTCP("messenger.hotmail.com", 1863, dispatchFactory
))
61 LogEvent(INFO
, self
.ident
)
63 def _gotNotificationReferral(self
, (host
, port
)):
65 # Create the NotificationClient
66 self
.notificationFactory
= msn
.NotificationFactory()
67 self
.notificationFactory
.userHandle
= self
.username
68 self
.notificationFactory
.password
= self
.password
69 self
.notificationFactory
.msncon
= self
70 self
.notificationFactory
.protocol
= NotificationClient
71 self
.connectors
.append(reactor
.connectTCP(host
, port
, self
.notificationFactory
))
72 LogEvent(INFO
, self
.ident
)
74 def _sendSavedEvents(self
):
75 self
.savedEvents
.send(self
)
76 self
.savedEvents
= None
78 def _notificationClientReady(self
, notificationClient
):
79 self
.notificationClient
= notificationClient
81 def _ensureSwitchboardSession(self
, userHandle
):
82 if not self
.switchboardSessions
.has_key(userHandle
):
83 sb
= OneSwitchboardSession(self
, userHandle
)
85 self
.switchboardSessions
[userHandle
] = sb
90 def getContacts(self
):
91 """ Gets the contact list.
93 @return an instance of MSNContactList (do not modify) if connected,
96 if self
.notificationFactory
:
97 return self
.notificationFactory
.contacts
101 def sendMessage(self
, userHandle
, text
, noerror
=False):
103 Sends a message to a contact. Can only be called after listSynchronized().
105 @param userHandle: the contact's MSN passport.
106 @param text: the text to send.
107 @param noerror: Set this to True if you don't want failed messages to bounce.
109 LogEvent(INFO
, self
.ident
)
110 if self
.notificationClient
:
111 self
._ensureSwitchboardSession
(userHandle
)
112 self
.switchboardSessions
[userHandle
].sendMessage(text
, noerror
)
114 self
.failedMessage(userHandle
, text
)
116 def sendAvatarRequest(self
, userHandle
):
118 Requests the avatar of a contact.
120 @param userHandle: the contact to request an avatar from.
121 @return: a Deferred() if the avatar can be fetched at this time.
122 This will fire with an argument of a tuple with the PNG
123 image data as the only element.
124 Otherwise returns None
127 LogEvent(INFO
, self
.ident
)
128 if not self
.notificationClient
: return
130 self
._ensureSwitchboardSession
(userHandle
)
131 sb
= self
.switchboardSessions
.get(userHandle
)
132 if sb
: return sb
.sendAvatarRequest()
134 def sendFile(self
, userHandle
, filename
, filesize
):
136 Used to send a file to a contact.
138 @param username: the passport of the contact to send a file to.
139 @param filename: the name of the file to send.
140 @param filesize: the size of the file to send.
142 @return: A Deferred, which will fire with an argument of:
143 (fileSend, d) A FileSend object and a Deferred.
144 The Deferred will pass one argument in a tuple,
145 whether or not the transfer is accepted. If you
146 receive a True, then you can call write() on the
147 fileSend object to send your file. Call close()
148 when the file is done.
149 NOTE: You MUST write() exactly as much as you
152 msnContact
= self
.getContacts().getContact(userHandle
)
154 raise ValueError, "Contact not found"
155 self
._ensureSwitchboardSession
(userHandle
)
156 return self
.switchboardSessions
[userHandle
].sendFile(msnContact
, filename
, filesize
)
158 def sendTypingToContact(self
, userHandle
):
160 Sends typing notification to a contact. Should send every 5secs.
161 @param userHandle: the contact to notify of our typing.
164 sb
= self
.switchboardSessions
.get(userHandle
)
165 if sb
: return sb
.sendTypingNotification()
167 def changeAvatar(self
, imageData
):
169 Changes the user's avatar.
170 @param imageData: the new PNG avatar image data.
172 if self
.notificationClient
:
173 LogEvent(INFO
, self
.ident
)
174 self
.notificationClient
.changeAvatar(imageData
, push
=True)
176 self
.savedEvents
.avatarImageData
= imageData
178 def changeStatus(self
, statusCode
, screenName
, personal
):
180 Changes your status details. All details must be given with
181 each call. This can be called before connection if you wish.
183 @param statusCode: the user's new status (look in msn.statusCodes).
184 @param screenName: the user's new screenName (up to 127 characters).
185 @param personal: the user's new personal message.
188 if not screenName
: screenName
= self
.username
189 if self
.notificationClient
:
190 changeCount
= [0] # Hack
191 def cb(ignored
=None):
193 if changeCount
[0] == 3:
194 self
.ourStatusChanged(statusCode
, screenName
, personal
)
195 LogEvent(INFO
, self
.ident
)
196 self
.notificationClient
.changeStatus(statusCode
.encode("utf-8")).addCallback(cb
)
197 self
.notificationClient
.changeScreenName(screenName
.encode("utf-8")).addCallback(cb
)
198 if not personal
: personal
= ""
199 self
.notificationClient
.changePersonalMessage(personal
.encode("utf-8")).addCallback(cb
)
201 self
.savedEvents
.statusCode
= statusCode
202 self
.savedEvents
.screenName
= screenName
203 self
.savedEvents
.personal
= personal
205 def addContact(self
, listType
, userHandle
):
206 """ See msn.NotificationClient.addContact """
207 if self
.notificationClient
:
208 return self
.notificationClient
.addContact(listType
, str(userHandle
))
210 self
.savedEvents
.addContacts
.append((listType
, str(userHandle
)))
212 def remContact(self
, listType
, userHandle
):
213 """ See msn.NotificationClient.remContact """
214 if self
.notificationClient
:
215 return self
.notificationClient
.remContact(listType
, str(userHandle
))
217 self
.savedEvents
.remContacts
.append((listType
, str(userHandle
)))
220 """ Shuts down the whole connection. Don't try to call any
221 other methods after this one. """
222 if self
.notificationClient
:
223 self
.notificationClient
.logOut()
224 for c
in self
.connectors
:
226 if self
.notificationFactory
:
227 self
.notificationFactory
.msncon
= None
229 self
.switchboardSessions
= {}
230 LogEvent(INFO
, self
.ident
)
234 def connectionFailed(self
, reason
=''):
235 """ Called when the connection to the server failed. """
237 def loginFailed(self
, reason
=''):
238 """ Called when the account could not be logged in. """
240 def connectionLost(self
, reason
=''):
241 """ Called when we are disconnected. """
243 def multipleLogin(self
):
244 """ Called when the server says there has been another login
245 for this account. """
247 def serverGoingDown(self
):
248 """ Called when the server says that it will be going down. """
250 def accountNotVerified(self
):
251 """ Called if this passport has not been verified. Certain
252 functions are not available. """
254 def userMapping(self
, passport
, jid
):
255 """ Called when it is brought to our attention that one of the
256 MSN contacts has a Jabber ID. You should communicate with Jabber. """
259 """ Called when we have authenticated, but before we receive
260 the contact list. """
262 def listSynchronized(self
):
263 """ Called when we have received the contact list. All methods
264 in this class are now valid. """
266 def ourStatusChanged(self
, statusCode
, screenName
, personal
):
267 """ Called when the user's status has changed. """
269 def gotMessage(self
, userHandle
, text
):
270 """ Called when a contact sends us a message """
272 def gotGroupchat(self
, msnGroupchat
, userHandle
):
273 """ Called when a conversation with more than one contact begins.
274 userHandle is the person who invited us.
275 The overriding method is expected to set msnGroupchat.groupchat to an object
276 that implements the following methods:
277 contactJoined(userHandle)
278 contactLeft(userHandle)
279 gotMessage(userHandle, text)
281 The object received as 'msnGroupchat' is an instance of MultiSwitchboardSession.
284 def gotContactTyping(self
, userHandle
):
285 """ Called when a contact sends typing notification.
286 Will be called once every 5 seconds. """
288 def failedMessage(self
, userHandle
, text
):
289 """ Called when a message we sent has been bounced back. """
291 def contactAvatarChanged(self
, userHandle
, hash):
292 """ Called when we receive a changed avatar hash for a contact.
293 You should call sendAvatarRequest(). """
295 def contactStatusChanged(self
, userHandle
):
296 """ Called when we receive status information for a contact. """
298 def gotFileReceive(self
, fileReceive
):
299 """ Called when a contact sends the user a file.
300 Call accept(fileHandle) or reject() on the object. """
302 def contactAddedMe(self
, userHandle
):
303 """ Called when a contact adds the user to their list. """
305 def contactRemovedMe(self
, userHandle
):
306 """ Called when a contact removes the user from their list. """
308 def gotInitialEmailNotification(self
, inboxunread
, foldersunread
):
309 """ Received at login to tell about the user's Hotmail status """
311 def gotRealtimeEmailNotification(self
, mailfrom
, fromaddr
, subject
):
312 """ Received in realtime whenever an email comes into the hotmail account """
314 def gotMSNAlert(self
, body
, action
, subscr
):
315 """ An MSN Alert (http://alerts.msn.com) was received. Body is the
316 text of the alert. 'action' is a url for more information,
317 'subscr' is a url to modify your your alerts subscriptions. """
319 def gotAvatarImageData(self
, userHandle
, imageData
):
320 """ An contact's avatar has been received because a switchboard
321 session with them was started. """
329 self
.avatarImageData
= ""
330 self
.addContacts
= []
331 self
.remContacts
= []
333 def send(self
, msncon
):
334 if self
.avatarImageData
:
335 msncon
.notificationClient
.changeAvatar(self
.avatarImageData
, push
=False)
336 if self
.nickname
or self
.statusCode
or self
.personal
:
337 msncon
.changeStatus(self
.statusCode
, self
.nickname
, self
.personal
)
338 for listType
, userHandle
in self
.addContacts
:
339 msncon
.addContact(listType
, userHandle
)
340 for listType
, userHandle
in self
.remContacts
:
341 msncon
.remContact(listType
, userHandle
)
345 class DispatchClient(msn
.DispatchClient
):
346 def gotNotificationReferral(self
, host
, port
):
347 if self
.factory
.d
.called
: return # Too slow! We've already timed out
348 self
.factory
.d
.callback((host
, port
))
351 class NotificationClient(msn
.NotificationClient
):
352 def loginFailure(self
, message
):
353 self
.factory
.msncon
.loginFailed(message
)
355 def loggedIn(self
, userHandle
, verified
):
356 LogEvent(INFO
, self
.factory
.msncon
.ident
)
357 msn
.NotificationClient
.loggedIn(self
, userHandle
, verified
)
358 self
.factory
.msncon
._notificationClientReady
(self
)
359 self
.factory
.msncon
.loggedIn()
361 self
.factory
.msncon
.accountNotVerified()
364 msn
.NotificationClient
.logOut(self
)
366 def connectionLost(self
, reason
):
367 if not self
.factory
.msncon
: return # If we called logOut
369 LogEvent(INFO
, self
.factory
.msncon
.ident
)
370 msn
.NotificationClient
.connectionLost(self
, reason
)
371 self
.factory
.msncon
.connectionLost(reason
)
372 # Make sure this event is handled after any others
373 reactor
.callLater(0, wait
)
375 def gotMSNAlert(self
, body
, action
, subscr
):
376 LogEvent(INFO
, self
.factory
.msncon
.ident
)
377 self
.factory
.msncon
.gotMSNAlert(body
, action
, subscr
)
379 def gotInitialEmailNotification(self
, inboxunread
, foldersunread
):
380 LogEvent(INFO
, self
.factory
.msncon
.ident
)
381 self
.factory
.msncon
.gotInitialEmailNotification(inboxunread
, foldersunread
)
383 def gotRealtimeEmailNotification(self
, mailfrom
, fromaddr
, subject
):
384 LogEvent(INFO
, self
.factory
.msncon
.ident
)
385 self
.factory
.msncon
.gotRealtimeEmailNotification(mailfrom
, fromaddr
, subject
)
387 def userAddedMe(self
, userGuid
, userHandle
, screenName
):
388 LogEvent(INFO
, self
.factory
.msncon
.ident
)
389 self
.factory
.msncon
.contactAddedMe(userHandle
)
391 def userRemovedMe(self
, userHandle
):
392 LogEvent(INFO
, self
.factory
.msncon
.ident
)
393 self
.factory
.msncon
.contactRemovedMe(userHandle
)
395 def listSynchronized(self
, *args
):
396 LogEvent(INFO
, self
.factory
.msncon
.ident
)
397 self
.factory
.msncon
._sendSavedEvents
()
398 self
.factory
.msncon
.listSynchronized()
400 def contactAvatarChanged(self
, userHandle
, hash):
401 LogEvent(INFO
, self
.factory
.msncon
.ident
)
402 self
.factory
.msncon
.contactAvatarChanged(userHandle
, hash)
404 def gotContactStatus(self
, userHandle
, statusCode
, screenName
):
405 LogEvent(INFO
, self
.factory
.msncon
.ident
)
406 self
.factory
.msncon
.contactStatusChanged(userHandle
)
408 def contactStatusChanged(self
, userHandle
, statusCode
, screenName
):
409 LogEvent(INFO
, self
.factory
.msncon
.ident
)
410 self
.factory
.msncon
.contactStatusChanged(userHandle
)
412 def contactPersonalChanged(self
, userHandle
, personal
):
413 LogEvent(INFO
, self
.factory
.msncon
.ident
)
414 self
.factory
.msncon
.contactStatusChanged(userHandle
)
416 def contactOffline(self
, userHandle
):
417 LogEvent(INFO
, self
.factory
.msncon
.ident
)
418 self
.factory
.msncon
.contactStatusChanged(userHandle
)
420 def gotSwitchboardInvitation(self
, sessionID
, host
, port
, key
, userHandle
, screenName
):
421 LogEvent(INFO
, self
.factory
.msncon
.ident
)
422 sb
= self
.factory
.msncon
.switchboardSessions
.get(userHandle
)
423 if sb
and sb
.transport
:
424 sb
.transport
.loseConnection()
426 sb
= OneSwitchboardSession(self
.factory
.msncon
, userHandle
)
427 self
.factory
.msncon
.switchboardSessions
[userHandle
] = sb
428 sb
.connectReply(host
, port
, key
, sessionID
)
430 def multipleLogin(self
):
431 LogEvent(INFO
, self
.factory
.msncon
.ident
)
432 self
.factory
.msncon
.multipleLogin()
434 def serverGoingDown(self
):
435 LogEvent(INFO
, self
.factory
.msncon
.ident
)
436 self
.factory
.msncon
.serverGoingDown()
440 class SwitchboardSessionBase(msn
.SwitchboardClient
):
441 def __init__(self
, msncon
):
442 msn
.SwitchboardClient
.__init
__(self
)
444 self
.msnobj
= msncon
.notificationClient
.msnobj
445 self
.userHandle
= msncon
.username
446 self
.ident
= (msncon
.ident
, "INVALID!!")
447 self
.messageBuffer
= []
452 LogEvent(INFO
, self
.ident
)
454 self
.transport
.disconnect()
457 LogEvent(INFO
, self
.ident
)
462 LogEvent(INFO
, self
.ident
)
464 def sbRequestAccepted((host
, port
, key
)):
465 LogEvent(INFO
, self
.ident
)
468 factory
= ClientFactory()
469 factory
.buildProtocol
= lambda addr
: self
470 reactor
.connectTCP(host
, port
, factory
)
471 def sbRequestFailed(ignored
=None):
472 LogEvent(INFO
, self
.ident
)
473 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
474 d
= self
.msncon
.notificationClient
.requestSwitchboardServer()
475 d
.addCallbacks(sbRequestAccepted
, sbRequestFailed
)
477 def connectReply(self
, host
, port
, key
, sessionID
):
478 LogEvent(INFO
, self
.ident
)
481 self
.sessionID
= sessionID
483 factory
= ClientFactory()
484 factory
.buildProtocol
= lambda addr
: self
485 reactor
.connectTCP(host
, port
, factory
)
487 def flushBuffer(self
):
488 for message
, noerror
in self
.messageBuffer
[:]:
489 self
.messageBuffer
.remove((message
, noerror
))
490 self
.sendMessage(message
, noerror
)
491 for f
in self
.funcBuffer
[:]:
492 self
.funcBuffer
.remove(f
)
495 def failedMessage(self
, *ignored
):
496 raise NotImplementedError
498 def sendClientCaps(self
):
499 message
= msn
.MSNMessage()
500 message
.setHeader("Content-Type", "text/x-clientcaps")
501 message
.setHeader("Client-Name", "PyMSNt")
502 if hasattr(self
.msncon
, "jabberID"):
503 message
.setHeader("JabberID", str(self
.msncon
.jabberID
))
504 self
.sendMessage(message
)
506 def sendMessage(self
, message
, noerror
=False):
507 # Check to make sure that clientcaps only gets sent after
508 # the first text type message.
509 if isinstance(message
, msn
.MSNMessage
) and message
.getHeader("Content-Type").startswith("text"):
510 self
.sendMessage
= self
.sendMessageReal
511 self
.sendClientCaps()
512 return self
.sendMessage(message
, noerror
)
514 return self
.sendMessageReal(message
, noerror
)
516 def sendMessageReal(self
, text
, noerror
=False):
517 if not isinstance(text
, basestring
):
518 msn
.SwitchboardClient
.sendMessage(self
, text
)
521 self
.messageBuffer
.append((text
, noerror
))
523 LogEvent(INFO
, self
.ident
)
524 def failedMessage(ignored
):
526 self
.failedMessage(text
)
528 if len(text
) < MAXMESSAGESIZE
:
529 message
= msn
.MSNMessage(message
=str(text
.replace("\n", "\r\n").encode("utf-8")))
530 message
.setHeader("Content-Type", "text/plain; charset=UTF-8")
531 message
.ack
= msn
.MSNMessage
.MESSAGE_NACK
533 d
= msn
.SwitchboardClient
.sendMessage(self
, message
)
535 d
.addCallback(failedMessage
)
538 chunks
= int(math
.ceil(len(text
) / float(MAXMESSAGESIZE
)))
540 guid
= msn
.random_guid()
541 while chunk
< chunks
:
542 offset
= chunk
* MAXMESSAGESIZE
543 text
= message
[offset
: offset
+ MAXMESSAGESIZE
]
544 message
= msn
.MSNMessage(message
=str(text
.replace("\n", "\r\n").encode("utf-8")))
545 message
.ack
= msn
.MSNMessage
.MESSAGE_NACK
547 message
.setHeader("Content-Type", "text/plain; charset=UTF-8")
548 message
.setHeader("Chunks", str(chunks
))
550 message
.setHeader("Chunk", str(chunk
))
552 d
= msn
.SwitchboardClient
.sendMessage(self
, message
)
554 d
.addCallback(failedMessage
)
559 class MultiSwitchboardSession(SwitchboardSessionBase
):
560 """ Create one of me to chat to multiple contacts """
562 def __init__(self
, msncon
):
563 """ Automatically creates a new switchboard connection to the server """
564 SwitchboardSessionBase
.__init
__(self
, msncon
)
565 self
.ident
= (self
.msncon
.ident
, self
)
566 self
.contactCount
= 0
567 self
.groupchat
= None
570 def failedMessage(self
, text
):
571 self
.groupchat
.gotMessage("BOUNCE", text
)
573 def sendMessage(self
, text
, noerror
=False):
574 """ Used to send a mesage to the groupchat. Can be called immediately
575 after instantiation. """
576 if self
.contactCount
> 0:
577 SwitchboardSessionBase
.sendMessage(self
, text
, noerror
)
579 #self.messageBuffer.append((message, noerror))
580 pass # They're sending messages to an empty room. Ignore.
582 def inviteUser(self
, userHandle
):
583 """ Used to invite a contact to the groupchat. Can be called immediately
584 after instantiation. """
585 userHandle
= str(userHandle
)
587 LogEvent(INFO
, self
.ident
, "immediate")
588 msn
.SwitchboardClient
.inviteUser(self
, userHandle
)
590 LogEvent(INFO
, self
.ident
, "pending")
591 self
.funcBuffer
.append(lambda: msn
.SwitchboardClient
.inviteUser(self
, userHandle
))
593 def gotMessage(self
, message
):
594 self
.groupchat
.gotMessage(message
.userHandle
, message
.getMessage())
596 def userJoined(self
, userHandle
, screenName
=''):
597 LogEvent(INFO
, self
.ident
)
598 self
.contactCount
+= 1
599 self
.groupchat
.contactJoined(userHandle
)
601 def userLeft(self
, userHandle
):
602 LogEvent(INFO
, self
.ident
)
603 self
.contactCount
-= 1
604 self
.groupchat
.contactLeft(userHandle
)
608 class OneSwitchboardSession(SwitchboardSessionBase
):
609 def __init__(self
, msncon
, remoteUser
):
610 SwitchboardSessionBase
.__init
__(self
, msncon
)
611 self
.remoteUser
= str(remoteUser
)
612 self
.ident
= (self
.msncon
.ident
, self
.remoteUser
)
613 self
.chattingUsers
= []
617 for message
, noerror
in self
.messageBuffer
:
619 self
.failedMessage(self
.remoteUser
, message
)
622 LogEvent(INFO
, self
.ident
)
624 for user
in self
.chattingUsers
:
625 self
.userJoined(user
)
627 self
.timeout
.cancel()
631 def _switchToMulti(self
, userHandle
):
632 LogEvent(INFO
, self
.ident
)
633 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
634 self
.__class
__ = MultiSwitchboardSession
636 self
.contactCount
= 0
637 self
.msncon
.gotGroupchat(self
, userHandle
)
638 if not self
.groupchat
:
639 LogEvent(ERROR
, self
.ident
)
640 raise Exception("YouNeedAGroupchat-WeHaveAProblemError") # FIXME
642 def failedMessage(self
, text
):
643 self
.msncon
.failedMessage(self
.remoteUser
, text
)
647 LogEvent(INFO
, self
.ident
)
649 def failCB(arg
=None):
650 LogEvent(INFO
, self
.ident
, "User has not joined after 30 seconds.")
651 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
652 d
= self
.inviteUser(self
.remoteUser
)
654 self
.timeout
= reactor
.callLater(30.0, failCB
)
658 def gotChattingUsers(self
, users
):
659 for userHandle
in users
.keys():
660 self
.chattingUsers
.append(userHandle
)
662 def userJoined(self
, userHandle
, screenName
=''):
663 LogEvent(INFO
, self
.ident
)
666 if userHandle
!= self
.remoteUser
:
667 # Another user has joined, so we now have three participants.
668 remoteUser
= self
.remoteUser
669 self
._switchToMulti
(remoteUser
)
670 self
.userJoined(remoteUser
)
671 self
.userJoined(userHandle
)
673 def updateAvatarCB((imageData
, )):
675 self
.msncon
.gotAvatarImageData(self
.remoteUser
, imageData
)
676 d
= self
.sendAvatarRequest()
678 d
.addCallback(updateAvatarCB
)
680 def userLeft(self
, userHandle
):
682 if userHandle
== self
.remoteUser
:
683 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
684 reactor
.callLater(0, wait
) # Make sure this is handled after everything else
686 def gotMessage(self
, message
):
687 LogEvent(INFO
, self
.ident
)
688 cTypes
= [s
.strip() for s
in message
.getHeader("Content-Type").split(';')]
689 if "text/plain" == cTypes
[0]:
691 if len(cTypes
) > 1 and cTypes
[1].lower().find("utf-8") >= 0:
692 text
= message
.getMessage().decode("utf-8")
694 text
= message
.getMessage()
695 self
.msncon
.gotMessage(self
.remoteUser
, text
)
697 self
.msncon
.gotMessage("A message was lost.")
699 elif "text/x-clientcaps" == cTypes
[0]:
700 if message
.hasHeader("JabberID"):
701 jid
= message
.getHeader("JabberID")
702 self
.switchboardSession
.msncon
.userMapping(message
.userHandle
, jid
)
704 LogEvent(INFO
, self
.ident
, "Discarding unknown message type.")
706 def gotFileReceive(self
, fileReceive
):
707 LogEvent(INFO
, self
.ident
)
708 self
.msncon
.gotFileReceive(fileReceive
)
710 def gotContactTyping(self
, message
):
711 LogEvent(INFO
, self
.ident
)
712 self
.msncon
.gotContactTyping(message
.userHandle
)
714 def sendTypingNotification(self
):
715 LogEvent(INFO
, self
.ident
)
717 msn
.SwitchboardClient
.sendTypingNotification(self
)
719 CAPS
= msn
.MSNContact
.MSNC1 | msn
.MSNContact
.MSNC2 | msn
.MSNContact
.MSNC3 | msn
.MSNContact
.MSNC4
720 def sendAvatarRequest(self
):
721 if not self
.ready
: return
722 msnContacts
= self
.msncon
.getContacts()
723 if not msnContacts
: return
724 msnContact
= msnContacts
.getContact(self
.remoteUser
)
725 if not (msnContact
and msnContact
.caps
& self
.CAPS
and msnContact
.msnobj
): return
726 if msnContact
.msnobjGot
: return
727 msnContact
.msnobjGot
= True # This is deliberately set before we get the avatar. So that we don't try to reget failed avatars over & over
728 return msn
.SwitchboardClient
.sendAvatarRequest(self
, msnContact
)
730 def sendFile(self
, msnContact
, filename
, filesize
):
731 def doSendFile(ignored
=None):
732 d
.callback(msn
.SwitchboardClient
.sendFile(self
, msnContact
, filename
, filesize
))
735 reactor
.callLater(0, doSendFile
)
737 self
.funcBuffer
.append(doSendFile
)