1
# -*- coding: utf-8 -*-
3
# pymsn - a python client library for Msn
5
# Copyright (C) 2005-2006 Ali Sabil <ali.sabil@gmail.com>
6
# Copyright (C) 2007-2008 Johann Prieur <johann.prieur@gmail.com>
8
# This program is free software; you can redistribute it and/or modify
9
# it under the terms of the GNU General Public License as published by
10
# the Free Software Foundation; either version 2 of the License, or
11
# (at your option) any later version.
13
# This program is distributed in the hope that it will be useful,
14
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
# GNU General Public License for more details.
18
# You should have received a copy of the GNU General Public License
19
# along with this program; if not, write to the Free Software
20
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22
"""Profile of the User connecting to the service, as well as the profile of
23
contacts in his/her contact list.
25
@sort: Profile, Contact, Group, ClientCapabilities
26
@group Enums: Presence, Membership, Privacy, NetworkID
27
@sort: Presence, Membership, Privacy, NetworkID"""
29
from pymsn.util.decorator import rw_property
33
__all__ = ['Profile', 'Contact', 'Group',
34
'Presence', 'Membership', 'ContactType', 'Privacy', 'NetworkID', 'ClientCapabilities']
37
class ClientCapabilities(object):
38
"""Capabilities of the client. This allow adverstising what the User Agent
39
is capable of, for example being able to receive video stream, and being
40
able to receive nudges...
42
@ivar is_bot: is the client a bot
45
@ivar is_mobile_device: is the client running on a mobile device
46
@type is_mobile_device: bool
48
@ivar is_msn_mobile: is the client an MSN Mobile device
49
@type is_msn_mobile: bool
51
@ivar is_msn_direct_device: is the client an MSN Direct device
52
@type is_msn_direct_device: bool
54
@ivar is_media_center_user: is the client running on a Media Center
55
@type is_media_center_user: bool
57
@ivar is_msn8_user: is the client using WLM 8
58
@type is_msn8_user: bool
60
@ivar is_web_client: is the client web based
61
@type is_web_client: bool
63
@ivar is_tgw_client: is the client a gateway
64
@type is_tgw_client: bool
66
@ivar has_space: does the user has a space account
69
@ivar has_webcam: does the user has a webcam plugged in
70
@type has_webcam: bool
72
@ivar has_onecare: does the user has the OneCare service
73
@type has_onecare: bool
75
@ivar renders_gif: can the client render gif (for ink)
76
@type renders_gif: bool
78
@ivar renders_isf: can the client render ISF (for ink)
79
@type renders_isf: bool
81
@ivar supports_chunking: does the client supports chunking messages
82
@type supports_chunking: bool
84
@ivar supports_direct_im: does the client supports direct IM
85
@type supports_direct_im: bool
87
@ivar supports_winks: does the client supports Winks
88
@type supports_winks: bool
90
@ivar supports_shared_search: does the client supports Shared Search
91
@type supports_shared_search: bool
93
@ivar supports_voice_im: does the client supports voice clips
94
@type supports_voice_im: bool
96
@ivar supports_secure_channel: does the client supports secure channels
97
@type supports_secure_channel: bool
99
@ivar supports_sip_invite: does the client supports SIP
100
@type supports_sip_invite: bool
102
@ivar supports_shared_drive: does the client supports File sharing
103
@type supports_shared_drive: bool
105
@ivar p2p_supports_turn: does the client supports TURN for p2p transfer
106
@type p2p_supports_turn: bool
108
@ivar p2p_bootstrap_via_uun: is the client able to use and understand UUN commands
109
@type p2p_bootstrap_via_uun: bool
111
@undocumented: __getattr__, __setattr__, __str__
115
'is_bot': 0x00020000,
116
'is_mobile_device': 0x00000001,
117
'is_msn_mobile': 0x00000040,
118
'is_msn_direct_device': 0x00000080,
120
'is_media_center_user': 0x00002000,
121
'is_msn8_user': 0x00000002,
123
'is_web_client': 0x00000200,
124
'is_tgw_client': 0x00000800,
126
'has_space': 0x00001000,
127
'has_webcam': 0x00000010,
128
'has_onecare': 0x01000000,
130
'renders_gif': 0x00000004,
131
'renders_isf': 0x00000008,
133
'supports_chunking': 0x00000020,
134
'supports_direct_im': 0x00004000,
135
'supports_winks': 0x00008000,
136
'supports_shared_search': 0x00010000,
137
'supports_voice_im': 0x00040000,
138
'supports_secure_channel': 0x00080000,
139
'supports_sip_invite': 0x00100000,
140
'supports_shared_drive': 0x00400000,
142
'p2p_supports_turn': 0x02000000,
143
'p2p_bootstrap_via_uun': 0x04000000
146
def __init__(self, msnc=0, client_id=0):
149
@param msnc: The MSNC version
150
@type msnc: integer < 8 and >= 0
152
@param client_id: the full client ID"""
161
object.__setattr__(self, 'client_id', MSNC[msnc] | client_id)
163
def __getattr__(self, name):
164
if name == "p2p_aware":
166
elif name in self._CAPABILITIES:
167
mask = self._CAPABILITIES[name]
169
raise AttributeError("object 'ClientCapabilities' has no attribute '%s'" % name)
170
return (self.client_id & mask != 0)
172
def __setattr__(self, name, value):
173
if name in self._CAPABILITIES:
174
mask = self._CAPABILITIES[name]
176
object.__setattr__(self, 'client_id', self.client_id | mask)
178
object.__setattr__(self, 'client_id', self.client_id ^ mask)
180
raise AttributeError("object 'ClientCapabilities' has no attribute '%s'" % name)
183
return str(self.client_id)
186
class NetworkID(object):
187
"""Refers to the contact Network ID"""
190
"""Microsoft Network"""
193
"""Microsoft Live Communication Server"""
199
"""External IM etwork, currently Yahoo!"""
202
class Presence(object):
205
The members of this class are used to identify the Presence that a user
206
wants to advertise to the contacts on his/her contact list.
212
@cvar BE_RIGHT_BACK: be right back
213
@cvar ON_THE_PHONE: on the phone
214
@cvar OUT_TO_LUNCH: out to lunch
215
@cvar INVISIBLE: status hidden from contacts
216
@cvar OFFLINE: offline"""
221
BE_RIGHT_BACK = 'BRB'
228
class Privacy(object):
229
"""User privacy, defines the default policy concerning contacts not
230
belonging to the ALLOW list nor to the BLOCK list.
232
@cvar ALLOW: allow by default
233
@cvar BLOCK: block by default"""
238
class Membership(object):
239
"""Contact Membership"""
242
"""Contact doesn't belong to the contact list, but belongs to the address book"""
245
"""Contact belongs to our contact list"""
248
"""Contact is explicitely allowed to see our presence regardless of the
249
currently set L{Privacy<pymsn.profile.Privacy>}"""
252
"""Contact is explicitely forbidden from seeing our presence regardless of
253
the currently set L{Privacy<pymsn.profile.Privacy>}"""
256
"""We belong to the FORWARD list of the contact"""
259
"""Contact pending"""
262
class ContactType(object):
263
"""Automatic update status flag"""
266
"""Contact is the user so there's no automatic update relationship"""
268
EXTERNAL = "Messenger2"
269
"""Contact is part of an external messenger service so there's no automatic
270
update relationship with the user"""
273
"""Contact has no automatic update relationship with the user"""
276
"""Contact has an automatic update relationship with the user and an
277
automatic update already occured"""
279
LIVE_PENDING = "LivePending"
280
"""Contact was requested automatic update from the user and didn't
281
give its authorization yet"""
283
LIVE_REJECTED = "LiveRejected"
284
"""Contact was requested automatic update from the user and rejected
287
LIVE_DROPPED = "LiveDropped"
288
"""Contact had an automatic update relationship with the user but
289
the contact dropped it"""
292
class Profile(gobject.GObject):
293
"""Profile of the User connecting to the service
295
@undocumented: __gsignals__, __gproperties__, do_get_property"""
298
"display-name": (gobject.TYPE_STRING,
300
"A nickname that the user chooses to display to others",
302
gobject.PARAM_READABLE),
304
"personal-message": (gobject.TYPE_STRING,
306
"The personal message that the user wants to display",
308
gobject.PARAM_READABLE),
310
"current-media": (gobject.TYPE_PYOBJECT,
312
"The current media that the user wants to display",
313
gobject.PARAM_READABLE),
315
"profile": (gobject.TYPE_STRING,
317
"the text/x-msmsgsprofile sent by the server",
319
gobject.PARAM_READABLE),
321
"presence": (gobject.TYPE_STRING,
323
"The presence to show to others",
325
gobject.PARAM_READABLE),
327
"privacy": (gobject.TYPE_STRING,
329
"The privacy policy to use",
331
gobject.PARAM_READABLE),
333
"msn-object": (gobject.TYPE_STRING,
335
"MSN Object attached to the user, this generally represent "
336
"its display picture",
338
gobject.PARAM_READABLE),
341
def __init__(self, account, ns_client):
342
gobject.GObject.__init__(self)
343
self._ns_client = ns_client
344
self._account = account[0]
345
self._password = account[1]
348
self._display_name = self._account.split("@", 1)[0]
349
self._presence = Presence.OFFLINE
350
self._privacy = Privacy.BLOCK
351
self._personal_message = ""
352
self._current_media = None
354
self.client_id = ClientCapabilities(7)
355
#self.client_id.supports_sip_invite = True
356
#FIXME: this should only be advertised when a webcam is plugged
357
#self.client_id.has_webcam = True
359
self._msn_object = None
361
self.__pending_set_presence = [self._presence, self.client_id, self._msn_object]
362
self.__pending_set_personal_message = [self._personal_message, self._current_media]
367
@type: utf-8 encoded string"""
373
@type: utf-8 encoded string"""
374
return self._password
378
"""The user profile retrieved from the MSN servers
379
@type: utf-8 encoded string"""
384
"""The user identifier in a GUID form
385
@type: GUID string"""
386
return "00000000-0000-0000-0000-000000000000"
390
"""The display name shown to you contacts
391
@type: utf-8 encoded string"""
392
def fset(self, display_name):
395
self._ns_client.set_display_name(display_name)
397
return self._display_name
402
"""The presence displayed to you contacts
403
@type: L{Presence<pymsn.profile.Presence>}"""
404
def fset(self, presence):
405
if presence == self._presence:
407
self.__pending_set_presence[0] = presence
408
self._ns_client.set_presence(*self.__pending_set_presence)
410
return self._presence
415
"""The default privacy, can be either Privacy.ALLOW or Privacy.BLOCK
416
@type: L{Privacy<pymsn.profile.Privacy>}"""
417
def fset(self, privacy):
418
pass #FIXME: set the privacy setting
424
def personal_message():
425
"""The personal message displayed to you contacts
426
@type: utf-8 encoded string"""
427
def fset(self, personal_message):
428
if personal_message == self._personal_message:
430
self.__pending_set_personal_message[0] = personal_message
431
self._ns_client.set_personal_message(*self.__pending_set_personal_message)
433
return self._personal_message
438
"""The current media displayed to you contacts
439
@type: (artist: string, track: string)"""
440
def fset(self, current_media):
441
if current_media == self._current_media:
443
self.__pending_set_personal_message[1] = current_media
444
self._ns_client.set_personal_message(*self.__pending_set_personal_message)
446
return self._current_media
451
"""The MSNObject attached to your contact, this MSNObject represents the
452
display picture to be shown to your peers
453
@type: L{MSNObject<pymsn.p2p.MSNObject>}"""
454
def fset(self, msn_object):
455
if msn_object == self._msn_object:
457
self.__pending_set_presence[2] = msn_object
458
self._ns_client.set_presence(*self.__pending_set_presence)
460
return self._msn_object
464
def presence_msn_object():
465
def fset(self, args):
466
presence, msn_object = args
467
if presence == self._presence and msn_object == self._msn_object:
469
self.__pending_set_presence[0] = presence
470
self.__pending_set_presence[2] = msn_object
471
self._ns_client.set_presence(*self.__pending_set_presence)
473
return self._presence, self._msn_object
477
def personal_message_current_media():
478
def fset(self, args):
479
personal_message, current_media = args
480
if personal_message == self._personal_message and \
481
current_media == self._current_media:
483
self.__pending_set_personal_message[0] = personal_message
484
self.__pending_set_personal_message[1] = current_media
485
self._ns_client.set_personal_message(*self.__pending_set_personal_message)
487
return self._personal_message, self._current_media
490
def _server_property_changed(self, name, value):
491
attr_name = "_" + name.lower().replace("-", "_")
492
old_value = getattr(self, attr_name)
493
if value != old_value:
494
setattr(self, attr_name, value)
497
def do_get_property(self, pspec):
498
name = pspec.name.lower().replace("-", "_")
499
return getattr(self, name)
500
gobject.type_register(Profile)
503
class Contact(gobject.GObject):
504
"""Contact related information
505
@undocumented: __gsignals__, __gproperties__, do_get_property"""
508
"infos-changed": (gobject.SIGNAL_RUN_FIRST,
514
"memberships": (gobject.TYPE_UINT,
516
"Membership relation with the contact.",
517
0, 15, 0, gobject.PARAM_READABLE),
519
"display-name": (gobject.TYPE_STRING,
521
"A nickname that the user chooses to display to others",
523
gobject.PARAM_READWRITE),
525
"personal-message": (gobject.TYPE_STRING,
527
"The personal message that the user wants to display",
529
gobject.PARAM_READABLE),
531
"current-media": (gobject.TYPE_PYOBJECT,
533
"The current media that the user wants to display",
534
gobject.PARAM_READABLE),
536
"presence": (gobject.TYPE_STRING,
538
"The presence to show to others",
540
gobject.PARAM_READABLE),
542
"groups": (gobject.TYPE_PYOBJECT,
544
"The groups the contact belongs to",
545
gobject.PARAM_READABLE),
547
"infos": (gobject.TYPE_PYOBJECT,
549
"The contact informations",
550
gobject.PARAM_READABLE),
552
"contact-type": (gobject.TYPE_PYOBJECT,
554
"The contact automatic update status flag",
555
gobject.PARAM_READABLE),
557
"client-capabilities": (gobject.TYPE_UINT64,
558
"Client capabilities",
559
"The client capabilities of the contact 's client",
561
gobject.PARAM_READABLE),
563
"msn-object": (gobject.TYPE_STRING,
565
"MSN Object attached to the contact, this generally represent "
566
"its display picture",
568
gobject.PARAM_READABLE),
571
def __init__(self, id, network_id, account, display_name, cid=None,
572
memberships=Membership.NONE, contact_type=ContactType.REGULAR):
574
gobject.GObject.__init__(self)
576
self._cid = cid or "00000000-0000-0000-0000-000000000000"
577
self._network_id = network_id
578
self._account = account
580
self._display_name = display_name
581
self._presence = Presence.OFFLINE
582
self._personal_message = ""
583
self._current_media = None
586
self._memberships = memberships
587
self._contact_type = contact_type
588
self._client_capabilities = ClientCapabilities()
589
self._msn_object = None
591
self._attributes = {'icon_url' : None}
594
def memberships_str():
596
memberships = self._memberships
597
if memberships & Membership.FORWARD:
599
if memberships & Membership.ALLOW:
601
if memberships & Membership.BLOCK:
603
if memberships & Membership.REVERSE:
605
if memberships & Membership.PENDING:
608
template = "<pymsn.Contact id='%s' network='%u' account='%s' memberships='%s'>"
609
return template % (self._id, self._network_id, self._account, memberships_str())
613
"""Contact identifier in a GUID form
614
@type: GUID string"""
618
def attributes(self):
619
"""Contact attributes
620
@type: {key: string => value: string}"""
621
return self._attributes.copy()
626
@type: GUID string"""
630
def network_id(self):
631
"""Contact network ID
632
@type: L{NetworkID<pymsn.profile.NetworkID>}"""
633
return self._network_id
638
@type: utf-8 encoded string"""
644
@type: L{Presence<pymsn.profile.Presence>}"""
645
return self._presence
648
def display_name(self):
649
"""Contact display name
650
@type: utf-8 encoded string"""
651
return self._display_name
654
def personal_message(self):
655
"""Contact personal message
656
@type: utf-8 encoded string"""
657
return self._personal_message
660
def current_media(self):
661
"""Contact current media
662
@type: (artist: string, track: string)"""
663
return self._current_media
667
"""Contact list of groups
668
@type: set(L{Group<pymsn.profile.Group>}...)"""
673
"""Contact informations
674
@type: {key: string => value: string}"""
678
def memberships(self):
679
"""Contact membership value
680
@type: bitmask of L{Membership<pymsn.profile.Membership>}s"""
681
return self._memberships
684
def contact_type(self):
685
"""Contact automatic update status flag
686
@type: L{ContactType<pymsn.profile.ContactType>}"""
687
return self._contact_type
690
def client_capabilities(self):
691
"""Contact client capabilities
692
@type: L{ClientCapabilities}"""
693
return self._client_capabilities
696
def msn_object(self):
697
"""Contact MSN Object
698
@type: L{MSNObject<pymsn.p2p.MSNObject>}"""
699
return self._msn_object
703
"""Contact domain, which is basically the part after @ in the account
704
@type: utf-8 encoded string"""
705
result = self._account.split('@', 1)
711
### membership management
712
def is_member(self, memberships):
713
"""Determines if this contact belongs to the specified memberships
714
@type memberships: bitmask of L{Membership<pymsn.profile.Membership>}s"""
715
return (self.memberships & memberships) == memberships
717
def _set_memberships(self, memberships):
718
self._memberships = memberships
719
self.notify("memberships")
721
def _add_membership(self, membership):
722
self._memberships |= membership
723
self.notify("memberships")
725
def _remove_membership(self, membership):
726
"""removes the given membership from the contact
728
@param membership: the membership to remove
729
@type membership: int L{Membership}"""
730
self._memberships ^= membership
731
self.notify("memberships")
733
def _server_property_changed(self, name, value): #FIXME, should not be used for memberships
734
if name == "client-capabilities":
735
value = ClientCapabilities(client_id=value)
736
attr_name = "_" + name.lower().replace("-", "_")
737
old_value = getattr(self, attr_name)
738
if value != old_value:
739
setattr(self, attr_name, value)
742
def _server_attribute_changed(self, name, value):
743
self._attributes[name] = value
745
def _server_infos_changed(self, updated_infos):
746
self._infos.update(updated_infos)
747
self.emit("infos-changed", updated_infos)
751
def _add_group_ownership(self, group):
752
self._groups.add(group)
754
def _delete_group_ownership(self, group):
755
self._groups.discard(group)
757
def do_get_property(self, pspec):
758
name = pspec.name.lower().replace("-", "_")
759
return getattr(self, name)
760
gobject.type_register(Contact)
763
class Group(gobject.GObject):
765
@undocumented: __gsignals__, __gproperties__, do_get_property"""
768
"name": (gobject.TYPE_STRING,
770
"Name that the user chooses for the group",
772
gobject.PARAM_READABLE)
775
def __init__(self, id, name):
777
gobject.GObject.__init__(self)
783
"""Group identifier in a GUID form
784
@type: GUID string"""
790
@type: utf-8 encoded string"""
793
def _server_property_changed(self, name, value):
794
attr_name = "_" + name.lower().replace("-", "_")
795
old_value = getattr(self, attr_name)
796
if value != old_value:
797
setattr(self, attr_name, value)
800
def do_get_property(self, pspec):
801
name = pspec.name.lower().replace("-", "_")
802
return getattr(self, name)
803
gobject.type_register(Group)