]>
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 if utils
. checkTwisted ():
7 from twisted
. xish
. domish
import Element
9 from tlib
. domish
import Element
10 from tlib
import msn
, msnp2p
11 from debug
import LogEvent
, INFO
, WARN
, ERROR
23 name
= "MSN Transport" # The name of the transport
24 url
= "http://msn-transport.jabberstudio.org"
25 version
= "0.11-dev" # The transport version
26 mangle
= True # XDB '@' -> '%' mangling
27 id = "msn" # The transport identifier
30 # Load the default avatar
31 f
= open ( "src/legacy/defaultJabberAvatar.png" )
32 defaultJabberAvatarData
= f
. read ()
37 """ Returns True if the JID passed is a valid groupchat JID (for MSN, does not contain '%') """
38 return ( jid
. find ( '%' ) == - 1 )
42 # This should be set to the name space the registration entries are in, in the xdb spool
43 namespace
= "jabber:iq:register"
46 def formRegEntry ( username
, password
):
47 """ Returns a domish.Element representation of the data passed. This element will be written to the XDB spool file """
48 reginfo
= Element (( None , "query" ))
49 reginfo
. attributes
[ "xmlns" ] = "jabber:iq:register"
51 userEl
= reginfo
. addElement ( "username" )
52 userEl
. addContent ( username
)
54 passEl
= reginfo
. addElement ( "password" )
55 passEl
. addContent ( password
)
62 def getAttributes ( base
):
63 """ This function should, given a spool domish.Element, pull the username, password,
64 and out of it and return them """
67 for child
in base
. elements ():
69 if child
. name
== "username" :
70 username
= child
.__ str
__ ()
71 elif child
. name
== "password" :
72 password
= child
.__ str
__ ()
73 except AttributeError :
76 return username
, password
79 def startStats ( statistics
):
80 stats
= statistics
. stats
81 stats
[ "MessageCount" ] = 0
82 stats
[ "FailedMessageCount" ] = 0
83 stats
[ "AvatarCount" ] = 0
84 stats
[ "FailedAvatarCount" ] = 0
86 def updateStats ( statistics
):
87 stats
= statistics
. stats
88 stats
[ "AvatarCount" ] = msnp2p
. MSNP2P_Avatar
. TRANSFER_COUNT
89 stats
[ "FailedAvatarCount" ] = msnp2p
. MSNP2P_Avatar
. ERROR_COUNT
93 """ Converts a MSN passport into a JID representation to be used with the transport """
94 return msnid
. replace ( '@' , '%' ) + "@" + config
. jid
96 translateAccount
= msn2jid
# Marks this as the function to be used in jabber:iq:gateway (Service ID Translation)
99 """ Converts a JID representation of a MSN passport into the original MSN passport """
100 return unicode ( jid
[: jid
. find ( '@' )]. replace ( '%' , '@' ))
103 def presence2state ( show
, ptype
):
104 """ Converts a Jabber presence into an MSN status code """
105 if ptype
== "unavailable" :
106 return msn
. STATUS_OFFLINE
107 elif not show
or show
== "online" or show
== "chat" :
108 return msn
. STATUS_ONLINE
110 return msn
. STATUS_BUSY
111 elif show
== "away" or show
== "xa" :
112 return msn
. STATUS_AWAY
115 def state2presence ( state
):
116 """ Converts a MSN status code into a Jabber presence """
117 if state
== msn
. STATUS_ONLINE
:
119 elif state
== msn
. STATUS_BUSY
:
121 elif state
== msn
. STATUS_AWAY
:
122 return ( "away" , None )
123 elif state
== msn
. STATUS_IDLE
:
124 return ( "away" , None )
125 elif state
== msn
. STATUS_BRB
:
126 return ( "away" , None )
127 elif state
== msn
. STATUS_PHONE
:
129 elif state
== msn
. STATUS_LUNCH
:
130 return ( "away" , None )
132 return ( None , "unavailable" )
137 # This class handles groupchats with the legacy protocol
138 class LegacyGroupchat ( groupchat
. BaseGroupchat
):
139 def __init__ ( self
, session
, resource
, ID
= None , existing
= False , switchboardSession
= None ):
140 """ Possible entry points for groupchat
141 - User starts an empty switchboard session by sending presence to a blank room
142 - An existing switchboard session is joined by another MSN user
143 - User invited to an existing switchboard session with more than one user
145 groupchat
. BaseGroupchat
.__ init
__ ( self
, session
, resource
, ID
)
147 self
. switchboardSession
= msnw
. GroupchatSwitchboardSession ( self
, makeSwitchboard
= True )
149 self
. switchboardSession
= switchboardSession
151 assert ( self
. switchboardSession
!= None )
153 LogEvent ( INFO
, self
. roomJID ())
156 self
. switchboardSession
. removeMe ()
157 self
. switchboardSession
= None
158 groupchat
. BaseGroupchat
. removeMe ( self
)
159 LogEvent ( INFO
, self
. roomJID ())
160 utils
. mutilateMe ( self
)
162 def sendLegacyMessage ( self
, message
, noerror
):
163 LogEvent ( INFO
, self
. roomJID ())
164 self
. switchboardSession
. sendMessage ( message
. replace ( " \n " , " \r\n " ), noerror
)
166 def sendContactInvite ( self
, contactJID
):
167 LogEvent ( INFO
, self
. roomJID ())
168 userHandle
= jid2msn ( contactJID
)
169 self
. switchboardSession
. inviteUser ( userHandle
)
173 # This class handles most interaction with the legacy protocol
174 class LegacyConnection ( msnw
. MSNConnection
):
175 """ A glue class that connects to the legacy network """
176 def __init__ ( self
, username
, password
, session
):
177 self
. session
= session
178 self
. listSynced
= False
179 self
. initialListVersion
= 0
182 self
. remoteStatus
= ""
186 msnw
. MSNConnection
.__ init
__ ( self
, username
, password
)
188 # User typing notification stuff
189 self
. userTyping
= dict () # Indexed by contact MSN ID, stores whether the user is typing to this contact
190 # Contact typing notification stuff
191 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
193 self
. userTypingSend
= task
. LoopingCall ( self
. sendTypingNotifications
)
194 self
. userTypingSend
. start ( 5.0 )
196 import legacylist
# Is in here to prevent an ImportError loop
197 self
. legacyList
= legacylist
. LegacyList ( self
. session
)
199 LogEvent ( INFO
, self
. session
. jabberID
)
202 LogEvent ( INFO
, self
. session
. jabberID
)
204 self
. userTypingSend
. stop ()
206 msnw
. MSNConnection
. removeMe ( self
)
207 self
. legacyList
. removeMe ()
208 self
. legacyList
= None
211 utils
. mutilateMe ( self
)
213 def jidRes ( self
, resource
):
214 to
= self
. session
. jabberID
220 def highestResource ( self
):
221 """ Returns highest priority resource """
222 return self
. session
. highestResource ()
224 def sendMessage ( self
, dest
, resource
, body
, noerror
):
226 if self
. userTyping
. has_key ( dest
):
227 del self
. userTyping
[ dest
]
229 msnw
. MSNConnection
. sendMessage ( self
, dest
, resource
, body
, noerror
)
230 self
. session
. pytrans
. statistics
. stats
[ "MessageCount" ] += 1
232 self
. failedMessage ( dest
, body
)
235 def msnAlert ( self
, text
, actionurl
, subscrurl
):
236 if not self
. session
: return
238 el
= Element (( None , "message" ))
239 el
. attributes
[ "to" ] = self
. session
. jabberID
240 el
. attributes
[ "from" ] = config
. jid
241 el
. attributes
[ "type" ] = "headline"
242 body
= el
. addElement ( "body" )
243 body
. addContent ( text
)
245 x
= el
. addElement ( "x" )
246 x
. attributes
[ "xmlns" ] = "jabber:x:oob"
247 x
. addElement ( "desc" ). addContent ( "More information on this notice." )
248 x
. addElement ( "url" ). addContent ( actionurl
)
250 x
= el
. addElement ( "x" )
251 x
. attributes
[ "xmlns" ] = "jabber:x:oob"
252 x
. addElement ( "desc" ). addContent ( "Manage subscriptions to alerts." )
253 x
. addElement ( "url" ). addContent ( subscrurl
)
255 self
. session
. pytrans
. send ( el
)
257 def setStatus ( self
, nickname
, show
, status
):
258 statusCode
= presence2state ( show
, None )
259 msnw
. MSNConnection
. changeStatus ( self
, statusCode
, nickname
, status
)
261 def updateAvatar ( self
, av
= None ):
262 global defaultJabberAvatarData
265 msnw
. MSNConnection
. changeAvatar ( self
, av
. getImageData ())
267 msnw
. MSNConnection
. changeAvatar ( self
, defaultJabberAvatarData
)
269 def sendTypingNotifications ( self
):
270 if not self
. session
: return
272 # Send any typing notification messages to the user's contacts
273 for contact
in self
. userTyping
. keys ():
274 if self
. userTyping
[ contact
]:
275 self
. sendTypingToContact ( contact
)
277 # Send any typing notification messages from contacts to the user
278 for contact
, resource
in self
. contactTyping
. keys ():
279 self
. contactTyping
[( contact
, resource
)] += 1
280 if self
. contactTyping
[( contact
, resource
)] >= 3 :
281 self
. session
. sendTypingNotification ( self
. jidRes ( resource
), msn2jid ( contact
), False )
282 del self
. contactTyping
[( contact
, resource
)]
284 def gotContactTyping ( self
, contact
, resource
):
285 if not self
. session
: return
286 # Check if the contact has only just started typing
287 if not self
. contactTyping
. has_key (( contact
, resource
)):
288 self
. session
. sendTypingNotification ( self
. jidRes ( resource
), msn2jid ( contact
), True )
291 self
. contactTyping
[( contact
, resource
)] = 0
293 def userTypingNotification ( self
, dest
, resource
, composing
):
294 if not self
. session
: return
296 self
. userTyping
[ dest
] = composing
297 if composing
: # Make it instant
298 self
. sendTypingToContact ( dest
)
300 def listSynchronized ( self
):
301 if not self
. session
: return
302 self
. session
. sendPresence ( to
= self
. session
. jabberID
, fro
= config
. jid
)
303 self
. legacyList
. syncJabberLegacyLists ()
304 self
. listSynced
= True
305 #self.legacyList.flushSubscriptionBuffer()
307 def gotMessage ( self
, remoteUser
, resource
, text
):
308 if not self
. session
: return
309 source
= msn2jid ( remoteUser
)
310 self
. session
. sendMessage ( self
. jidRes ( resource
), fro
= source
, body
= text
, mtype
= "chat" )
311 self
. session
. pytrans
. statistics
. stats
[ "MessageCount" ] += 1
313 def avatarHashChanged ( self
, userHandle
, hash ):
314 if not self
. session
: return
317 # They've turned off their avatar
318 c
= self
. session
. contactList
. findContact ( jid
)
323 av
= self
. session
. pytrans
. avatarCache
. getAvatar ( hash )
325 msnContact
= self
. getContacts (). getContact ( userHandle
)
326 msnContact
. msnobjGot
= True
327 jid
= msn2jid ( userHandle
)
328 c
= self
. session
. contactList
. findContact ( jid
)
332 self
. requestAvatar ( userHandle
)
334 def gotAvatarImage ( self
, userHandle
, imageData
):
335 if not self
. session
: return
336 jid
= msn2jid ( userHandle
)
337 c
= self
. session
. contactList
. findContact ( jid
)
339 av
= self
. session
. pytrans
. avatarCache
. setAvatar ( imageData
)
342 def gotSendRequest ( self
, fileReceive
):
343 if not self
. session
: return
344 LogEvent ( INFO
, self
. session
. jabberID
)
345 ft
. FTReceive ( self
. session
, msn2jid ( fileReceive
. userHandle
), fileReceive
)
348 if not self
. session
: return
349 LogEvent ( INFO
, self
. session
. jabberID
)
350 self
. session
. ready
= True
352 def contactStatusChanged ( self
, remoteUser
):
353 if not ( self
. session
and self
. getContacts ()): return
354 LogEvent ( INFO
, self
. session
. jabberID
)
356 msnContact
= self
. getContacts (). getContact ( remoteUser
)
357 c
= self
. session
. contactList
. findContact ( msn2jid ( remoteUser
))
358 if not ( c
and msnContact
): return
360 show
, ptype
= state2presence ( msnContact
. status
)
361 status
= msnContact
. personal
. decode ( "utf-8" )
362 screenName
= msnContact
. screenName
. decode ( "utf-8" )
364 c
. updateNickname ( screenName
, push
= False )
365 c
. updatePresence ( show
, status
, ptype
, force
= True )
367 def ourStatusChanged ( self
, statusCode
):
368 # Send out a new presence packet to the Jabber user so that the MSN-t icon changes
369 if not self
. session
: return
370 LogEvent ( INFO
, self
. session
. jabberID
)
371 self
. remoteShow
, ptype
= state2presence ( statusCode
)
372 self
. sendShowStatus ()
374 def ourPersonalChanged ( self
, statusMessage
):
375 if not self
. session
: return
376 LogEvent ( INFO
, self
. session
. jabberID
)
377 self
. remoteStatus
= statusMessage
378 self
. sendShowStatus ()
380 def ourNickChanged ( self
, nick
):
381 if not self
. session
: return
382 LogEvent ( INFO
, self
. session
. jabberID
)
383 self
. remoteNick
= nick
384 self
. sendShowStatus ()
386 def sendShowStatus ( self
):
387 if not self
. session
: return
389 to
= self
. session
. jabberID
390 self
. session
. sendPresence ( to
= to
, fro
= source
, show
= self
. remoteShow
, status
= self
. remoteStatus
, nickname
= self
. remoteNick
)
392 def userMapping ( self
, passport
, jid
):
393 if not self
. session
: return
394 text
= lang
. get ( self
. session
. lang
). userMapping
% ( passport
, jid
)
395 self
. session
. sendMessage ( to
= self
. session
. jabberID
, fro
= msn2jid ( passport
), body
= text
)
397 def userAddedMe ( self
, userHandle
):
398 if not self
. session
: return
399 self
. session
. contactList
. getContact ( msn2jid ( userHandle
)). contactRequestsAuth ()
401 def userRemovedMe ( self
, userHandle
):
402 if not self
. session
: return
403 c
= self
. session
. contactList
. getContact ( msn2jid ( userHandle
))
404 c
. contactDerequestsAuth ()
405 c
. contactRemovesAuth ()
407 def serverGoingDown ( self
):
408 if not self
. session
: return
409 self
. session
. sendMessage ( to
= self
. session
. jabberID
, fro
= config
. jid
, body
= lang
. get ( self
. session
. lang
). msnMaintenance
)
411 def multipleLogin ( self
):
412 if not self
. session
: return
413 self
. session
. sendMessage ( to
= self
. session
. jabberID
, fro
= config
. jid
, body
= lang
. get ( self
. session
. lang
). msnMultipleLogin
)
414 self
. session
. removeMe ()
416 def accountNotVerified ( self
):
417 if not self
. session
: return
418 text
= lang
. get ( self
. session
. lang
). msnNotVerified
% ( self
. session
. username
)
419 self
. session
. sendMessage ( to
= self
. session
. jabberID
, fro
= config
. jid
, body
= text
)
421 def loginFailure ( self
, message
):
422 if not self
. session
: return
423 text
= lang
. get ( self
. session
. lang
). msnLoginFailure
% ( self
. session
. username
)
424 self
. session
. sendErrorMessage ( to
= self
. session
. jabberID
, fro
= config
. jid
, etype
= "auth" , condition
= "not-authorized" , explanation
= text
, body
= "Login Failure" )
425 self
. session
. removeMe ()
427 def failedMessage ( self
, remoteUser
, message
):
428 if not self
. session
: return
429 self
. session
. pytrans
. statistics
. stats
[ "FailedMessageCount" ] += 1
430 fro
= msn2jid ( remoteUser
)
431 self
. session
. sendErrorMessage ( to
= self
. session
. jabberID
, fro
= fro
, etype
= "wait" , condition
= "recipient-unavailable" , explanation
= lang
. get ( self
. session
. lang
). msnFailedMessage
, body
= message
)
433 def initialEmailNotification ( self
, inboxunread
, foldersunread
):
434 if not self
. session
: return
435 text
= lang
. get ( self
. session
. lang
). msnInitialMail
% ( inboxunread
, foldersunread
)
436 self
. session
. sendMessage ( to
= self
. session
. jabberID
, fro
= config
. jid
, body
= text
, mtype
= "headline" )
438 def realtimeEmailNotification ( self
, mailfrom
, fromaddr
, subject
):
439 if not self
. session
: return
440 text
= lang
. get ( self
. session
. lang
). msnRealtimeMail
% ( mailfrom
, fromaddr
, subject
)
441 self
. session
. sendMessage ( to
= self
. session
. jabberID
, fro
= config
. jid
, body
= text
, mtype
= "headline" )
443 def connectionLost ( self
, reason
):
444 if not self
. session
: return
445 LogEvent ( INFO
, self
. jabberID
)
446 text
= lang
. get ( self
. session
. lang
). msnDisconnected
% ( "Error" ) # FIXME, a better error would be nice =P
447 self
. session
. sendMessage ( to
= self
. session
. jabberID
, fro
= config
. jid
, body
= text
)
448 self
. session
. removeMe () # Tear down the session