1 # Copyright 2004 James Bunton <james@delx.cjb.net>
2 # Licensed for distribution under the GPL version 2, check COPYING for details
4 from twisted
.internet
import reactor
5 from twisted
.internet
.protocol
import ClientFactory
15 """ Manages all the Twisted factories, etc """
16 def __init__(self
, username
, password
):
17 self
.username
= username
18 self
.password
= password
22 debug
.log("MSNConnection: \"%s\" created" % (self
.username
))
26 MSNConnection
.removeMe(self
)
28 self
.switchboardSessions
= {}
29 dispatchFactory
= DispatchFactory(self
)
30 reactor
.connectTCP('messenger.hotmail.com', 1863, dispatchFactory
)
31 self
.notificationFactory
= msn
.NotificationFactory()
32 self
.notificationFactory
.userHandle
= self
.username
33 self
.notificationFactory
.password
= self
.password
34 self
.notificationFactory
.msncon
= self
35 self
.notificationFactory
.protocol
= Notification
36 self
.notificationFactory
.initialListVersion
= self
.initialListVersion
37 self
.notificationProtocol
= None
39 self
.savedStatus
= None
43 debug
.log("MSNConnection: \"%s\" initialised" % (self
.username
))
46 debug
.log("MSNConnection: \"%s\" destroyed" % (self
.username
))
47 if(self
.notificationProtocol
):
48 self
.notificationProtocol
.removeMe()
49 if(self
.notificationFactory
):
50 self
.notificationFactory
.msncon
= None
51 self
.notificationFactory
= None
52 self
.notificationProtocol
= None
53 for userHandle
in utils
.copyDict(self
.switchboardSessions
):
54 self
.switchboardSessions
[userHandle
].removeMe()
55 self
.switchboardSessions
= {}
57 def resourceOffline(self
, offlineResource
):
58 for contact
in self
.switchboardSessions
.keys():
59 if(self
.switchboardSessions
[contact
].resource
== offlineResource
):
60 self
.switchboardSessions
[contact
].resource
= self
.highestResource()
62 def getContacts(self
):
63 if(self
.notificationFactory
):
64 return self
.notificationFactory
.contacts
69 def sendMessage(self
, remoteUser
, resource
, text
, noerror
):
70 debug
.log("MSNConnection: \"%s\" sendMessage(\"%s\", \"%s\")" % (self
.username
, remoteUser
, text
))
71 if(self
.notificationProtocol
):
72 if(not self
.switchboardSessions
.has_key(remoteUser
)):
73 self
.switchboardSessions
[remoteUser
] = SwitchboardSession(self
, remoteUser
, resource
)
74 self
.switchboardSessions
[remoteUser
].resource
= resource
75 self
.switchboardSessions
[remoteUser
].sendMessage(text
.replace("\n", "\r\n"), noerror
)
77 self
.failedMessage(remoteUser
, text
)
79 def sendTypingToContact(self
, remoteUser
):
80 if(self
.switchboardSessions
.has_key(remoteUser
)):
81 self
.switchboardSessions
[remoteUser
].sendTypingNofication()
83 def notificationProtocolReady(self
, notificationProtocol
):
84 self
.notificationProtocol
= notificationProtocol
88 def sendSavedStatus(self
):
89 # Hack for initial status
91 statusCode
, screenName
= self
.savedStatus
92 self
.savedStatus
= None
93 self
.changeStatus(statusCode
, screenName
)
95 def changeStatus(self
, statusCode
, screenName
):
96 if(self
.notificationProtocol
):
98 self
.ourStatusChanged(arg
[0])
100 self
.ourNickChanged(arg
[0])
101 debug
.log("MSNConnection: \"%s\" - changing status and screenName (\"%s\", \"%s\")" % (self
.username
, statusCode
, screenName
))
103 statusCode
= str(statusCode
.encode("utf-8"))
104 self
.notificationProtocol
.changeStatus(statusCode
).addCallback(cb1
)
106 screenName
= str(screenName
.encode("utf-8"))
107 self
.notificationProtocol
.changeScreenName(screenName
).addCallback(cb2
)
109 self
.savedStatus
= (statusCode
, screenName
)
111 def connectionLostBase(self
, reason
):
112 # Attempts to reconnect
113 if(self
.tries
< 5 and self
.session
):
114 reactor
.callLater(2 ** self
.tries
, self
.initMe
)
117 self
.connectionLost(self
)
119 def addContact(self
, listType
, userHandle
):
120 return self
.notificationProtocol
.addContact(listType
, str(userHandle
))
122 def remContact(self
, listType
, userHandle
, groupID
=0):
123 return self
.notificationProtocol
.remContact(listType
, str(userHandle
))
127 def initialEmailNotification(self
, inboxunread
, foldersunread
):
130 def realtimeEmailNotification(self
, mailfrom
, fromaddr
, subject
):
136 def loginFailure(self
, message
):
139 def multipleLogin(self
):
142 def gotMessage(self
, remoteUser
, resource
, text
):
145 def listSynchronized(self
):
148 def contactStatusChanged(self
, remoteUser
):
151 def ourStatusChanged(self
, statusCode
):
154 def userMapping(self
, passport
, jid
):
157 def gotContactTyping(self
, remoteUser
, resource
):
160 def ourNickChanged(self
, arg
):
163 def serverGoingDown(self
):
166 def accountNotVerified(self
):
169 def userAddedMe(self
, userHandle
):
172 def userRemovedMe(self
, userHandle
):
175 def failedMessage(self
, remoteUser
, message
):
178 def connectionLost(self
):
184 def switchToGroupchat(switchboardSession
, user1
, user2
):
185 gcsbs
= GroupchatSwitchboardSession()
186 from glue
import LegacyGroupchat
, msn2jid
187 groupchat
= LegacyGroupchat(session
=switchboardSession
.msncon
.session
, resource
=None, existing
=True, switchboardSession
=gcsbs
)
188 gcsbs
.groupchat
= groupchat
189 gcsbs
.msncon
= switchboardSession
.msncon
190 gcsbs
.switchboard
= switchboardSession
.switchboard
191 gcsbs
.switchboard
.switchboardSession
= gcsbs
193 gcsbs
.userJoined(user1
)
194 gcsbs
.userJoined(user2
)
195 groupchat
.sendUserInvite(msn2jid(switchboardSession
.remoteUser
))
196 switchboardSession
.removeMe(False)
197 debug
.log("GroupchatSwitchboardSession: \"%s\" \"%s\" created by conversion" % (gcsbs
.groupchat
.roomJID(), gcsbs
))
201 class GroupchatSwitchboardSession
:
202 def __init__(self
, groupchat
=None, makeSwitchboard
=False):
206 self
.groupchat
= None
208 self
.groupchat
= groupchat
209 self
.msncon
= self
.groupchat
.session
.legacycon
210 self
.switchboard
= None
212 self
.messageBuffer
= []
213 self
.invitedUsers
= []
214 self
.oneUserHasJoined
= False
216 if(makeSwitchboard
and groupchat
):
217 debug
.log("GroupchatSwitchboardSession: \"%s\" \"%s\" requesting a switchboard session" % (self
.groupchat
.roomJID(), self
))
218 d
= self
.msncon
.notificationProtocol
.requestSwitchboardServer()
219 d
.addCallback(self
.sbRequestAccepted
)
220 d
.addErrback(self
.removeMe
)
223 debug
.log("GroupchatSwitchboardSession: \"%s\" \"%s\" created" % (self
.msncon
.username
, self
))
227 debug
.log("GroupchatSwitchboardSession: removeMe called more than once! Traceback!")
231 debug
.log("GroupchatSwitchboardSession: \"%s\" \"%s\" destroyed" % (self
.groupchat
.roomJID(), self
))
233 if(self
.switchboard
):
234 self
.switchboard
.removeMe()
235 self
.switchboard
= None
236 self
.groupchat
= None
239 utils
.mutilateMe(self
)
241 def sbRequestAccepted(self
, (host
, port
, key
)):
242 # Connect to the switchboard server
243 debug
.log("GroupchatSwitchboardSession: \"%s\" \"%s\" sbRequestAccepted()" % (self
.msncon
.username
, self
))
244 reactor
.connectTCP(host
, port
, SwitchboardFactory(self
, key
))
246 def sendMessage(self
, message
, noerror
):
247 if(self
.ready
and self
.oneUserHasJoined
):
248 def failedMessage(ignored
=None):
249 tempmsncon
.failedMessage(self
.groupchat
.roomJID(), message
)
250 message
= str(message
.encode("utf-8"))
251 msnmessage
= msn
.MSNMessage(message
=message
)
252 msnmessage
.setHeader("Content-Type", "text/plain; charset=UTF-8")
253 msnmessage
.ack
= msn
.MSNMessage
.MESSAGE_NACK
254 tempmsncon
= self
.msncon
# In case MSN tells us the message failed after removeMe()
255 d
= self
.switchboard
.sendMessage(msnmessage
)
257 d
.addCallback(failedMessage
)
259 self
.messageBuffer
.append(message
)
261 def inviteUser(self
, userHandle
):
262 userHandle
= str(userHandle
)
264 debug
.log("GroupchatSwitchboardSession: \"%s\" \"%s\" inviting %s" % (self
.msncon
.username
, self
, userHandle
))
265 self
.switchboard
.inviteUser(userHandle
)
267 self
.invitedUsers
.append(userHandle
)
269 def gotMessage(self
, message
):
270 self
.groupchat
.messageReceived(message
.userHandle
, message
.getMessage())
272 def flushBuffer(self
):
273 for m
in utils
.copyList(self
.messageBuffer
):
274 self
.messageBuffer
.remove(m
)
275 self
.sendMessage(m
, True)
277 for i
in utils
.copyList(self
.invitedUsers
):
278 self
.invitedUsers
.remove(i
)
281 def userJoined(self
, userHandle
):
282 debug
.log("GroupchatSwitchboardSession: \"%s\" \"%s\" userJoined(\"%s\")" % (self
.msncon
.username
, self
, userHandle
))
283 self
.oneUserHasJoined
= True
285 self
.groupchat
.contactJoined(userHandle
)
287 def userLeft(self
, userHandle
):
288 debug
.log("GroupchatSwitchboardSession: \"%s\" \"%s\" userLeft(\"%s\")" % (self
.msncon
.username
, self
, userHandle
))
289 self
.groupchat
.contactLeft(userHandle
)
293 class SwitchboardSession
:
294 def __init__(self
, msncon
, remoteUser
, resource
, reply
=False, host
=None, port
=None, key
=None, sessionID
=None):
298 self
.remoteUser
= str(remoteUser
)
299 self
.resource
= str(resource
)
301 self
.killTimer
= reactor
.callLater(30.0*60.0, self
.removeMe
)
303 self
.switchboard
= None # The SwitchboardClient class
304 self
.messageBuffer
= [] # Any messages sent before the switchboard is ready are buffered
305 self
.ready
= False # Is True when we are connected to the switchboard, and the remote user has accepted our invite
308 # Request a switchboard
309 d
= self
.msncon
.notificationProtocol
.requestSwitchboardServer()
310 d
.addCallback(self
.sbRequestAccepted
)
311 d
.addErrback(self
.removeMe
)
313 reactor
.connectTCP(host
, port
, SwitchboardFactory(self
, key
, sessionID
, reply
))
315 debug
.log("SwitchboardSession: \"%s\" \"%s\" \"%s\" created" % (self
.msncon
.username
, self
.remoteUser
, self
.resource
))
317 def removeMe(self
, sbflag
=True):
319 debug
.log("SwitchboardSession: removeMe called more than once! Traceback!")
323 debug
.log("SwitchboardSession: \"%s\" \"%s\" \"%s\" destroyed" % (self
.msncon
.username
, self
.remoteUser
, self
.resource
))
324 for message
, noerror
in self
.messageBuffer
:
326 self
.msncon
.failedMessage(self
.remoteUser
, message
)
327 self
.messageBuffer
= []
329 del self
.msncon
.switchboardSessions
[self
.remoteUser
]
331 if(sbflag
and self
.switchboard
):
332 self
.switchboard
.removeMe()
333 self
.switchboard
= None
335 if(self
.killTimer
and not self
.killTimer
.called
):
336 self
.killTimer
.cancel()
337 self
.killTimer
= None
339 utils
.mutilateMe(self
)
341 def resetTimer(self
):
342 # Sets a count down timer to kill this switchboard session in 30 minutes
343 self
.killTimer
.cancel()
344 self
.killTimer
= reactor
.callLater(30.0*60.0, self
.removeMe
)
346 def sbRequestAccepted(self
, (host
, port
, key
)):
347 # Connect to the switchboard server
348 reactor
.connectTCP(host
, port
, SwitchboardFactory(self
, key
))
350 def sendMessage(self
, message
, noerror
):
352 debug
.log("SwitchboardSession: \"%s\" \"%s\" sending message \"%s\"" % (self
.msncon
.username
, self
.remoteUser
, message
))
353 message
= str(message
.encode("utf-8"))
354 msnmessage
= msn
.MSNMessage(message
=message
)
355 msnmessage
.setHeader("Content-Type", "text/plain; charset=UTF-8")
356 msnmessage
.ack
= msn
.MSNMessage
.MESSAGE_NACK
357 def failedMessage(ignored
):
358 tempmsncon
.failedMessage(self
.remoteUser
, message
)
359 d
= self
.switchboard
.sendMessage(msnmessage
)
360 tempmsncon
= self
.msncon
# In case MSN tells us the message failed after removeMe()
362 d
.addCallback(failedMessage
)
365 self
.messageBuffer
.append((message
, noerror
))
367 def sendTypingNofication(self
):
369 self
.switchboard
.sendTypingNotification()
371 def contactTyping(self
):
372 self
.msncon
.gotContactTyping(self
.remoteUser
, self
.resource
)
374 def flushBuffer(self
):
375 for m
, noerror
in utils
.copyList(self
.messageBuffer
):
376 self
.messageBuffer
.remove((m
, noerror
))
377 self
.sendMessage(m
, noerror
)
379 def gotMessage(self
, message
):
380 self
.msncon
.gotMessage(self
.remoteUser
, self
.resource
, message
.getMessage())
383 def userJoined(self
, userHandle
):
384 if(userHandle
!= self
.remoteUser
):
385 # Another user has joined, so we now have three participants (these two and ourself)
386 switchToGroupchat(self
, self
.remoteUser
, userHandle
)
388 def userLeft(self
, userHandle
):
389 if(userHandle
== self
.remoteUser
):
397 class DispatchFactory(ClientFactory
):
398 def __init__(self
, msncon
):
401 def buildProtocol(self
, addr
):
402 p
= Dispatch(self
.msncon
)
403 del self
.msncon
# No longer needed
407 class Dispatch(msn
.DispatchClient
):
408 def __init__(self
, msncon
):
409 msn
.DispatchClient
.__init
__(self
)
411 self
.userHandle
= self
.msncon
.username
417 def gotNotificationReferral(self
, host
, port
):
418 self
.transport
.loseConnection()
419 if(self
.msncon
and self
.msncon
.session
and self
.msncon
.session
.alive
):
420 reactor
.connectTCP(host
, port
, self
.msncon
.notificationFactory
)
424 class Notification(msn
.NotificationClient
):
428 msn
.NotificationClient
.__init
__(self
, proxy
=config
.proxyServer
, proxyport
=config
.proxyPort
)
432 debug
.log("Notification: removeMe called more than once! Traceback!")
437 self
.transport
.loseConnection()
438 if(self
.factory
.msncon
):
439 self
.factory
.msncon
.notificationProtocol
= None
440 self
.factory
.msncon
= None
443 utils
.mutilateMe(self
)
445 def badConditions(self
):
446 if(not (self
.factory
and self
.factory
.msncon
and self
.factory
.msncon
.session
and self
.factory
.msncon
.session
.alive
)):
447 if(not self
.removed
):
453 def loginFailure(self
, message
):
454 if(self
.badConditions()): return
455 self
.factory
.msncon
.loginFailure(message
)
457 def loggedIn(self
, userHandle
, screenName
, verified
):
458 if(self
.badConditions()): return
460 self
.factory
.msncon
.notificationProtocolReady(self
)
462 self
.factory
.msncon
.accountNotVerified()
464 msn
.NotificationClient
.loggedIn(self
, userHandle
, screenName
, verified
)
466 debug
.log("NotificationClient: \"%s\" authenticated with MSN servers" % (self
.factory
.msncon
.username
))
468 def gotMessage(self
, msnmessage
):
469 if(self
.badConditions()): return
470 debug
.log("NotificationClient: \"%s\" gotMessage()" % (self
.factory
.msncon
.username
))
472 cTypes
= [s
.lstrip() for s
in msnmessage
.getHeader("Content-Type").split(';')]
474 fields
= msnmessage
.getMessage().strip().split('\n')
478 if(len(a
) != 2): continue
485 if("text/x-msmsgsinitialemailnotification" in cTypes
and config
.mailNotifications
):
488 inboxunread
= int(values
["Inbox-Unread"])
489 foldersunread
= int(values
["Folders-Unread"])
492 if(foldersunread
+ inboxunread
== 0): return # For some reason MSN sends notifications about empty inboxes sometimes?
493 debug
.log("NotificationClient: \"%s\" Initial hotmail notification" % (self
.factory
.msncon
.username
))
494 self
.factory
.msncon
.initialEmailNotification(inboxunread
, foldersunread
)
496 elif("text/x-msmsgsemailnotification" in cTypes
and config
.mailNotifications
):
499 mailfrom
= values
["From"]
500 fromaddr
= values
["From-Addr"]
501 subject
= values
["Subject"]
502 junkbeginning
= "=?\"us-ascii\"?Q?"
504 subject
= subject
.replace(junkbeginning
, "").replace(junkend
, "").replace("_", " ")
506 # If any of the fields weren't found then it's not a big problem. We just ignore the message
508 debug
.log("NotificationClient: \"%s\" Live hotmail notification" % (self
.factory
.msncon
.username
))
509 self
.factory
.msncon
.realtimeEmailNotification(mailfrom
, fromaddr
, subject
)
511 elif("NOTIFICATION" == msnmessage
.userHandle
):
512 notification
= utils
.parseText(msnmessage
.message
)
513 siteurl
= notification
.getAttribute("siteurl")
514 notid
= notification
.getAttribute("id")
517 for e
in notification
.elements():
523 msgid
= msg
.getAttribute("id")
528 for e
in msg
.elements():
529 if(e
.name
== "ACTION"):
530 action
= e
.getAttribute("url")
531 if(e
.name
== "SUBSCR"):
532 subscr
= e
.getAttribute("url")
533 if(e
.name
== "BODY"):
534 for e2
in e
.elements():
535 if(e2
.name
== "TEXT"):
536 bodytext
= e2
.__str__()
537 if(not (action
and subscr
and bodytext
)): return
540 actionurl
= "%s¬ification_id=%s&message_id=%s&agent=messenger" % (action
, notid
, msgid
)
541 subscrurl
= "%s¬ification_id=%s&message_id=%s&agent=messenger" % (subscr
, notid
, msgid
)
543 self
.factory
.msncon
.msnAlert(bodytext
, actionurl
, subscrurl
)
547 def connectionLost(self
, reason
):
548 if(self
.badConditions()): return
550 debug
.log("NotificationClient: \"%s\" lost connection with MSN servers" % (self
.factory
.userHandle
))
551 msn
.NotificationClient
.connectionLost(self
, reason
)
552 self
.factory
.msncon
.connectionLostBase(reason
)
553 # Make sure this event is handled after any others
554 reactor
.callLater(0, wait
)
556 def listSynchronized(self
, *args
):
557 if(self
.badConditions()): return
558 debug
.log("NotificationClient: \"%s\" MSN contact lists synchronised" % (self
.factory
.userHandle
))
559 self
.factory
.msncon
.listSynchronized()
560 if(self
.badConditions()): return # Just in case the session is deregistered
561 self
.factory
.msncon
.sendSavedStatus()
563 def gotSwitchboardInvitation(self
, sessionID
, host
, port
, key
, remoteUser
, screenName
):
564 if(self
.badConditions()): return
565 debug
.log("NotificationClient: \"%s\" gotSwitchboardInvitation(\"%s\")" % (self
.factory
.userHandle
, remoteUser
))
566 sbs
= SwitchboardSession(self
.factory
.msncon
, remoteUser
, self
.factory
.msncon
.session
.highestResource(), True, host
, port
, key
, sessionID
)
567 if(self
.factory
.msncon
.switchboardSessions
.has_key(remoteUser
)):
568 self
.factory
.msncon
.switchboardSessions
[remoteUser
].removeMe()
569 self
.factory
.msncon
.switchboardSessions
[remoteUser
] = sbs
571 def contactStatusChanged(self
, statusCode
, userHandle
, screenName
):
572 if(self
.badConditions()): return
573 debug
.log("NotificationClient: \"%s\" contactStatusChanged(\"%s\", \"%s\")" % (self
.factory
.userHandle
, statusCode
, userHandle
))
574 msn
.NotificationClient
.contactStatusChanged(self
, statusCode
, userHandle
, screenName
)
576 self
.factory
.msncon
.contactStatusChanged(userHandle
)
578 def gotContactStatus(self
, statusCode
, userHandle
, screenName
):
579 if(self
.badConditions()): return
580 msn
.NotificationClient
.gotContactStatus(self
, statusCode
, userHandle
, screenName
)
581 debug
.log("NotificationClient: \"%s\" gotContactStatus(\"%s\", \"%s\")" % (self
.factory
.userHandle
, statusCode
, userHandle
))
583 self
.factory
.msncon
.contactStatusChanged(userHandle
)
585 def contactOffline(self
, userHandle
):
586 if(self
.badConditions()): return
587 debug
.log("NotificationClient: \"%s\" contactOffline(\"%s\")" % (self
.factory
.userHandle
, userHandle
))
588 msn
.NotificationClient
.contactOffline(self
, userHandle
)
590 self
.factory
.msncon
.contactStatusChanged(userHandle
)
592 def userAddedMe(self
, userHandle
, screenName
, listVersion
):
593 if(self
.badConditions()): return
594 debug
.log("NotificationClient: \"%s\" userAddedMe(\"%s\", \"%s\")" % (self
.factory
.userHandle
, userHandle
, listVersion
))
595 msn
.NotificationClient
.userAddedMe(self
, userHandle
, screenName
, listVersion
)
596 self
.factory
.msncon
.userAddedMe(userHandle
)
598 def userRemovedMe(self
, userHandle
, listVersion
):
599 if(self
.badConditions()): return
600 debug
.log("NotificationClient: \"%s\" userRemovedMe(\"%s\", \"%s\")" % (self
.factory
.userHandle
, userHandle
, listVersion
))
601 msn
.NotificationClient
.userRemovedMe(self
, userHandle
, listVersion
)
602 self
.factory
.msncon
.userRemovedMe(userHandle
)
604 def multipleLogin(self
):
605 if(self
.badConditions()): return
606 debug
.log("NotificationClient: \"%s\" multiple logins" % (self
.factory
.msncon
.username
))
607 self
.factory
.msncon
.multipleLogin()
611 class SwitchboardFactory(ClientFactory
):
612 def __init__(self
, switchboardSession
, key
, sessionID
=None, reply
=False):
613 self
.switchboardSession
= switchboardSession
615 self
.sessionID
= sessionID
618 def buildProtocol(self
, addr
):
619 p
= Switchboard(self
.switchboardSession
)
620 if(p
.badConditions()): return p
622 p
.sessionID
= self
.sessionID
624 p
.userHandle
= self
.switchboardSession
.msncon
.username
628 class Switchboard(msn
.SwitchboardClient
):
629 def __init__(self
, switchboardSession
):
632 msn
.SwitchboardClient
.__init
__(self
)
633 self
.switchboardSession
= switchboardSession
634 self
.chattingUsers
= []
636 if(self
.badConditions()): return
637 debug
.log("SwitchboardClient: \"%s\" \"%s\" - created" % (self
.switchboardSession
.msncon
.username
, self
.switchboardSession
))
641 debug
.log("Switchboard: removeMe called more than once! Traceback!")
645 self
.transport
.loseConnection()
646 debug
.log("SwitchboardClient: \"%s\" - destroyed" % (self
.switchboardSession
))
647 self
.switchboardSession
= None
648 self
.factory
.switchboardSession
= None
651 if(self
.callid
and not self
.callid
.called
):
652 self
.callid
.cancel() # Cancel the invite fail message
655 utils
.mutilateMe(self
)
657 def badConditions(self
):
658 if(not (self
.switchboardSession
and self
.switchboardSession
.msncon
and self
.switchboardSession
.msncon
.session
and self
.switchboardSession
.msncon
.session
.alive
)):
659 if(self
.switchboardSession
):
660 if(not self
.switchboardSession
.removed
):
661 self
.switchboardSession
.removeMe()
662 elif(not self
.removed
):
668 if(self
.badConditions()): return
669 if((not self
.reply
) and self
.switchboardSession
.__class
__ == SwitchboardSession
):
670 def failCB(arg
=None):
671 debug
.log(templogmessage
)
672 self
.switchboardSession
.removeMe()
673 d
= self
.inviteUser(self
.switchboardSession
.remoteUser
)
675 templogmessage
= "SwitchboardClient: \"%s\" \"%s\" - user has NOT joined after 30 seconds" % (self
.switchboardSession
.msncon
.username
, self
.switchboardSession
.remoteUser
)
676 # If the user doesn't join then we want to tear down the SwitchboardSession
677 self
.callid
= reactor
.callLater(30.0, failCB
)
680 self
.readySwitchboardSession()
682 def readySwitchboardSession(self
, ignored
=None):
683 if(self
.badConditions()): return
684 debug
.log("SwitchboardClient: \"%s\" \"%s\" - ready for use" % (self
.switchboardSession
.msncon
.username
, self
.switchboardSession
))
685 self
.switchboardSession
.ready
= True
686 self
.switchboardSession
.switchboard
= self
687 self
.switchboardSession
.flushBuffer()
688 for user
in self
.chattingUsers
:
689 self
.switchboardSession
.userJoined(user
)
690 if(self
.callid
and not self
.callid
.called
):
691 self
.callid
.cancel() # Cancel the invite fail message (only applies if we needed to invite the user)
694 def gotChattingUsers(self
, users
):
696 self
.chattingUsers
.append(user
)
698 def userJoined(self
, userHandle
, screenName
):
699 if(self
.badConditions()): return
700 if((not self
.reply
) and self
.switchboardSession
.__class
__ == SwitchboardSession
):
701 self
.readySwitchboardSession()
702 debug
.log("SwitchboardClient: \"%s\" \"%s\" - userJoined(\"%s\")" % (self
.switchboardSession
.msncon
.username
, self
.switchboardSession
, userHandle
))
703 self
.switchboardSession
.userJoined(userHandle
)
704 self
.sendClientCaps()
706 def userLeft(self
, userHandle
):
707 if(self
.badConditions()): return
708 debug
.log("SwitchboardClient: \"%s\" \"%s\" - userLeft(\"%s\")" % (self
.switchboardSession
.msncon
.username
, self
.switchboardSession
, userHandle
))
710 self
.switchboardSession
.userLeft(userHandle
)
711 # Make sure this event is handled after any others (eg, gotMessage)
712 reactor
.callLater(0, wait
)
714 def gotMessage(self
, message
):
715 if(self
.badConditions()):
716 debug
.log("SwitchboardClient: gotMessage called too late! Traceback!")
718 debug
.log("SwitchboardClient: \"%s\" \"%s\" gotMessage(\"%s\")" % (self
.switchboardSession
.msncon
.username
, message
.userHandle
, message
.getMessage()))
719 cTypes
= [s
.lstrip() for s
in message
.getHeader("Content-Type").split(';')]
720 if("text/plain" in cTypes
):
721 if(len(cTypes
) > 1 and cTypes
[1].find("UTF-8") >= 0):
722 message
.message
= message
.message
.decode("utf-8")
723 self
.switchboardSession
.gotMessage(message
)
725 if("text/x-clientcaps" in cTypes
):
726 if(message
.hasHeader("JabberID")):
727 jid
= message
.getHeader("JabberID")
728 self
.switchboardSession
.msncon
.userMapping(message
.userHandle
, jid
)
730 debug
.log("Discarding unknown message type: %s" % (message
.getMessage()))
732 def userTyping(self
, message
):
733 if(self
.badConditions()): return
734 if(self
.switchboardSession
.__class
__ == SwitchboardSession
): # Ignore typing in groupchats
735 if(message
.userHandle
== self
.switchboardSession
.remoteUser
):
736 self
.switchboardSession
.contactTyping()
738 def sendClientCaps(self
):
739 message
= msn
.MSNMessage()
740 message
.setHeader("Content-Type", "text/x-clientcaps")
741 message
.setHeader("Client-Name", "PyMSNt")
742 message
.setHeader("JabberID", str(self
.switchboardSession
.msncon
.session
.jabberID
)) # FIXME, this is a little deep
743 self
.sendMessage(message
)