]>
code.delx.au - pymsnt/blob - src/legacy/glue.py
939751f4b08bf0f1653949fc6b0412fab7593eb8
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 task
6 from tlib
. xmlw
import Element
8 from debug
import LogEvent
, INFO
, WARN
, ERROR
20 name
= "MSN Transport" # The name of the transport
21 url
= "http://msn-transport.jabberstudio.org"
22 version
= "0.11-dev" # The transport version
23 mangle
= True # XDB '@' -> '%' mangling
24 id = "msn" # The transport identifier
27 # Load the default avatars
28 f
= open ( "src/legacy/defaultJabberAvatar.png" )
29 defaultJabberAvatarData
= f
. read ()
32 f
= open ( "src/legacy/defaultAvatar.png" )
33 defaultAvatarData
= f
. read ()
35 defaultAvatar
= avatar
. AvatarCache (). setAvatar ( defaultAvatarData
)
39 """ Returns True if the JID passed is a valid groupchat JID (for MSN, does not contain '%') """
40 return ( jid
. find ( '%' ) == - 1 )
44 # This should be set to the name space the registration entries are in, in the xdb spool
45 namespace
= "jabber:iq:register"
48 def formRegEntry ( username
, password
):
49 """ Returns a domish.Element representation of the data passed. This element will be written to the XDB spool file """
50 reginfo
= Element (( None , "query" ))
51 reginfo
. attributes
[ "xmlns" ] = "jabber:iq:register"
53 userEl
= reginfo
. addElement ( "username" )
54 userEl
. addContent ( username
)
56 passEl
= reginfo
. addElement ( "password" )
57 passEl
. addContent ( password
)
64 def getAttributes ( base
):
65 """ This function should, given a spool domish.Element, pull the username, password,
66 and out of it and return them """
69 for child
in base
. elements ():
71 if child
. name
== "username" :
72 username
= child
.__ str
__ ()
73 elif child
. name
== "password" :
74 password
= child
.__ str
__ ()
75 except AttributeError :
78 return username
, password
81 def startStats ( statistics
):
82 stats
= statistics
. stats
83 stats
[ "MessageCount" ] = 0
84 stats
[ "FailedMessageCount" ] = 0
85 stats
[ "AvatarCount" ] = 0
86 stats
[ "FailedAvatarCount" ] = 0
88 def updateStats ( statistics
):
89 stats
= statistics
. stats
91 #stats["AvatarCount"] = msnp2p.MSNP2P_Avatar.TRANSFER_COUNT
92 #stats["FailedAvatarCount"] = msnp2p.MSNP2P_Avatar.ERROR_COUNT
96 """ Converts a MSN passport into a JID representation to be used with the transport """
97 return msnid
. replace ( '@' , '%' ) + "@" + config
. jid
99 translateAccount
= msn2jid
# Marks this as the function to be used in jabber:iq:gateway (Service ID Translation)
102 """ Converts a JID representation of a MSN passport into the original MSN passport """
103 return unicode ( jid
[: jid
. find ( '@' )]. replace ( '%' , '@' ))
106 def presence2state ( show
, ptype
):
107 """ Converts a Jabber presence into an MSN status code """
108 if ptype
== "unavailable" :
109 return msn
. STATUS_OFFLINE
110 elif not show
or show
== "online" or show
== "chat" :
111 return msn
. STATUS_ONLINE
113 return msn
. STATUS_BUSY
114 elif show
== "away" or show
== "xa" :
115 return msn
. STATUS_AWAY
118 def state2presence ( state
):
119 """ Converts a MSN status code into a Jabber presence """
120 if state
== msn
. STATUS_ONLINE
:
122 elif state
== msn
. STATUS_BUSY
:
124 elif state
== msn
. STATUS_AWAY
:
125 return ( "away" , None )
126 elif state
== msn
. STATUS_IDLE
:
127 return ( "away" , None )
128 elif state
== msn
. STATUS_BRB
:
129 return ( "away" , None )
130 elif state
== msn
. STATUS_PHONE
:
132 elif state
== msn
. STATUS_LUNCH
:
133 return ( "away" , None )
135 return ( None , "unavailable" )
138 def getGroupNames ( msnContact
, msnContactList
):
139 """ Gets a list of groups that this contact is in """
141 for groupGUID
in msnContact
. groups
:
143 groups
. append ( msnContactList
. groups
[ groupGUID
])
148 def msnlist2jabsub ( lists
):
149 """ Converts MSN contact lists ORed together into the corresponding Jabber subscription state """
150 if lists
& msn
. FORWARD_LIST
and lists
& msn
. REVERSE_LIST
:
152 elif lists
& msn
. REVERSE_LIST
:
154 elif lists
& msn
. FORWARD_LIST
:
160 def jabsub2msnlist ( sub
):
161 """ Converts a Jabber subscription state into the corresponding MSN contact lists ORed together """
163 return msn
. FORWARD_LIST
165 return msn
. REVERSE_LIST
167 return ( msn
. FORWARD_LIST | msn
. REVERSE_LIST
)
175 # This class handles groupchats with the legacy protocol
176 class LegacyGroupchat ( groupchat
. BaseGroupchat
):
177 def __init__ ( self
, session
, resource
, ID
= None , existing
= False , switchboardSession
= None ):
178 """ Possible entry points for groupchat
179 - User starts an empty switchboard session by sending presence to a blank room
180 - An existing switchboard session is joined by another MSN user
181 - User invited to an existing switchboard session with more than one user
183 groupchat
. BaseGroupchat
.__ init
__ ( self
, session
, resource
, ID
)
185 self
. switchboardSession
= msn
. GroupchatSwitchboardSession ( self
, makeSwitchboard
= True )
187 self
. switchboardSession
= switchboardSession
189 assert ( self
. switchboardSession
!= None )
191 LogEvent ( INFO
, self
. roomJID ())
194 self
. switchboardSession
. removeMe ()
195 self
. switchboardSession
= None
196 groupchat
. BaseGroupchat
. removeMe ( self
)
197 LogEvent ( INFO
, self
. roomJID ())
199 def sendLegacyMessage ( self
, message
, noerror
):
200 LogEvent ( INFO
, self
. roomJID ())
201 self
. switchboardSession
. sendMessage ( message
. replace ( " \n " , " \r\n " ), noerror
)
203 def sendContactInvite ( self
, contactJID
):
204 LogEvent ( INFO
, self
. roomJID ())
205 userHandle
= jid2msn ( contactJID
)
206 self
. switchboardSession
. inviteUser ( userHandle
)
210 # This class handles most interaction with the legacy protocol
211 class LegacyConnection ( msn
. MSNConnection
):
212 """ A glue class that connects to the legacy network """
213 def __init__ ( self
, username
, password
, session
):
214 self
. session
= session
215 self
. listSynced
= False
216 self
. initialListVersion
= 0
219 self
. remoteStatus
= ""
223 msn
. MSNConnection
.__ init
__ ( self
, username
, password
, self
. session
. jabberID
)
225 # User typing notification stuff
226 self
. userTyping
= dict () # Indexed by contact MSN ID, stores whether the user is typing to this contact
227 # Contact typing notification stuff
228 self
. contactTyping
= dict () # Indexed by contact MSN ID, stores an integer that is incremented at 5 second intervals. If it reaches 3 then the contact has stopped typing. It is set to zero whenever MSN typing notification messages are received
230 self
. userTypingSend
= task
. LoopingCall ( self
. sendTypingNotifications
)
231 self
. userTypingSend
. start ( 5.0 )
233 self
. legacyList
= LegacyList ( self
. session
)
235 LogEvent ( INFO
, self
. session
. jabberID
)
238 LogEvent ( INFO
, self
. session
. jabberID
)
240 self
. userTypingSend
. stop ()
242 self
. legacyList
. removeMe ()
243 self
. legacyList
= None
246 def resourceOffline ( self
, resource
):
249 def highestResource ( self
):
250 """ Returns highest priority resource """
251 return self
. session
. highestResource ()
253 def sendMessage ( self
, dest
, resource
, body
, noerror
):
255 if self
. userTyping
. has_key ( dest
):
256 del self
. userTyping
[ dest
]
258 msn
. MSNConnection
. sendMessage ( self
, dest
, body
, noerror
)
259 self
. session
. pytrans
. statistics
. stats
[ "MessageCount" ] += 1
261 self
. failedMessage ( dest
, body
)
264 def msnAlert ( self
, text
, actionurl
, subscrurl
):
265 if not self
. session
: return
267 el
= Element (( None , "message" ))
268 el
. attributes
[ "to" ] = self
. session
. jabberID
269 el
. attributes
[ "from" ] = config
. jid
270 el
. attributes
[ "type" ] = "headline"
271 body
= el
. addElement ( "body" )
272 body
. addContent ( text
)
274 x
= el
. addElement ( "x" )
275 x
. attributes
[ "xmlns" ] = "jabber:x:oob"
276 x
. addElement ( "desc" ). addContent ( "More information on this notice." )
277 x
. addElement ( "url" ). addContent ( actionurl
)
279 x
= el
. addElement ( "x" )
280 x
. attributes
[ "xmlns" ] = "jabber:x:oob"
281 x
. addElement ( "desc" ). addContent ( "Manage subscriptions to alerts." )
282 x
. addElement ( "url" ). addContent ( subscrurl
)
284 self
. session
. pytrans
. send ( el
)
286 def setStatus ( self
, nickname
, show
, status
):
287 statusCode
= presence2state ( show
, None )
288 msn
. MSNConnection
. changeStatus ( self
, statusCode
, nickname
, status
)
290 def updateAvatar ( self
, av
= None ):
291 global defaultJabberAvatarData
294 msn
. MSNConnection
. changeAvatar ( self
, av
. getImageData ())
296 msn
. MSNConnection
. changeAvatar ( self
, defaultJabberAvatarData
)
298 def sendTypingNotifications ( self
):
299 if not self
. session
: return
301 # Send any typing notification messages to the user's contacts
302 for contact
in self
. userTyping
. keys ():
303 if self
. userTyping
[ contact
]:
304 self
. sendTypingToContact ( contact
)
306 # Send any typing notification messages from contacts to the user
307 for contact
in self
. contactTyping
. keys ():
308 self
. contactTyping
[ contact
] += 1
309 if self
. contactTyping
[ contact
] >= 3 :
310 self
. session
. sendTypingNotification ( self
. session
. jabberID
, msn2jid ( contact
), False )
311 del self
. contactTyping
[ contact
]
313 def gotContactTyping ( self
, contact
):
314 if not self
. session
: return
315 # Check if the contact has only just started typing
316 if not self
. contactTyping
. has_key ( contact
):
317 self
. session
. sendTypingNotification ( self
. session
. jabberID
, msn2jid ( contact
), True )
320 self
. contactTyping
[ contact
] = 0
322 def userTypingNotification ( self
, dest
, resource
, composing
):
323 if not self
. session
: return
325 self
. userTyping
[ dest
] = composing
326 if composing
: # Make it instant
327 self
. sendTypingToContact ( dest
)
329 def listSynchronized ( self
):
330 if not self
. session
: return
331 self
. session
. sendPresence ( to
= self
. session
. jabberID
, fro
= config
. jid
)
332 self
. legacyList
. syncJabberLegacyLists ()
333 self
. listSynced
= True
334 #self.legacyList.flushSubscriptionBuffer()
336 def gotMessage ( self
, remoteUser
, text
):
337 if not self
. session
: return
338 source
= msn2jid ( remoteUser
)
339 self
. session
. sendMessage ( self
. session
. jabberID
, fro
= source
, body
= text
, mtype
= "chat" )
340 self
. session
. pytrans
. statistics
. stats
[ "MessageCount" ] += 1
342 def avatarHashChanged ( self
, userHandle
, hash ):
343 if not self
. session
: return
346 # They've turned off their avatar
347 c
= self
. session
. contactList
. findContact ( jid
)
352 av
= self
. session
. pytrans
. avatarCache
. getAvatar ( hash )
354 msnContact
= self
. getContacts (). getContact ( userHandle
)
355 msnContact
. msnobjGot
= True
356 jid
= msn2jid ( userHandle
)
357 c
= self
. session
. contactList
. findContact ( jid
)
361 self
. requestAvatar ( userHandle
)
363 def gotAvatarImage ( self
, userHandle
, imageData
):
364 if not self
. session
: return
365 jid
= msn2jid ( userHandle
)
366 c
= self
. session
. contactList
. findContact ( jid
)
368 av
= self
. session
. pytrans
. avatarCache
. setAvatar ( imageData
)
371 def gotSendRequest ( self
, fileReceive
):
372 if not self
. session
: return
373 LogEvent ( INFO
, self
. session
. jabberID
)
374 ft
. FTReceive ( self
. session
, msn2jid ( fileReceive
. userHandle
), fileReceive
)
377 if not self
. session
: return
378 LogEvent ( INFO
, self
. session
. jabberID
)
379 self
. session
. ready
= True
381 def contactStatusChanged ( self
, remoteUser
, statusCode
, screenName
):
382 if not ( self
. session
and self
. getContacts ()): return
383 LogEvent ( INFO
, self
. session
. jabberID
)
385 msnContact
= self
. getContacts (). getContact ( remoteUser
)
386 c
= self
. session
. contactList
. findContact ( msn2jid ( remoteUser
))
387 if not ( c
and msnContact
): return
389 show
, ptype
= state2presence ( msnContact
. status
)
390 status
= msnContact
. personal
. decode ( "utf-8" )
391 screenName
= msnContact
. screenName
. decode ( "utf-8" )
393 c
. updateNickname ( screenName
, push
= False )
394 c
. updatePresence ( show
, status
, ptype
, force
= True )
396 def ourStatusChanged ( self
, statusCode
):
397 # Send out a new presence packet to the Jabber user so that the MSN-t icon changes
398 if not self
. session
: return
399 LogEvent ( INFO
, self
. session
. jabberID
)
400 self
. remoteShow
, ptype
= state2presence ( statusCode
)
401 self
. sendShowStatus ()
403 def ourPersonalChanged ( self
, statusMessage
):
404 if not self
. session
: return
405 LogEvent ( INFO
, self
. session
. jabberID
)
406 self
. remoteStatus
= statusMessage
407 self
. sendShowStatus ()
409 def ourNickChanged ( self
, nick
):
410 if not self
. session
: return
411 LogEvent ( INFO
, self
. session
. jabberID
)
412 self
. remoteNick
= nick
413 self
. sendShowStatus ()
415 def sendShowStatus ( self
):
416 if not self
. session
: return
418 to
= self
. session
. jabberID
419 self
. session
. sendPresence ( to
= to
, fro
= source
, show
= self
. remoteShow
, status
= self
. remoteStatus
, nickname
= self
. remoteNick
)
421 def userMapping ( self
, passport
, jid
):
422 if not self
. session
: return
423 text
= lang
. get ( self
. session
. lang
). userMapping
% ( passport
, jid
)
424 self
. session
. sendMessage ( to
= self
. session
. jabberID
, fro
= msn2jid ( passport
), body
= text
)
426 def userAddedMe ( self
, userHandle
):
427 if not self
. session
: return
428 self
. session
. contactList
. getContact ( msn2jid ( userHandle
)). contactRequestsAuth ()
430 def userRemovedMe ( self
, userHandle
):
431 if not self
. session
: return
432 c
= self
. session
. contactList
. getContact ( msn2jid ( userHandle
))
433 c
. contactDerequestsAuth ()
434 c
. contactRemovesAuth ()
436 def serverGoingDown ( self
):
437 if not self
. session
: return
438 self
. session
. sendMessage ( to
= self
. session
. jabberID
, fro
= config
. jid
, body
= lang
. get ( self
. session
. lang
). msnMaintenance
)
440 def multipleLogin ( self
):
441 if not self
. session
: return
442 self
. session
. sendMessage ( to
= self
. session
. jabberID
, fro
= config
. jid
, body
= lang
. get ( self
. session
. lang
). msnMultipleLogin
)
443 self
. session
. removeMe ()
445 def accountNotVerified ( self
):
446 if not self
. session
: return
447 text
= lang
. get ( self
. session
. lang
). msnNotVerified
% ( self
. session
. username
)
448 self
. session
. sendMessage ( to
= self
. session
. jabberID
, fro
= config
. jid
, body
= text
)
450 def loginFailure ( self
, message
):
451 if not self
. session
: return
452 text
= lang
. get ( self
. session
. lang
). msnLoginFailure
% ( self
. session
. username
)
453 self
. session
. sendErrorMessage ( to
= self
. session
. jabberID
, fro
= config
. jid
, etype
= "auth" , condition
= "not-authorized" , explanation
= text
, body
= "Login Failure" )
454 self
. session
. removeMe ()
456 def failedMessage ( self
, remoteUser
, message
):
457 if not self
. session
: return
458 self
. session
. pytrans
. statistics
. stats
[ "FailedMessageCount" ] += 1
459 fro
= msn2jid ( remoteUser
)
460 self
. session
. sendErrorMessage ( to
= self
. session
. jabberID
, fro
= fro
, etype
= "wait" , condition
= "recipient-unavailable" , explanation
= lang
. get ( self
. session
. lang
). msnFailedMessage
, body
= message
)
462 def initialEmailNotification ( self
, inboxunread
, foldersunread
):
463 if not self
. session
: return
464 text
= lang
. get ( self
. session
. lang
). msnInitialMail
% ( inboxunread
, foldersunread
)
465 self
. session
. sendMessage ( to
= self
. session
. jabberID
, fro
= config
. jid
, body
= text
, mtype
= "headline" )
467 def realtimeEmailNotification ( self
, mailfrom
, fromaddr
, subject
):
468 if not self
. session
: return
469 text
= lang
. get ( self
. session
. lang
). msnRealtimeMail
% ( mailfrom
, fromaddr
, subject
)
470 self
. session
. sendMessage ( to
= self
. session
. jabberID
, fro
= config
. jid
, body
= text
, mtype
= "headline" )
472 def connectionLost ( self
, reason
):
473 if not self
. session
: return
474 LogEvent ( INFO
, self
. session
. jabberID
)
475 text
= lang
. get ( self
. session
. lang
). msnDisconnected
% ( "Error" ) # FIXME, a better error would be nice =P
476 self
. session
. sendMessage ( to
= self
. session
. jabberID
, fro
= config
. jid
, body
= text
)
477 self
. session
. removeMe () # Tear down the session
481 def __init__ ( self
, session
):
482 self
. session
= session
483 self
. subscriptionBuffer
= []
486 self
. subscriptionBuffer
= None
489 def addContact ( self
, jid
):
490 LogEvent ( INFO
, self
. session
. jabberID
)
491 userHandle
= jid2msn ( jid
)
492 self
. session
. legacycon
. addContact ( msn
. FORWARD_LIST
, userHandle
)
493 self
. session
. contactList
. getContact ( jid
). contactGrantsAuth ()
495 def removeContact ( self
, jid
):
496 LogEvent ( INFO
, self
. session
. jabberID
)
498 self
. session
. legacycon
. remContact ( msn
. FORWARD_LIST
, jid
)
501 def authContact ( self
, jid
):
502 LogEvent ( INFO
, self
. session
. jabberID
)
504 d
= self
. session
. legacycon
. remContact ( msn
. PENDING_LIST
, jid
)
506 self
. session
. legacycon
. addContact ( msn
. REVERSE_LIST
, jid
)
507 self
. session
. legacycon
. remContact ( msn
. BLOCK_LIST
, jid
)
508 self
. session
. legacycon
. addContact ( msn
. ALLOW_LIST
, jid
)
510 def deauthContact ( self
, jid
):
511 LogEvent ( INFO
, self
. session
. jabberID
)
513 self
. session
. legacycon
. remContact ( msn
. ALLOW_LIST
, jid
)
514 self
. session
. legacycon
. addContact ( msn
. BLOCK_LIST
, jid
)
518 def syncJabberLegacyLists ( self
):
519 """ Synchronises the MSN contact list on server with the Jabber contact list """
523 # We have to make an MSNContactList from the XDB data, then compare it with the one the server sent
524 # Any subscription changes must be sent to the client, as well as changed in the XDB
525 LogEvent ( INFO
, self
. session
. jabberID
, "Start." )
526 result
= self
. session
. pytrans
. xdb
. request ( self
. session
. jabberID
, disco
. IQROSTER
)
527 oldContactList
= msn
. MSNContactList ()
529 for item
in result
. elements ():
530 user
= item
. getAttribute ( "jid" )
531 sub
= item
. getAttribute ( "subscription" )
532 lists
= item
. getAttribute ( "lists" )
534 lists
= jabsub2msnlist ( sub
) # Backwards compatible
536 contact
= msn
. MSNContact ( userHandle
= user
, screenName
= "" , lists
= lists
)
537 oldContactList
. addContact ( contact
)
539 newXDB
= Element (( None , "query" ))
540 newXDB
. attributes
[ "xmlns" ] = disco
. IQROSTER
542 contactList
= self
. session
. legacycon
. getContacts ()
545 # Convienence functions
546 def addedToList ( num
):
547 return ( not ( oldLists
& num
) and ( lists
& num
))
548 def removedFromList ( num
):
549 return (( oldLists
& num
) and not ( lists
& num
))
551 for contact
in contactList
. contacts
. values ():
552 # Compare with the XDB <item/> entry
553 oldContact
= oldContactList
. getContact ( contact
. userHandle
)
554 if oldContact
== None :
557 oldLists
= oldContact
. lists
558 lists
= contact
. lists
560 # Create the Jabber representation of the
561 # contact base on the old list data and then
562 # sync it with current
563 jabContact
= self
. session
. contactList
. createContact ( msn2jid ( contact
. userHandle
), msnlist2jabsub ( oldLists
))
564 jabContact
. updateAvatar ( defaultAvatar
, push
= False )
566 if addedToList ( msn
. FORWARD_LIST
):
567 jabContact
. syncGroups ( getGroupNames ( contact
, contactList
), push
= False )
568 jabContact
. syncContactGrantedAuth ()
570 if removedFromList ( msn
. FORWARD_LIST
):
571 jabContact
. syncContactRemovedAuth ()
573 if addedToList ( msn
. ALLOW_LIST
):
574 jabContact
. syncUserGrantedAuth ()
576 if addedToList ( msn
. BLOCK_LIST
) or removedFromList ( msn
. ALLOW_LIST
):
577 jabContact
. syncUserRemovedAuth ()
579 if ( not ( lists
& msn
. ALLOW_LIST
) and not ( lists
& msn
. BLOCK_LIST
) and ( lists
& msn
. REVERSE_LIST
)) or ( lists
& msn
. PENDING_LIST
):
580 jabContact
. contactRequestsAuth ()
582 if removedFromList ( msn
. REVERSE_LIST
):
583 jabContact
. contactDerequestsAuth ()
585 item
= newXDB
. addElement ( "item" )
586 item
. attributes
[ "jid" ] = contact
. userHandle
587 item
. attributes
[ "subscription" ] = msnlist2jabsub ( lists
)
588 item
. attributes
[ "lists" ] = str ( lists
)
591 self
. session
. pytrans
. xdb
. set ( self
. session
. jabberID
, disco
. IQROSTER
, newXDB
)
592 LogEvent ( INFO
, self
. session
. jabberID
, "End." )
594 def saveLegacyList ( self
):
595 contactList
= self
. session
. legacycon
. getContacts ()
596 if not contactList
: return
598 newXDB
= Element (( None , "query" ))
599 newXDB
. attributes
[ "xmlns" ] = disco
. IQROSTER
601 for contact
in contactList
. contacts
. values ():
602 item
= newXDB
. addElement ( "item" )
603 item
. attributes
[ "jid" ] = contact
. userHandle
604 item
. attributes
[ "subscription" ] = msnlist2jabsub ( contact
. lists
) # Backwards compat
605 item
. attributes
[ "lists" ] = str ( contact
. lists
)
607 self
. session
. pytrans
. xdb
. set ( self
. session
. jabberID
, disco
. IQROSTER
, newXDB
)
608 LogEvent ( INFO
, self
. session
. jabberID
, "Finished saving list." )