]>
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
22 name
= "MSN Transport" # The name of the transport
23 url
= "http://msn-transport.jabberstudio.org"
24 version
= "0.10.1" # The transport version
25 mangle
= True # XDB '@' -> '%' mangling
26 id = "msn" # The transport identifier
29 # Load the default avatar
30 f
= open ( "src/legacy/defaultJabberAvatar.png" )
31 defaultJabberAvatarData
= f
. read ()
36 """ Returns True if the JID passed is a valid groupchat JID (for MSN, does not contain '%') """
37 return ( jid
. find ( '%' ) == - 1 )
41 # This should be set to the name space the registration entries are in, in the xdb spool
42 namespace
= "jabber:iq:register"
45 def formRegEntry ( username
, password
):
46 """ Returns a domish.Element representation of the data passed. This element will be written to the XDB spool file """
47 reginfo
= Element (( None , "query" ))
48 reginfo
. attributes
[ "xmlns" ] = "jabber:iq:register"
50 userEl
= reginfo
. addElement ( "username" )
51 userEl
. addContent ( username
)
53 passEl
= reginfo
. addElement ( "password" )
54 passEl
. addContent ( password
)
61 def getAttributes ( base
):
62 """ This function should, given a spool domish.Element, pull the username, password,
63 and out of it and return them """
66 for child
in base
. elements ():
68 if child
. name
== "username" :
69 username
= child
.__ str
__ ()
70 elif child
. name
== "password" :
71 password
= child
.__ str
__ ()
72 except AttributeError :
75 return username
, password
78 def startStats ( statistics
):
79 stats
= statistics
. stats
80 stats
[ "MessageCount" ] = 0
81 stats
[ "FailedMessageCount" ] = 0
82 stats
[ "AvatarCount" ] = 0
83 stats
[ "FailedAvatarCount" ] = 0
85 def updateStats ( statistics
):
86 stats
= statistics
. stats
87 stats
[ "AvatarCount" ] = msnp2p
. MSNP2P_Avatar
. TRANSFER_COUNT
88 stats
[ "FailedAvatarCount" ] = msnp2p
. MSNP2P_Avatar
. ERROR_COUNT
92 """ Converts a MSN passport into a JID representation to be used with the transport """
93 return msnid
. replace ( '@' , '%' ) + "@" + config
. jid
95 translateAccount
= msn2jid
# Marks this as the function to be used in jabber:iq:gateway (Service ID Translation)
98 """ Converts a JID representation of a MSN passport into the original MSN passport """
99 return unicode ( jid
[: jid
. find ( '@' )]. replace ( '%' , '@' ))
102 def presence2state ( show
, ptype
):
103 """ Converts a Jabber presence into an MSN status code """
104 if ptype
== "unavailable" :
105 return msn
. STATUS_OFFLINE
106 elif not show
or show
== "online" or show
== "chat" :
107 return msn
. STATUS_ONLINE
109 return msn
. STATUS_BUSY
110 elif show
== "away" or show
== "xa" :
111 return msn
. STATUS_AWAY
114 def state2presence ( state
):
115 """ Converts a MSN status code into a Jabber presence """
116 if state
== msn
. STATUS_ONLINE
:
118 elif state
== msn
. STATUS_BUSY
:
120 elif state
== msn
. STATUS_AWAY
:
121 return ( "away" , None )
122 elif state
== msn
. STATUS_IDLE
:
123 return ( "away" , None )
124 elif state
== msn
. STATUS_BRB
:
125 return ( "away" , None )
126 elif state
== msn
. STATUS_PHONE
:
128 elif state
== msn
. STATUS_LUNCH
:
129 return ( "away" , None )
131 return ( None , "unavailable" )
136 # This class handles groupchats with the legacy protocol
137 class LegacyGroupchat ( groupchat
. BaseGroupchat
):
138 def __init__ ( self
, session
, resource
, ID
= None , existing
= False , switchboardSession
= None ):
139 """ Possible entry points for groupchat
140 - User starts an empty switchboard session by sending presence to a blank room
141 - An existing switchboard session is joined by another MSN user
142 - User invited to an existing switchboard session with more than one user
144 groupchat
. BaseGroupchat
.__ init
__ ( self
, session
, resource
, ID
)
146 self
. switchboardSession
= msnw
. GroupchatSwitchboardSession ( self
, makeSwitchboard
= True )
148 self
. switchboardSession
= switchboardSession
150 assert ( self
. switchboardSession
!= None )
152 LogEvent ( INFO
, self
. roomJID ())
155 self
. switchboardSession
. removeMe ()
156 self
. switchboardSession
= None
157 groupchat
. BaseGroupchat
. removeMe ( self
)
158 LogEvent ( INFO
, self
. roomJID ())
159 utils
. mutilateMe ( self
)
161 def sendLegacyMessage ( self
, message
, noerror
):
162 LogEvent ( INFO
, self
. roomJID ())
163 self
. switchboardSession
. sendMessage ( message
. replace ( " \n " , " \r\n " ), noerror
)
165 def sendContactInvite ( self
, contactJID
):
166 LogEvent ( INFO
, self
. roomJID ())
167 userHandle
= jid2msn ( contactJID
)
168 self
. switchboardSession
. inviteUser ( userHandle
)
172 # This class handles most interaction with the legacy protocol
173 class LegacyConnection ( msnw
. MSNConnection
):
174 """ A glue class that connects to the legacy network """
175 def __init__ ( self
, username
, password
, session
):
176 self
. session
= session
177 self
. listSynced
= False
178 self
. initialListVersion
= 0
181 self
. remoteStatus
= ""
185 msnw
. MSNConnection
.__ init
__ ( self
, username
, password
)
187 # User typing notification stuff
188 self
. userTyping
= dict () # Indexed by contact MSN ID, stores whether the user is typing to this contact
189 # Contact typing notification stuff
190 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
192 self
. userTypingSend
= task
. LoopingCall ( self
. sendTypingNotifications
)
193 self
. userTypingSend
. start ( 5.0 )
195 import legacylist
# Is in here to prevent an ImportError loop
196 self
. legacyList
= legacylist
. LegacyList ( self
. session
)
198 LogEvent ( INFO
, self
. session
. jabberID
)
201 LogEvent ( INFO
, self
. session
. jabberID
)
203 self
. userTypingSend
. stop ()
205 msnw
. MSNConnection
. removeMe ( self
)
206 self
. legacyList
. removeMe ()
207 self
. legacyList
= None
210 utils
. mutilateMe ( self
)
212 def jidRes ( self
, resource
):
213 to
= self
. session
. jabberID
219 def highestResource ( self
):
220 """ Returns highest priority resource """
221 return self
. session
. highestResource ()
223 def sendMessage ( self
, dest
, resource
, body
, noerror
):
225 if self
. userTyping
. has_key ( dest
):
226 del self
. userTyping
[ dest
]
228 msnw
. MSNConnection
. sendMessage ( self
, dest
, resource
, body
, noerror
)
229 self
. session
. pytrans
. statistics
. stats
[ "MessageCount" ] += 1
231 self
. failedMessage ( dest
, body
)
234 def msnAlert ( self
, text
, actionurl
, subscrurl
):
235 if not self
. session
: return
237 el
= Element (( None , "message" ))
238 el
. attributes
[ "to" ] = self
. session
. jabberID
239 el
. attributes
[ "from" ] = config
. jid
240 el
. attributes
[ "type" ] = "headline"
241 body
= el
. addElement ( "body" )
242 body
. addContent ( text
)
244 x
= el
. addElement ( "x" )
245 x
. attributes
[ "xmlns" ] = "jabber:x:oob"
246 x
. addElement ( "desc" ). addContent ( "More information on this notice." )
247 x
. addElement ( "url" ). addContent ( actionurl
)
249 x
= el
. addElement ( "x" )
250 x
. attributes
[ "xmlns" ] = "jabber:x:oob"
251 x
. addElement ( "desc" ). addContent ( "Manage subscriptions to alerts." )
252 x
. addElement ( "url" ). addContent ( subscrurl
)
254 self
. session
. pytrans
. send ( el
)
256 def setStatus ( self
, nickname
, show
, status
):
257 statusCode
= presence2state ( show
, None )
258 msnw
. MSNConnection
. changeStatus ( self
, statusCode
, nickname
, status
)
260 def updateAvatar ( self
, av
= None ):
261 global defaultJabberAvatarData
264 msnw
. MSNConnection
. changeAvatar ( self
, av
. getImageData ())
266 msnw
. MSNConnection
. changeAvatar ( self
, defaultJabberAvatarData
)
268 def sendTypingNotifications ( self
):
269 if not self
. session
: return
271 # Send any typing notification messages to the user's contacts
272 for contact
in self
. userTyping
. keys ():
273 if self
. userTyping
[ contact
]:
274 self
. sendTypingToContact ( contact
)
276 # Send any typing notification messages from contacts to the user
277 for contact
, resource
in self
. contactTyping
. keys ():
278 self
. contactTyping
[( contact
, resource
)] += 1
279 if self
. contactTyping
[( contact
, resource
)] >= 3 :
280 self
. session
. sendTypingNotification ( self
. jidRes ( resource
), msn2jid ( contact
), False )
281 del self
. contactTyping
[( contact
, resource
)]
283 def gotContactTyping ( self
, contact
, resource
):
284 if not self
. session
: return
285 # Check if the contact has only just started typing
286 if not self
. contactTyping
. has_key (( contact
, resource
)):
287 self
. session
. sendTypingNotification ( self
. jidRes ( resource
), msn2jid ( contact
), True )
290 self
. contactTyping
[( contact
, resource
)] = 0
292 def userTypingNotification ( self
, dest
, resource
, composing
):
293 if not self
. session
: return
295 self
. userTyping
[ dest
] = composing
296 if composing
: # Make it instant
297 self
. sendTypingToContact ( dest
)
299 def listSynchronized ( self
):
300 if not self
. session
: return
301 self
. session
. sendPresence ( to
= self
. session
. jabberID
, fro
= config
. jid
)
302 self
. legacyList
. syncJabberLegacyLists ()
303 self
. listSynced
= True
304 #self.legacyList.flushSubscriptionBuffer()
306 def gotMessage ( self
, remoteUser
, resource
, text
):
307 if not self
. session
: return
308 source
= msn2jid ( remoteUser
)
309 self
. session
. sendMessage ( self
. jidRes ( resource
), fro
= source
, body
= text
, mtype
= "chat" )
310 self
. session
. pytrans
. statistics
. stats
[ "MessageCount" ] += 1
312 def avatarHashChanged ( self
, userHandle
, hash ):
313 if not self
. session
: return
314 av
= self
. session
. pytrans
. avatarCache
. getAvatar ( hash )
316 msnContact
= self
. getContacts (). getContact ( userHandle
)
317 msnContact
. msnobjGot
= True
318 jid
= msn2jid ( userHandle
)
319 c
= self
. session
. contactList
. findContact ( jid
)
323 self
. requestAvatar ( userHandle
)
325 def gotAvatarImage ( self
, userHandle
, imageData
):
326 if not self
. session
: return
327 jid
= msn2jid ( userHandle
)
328 c
= self
. session
. contactList
. findContact ( jid
)
330 av
= self
. session
. pytrans
. avatarCache
. setAvatar ( imageData
)
334 if not self
. session
: return
335 LogEvent ( INFO
, self
. session
. jabberID
)
336 self
. session
. ready
= True
338 def contactStatusChanged ( self
, remoteUser
):
339 if not ( self
. session
and self
. getContacts ()): return
340 LogEvent ( INFO
, self
. session
. jabberID
)
342 msnContact
= self
. getContacts (). getContact ( remoteUser
)
343 c
= self
. session
. contactList
. findContact ( msn2jid ( remoteUser
))
344 if not ( c
and msnContact
): return
346 show
, ptype
= state2presence ( msnContact
. status
)
347 status
= msnContact
. personal
. decode ( "utf-8" )
348 screenName
= msnContact
. screenName
. decode ( "utf-8" )
350 c
. updateNickname ( screenName
, push
= False )
351 c
. updatePresence ( show
, status
, ptype
, force
= True )
353 def ourStatusChanged ( self
, statusCode
):
354 # Send out a new presence packet to the Jabber user so that the MSN-t icon changes
355 if not self
. session
: return
356 LogEvent ( INFO
, self
. session
. jabberID
)
357 self
. remoteShow
, ptype
= state2presence ( statusCode
)
358 self
. sendShowStatus ()
360 def ourPersonalChanged ( self
, statusMessage
):
361 if not self
. session
: return
362 LogEvent ( INFO
, self
. session
. jabberID
)
363 self
. remoteStatus
= statusMessage
364 self
. sendShowStatus ()
366 def ourNickChanged ( self
, nick
):
367 if not self
. session
: return
368 LogEvent ( INFO
, self
. session
. jabberID
)
369 self
. remoteNick
= nick
370 self
. sendShowStatus ()
372 def sendShowStatus ( self
):
373 if not self
. session
: return
375 to
= self
. session
. jabberID
376 self
. session
. sendPresence ( to
= to
, fro
= source
, show
= self
. remoteShow
, status
= self
. remoteStatus
, nickname
= self
. remoteNick
)
378 def userMapping ( self
, passport
, jid
):
379 if not self
. session
: return
380 text
= lang
. get ( self
. session
. lang
). userMapping
% ( passport
, jid
)
381 self
. session
. sendMessage ( to
= self
. session
. jabberID
, fro
= msn2jid ( passport
), body
= text
)
383 def userAddedMe ( self
, userHandle
):
384 if not self
. session
: return
385 self
. session
. contactList
. getContact ( msn2jid ( userHandle
)). contactRequestsAuth ()
387 def userRemovedMe ( self
, userHandle
):
388 if not self
. session
: return
389 c
= self
. session
. contactList
. getContact ( msn2jid ( userHandle
))
390 c
. contactDerequestsAuth ()
391 c
. contactRemovesAuth ()
393 def serverGoingDown ( self
):
394 if not self
. session
: return
395 self
. session
. sendMessage ( to
= self
. session
. jabberID
, fro
= config
. jid
, body
= lang
. get ( self
. session
. lang
). msnMaintenance
)
397 def multipleLogin ( self
):
398 if not self
. session
: return
399 self
. session
. sendMessage ( to
= self
. session
. jabberID
, fro
= config
. jid
, body
= lang
. get ( self
. session
. lang
). msnMultipleLogin
)
400 self
. session
. removeMe ()
402 def accountNotVerified ( self
):
403 if not self
. session
: return
404 text
= lang
. get ( self
. session
. lang
). msnNotVerified
% ( self
. session
. username
)
405 self
. session
. sendMessage ( to
= self
. session
. jabberID
, fro
= config
. jid
, body
= text
)
407 def loginFailure ( self
, message
):
408 if not self
. session
: return
409 text
= lang
. get ( self
. session
. lang
). msnLoginFailure
% ( self
. session
. username
)
410 self
. session
. sendErrorMessage ( to
= self
. session
. jabberID
, fro
= config
. jid
, etype
= "auth" , condition
= "not-authorized" , explanation
= text
, body
= "Login Failure" )
411 self
. session
. removeMe ()
413 def failedMessage ( self
, remoteUser
, message
):
414 if not self
. session
: return
415 self
. session
. pytrans
. statistics
. stats
[ "FailedMessageCount" ] += 1
416 fro
= msn2jid ( remoteUser
)
417 self
. session
. sendErrorMessage ( to
= self
. session
. jabberID
, fro
= fro
, etype
= "wait" , condition
= "recipient-unavailable" , explanation
= lang
. get ( self
. session
. lang
). msnFailedMessage
, body
= message
)
419 def initialEmailNotification ( self
, inboxunread
, foldersunread
):
420 if not self
. session
: return
421 text
= lang
. get ( self
. session
. lang
). msnInitialMail
% ( inboxunread
, foldersunread
)
422 self
. session
. sendMessage ( to
= self
. session
. jabberID
, fro
= config
. jid
, body
= text
, mtype
= "headline" )
424 def realtimeEmailNotification ( self
, mailfrom
, fromaddr
, subject
):
425 if not self
. session
: return
426 text
= lang
. get ( self
. session
. lang
). msnRealtimeMail
% ( mailfrom
, fromaddr
, subject
)
427 self
. session
. sendMessage ( to
= self
. session
. jabberID
, fro
= config
. jid
, body
= text
, mtype
= "headline" )
429 def connectionLost ( self
, reason
):
430 if not self
. session
: return
431 LogEvent ( INFO
, self
. jabberID
)
432 text
= lang
. get ( self
. session
. lang
). msnDisconnected
% ( "Error" ) # FIXME, a better error would be nice =P
433 self
. session
. sendMessage ( to
= self
. session
. jabberID
, fro
= config
. jid
, body
= text
)
434 self
. session
. removeMe () # Tear down the session