]>
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
:
191 def cb(ignored
=None):
192 self
.changeCount
+= 1
193 if self
.changeCount
== 3:
194 self
.ourStatusChanged(statusCode
, screenName
, personal
)
196 LogEvent(INFO
, self
.ident
)
197 self
.notificationClient
.changeStatus(statusCode
.encode("utf-8")).addCallback(cb
)
198 self
.notificationClient
.changeScreenName(screenName
.encode("utf-8")).addCallback(cb
)
199 if not personal
: personal
= ""
200 self
.notificationClient
.changePersonalMessage(personal
.encode("utf-8")).addCallback(cb
)
202 self
.savedEvents
.statusCode
= statusCode
203 self
.savedEvents
.screenName
= screenName
204 self
.savedEvents
.personal
= personal
206 def addContact(self
, listType
, userHandle
):
207 """ See msn.NotificationClient.addContact """
208 if self
.notificationClient
:
209 return self
.notificationClient
.addContact(listType
, str(userHandle
))
211 self
.savedEvents
.addContacts
.append((listType
, str(userHandle
)))
213 def remContact(self
, listType
, userHandle
):
214 """ See msn.NotificationClient.remContact """
215 if self
.notificationClient
:
216 return self
.notificationClient
.remContact(listType
, str(userHandle
))
218 self
.savedEvents
.remContacts
.append((listType
, str(userHandle
)))
221 """ Shuts down the whole connection. Don't try to call any
222 other methods after this one. """
223 if self
.notificationClient
:
224 self
.notificationClient
.logOut()
225 for c
in self
.connectors
:
227 if self
.notificationFactory
:
228 self
.notificationFactory
.msncon
= None
230 self
.switchboardSessions
= {}
231 LogEvent(INFO
, self
.ident
)
235 def connectionFailed(self
, reason
=''):
236 """ Called when the connection to the server failed. """
238 def loginFailed(self
, reason
=''):
239 """ Called when the account could not be logged in. """
241 def connectionLost(self
, reason
=''):
242 """ Called when we are disconnected. """
244 def multipleLogin(self
):
245 """ Called when the server says there has been another login
246 for this account. """
248 def serverGoingDown(self
):
249 """ Called when the server says that it will be going down. """
251 def accountNotVerified(self
):
252 """ Called if this passport has not been verified. Certain
253 functions are not available. """
255 def userMapping(self
, passport
, jid
):
256 """ Called when it is brought to our attention that one of the
257 MSN contacts has a Jabber ID. You should communicate with Jabber. """
260 """ Called when we have authenticated, but before we receive
261 the contact list. """
263 def listSynchronized(self
):
264 """ Called when we have received the contact list. All methods
265 in this class are now valid. """
267 def ourStatusChanged(self
, statusCode
, screenName
, personal
):
268 """ Called when the user's status has changed. """
270 def gotMessage(self
, userHandle
, text
):
271 """ Called when a contact sends us a message """
273 def gotGroupchat(self
, msnGroupchat
, userHandle
):
274 """ Called when a conversation with more than one contact begins.
275 userHandle is the person who invited us.
276 The overriding method is expected to set msnGroupchat.groupchat to an object
277 that implements the following methods:
278 contactJoined(userHandle)
279 contactLeft(userHandle)
280 gotMessage(userHandle, text)
282 The object received as 'msnGroupchat' is an instance of MultiSwitchboardSession.
285 def gotContactTyping(self
, userHandle
):
286 """ Called when a contact sends typing notification.
287 Will be called once every 5 seconds. """
289 def failedMessage(self
, userHandle
, text
):
290 """ Called when a message we sent has been bounced back. """
292 def contactAvatarChanged(self
, userHandle
, hash):
293 """ Called when we receive a changed avatar hash for a contact.
294 You should call sendAvatarRequest(). """
296 def contactStatusChanged(self
, userHandle
):
297 """ Called when we receive status information for a contact. """
299 def gotFileReceive(self
, fileReceive
):
300 """ Called when a contact sends the user a file.
301 Call accept(fileHandle) or reject() on the object. """
303 def contactAddedMe(self
, userHandle
):
304 """ Called when a contact adds the user to their list. """
306 def contactRemovedMe(self
, userHandle
):
307 """ Called when a contact removes the user from their list. """
309 def gotInitialEmailNotification(self
, inboxunread
, foldersunread
):
310 """ Received at login to tell about the user's Hotmail status """
312 def gotRealtimeEmailNotification(self
, mailfrom
, fromaddr
, subject
):
313 """ Received in realtime whenever an email comes into the hotmail account """
315 def gotMSNAlert(self
, body
, action
, subscr
):
316 """ An MSN Alert (http://alerts.msn.com) was received. Body is the
317 text of the alert. 'action' is a url for more information,
318 'subscr' is a url to modify your your alerts subscriptions. """
326 self
.avatarImageData
= ""
327 self
.addContacts
= []
328 self
.remContacts
= []
330 def send(self
, msncon
):
331 if self
.avatarImageData
:
332 msncon
.notificationClient
.changeAvatar(self
.avatarImageData
, push
=False)
333 if self
.nickname
or self
.statusCode
or self
.personal
:
334 msncon
.changeStatus(self
.statusCode
, self
.nickname
, self
.personal
)
335 for listType
, userHandle
in self
.addContacts
:
336 msncon
.addContact(listType
, userHandle
)
337 for listType
, userHandle
in self
.remContacts
:
338 msncon
.remContact(listType
, userHandle
)
342 class DispatchClient(msn
.DispatchClient
):
343 def gotNotificationReferral(self
, host
, port
):
344 if self
.factory
.d
.called
: return # Too slow! We've already timed out
345 self
.factory
.d
.callback((host
, port
))
348 class NotificationClient(msn
.NotificationClient
):
349 def loginFailure(self
, message
):
350 self
.factory
.msncon
.loginFailed(message
)
352 def loggedIn(self
, userHandle
, verified
):
353 LogEvent(INFO
, self
.factory
.msncon
.ident
)
354 msn
.NotificationClient
.loggedIn(self
, userHandle
, verified
)
355 self
.factory
.msncon
._notificationClientReady
(self
)
356 self
.factory
.msncon
.loggedIn()
358 self
.factory
.msncon
.accountNotVerified()
361 msn
.NotificationClient
.logOut(self
)
363 def connectionLost(self
, reason
):
364 if not self
.factory
.msncon
: return # If we called logOut
366 LogEvent(INFO
, self
.factory
.msncon
.ident
)
367 msn
.NotificationClient
.connectionLost(self
, reason
)
368 self
.factory
.msncon
.connectionLost(reason
)
369 # Make sure this event is handled after any others
370 reactor
.callLater(0, wait
)
372 def gotMSNAlert(self
, body
, action
, subscr
):
373 LogEvent(INFO
, self
.factory
.msncon
.ident
)
374 self
.factory
.msncon
.gotMSNAlert(body
, action
, subscr
)
376 def gotInitialEmailNotification(self
, inboxunread
, foldersunread
):
377 LogEvent(INFO
, self
.factory
.msncon
.ident
)
378 self
.factory
.msncon
.gotInitialEmailNotification(inboxunread
, foldersunread
)
380 def gotRealtimeEmailNotification(self
, mailfrom
, fromaddr
, subject
):
381 LogEvent(INFO
, self
.factory
.msncon
.ident
)
382 self
.factory
.msncon
.gotRealtimeEmailNotification(mailfrom
, fromaddr
, subject
)
384 def userAddedMe(self
, userGuid
, userHandle
, screenName
):
385 LogEvent(INFO
, self
.factory
.msncon
.ident
)
386 self
.factory
.msncon
.contactAddedMe(userHandle
)
388 def userRemovedMe(self
, userHandle
):
389 LogEvent(INFO
, self
.factory
.msncon
.ident
)
390 self
.factory
.msncon
.contactRemovedMe(userHandle
)
392 def listSynchronized(self
, *args
):
393 LogEvent(INFO
, self
.factory
.msncon
.ident
)
394 self
.factory
.msncon
._sendSavedEvents
()
395 self
.factory
.msncon
.listSynchronized()
397 def contactAvatarChanged(self
, userHandle
, hash):
398 LogEvent(INFO
, self
.factory
.msncon
.ident
)
399 self
.factory
.msncon
.contactAvatarChanged(userHandle
, hash)
401 def gotContactStatus(self
, userHandle
, statusCode
, screenName
):
402 LogEvent(INFO
, self
.factory
.msncon
.ident
)
403 self
.factory
.msncon
.contactStatusChanged(userHandle
)
405 def contactStatusChanged(self
, userHandle
, statusCode
, screenName
):
406 LogEvent(INFO
, self
.factory
.msncon
.ident
)
407 self
.factory
.msncon
.contactStatusChanged(userHandle
)
409 def contactPersonalChanged(self
, userHandle
, personal
):
410 LogEvent(INFO
, self
.factory
.msncon
.ident
)
411 self
.factory
.msncon
.contactStatusChanged(userHandle
)
413 def contactOffline(self
, userHandle
):
414 LogEvent(INFO
, self
.factory
.msncon
.ident
)
415 self
.factory
.msncon
.contactStatusChanged(userHandle
)
417 def gotSwitchboardInvitation(self
, sessionID
, host
, port
, key
, userHandle
, screenName
):
418 LogEvent(INFO
, self
.factory
.msncon
.ident
)
419 sb
= self
.factory
.msncon
.switchboardSessions
.get(userHandle
)
420 if sb
and sb
.transport
:
421 sb
.transport
.loseConnection()
423 sb
= OneSwitchboardSession(self
.factory
.msncon
, userHandle
)
424 self
.factory
.msncon
.switchboardSessions
[userHandle
] = sb
425 sb
.connectReply(host
, port
, key
, sessionID
)
427 def multipleLogin(self
):
428 LogEvent(INFO
, self
.factory
.msncon
.ident
)
429 self
.factory
.msncon
.multipleLogin()
431 def serverGoingDown(self
):
432 LogEvent(INFO
, self
.factory
.msncon
.ident
)
433 self
.factory
.msncon
.serverGoingDown()
437 class SwitchboardSessionBase(msn
.SwitchboardClient
):
438 def __init__(self
, msncon
):
439 msn
.SwitchboardClient
.__init
__(self
)
441 self
.userHandle
= msncon
.username
442 self
.ident
= (msncon
.ident
, "INVALID!!")
443 self
.messageBuffer
= []
448 LogEvent(INFO
, self
.ident
)
450 self
.transport
.disconnect()
453 LogEvent(INFO
, self
.ident
)
458 LogEvent(INFO
, self
.ident
)
460 def sbRequestAccepted((host
, port
, key
)):
461 LogEvent(INFO
, self
.ident
)
464 factory
= ClientFactory()
465 factory
.buildProtocol
= lambda addr
: self
466 reactor
.connectTCP(host
, port
, factory
)
467 def sbRequestFailed(ignored
=None):
468 LogEvent(INFO
, self
.ident
)
469 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
470 d
= self
.msncon
.notificationClient
.requestSwitchboardServer()
471 d
.addCallbacks(sbRequestAccepted
, sbRequestFailed
)
473 def connectReply(self
, host
, port
, key
, sessionID
):
474 LogEvent(INFO
, self
.ident
)
477 self
.sessionID
= sessionID
479 factory
= ClientFactory()
480 factory
.buildProtocol
= lambda addr
: self
481 reactor
.connectTCP(host
, port
, factory
)
483 def flushBuffer(self
):
484 for message
, noerror
in self
.messageBuffer
[:]:
485 self
.messageBuffer
.remove((message
, noerror
))
486 self
.sendMessage(message
, noerror
)
487 for f
in self
.funcBuffer
[:]:
488 self
.funcBuffer
.remove(f
)
491 def failedMessage(self
, *ignored
):
492 raise NotImplementedError
494 def sendMessage(self
, text
, noerror
=False):
495 if not isinstance(text
, basestring
):
496 msn
.SwitchboardClient
.sendMessage(self
, text
)
499 self
.messageBuffer
.append((text
, noerror
))
501 LogEvent(INFO
, self
.ident
)
502 def failedMessage(ignored
):
504 self
.failedMessage(text
)
506 if len(text
) < MAXMESSAGESIZE
:
507 message
= msn
.MSNMessage(message
=str(text
.replace("\n", "\r\n").encode("utf-8")))
508 message
.setHeader("Content-Type", "text/plain; charset=UTF-8")
509 message
.ack
= msn
.MSNMessage
.MESSAGE_NACK
511 d
= msn
.SwitchboardClient
.sendMessage(self
, message
)
513 d
.addCallback(failedMessage
)
516 chunks
= int(math
.ceil(len(text
) / float(MAXMESSAGESIZE
)))
518 guid
= msn
.random_guid()
519 while chunk
< chunks
:
520 offset
= chunk
* MAXMESSAGESIZE
521 text
= message
[offset
: offset
+ MAXMESSAGESIZE
]
522 message
= msn
.MSNMessage(message
=str(text
.replace("\n", "\r\n").encode("utf-8")))
523 message
.ack
= msn
.MSNMessage
.MESSAGE_NACK
525 message
.setHeader("Content-Type", "text/plain; charset=UTF-8")
526 message
.setHeader("Chunks", str(chunks
))
528 message
.setHeader("Chunk", str(chunk
))
530 d
= msn
.SwitchboardClient
.sendMessage(self
, message
)
532 d
.addCallback(failedMessage
)
537 class MultiSwitchboardSession(SwitchboardSessionBase
):
538 """ Create one of me to chat to multiple contacts """
540 def __init__(self
, msncon
):
541 """ Automatically creates a new switchboard connection to the server """
542 SwitchboardSessionBase
.__init
__(self
, msncon
)
543 self
.ident
= (self
.msncon
.ident
, self
)
544 self
.contactCount
= 0
545 self
.groupchat
= None
548 def failedMessage(self
, text
):
549 self
.groupchat
.gotMessage("BOUNCE", text
)
551 def sendMessage(self
, text
, noerror
):
552 """ Used to send a mesage to the groupchat. Can be called immediately
553 after instantiation. """
554 if self
.contactCount
> 0:
555 SwitchboardSessionBase
.sendMessage(self
, text
, noerror
)
557 #self.messageBuffer.append((message, noerror))
558 pass # They're sending messages to an empty room. Ignore.
560 def inviteUser(self
, userHandle
):
561 """ Used to invite a contact to the groupchat. Can be called immediately
562 after instantiation. """
563 userHandle
= str(userHandle
)
565 LogEvent(INFO
, self
.ident
, "immediate")
566 msn
.SwitchboardClient
.inviteUser(self
, userHandle
)
568 LogEvent(INFO
, self
.ident
, "pending")
569 self
.funcBuffer
.append(lambda: msn
.SwitchboardClient
.inviteUser(self
, userHandle
))
571 def gotMessage(self
, message
):
572 self
.groupchat
.gotMessage(message
.userHandle
, message
.getMessage())
574 def userJoined(self
, userHandle
, screenName
=''):
575 LogEvent(INFO
, self
.ident
)
576 self
.contactCount
+= 1
577 self
.groupchat
.contactJoined(userHandle
)
579 def userLeft(self
, userHandle
):
580 LogEvent(INFO
, self
.ident
)
581 self
.contactCount
-= 1
582 self
.groupchat
.contactLeft(userHandle
)
586 class OneSwitchboardSession(SwitchboardSessionBase
):
587 def __init__(self
, msncon
, remoteUser
):
588 SwitchboardSessionBase
.__init
__(self
, msncon
)
589 self
.remoteUser
= str(remoteUser
)
590 self
.ident
= (self
.msncon
.ident
, self
.remoteUser
)
591 self
.chattingUsers
= []
595 for message
, noerror
in self
.messageBuffer
:
597 self
.failedMessage(self
.remoteUser
, message
)
600 LogEvent(INFO
, self
.ident
)
602 for user
in self
.chattingUsers
:
603 self
.userJoined(user
)
605 self
.timeout
.cancel()
609 def _switchToMulti(self
):
610 LogEvent(INFO
, self
.ident
)
611 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
612 self
.__class
__ = MultiSwitchboardSession
614 self
.contactCount
= 0
615 self
.msncon
.gotGroupchat(self
, userHandle
)
616 if not self
.groupchat
:
617 LogEvent(ERROR
, self
.ident
)
618 raise Exception("YouNeedAGroupchat-WeHaveAProblemError") # FIXME
620 def failedMessage(self
, text
):
621 self
.msncon
.failedMessage(self
.remoteUser
, text
)
625 LogEvent(INFO
, self
.ident
)
627 def failCB(arg
=None):
628 LogEvent(INFO
, self
.ident
, "User has not joined after 30 seconds.")
629 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
630 d
= self
.inviteUser(self
.remoteUser
)
632 self
.timeout
= reactor
.callLater(30.0, failCB
)
636 def gotChattingUsers(self
, users
):
637 for userHandle
in users
.keys():
638 self
.chattingUsers
.append(userHandle
)
640 def userJoined(self
, userHandle
, screenName
=''):
641 LogEvent(INFO
, self
.ident
)
644 if userHandle
!= self
.remoteUser
:
645 # Another user has joined, so we now have three participants.
646 remoteUser
= self
.remoteUser
647 self
._switchToMulti
(userHandle
)
648 self
.userJoined(remoteUser
)
649 self
.userJoined(userHandle
)
651 def userLeft(self
, userHandle
):
653 if userHandle
== self
.remoteUser
:
654 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
655 reactor
.callLater(0, wait
) # Make sure this is handled after everything else
657 def gotMessage(self
, message
):
658 LogEvent(INFO
, self
.ident
)
659 self
.msncon
.gotMessage(self
.remoteUser
, message
.getMessage())
661 def gotFileReceive(self
, fileReceive
):
662 LogEvent(INFO
, self
.ident
)
663 self
.msncon
.gotFileReceive(fileReceive
)
665 def gotContactTyping(self
, message
):
666 LogEvent(INFO
, self
.ident
)
667 self
.msncon
.gotContactTyping(message
.userHandle
)
669 def sendTypingNotification(self
):
670 LogEvent(INFO
, self
.ident
)
672 msn
.SwitchboardClient
.sendTypingNotification(self
)
674 CAPS
= msn
.MSNContact
.MSNC1 | msn
.MSNContact
.MSNC2 | msn
.MSNContact
.MSNC3 | msn
.MSNContact
.MSNC4
675 def sendAvatarRequest(self
):
676 if not self
.ready
: return
677 msnContacts
= self
.msncon
.getContacts()
678 if not msnContacts
: return
679 msnContact
= msnContacts
.getContact(self
.remoteUser
)
680 if not (msnContact
and msnContact
.caps
& self
.CAPS
and msnContact
.msnobj
): return
681 if msnContact
.msnobjGot
: return
682 msnContact
.msnobjGot
= True # This is deliberately set before we get the avatar. So that we don't try to reget failed avatars over & over
683 msn
.SwitchboardClient
.sendAvatarRequest(self
, msnContact
)
685 def sendFile(self
, msnContact
, filename
, filesize
):
686 def doSendFile(ignored
=None):
687 d
.callback(msn
.SwitchboardClient
.sendFile(self
, msnContact
, filename
, filesize
))
690 reactor
.callLater(0, doSendFile
)
692 self
.funcBuffer
.append(doSendFile
)