]>
code.delx.au - pymsnt/blob - src/legacy/glue.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 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 avatar
28 f
= open ( "src/legacy/defaultJabberAvatar.png" )
29 defaultJabberAvatarData
= f
. read ()
34 """ Returns True if the JID passed is a valid groupchat JID (for MSN, does not contain '%') """
35 return ( jid
. find ( '%' ) == - 1 )
39 # This should be set to the name space the registration entries are in, in the xdb spool
40 namespace
= "jabber:iq:register"
43 def formRegEntry ( username
, password
):
44 """ Returns a domish.Element representation of the data passed. This element will be written to the XDB spool file """
45 reginfo
= Element (( None , "query" ))
46 reginfo
. attributes
[ "xmlns" ] = "jabber:iq:register"
48 userEl
= reginfo
. addElement ( "username" )
49 userEl
. addContent ( username
)
51 passEl
= reginfo
. addElement ( "password" )
52 passEl
. addContent ( password
)
59 def getAttributes ( base
):
60 """ This function should, given a spool domish.Element, pull the username, password,
61 and out of it and return them """
64 for child
in base
. elements ():
66 if child
. name
== "username" :
67 username
= child
.__ str
__ ()
68 elif child
. name
== "password" :
69 password
= child
.__ str
__ ()
70 except AttributeError :
73 return username
, password
76 def startStats ( statistics
):
77 stats
= statistics
. stats
78 stats
[ "MessageCount" ] = 0
79 stats
[ "FailedMessageCount" ] = 0
80 stats
[ "AvatarCount" ] = 0
81 stats
[ "FailedAvatarCount" ] = 0
83 def updateStats ( statistics
):
84 stats
= statistics
. stats
86 #stats["AvatarCount"] = msnp2p.MSNP2P_Avatar.TRANSFER_COUNT
87 #stats["FailedAvatarCount"] = msnp2p.MSNP2P_Avatar.ERROR_COUNT
91 """ Converts a MSN passport into a JID representation to be used with the transport """
92 return msnid
. replace ( '@' , '%' ) + "@" + config
. jid
94 translateAccount
= msn2jid
# Marks this as the function to be used in jabber:iq:gateway (Service ID Translation)
97 """ Converts a JID representation of a MSN passport into the original MSN passport """
98 return unicode ( jid
[: jid
. find ( '@' )]. replace ( '%' , '@' ))
101 def presence2state ( show
, ptype
):
102 """ Converts a Jabber presence into an MSN status code """
103 if ptype
== "unavailable" :
104 return msn
. STATUS_OFFLINE
105 elif not show
or show
== "online" or show
== "chat" :
106 return msn
. STATUS_ONLINE
108 return msn
. STATUS_BUSY
109 elif show
== "away" or show
== "xa" :
110 return msn
. STATUS_AWAY
113 def state2presence ( state
):
114 """ Converts a MSN status code into a Jabber presence """
115 if state
== msn
. STATUS_ONLINE
:
117 elif state
== msn
. STATUS_BUSY
:
119 elif state
== msn
. STATUS_AWAY
:
120 return ( "away" , None )
121 elif state
== msn
. STATUS_IDLE
:
122 return ( "away" , None )
123 elif state
== msn
. STATUS_BRB
:
124 return ( "away" , None )
125 elif state
== msn
. STATUS_PHONE
:
127 elif state
== msn
. STATUS_LUNCH
:
128 return ( "away" , None )
130 return ( None , "unavailable" )
135 # This class handles groupchats with the legacy protocol
136 class LegacyGroupchat ( groupchat
. BaseGroupchat
):
137 def __init__ ( self
, session
, resource
, ID
= None , existing
= False , switchboardSession
= None ):
138 """ Possible entry points for groupchat
139 - User starts an empty switchboard session by sending presence to a blank room
140 - An existing switchboard session is joined by another MSN user
141 - User invited to an existing switchboard session with more than one user
143 groupchat
. BaseGroupchat
.__ init
__ ( self
, session
, resource
, ID
)
145 self
. switchboardSession
= msnw
. GroupchatSwitchboardSession ( self
, makeSwitchboard
= True )
147 self
. switchboardSession
= switchboardSession
149 assert ( self
. switchboardSession
!= None )
151 LogEvent ( INFO
, self
. roomJID ())
154 self
. switchboardSession
. removeMe ()
155 self
. switchboardSession
= None
156 groupchat
. BaseGroupchat
. removeMe ( self
)
157 LogEvent ( INFO
, self
. roomJID ())
159 def sendLegacyMessage ( self
, message
, noerror
):
160 LogEvent ( INFO
, self
. roomJID ())
161 self
. switchboardSession
. sendMessage ( message
. replace ( " \n " , " \r\n " ), noerror
)
163 def sendContactInvite ( self
, contactJID
):
164 LogEvent ( INFO
, self
. roomJID ())
165 userHandle
= jid2msn ( contactJID
)
166 self
. switchboardSession
. inviteUser ( userHandle
)
170 # This class handles most interaction with the legacy protocol
171 class LegacyConnection ( msnw
. MSNConnection
):
172 """ A glue class that connects to the legacy network """
173 def __init__ ( self
, username
, password
, session
):
174 self
. session
= session
175 self
. listSynced
= False
176 self
. initialListVersion
= 0
179 self
. remoteStatus
= ""
183 msnw
. MSNConnection
.__ init
__ ( self
, username
, password
)
185 # User typing notification stuff
186 self
. userTyping
= dict () # Indexed by contact MSN ID, stores whether the user is typing to this contact
187 # Contact typing notification stuff
188 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
190 self
. userTypingSend
= task
. LoopingCall ( self
. sendTypingNotifications
)
191 self
. userTypingSend
. start ( 5.0 )
193 import legacylist
# Is in here to prevent an ImportError loop
194 self
. legacyList
= legacylist
. LegacyList ( self
. session
)
196 LogEvent ( INFO
, self
. session
. jabberID
)
199 LogEvent ( INFO
, self
. session
. jabberID
)
201 self
. userTypingSend
. stop ()
203 msnw
. MSNConnection
. removeMe ( self
)
204 self
. legacyList
. removeMe ()
205 self
. legacyList
= None
208 def jidRes ( self
, resource
):
209 to
= self
. session
. jabberID
215 def highestResource ( self
):
216 """ Returns highest priority resource """
217 return self
. session
. highestResource ()
219 def sendMessage ( self
, dest
, resource
, body
, noerror
):
221 if self
. userTyping
. has_key ( dest
):
222 del self
. userTyping
[ dest
]
224 msnw
. MSNConnection
. sendMessage ( self
, dest
, resource
, body
, noerror
)
225 self
. session
. pytrans
. statistics
. stats
[ "MessageCount" ] += 1
227 self
. failedMessage ( dest
, body
)
230 def msnAlert ( self
, text
, actionurl
, subscrurl
):
231 if not self
. session
: return
233 el
= Element (( None , "message" ))
234 el
. attributes
[ "to" ] = self
. session
. jabberID
235 el
. attributes
[ "from" ] = config
. jid
236 el
. attributes
[ "type" ] = "headline"
237 body
= el
. addElement ( "body" )
238 body
. addContent ( text
)
240 x
= el
. addElement ( "x" )
241 x
. attributes
[ "xmlns" ] = "jabber:x:oob"
242 x
. addElement ( "desc" ). addContent ( "More information on this notice." )
243 x
. addElement ( "url" ). addContent ( actionurl
)
245 x
= el
. addElement ( "x" )
246 x
. attributes
[ "xmlns" ] = "jabber:x:oob"
247 x
. addElement ( "desc" ). addContent ( "Manage subscriptions to alerts." )
248 x
. addElement ( "url" ). addContent ( subscrurl
)
250 self
. session
. pytrans
. send ( el
)
252 def setStatus ( self
, nickname
, show
, status
):
253 statusCode
= presence2state ( show
, None )
254 msnw
. MSNConnection
. changeStatus ( self
, statusCode
, nickname
, status
)
256 def updateAvatar ( self
, av
= None ):
257 global defaultJabberAvatarData
260 msnw
. MSNConnection
. changeAvatar ( self
, av
. getImageData ())
262 msnw
. MSNConnection
. changeAvatar ( self
, defaultJabberAvatarData
)
264 def sendTypingNotifications ( self
):
265 if not self
. session
: return
267 # Send any typing notification messages to the user's contacts
268 for contact
in self
. userTyping
. keys ():
269 if self
. userTyping
[ contact
]:
270 self
. sendTypingToContact ( contact
)
272 # Send any typing notification messages from contacts to the user
273 for contact
, resource
in self
. contactTyping
. keys ():
274 self
. contactTyping
[( contact
, resource
)] += 1
275 if self
. contactTyping
[( contact
, resource
)] >= 3 :
276 self
. session
. sendTypingNotification ( self
. jidRes ( resource
), msn2jid ( contact
), False )
277 del self
. contactTyping
[( contact
, resource
)]
279 def gotContactTyping ( self
, contact
, resource
):
280 if not self
. session
: return
281 # Check if the contact has only just started typing
282 if not self
. contactTyping
. has_key (( contact
, resource
)):
283 self
. session
. sendTypingNotification ( self
. jidRes ( resource
), msn2jid ( contact
), True )
286 self
. contactTyping
[( contact
, resource
)] = 0
288 def userTypingNotification ( self
, dest
, resource
, composing
):
289 if not self
. session
: return
291 self
. userTyping
[ dest
] = composing
292 if composing
: # Make it instant
293 self
. sendTypingToContact ( dest
)
295 def listSynchronized ( self
):
296 if not self
. session
: return
297 self
. session
. sendPresence ( to
= self
. session
. jabberID
, fro
= config
. jid
)
298 self
. legacyList
. syncJabberLegacyLists ()
299 self
. listSynced
= True
300 #self.legacyList.flushSubscriptionBuffer()
302 def gotMessage ( self
, remoteUser
, resource
, text
):
303 if not self
. session
: return
304 source
= msn2jid ( remoteUser
)
305 self
. session
. sendMessage ( self
. jidRes ( resource
), fro
= source
, body
= text
, mtype
= "chat" )
306 self
. session
. pytrans
. statistics
. stats
[ "MessageCount" ] += 1
308 def avatarHashChanged ( self
, userHandle
, hash ):
309 if not self
. session
: return
312 # They've turned off their avatar
313 c
= self
. session
. contactList
. findContact ( jid
)
318 av
= self
. session
. pytrans
. avatarCache
. getAvatar ( hash )
320 msnContact
= self
. getContacts (). getContact ( userHandle
)
321 msnContact
. msnobjGot
= True
322 jid
= msn2jid ( userHandle
)
323 c
= self
. session
. contactList
. findContact ( jid
)
327 self
. requestAvatar ( userHandle
)
329 def gotAvatarImage ( self
, userHandle
, imageData
):
330 if not self
. session
: return
331 jid
= msn2jid ( userHandle
)
332 c
= self
. session
. contactList
. findContact ( jid
)
334 av
= self
. session
. pytrans
. avatarCache
. setAvatar ( imageData
)
337 def gotSendRequest ( self
, fileReceive
):
338 if not self
. session
: return
339 LogEvent ( INFO
, self
. session
. jabberID
)
340 ft
. FTReceive ( self
. session
, msn2jid ( fileReceive
. userHandle
), fileReceive
)
343 if not self
. session
: return
344 LogEvent ( INFO
, self
. session
. jabberID
)
345 self
. session
. ready
= True
347 def contactStatusChanged ( self
, remoteUser
):
348 if not ( self
. session
and self
. getContacts ()): return
349 LogEvent ( INFO
, self
. session
. jabberID
)
351 msnContact
= self
. getContacts (). getContact ( remoteUser
)
352 c
= self
. session
. contactList
. findContact ( msn2jid ( remoteUser
))
353 if not ( c
and msnContact
): return
355 show
, ptype
= state2presence ( msnContact
. status
)
356 status
= msnContact
. personal
. decode ( "utf-8" )
357 screenName
= msnContact
. screenName
. decode ( "utf-8" )
359 c
. updateNickname ( screenName
, push
= False )
360 c
. updatePresence ( show
, status
, ptype
, force
= True )
362 def ourStatusChanged ( self
, statusCode
):
363 # Send out a new presence packet to the Jabber user so that the MSN-t icon changes
364 if not self
. session
: return
365 LogEvent ( INFO
, self
. session
. jabberID
)
366 self
. remoteShow
, ptype
= state2presence ( statusCode
)
367 self
. sendShowStatus ()
369 def ourPersonalChanged ( self
, statusMessage
):
370 if not self
. session
: return
371 LogEvent ( INFO
, self
. session
. jabberID
)
372 self
. remoteStatus
= statusMessage
373 self
. sendShowStatus ()
375 def ourNickChanged ( self
, nick
):
376 if not self
. session
: return
377 LogEvent ( INFO
, self
. session
. jabberID
)
378 self
. remoteNick
= nick
379 self
. sendShowStatus ()
381 def sendShowStatus ( self
):
382 if not self
. session
: return
384 to
= self
. session
. jabberID
385 self
. session
. sendPresence ( to
= to
, fro
= source
, show
= self
. remoteShow
, status
= self
. remoteStatus
, nickname
= self
. remoteNick
)
387 def userMapping ( self
, passport
, jid
):
388 if not self
. session
: return
389 text
= lang
. get ( self
. session
. lang
). userMapping
% ( passport
, jid
)
390 self
. session
. sendMessage ( to
= self
. session
. jabberID
, fro
= msn2jid ( passport
), body
= text
)
392 def userAddedMe ( self
, userHandle
):
393 if not self
. session
: return
394 self
. session
. contactList
. getContact ( msn2jid ( userHandle
)). contactRequestsAuth ()
396 def userRemovedMe ( self
, userHandle
):
397 if not self
. session
: return
398 c
= self
. session
. contactList
. getContact ( msn2jid ( userHandle
))
399 c
. contactDerequestsAuth ()
400 c
. contactRemovesAuth ()
402 def serverGoingDown ( self
):
403 if not self
. session
: return
404 self
. session
. sendMessage ( to
= self
. session
. jabberID
, fro
= config
. jid
, body
= lang
. get ( self
. session
. lang
). msnMaintenance
)
406 def multipleLogin ( self
):
407 if not self
. session
: return
408 self
. session
. sendMessage ( to
= self
. session
. jabberID
, fro
= config
. jid
, body
= lang
. get ( self
. session
. lang
). msnMultipleLogin
)
409 self
. session
. removeMe ()
411 def accountNotVerified ( self
):
412 if not self
. session
: return
413 text
= lang
. get ( self
. session
. lang
). msnNotVerified
% ( self
. session
. username
)
414 self
. session
. sendMessage ( to
= self
. session
. jabberID
, fro
= config
. jid
, body
= text
)
416 def loginFailure ( self
, message
):
417 if not self
. session
: return
418 text
= lang
. get ( self
. session
. lang
). msnLoginFailure
% ( self
. session
. username
)
419 self
. session
. sendErrorMessage ( to
= self
. session
. jabberID
, fro
= config
. jid
, etype
= "auth" , condition
= "not-authorized" , explanation
= text
, body
= "Login Failure" )
420 self
. session
. removeMe ()
422 def failedMessage ( self
, remoteUser
, message
):
423 if not self
. session
: return
424 self
. session
. pytrans
. statistics
. stats
[ "FailedMessageCount" ] += 1
425 fro
= msn2jid ( remoteUser
)
426 self
. session
. sendErrorMessage ( to
= self
. session
. jabberID
, fro
= fro
, etype
= "wait" , condition
= "recipient-unavailable" , explanation
= lang
. get ( self
. session
. lang
). msnFailedMessage
, body
= message
)
428 def initialEmailNotification ( self
, inboxunread
, foldersunread
):
429 if not self
. session
: return
430 text
= lang
. get ( self
. session
. lang
). msnInitialMail
% ( inboxunread
, foldersunread
)
431 self
. session
. sendMessage ( to
= self
. session
. jabberID
, fro
= config
. jid
, body
= text
, mtype
= "headline" )
433 def realtimeEmailNotification ( self
, mailfrom
, fromaddr
, subject
):
434 if not self
. session
: return
435 text
= lang
. get ( self
. session
. lang
). msnRealtimeMail
% ( mailfrom
, fromaddr
, subject
)
436 self
. session
. sendMessage ( to
= self
. session
. jabberID
, fro
= config
. jid
, body
= text
, mtype
= "headline" )
438 def connectionLost ( self
, reason
):
439 if not self
. session
: return
440 LogEvent ( INFO
, self
. session
. jabberID
)
441 text
= lang
. get ( self
. session
. lang
). msnDisconnected
% ( "Error" ) # FIXME, a better error would be nice =P
442 self
. session
. sendMessage ( to
= self
. session
. jabberID
, fro
= config
. jid
, body
= text
)
443 self
. session
. removeMe () # Tear down the session