]> code.delx.au - pymsnt/blob - src/tlib/msn/msnw.py
ourStatusChanged working again. Groupchats working.
[pymsnt] / 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
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, math
11
12 # Local imports
13 from debug import LogEvent, INFO, WARN, ERROR
14 from tlib.msn import msn
15
16
17 MAXMESSAGESIZE = 1400
18 SWITCHBOARDTIMEOUT = 30.0*60.0
19 GETALLAVATARS = False
20
21
22 """
23 All interaction should be with the MSNConnection and MultiSwitchboardSession classes.
24 You should not directly instantiate any objects of other classes.
25 """
26
27 class MSNConnection:
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.
34 """
35 self.username = username
36 self.password = password
37 self.ident = ident
38 self.timeout = None
39 self.notificationClient = None
40 self.connect()
41 LogEvent(INFO, self.ident)
42
43 def connect(self):
44 """ Automatically called by the constructor """
45 self.connectors = []
46 self.switchboardSessions = {}
47 self.savedEvents = SavedEvents() # Save any events that occur before connect
48 self._getNotificationReferral()
49
50 def _getNotificationReferral(self):
51 def timeout():
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
57 d = Deferred()
58 dispatchFactory.d = d
59 d.addCallbacks(self._gotNotificationReferral, self.connectionFailed)
60 self.connectors.append(reactor.connectTCP("messenger.hotmail.com", 1863, dispatchFactory))
61 LogEvent(INFO, self.ident)
62
63 def _gotNotificationReferral(self, (host, port)):
64 self.timeout.cancel()
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)
73
74 def _sendSavedEvents(self):
75 self.savedEvents.send(self)
76 self.savedEvents = None
77
78 def _notificationClientReady(self, notificationClient):
79 self.notificationClient = notificationClient
80
81 def _ensureSwitchboardSession(self, userHandle):
82 if not self.switchboardSessions.has_key(userHandle):
83 sb = OneSwitchboardSession(self, userHandle)
84 sb.connect()
85 self.switchboardSessions[userHandle] = sb
86
87
88
89 # API calls
90 def getContacts(self):
91 """ Gets the contact list.
92
93 @return an instance of MSNContactList (do not modify) if connected,
94 or None if not.
95 """
96 if self.notificationFactory:
97 return self.notificationFactory.contacts
98 else:
99 return None
100
101 def sendMessage(self, userHandle, text, noerror=False):
102 """
103 Sends a message to a contact. Can only be called after listSynchronized().
104
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.
108 """
109 LogEvent(INFO, self.ident)
110 if self.notificationClient:
111 self._ensureSwitchboardSession(userHandle)
112 self.switchboardSessions[userHandle].sendMessage(text, noerror)
113 elif not noerror:
114 self.failedMessage(userHandle, text)
115
116 def sendAvatarRequest(self, userHandle):
117 """
118 Requests the avatar of a contact.
119
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
125 """
126
127 LogEvent(INFO, self.ident)
128 if not self.notificationClient: return
129 if GETALLAVATARS:
130 self._ensureSwitchboardSession(userHandle)
131 sb = self.switchboardSessions.get(userHandle)
132 if sb: return sb.sendAvatarRequest()
133
134 def sendFile(self, userHandle, filename, filesize):
135 """
136 Used to send a file to a contact.
137
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.
141
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
150 declare in filesize.
151 """
152 msnContact = self.getContacts().getContact(userHandle)
153 if not msnContact:
154 raise ValueError, "Contact not found"
155 self._ensureSwitchboardSession(userHandle)
156 return self.switchboardSessions[userHandle].sendFile(msnContact, filename, filesize)
157
158 def sendTypingToContact(self, userHandle):
159 """
160 Sends typing notification to a contact. Should send every 5secs.
161 @param userHandle: the contact to notify of our typing.
162 """
163
164 sb = self.switchboardSessions.get(userHandle)
165 if sb: return sb.sendTypingNotification()
166
167 def changeAvatar(self, imageData):
168 """
169 Changes the user's avatar.
170 @param imageData: the new PNG avatar image data.
171 """
172 if self.notificationClient:
173 LogEvent(INFO, self.ident)
174 self.notificationClient.changeAvatar(imageData, push=True)
175 else:
176 self.savedEvents.avatarImageData = imageData
177
178 def changeStatus(self, statusCode, screenName, personal):
179 """
180 Changes your status details. All details must be given with
181 each call. This can be called before connection if you wish.
182
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.
186 """
187
188 if not screenName: screenName = self.username
189 if self.notificationClient:
190 self.changeCount = 0
191 def cb(ignored=None):
192 self.changeCount += 1
193 if self.changeCount == 3:
194 self.ourStatusChanged(statusCode, screenName, personal)
195 del self.changeCount
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)
201 else:
202 self.savedEvents.statusCode = statusCode
203 self.savedEvents.screenName = screenName
204 self.savedEvents.personal = personal
205
206 def addContact(self, listType, userHandle):
207 """ See msn.NotificationClient.addContact """
208 if self.notificationClient:
209 return self.notificationClient.addContact(listType, str(userHandle))
210 else:
211 self.savedEvents.addContacts.append((listType, str(userHandle)))
212
213 def remContact(self, listType, userHandle):
214 """ See msn.NotificationClient.remContact """
215 if self.notificationClient:
216 return self.notificationClient.remContact(listType, str(userHandle))
217 else:
218 self.savedEvents.remContacts.append((listType, str(userHandle)))
219
220 def logOut(self):
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:
226 c.disconnect()
227 if self.notificationFactory:
228 self.notificationFactory.msncon = None
229 self.connectors = []
230 self.switchboardSessions = {}
231 LogEvent(INFO, self.ident)
232
233
234 # Reimplement these!
235 def connectionFailed(self, reason=''):
236 """ Called when the connection to the server failed. """
237
238 def loginFailed(self, reason=''):
239 """ Called when the account could not be logged in. """
240
241 def connectionLost(self, reason=''):
242 """ Called when we are disconnected. """
243
244 def multipleLogin(self):
245 """ Called when the server says there has been another login
246 for this account. """
247
248 def serverGoingDown(self):
249 """ Called when the server says that it will be going down. """
250
251 def accountNotVerified(self):
252 """ Called if this passport has not been verified. Certain
253 functions are not available. """
254
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. """
258
259 def loggedIn(self):
260 """ Called when we have authenticated, but before we receive
261 the contact list. """
262
263 def listSynchronized(self):
264 """ Called when we have received the contact list. All methods
265 in this class are now valid. """
266
267 def ourStatusChanged(self, statusCode, screenName, personal):
268 """ Called when the user's status has changed. """
269
270 def gotMessage(self, userHandle, text):
271 """ Called when a contact sends us a message """
272
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)
281
282 The object received as 'msnGroupchat' is an instance of MultiSwitchboardSession.
283 """
284
285 def gotContactTyping(self, userHandle):
286 """ Called when a contact sends typing notification.
287 Will be called once every 5 seconds. """
288
289 def failedMessage(self, userHandle, text):
290 """ Called when a message we sent has been bounced back. """
291
292 def contactAvatarChanged(self, userHandle, hash):
293 """ Called when we receive a changed avatar hash for a contact.
294 You should call sendAvatarRequest(). """
295
296 def contactStatusChanged(self, userHandle):
297 """ Called when we receive status information for a contact. """
298
299 def gotFileReceive(self, fileReceive):
300 """ Called when a contact sends the user a file.
301 Call accept(fileHandle) or reject() on the object. """
302
303 def contactAddedMe(self, userHandle):
304 """ Called when a contact adds the user to their list. """
305
306 def contactRemovedMe(self, userHandle):
307 """ Called when a contact removes the user from their list. """
308
309 def gotInitialEmailNotification(self, inboxunread, foldersunread):
310 """ Received at login to tell about the user's Hotmail status """
311
312 def gotRealtimeEmailNotification(self, mailfrom, fromaddr, subject):
313 """ Received in realtime whenever an email comes into the hotmail account """
314
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. """
319
320
321 class SavedEvents:
322 def __init__(self):
323 self.nickname = ""
324 self.statusCode = ""
325 self.personal = ""
326 self.avatarImageData = ""
327 self.addContacts = []
328 self.remContacts = []
329
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)
339
340
341
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))
346
347
348 class NotificationClient(msn.NotificationClient):
349 def loginFailure(self, message):
350 self.factory.msncon.loginFailed(message)
351
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()
357 if not verified:
358 self.factory.msncon.accountNotVerified()
359
360 def logOut(self):
361 msn.NotificationClient.logOut(self)
362
363 def connectionLost(self, reason):
364 if not self.factory.msncon: return # If we called logOut
365 def wait():
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)
371
372 def gotMSNAlert(self, body, action, subscr):
373 LogEvent(INFO, self.factory.msncon.ident)
374 self.factory.msncon.gotMSNAlert(body, action, subscr)
375
376 def gotInitialEmailNotification(self, inboxunread, foldersunread):
377 LogEvent(INFO, self.factory.msncon.ident)
378 self.factory.msncon.gotInitialEmailNotification(inboxunread, foldersunread)
379
380 def gotRealtimeEmailNotification(self, mailfrom, fromaddr, subject):
381 LogEvent(INFO, self.factory.msncon.ident)
382 self.factory.msncon.gotRealtimeEmailNotification(mailfrom, fromaddr, subject)
383
384 def userAddedMe(self, userGuid, userHandle, screenName):
385 LogEvent(INFO, self.factory.msncon.ident)
386 self.factory.msncon.contactAddedMe(userHandle)
387
388 def userRemovedMe(self, userHandle):
389 LogEvent(INFO, self.factory.msncon.ident)
390 self.factory.msncon.contactRemovedMe(userHandle)
391
392 def listSynchronized(self, *args):
393 LogEvent(INFO, self.factory.msncon.ident)
394 self.factory.msncon._sendSavedEvents()
395 self.factory.msncon.listSynchronized()
396
397 def contactAvatarChanged(self, userHandle, hash):
398 LogEvent(INFO, self.factory.msncon.ident)
399 self.factory.msncon.contactAvatarChanged(userHandle, hash)
400
401 def gotContactStatus(self, userHandle, statusCode, screenName):
402 LogEvent(INFO, self.factory.msncon.ident)
403 self.factory.msncon.contactStatusChanged(userHandle)
404
405 def contactStatusChanged(self, userHandle, statusCode, screenName):
406 LogEvent(INFO, self.factory.msncon.ident)
407 self.factory.msncon.contactStatusChanged(userHandle)
408
409 def contactPersonalChanged(self, userHandle, personal):
410 LogEvent(INFO, self.factory.msncon.ident)
411 self.factory.msncon.contactStatusChanged(userHandle)
412
413 def contactOffline(self, userHandle):
414 LogEvent(INFO, self.factory.msncon.ident)
415 self.factory.msncon.contactStatusChanged(userHandle)
416
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()
422 else:
423 sb = OneSwitchboardSession(self.factory.msncon, userHandle)
424 self.factory.msncon.switchboardSessions[userHandle] = sb
425 sb.connectReply(host, port, key, sessionID)
426
427 def multipleLogin(self):
428 LogEvent(INFO, self.factory.msncon.ident)
429 self.factory.msncon.multipleLogin()
430
431 def serverGoingDown(self):
432 LogEvent(INFO, self.factory.msncon.ident)
433 self.factory.msncon.serverGoingDown()
434
435
436
437 class SwitchboardSessionBase(msn.SwitchboardClient):
438 def __init__(self, msncon):
439 msn.SwitchboardClient.__init__(self)
440 self.msncon = msncon
441 self.userHandle = msncon.username
442 self.ident = (msncon.ident, "INVALID!!")
443 self.messageBuffer = []
444 self.funcBuffer = []
445 self.ready = False
446
447 def __del__(self):
448 LogEvent(INFO, self.ident)
449 del self.msncon
450 self.transport.disconnect()
451
452 def loggedIn(self):
453 LogEvent(INFO, self.ident)
454 self.ready = True
455 self.flushBuffer()
456
457 def connect(self):
458 LogEvent(INFO, self.ident)
459 self.ready = False
460 def sbRequestAccepted((host, port, key)):
461 LogEvent(INFO, self.ident)
462 self.key = key
463 self.reply = 0
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)
472
473 def connectReply(self, host, port, key, sessionID):
474 LogEvent(INFO, self.ident)
475 self.ready = False
476 self.key = key
477 self.sessionID = sessionID
478 self.reply = 1
479 factory = ClientFactory()
480 factory.buildProtocol = lambda addr: self
481 reactor.connectTCP(host, port, factory)
482
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)
489 f()
490
491 def failedMessage(self, *ignored):
492 raise NotImplementedError
493
494 def sendMessage(self, text, noerror=False):
495 if not isinstance(text, basestring):
496 msn.SwitchboardClient.sendMessage(self, text)
497 return
498 if not self.ready:
499 self.messageBuffer.append((text, noerror))
500 else:
501 LogEvent(INFO, self.ident)
502 def failedMessage(ignored):
503 if not noerror:
504 self.failedMessage(text)
505
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
510
511 d = msn.SwitchboardClient.sendMessage(self, message)
512 if not noerror:
513 d.addCallback(failedMessage)
514
515 else:
516 chunks = int(math.ceil(len(text) / float(MAXMESSAGESIZE)))
517 chunk = 0
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
524 if chunk == 0:
525 message.setHeader("Content-Type", "text/plain; charset=UTF-8")
526 message.setHeader("Chunks", str(chunks))
527 else:
528 message.setHeader("Chunk", str(chunk))
529
530 d = msn.SwitchboardClient.sendMessage(self, message)
531 if not noerror:
532 d.addCallback(failedMessage)
533
534 chunk += 1
535
536
537 class MultiSwitchboardSession(SwitchboardSessionBase):
538 """ Create one of me to chat to multiple contacts """
539
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
546 self.connect()
547
548 def failedMessage(self, text):
549 self.groupchat.gotMessage("BOUNCE", text)
550
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)
556 else:
557 #self.messageBuffer.append((message, noerror))
558 pass # They're sending messages to an empty room. Ignore.
559
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)
564 if self.ready:
565 LogEvent(INFO, self.ident, "immediate")
566 msn.SwitchboardClient.inviteUser(self, userHandle)
567 else:
568 LogEvent(INFO, self.ident, "pending")
569 self.funcBuffer.append(lambda: msn.SwitchboardClient.inviteUser(self, userHandle))
570
571 def gotMessage(self, message):
572 self.groupchat.gotMessage(message.userHandle, message.getMessage())
573
574 def userJoined(self, userHandle, screenName=''):
575 LogEvent(INFO, self.ident)
576 self.contactCount += 1
577 self.groupchat.contactJoined(userHandle)
578
579 def userLeft(self, userHandle):
580 LogEvent(INFO, self.ident)
581 self.contactCount -= 1
582 self.groupchat.contactLeft(userHandle)
583
584
585
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 = []
592 self.timeout = None
593
594 def __del__(self):
595 for message, noerror in self.messageBuffer:
596 if not noerror:
597 self.failedMessage(self.remoteUser, message)
598
599 def _ready(self):
600 LogEvent(INFO, self.ident)
601 self.ready = True
602 for user in self.chattingUsers:
603 self.userJoined(user)
604 if self.timeout:
605 self.timeout.cancel()
606 self.timeout = None
607 self.flushBuffer()
608
609 def _switchToMulti(self):
610 LogEvent(INFO, self.ident)
611 del self.msncon.switchboardSessions[self.remoteUser]
612 self.__class__ = MultiSwitchboardSession
613 del self.remoteUser
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
619
620 def failedMessage(self, text):
621 self.msncon.failedMessage(self.remoteUser, text)
622
623 # Callbacks
624 def loggedIn(self):
625 LogEvent(INFO, self.ident)
626 if not self.reply:
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)
631 d.addErrback(failCB)
632 self.timeout = reactor.callLater(30.0, failCB)
633 else:
634 self._ready()
635
636 def gotChattingUsers(self, users):
637 for userHandle in users.keys():
638 self.chattingUsers.append(userHandle)
639
640 def userJoined(self, userHandle, screenName=''):
641 LogEvent(INFO, self.ident)
642 if not self.reply:
643 self._ready()
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)
650
651 def userLeft(self, userHandle):
652 def wait():
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
656
657 def gotMessage(self, message):
658 LogEvent(INFO, self.ident)
659 self.msncon.gotMessage(self.remoteUser, message.getMessage())
660
661 def gotFileReceive(self, fileReceive):
662 LogEvent(INFO, self.ident)
663 self.msncon.gotFileReceive(fileReceive)
664
665 def gotContactTyping(self, message):
666 LogEvent(INFO, self.ident)
667 self.msncon.gotContactTyping(message.userHandle)
668
669 def sendTypingNotification(self):
670 LogEvent(INFO, self.ident)
671 if self.ready:
672 msn.SwitchboardClient.sendTypingNotification(self)
673
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)
684
685 def sendFile(self, msnContact, filename, filesize):
686 def doSendFile(ignored=None):
687 d.callback(msn.SwitchboardClient.sendFile(self, msnContact, filename, filesize))
688 d = Deferred()
689 if self.ready:
690 reactor.callLater(0, doSendFile)
691 else:
692 self.funcBuffer.append(doSendFile)
693 return d
694
695