]>
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
7 from tlib
import msn
, msnp2p
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
85 stats
[ "AvatarCount" ] = msnp2p
. MSNP2P_Avatar
. TRANSFER_COUNT
86 stats
[ "FailedAvatarCount" ] = msnp2p
. MSNP2P_Avatar
. ERROR_COUNT
90 """ Converts a MSN passport into a JID representation to be used with the transport """
91 return msnid
. replace ( '@' , '%' ) + "@" + config
. jid
93 translateAccount
= msn2jid
# Marks this as the function to be used in jabber:iq:gateway (Service ID Translation)
96 """ Converts a JID representation of a MSN passport into the original MSN passport """
97 return unicode ( jid
[: jid
. find ( '@' )]. replace ( '%' , '@' ))
100 def presence2state ( show
, ptype
):
101 """ Converts a Jabber presence into an MSN status code """
102 if ptype
== "unavailable" :
103 return msn
. STATUS_OFFLINE
104 elif not show
or show
== "online" or show
== "chat" :
105 return msn
. STATUS_ONLINE
107 return msn
. STATUS_BUSY
108 elif show
== "away" or show
== "xa" :
109 return msn
. STATUS_AWAY
112 def state2presence ( state
):
113 """ Converts a MSN status code into a Jabber presence """
114 if state
== msn
. STATUS_ONLINE
:
116 elif state
== msn
. STATUS_BUSY
:
118 elif state
== msn
. STATUS_AWAY
:
119 return ( "away" , None )
120 elif state
== msn
. STATUS_IDLE
:
121 return ( "away" , None )
122 elif state
== msn
. STATUS_BRB
:
123 return ( "away" , None )
124 elif state
== msn
. STATUS_PHONE
:
126 elif state
== msn
. STATUS_LUNCH
:
127 return ( "away" , None )
129 return ( None , "unavailable" )
134 # This class handles groupchats with the legacy protocol
135 class LegacyGroupchat ( groupchat
. BaseGroupchat
):
136 def __init__ ( self
, session
, resource
, ID
= None , existing
= False , switchboardSession
= None ):
137 """ Possible entry points for groupchat
138 - User starts an empty switchboard session by sending presence to a blank room
139 - An existing switchboard session is joined by another MSN user
140 - User invited to an existing switchboard session with more than one user
142 groupchat
. BaseGroupchat
.__ init
__ ( self
, session
, resource
, ID
)
144 self
. switchboardSession
= msnw
. GroupchatSwitchboardSession ( self
, makeSwitchboard
= True )
146 self
. switchboardSession
= switchboardSession
148 assert ( self
. switchboardSession
!= None )
150 LogEvent ( INFO
, self
. roomJID ())
153 self
. switchboardSession
. removeMe ()
154 self
. switchboardSession
= None
155 groupchat
. BaseGroupchat
. removeMe ( self
)
156 LogEvent ( INFO
, self
. roomJID ())
158 def sendLegacyMessage ( self
, message
, noerror
):
159 LogEvent ( INFO
, self
. roomJID ())
160 self
. switchboardSession
. sendMessage ( message
. replace ( " \n " , " \r\n " ), noerror
)
162 def sendContactInvite ( self
, contactJID
):
163 LogEvent ( INFO
, self
. roomJID ())
164 userHandle
= jid2msn ( contactJID
)
165 self
. switchboardSession
. inviteUser ( userHandle
)
169 # This class handles most interaction with the legacy protocol
170 class LegacyConnection ( msnw
. MSNConnection
):
171 """ A glue class that connects to the legacy network """
172 def __init__ ( self
, username
, password
, session
):
173 self
. session
= session
174 self
. listSynced
= False
175 self
. initialListVersion
= 0
178 self
. remoteStatus
= ""
182 msnw
. MSNConnection
.__ init
__ ( self
, username
, password
)
184 # User typing notification stuff
185 self
. userTyping
= dict () # Indexed by contact MSN ID, stores whether the user is typing to this contact
186 # Contact typing notification stuff
187 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
189 self
. userTypingSend
= task
. LoopingCall ( self
. sendTypingNotifications
)
190 self
. userTypingSend
. start ( 5.0 )
192 import legacylist
# Is in here to prevent an ImportError loop
193 self
. legacyList
= legacylist
. LegacyList ( self
. session
)
195 LogEvent ( INFO
, self
. session
. jabberID
)
198 LogEvent ( INFO
, self
. session
. jabberID
)
200 self
. userTypingSend
. stop ()
202 msnw
. MSNConnection
. removeMe ( self
)
203 self
. legacyList
. removeMe ()
204 self
. legacyList
= None
207 def jidRes ( self
, resource
):
208 to
= self
. session
. jabberID
214 def highestResource ( self
):
215 """ Returns highest priority resource """
216 return self
. session
. highestResource ()
218 def sendMessage ( self
, dest
, resource
, body
, noerror
):
220 if self
. userTyping
. has_key ( dest
):
221 del self
. userTyping
[ dest
]
223 msnw
. MSNConnection
. sendMessage ( self
, dest
, resource
, body
, noerror
)
224 self
. session
. pytrans
. statistics
. stats
[ "MessageCount" ] += 1
226 self
. failedMessage ( dest
, body
)
229 def msnAlert ( self
, text
, actionurl
, subscrurl
):
230 if not self
. session
: return
232 el
= Element (( None , "message" ))
233 el
. attributes
[ "to" ] = self
. session
. jabberID
234 el
. attributes
[ "from" ] = config
. jid
235 el
. attributes
[ "type" ] = "headline"
236 body
= el
. addElement ( "body" )
237 body
. addContent ( text
)
239 x
= el
. addElement ( "x" )
240 x
. attributes
[ "xmlns" ] = "jabber:x:oob"
241 x
. addElement ( "desc" ). addContent ( "More information on this notice." )
242 x
. addElement ( "url" ). addContent ( actionurl
)
244 x
= el
. addElement ( "x" )
245 x
. attributes
[ "xmlns" ] = "jabber:x:oob"
246 x
. addElement ( "desc" ). addContent ( "Manage subscriptions to alerts." )
247 x
. addElement ( "url" ). addContent ( subscrurl
)
249 self
. session
. pytrans
. send ( el
)
251 def setStatus ( self
, nickname
, show
, status
):
252 statusCode
= presence2state ( show
, None )
253 msnw
. MSNConnection
. changeStatus ( self
, statusCode
, nickname
, status
)
255 def updateAvatar ( self
, av
= None ):
256 global defaultJabberAvatarData
259 msnw
. MSNConnection
. changeAvatar ( self
, av
. getImageData ())
261 msnw
. MSNConnection
. changeAvatar ( self
, defaultJabberAvatarData
)
263 def sendTypingNotifications ( self
):
264 if not self
. session
: return
266 # Send any typing notification messages to the user's contacts
267 for contact
in self
. userTyping
. keys ():
268 if self
. userTyping
[ contact
]:
269 self
. sendTypingToContact ( contact
)
271 # Send any typing notification messages from contacts to the user
272 for contact
, resource
in self
. contactTyping
. keys ():
273 self
. contactTyping
[( contact
, resource
)] += 1
274 if self
. contactTyping
[( contact
, resource
)] >= 3 :
275 self
. session
. sendTypingNotification ( self
. jidRes ( resource
), msn2jid ( contact
), False )
276 del self
. contactTyping
[( contact
, resource
)]
278 def gotContactTyping ( self
, contact
, resource
):
279 if not self
. session
: return
280 # Check if the contact has only just started typing
281 if not self
. contactTyping
. has_key (( contact
, resource
)):
282 self
. session
. sendTypingNotification ( self
. jidRes ( resource
), msn2jid ( contact
), True )
285 self
. contactTyping
[( contact
, resource
)] = 0
287 def userTypingNotification ( self
, dest
, resource
, composing
):
288 if not self
. session
: return
290 self
. userTyping
[ dest
] = composing
291 if composing
: # Make it instant
292 self
. sendTypingToContact ( dest
)
294 def listSynchronized ( self
):
295 if not self
. session
: return
296 self
. session
. sendPresence ( to
= self
. session
. jabberID
, fro
= config
. jid
)
297 self
. legacyList
. syncJabberLegacyLists ()
298 self
. listSynced
= True
299 #self.legacyList.flushSubscriptionBuffer()
301 def gotMessage ( self
, remoteUser
, resource
, text
):
302 if not self
. session
: return
303 source
= msn2jid ( remoteUser
)
304 self
. session
. sendMessage ( self
. jidRes ( resource
), fro
= source
, body
= text
, mtype
= "chat" )
305 self
. session
. pytrans
. statistics
. stats
[ "MessageCount" ] += 1
307 def avatarHashChanged ( self
, userHandle
, hash ):
308 if not self
. session
: return
311 # They've turned off their avatar
312 c
= self
. session
. contactList
. findContact ( jid
)
317 av
= self
. session
. pytrans
. avatarCache
. getAvatar ( hash )
319 msnContact
= self
. getContacts (). getContact ( userHandle
)
320 msnContact
. msnobjGot
= True
321 jid
= msn2jid ( userHandle
)
322 c
= self
. session
. contactList
. findContact ( jid
)
326 self
. requestAvatar ( userHandle
)
328 def gotAvatarImage ( self
, userHandle
, imageData
):
329 if not self
. session
: return
330 jid
= msn2jid ( userHandle
)
331 c
= self
. session
. contactList
. findContact ( jid
)
333 av
= self
. session
. pytrans
. avatarCache
. setAvatar ( imageData
)
336 def gotSendRequest ( self
, fileReceive
):
337 if not self
. session
: return
338 LogEvent ( INFO
, self
. session
. jabberID
)
339 ft
. FTReceive ( self
. session
, msn2jid ( fileReceive
. userHandle
), fileReceive
)
342 if not self
. session
: return
343 LogEvent ( INFO
, self
. session
. jabberID
)
344 self
. session
. ready
= True
346 def contactStatusChanged ( self
, remoteUser
):
347 if not ( self
. session
and self
. getContacts ()): return
348 LogEvent ( INFO
, self
. session
. jabberID
)
350 msnContact
= self
. getContacts (). getContact ( remoteUser
)
351 c
= self
. session
. contactList
. findContact ( msn2jid ( remoteUser
))
352 if not ( c
and msnContact
): return
354 show
, ptype
= state2presence ( msnContact
. status
)
355 status
= msnContact
. personal
. decode ( "utf-8" )
356 screenName
= msnContact
. screenName
. decode ( "utf-8" )
358 c
. updateNickname ( screenName
, push
= False )
359 c
. updatePresence ( show
, status
, ptype
, force
= True )
361 def ourStatusChanged ( self
, statusCode
):
362 # Send out a new presence packet to the Jabber user so that the MSN-t icon changes
363 if not self
. session
: return
364 LogEvent ( INFO
, self
. session
. jabberID
)
365 self
. remoteShow
, ptype
= state2presence ( statusCode
)
366 self
. sendShowStatus ()
368 def ourPersonalChanged ( self
, statusMessage
):
369 if not self
. session
: return
370 LogEvent ( INFO
, self
. session
. jabberID
)
371 self
. remoteStatus
= statusMessage
372 self
. sendShowStatus ()
374 def ourNickChanged ( self
, nick
):
375 if not self
. session
: return
376 LogEvent ( INFO
, self
. session
. jabberID
)
377 self
. remoteNick
= nick
378 self
. sendShowStatus ()
380 def sendShowStatus ( self
):
381 if not self
. session
: return
383 to
= self
. session
. jabberID
384 self
. session
. sendPresence ( to
= to
, fro
= source
, show
= self
. remoteShow
, status
= self
. remoteStatus
, nickname
= self
. remoteNick
)
386 def userMapping ( self
, passport
, jid
):
387 if not self
. session
: return
388 text
= lang
. get ( self
. session
. lang
). userMapping
% ( passport
, jid
)
389 self
. session
. sendMessage ( to
= self
. session
. jabberID
, fro
= msn2jid ( passport
), body
= text
)
391 def userAddedMe ( self
, userHandle
):
392 if not self
. session
: return
393 self
. session
. contactList
. getContact ( msn2jid ( userHandle
)). contactRequestsAuth ()
395 def userRemovedMe ( self
, userHandle
):
396 if not self
. session
: return
397 c
= self
. session
. contactList
. getContact ( msn2jid ( userHandle
))
398 c
. contactDerequestsAuth ()
399 c
. contactRemovesAuth ()
401 def serverGoingDown ( self
):
402 if not self
. session
: return
403 self
. session
. sendMessage ( to
= self
. session
. jabberID
, fro
= config
. jid
, body
= lang
. get ( self
. session
. lang
). msnMaintenance
)
405 def multipleLogin ( self
):
406 if not self
. session
: return
407 self
. session
. sendMessage ( to
= self
. session
. jabberID
, fro
= config
. jid
, body
= lang
. get ( self
. session
. lang
). msnMultipleLogin
)
408 self
. session
. removeMe ()
410 def accountNotVerified ( self
):
411 if not self
. session
: return
412 text
= lang
. get ( self
. session
. lang
). msnNotVerified
% ( self
. session
. username
)
413 self
. session
. sendMessage ( to
= self
. session
. jabberID
, fro
= config
. jid
, body
= text
)
415 def loginFailure ( self
, message
):
416 if not self
. session
: return
417 text
= lang
. get ( self
. session
. lang
). msnLoginFailure
% ( self
. session
. username
)
418 self
. session
. sendErrorMessage ( to
= self
. session
. jabberID
, fro
= config
. jid
, etype
= "auth" , condition
= "not-authorized" , explanation
= text
, body
= "Login Failure" )
419 self
. session
. removeMe ()
421 def failedMessage ( self
, remoteUser
, message
):
422 if not self
. session
: return
423 self
. session
. pytrans
. statistics
. stats
[ "FailedMessageCount" ] += 1
424 fro
= msn2jid ( remoteUser
)
425 self
. session
. sendErrorMessage ( to
= self
. session
. jabberID
, fro
= fro
, etype
= "wait" , condition
= "recipient-unavailable" , explanation
= lang
. get ( self
. session
. lang
). msnFailedMessage
, body
= message
)
427 def initialEmailNotification ( self
, inboxunread
, foldersunread
):
428 if not self
. session
: return
429 text
= lang
. get ( self
. session
. lang
). msnInitialMail
% ( inboxunread
, foldersunread
)
430 self
. session
. sendMessage ( to
= self
. session
. jabberID
, fro
= config
. jid
, body
= text
, mtype
= "headline" )
432 def realtimeEmailNotification ( self
, mailfrom
, fromaddr
, subject
):
433 if not self
. session
: return
434 text
= lang
. get ( self
. session
. lang
). msnRealtimeMail
% ( mailfrom
, fromaddr
, subject
)
435 self
. session
. sendMessage ( to
= self
. session
. jabberID
, fro
= config
. jid
, body
= text
, mtype
= "headline" )
437 def connectionLost ( self
, reason
):
438 if not self
. session
: return
439 LogEvent ( INFO
, self
. session
. jabberID
)
440 text
= lang
. get ( self
. session
. lang
). msnDisconnected
% ( "Error" ) # FIXME, a better error would be nice =P
441 self
. session
. sendMessage ( to
= self
. session
. jabberID
, fro
= config
. jid
, body
= text
)
442 self
. session
. removeMe () # Tear down the session