from __future__ import with_statement
import logging
import urllib2, socket as socketmodule
import struct
import hashlib
import uuid
import traceback
import time
import hooks
import util
import util.xml_tag
import as net
import common
from common import protocol, action
from common.sms import validate_sms
from common.Buddy import icon_path_for
from util.callbacks import callsback
from util.primitives.funcs import Delegate, get, isint
from util.primitives.error_handling import try_this
from contacts import Group
import msn
import msn.AddressBook as MSNAB
log = logging.getLogger('msn.client')
require_connected = lambda self, *a, **kw : self.state == self.Statuses.ONLINE
def is_circle_name(name):
        email = net.EmailAddress(name)
    except (TypeError, ValueError):
        return False
    return True
class MSNClient(protocol):
    name = 'msn'
    message_format = 'html'
    message_bg = False
    message_sizes = []
    mobile_enable_url = ''
    mobile_edit_url = ''
    needs_update = [
                    ('remote_alias', 'set_display_name'),
    ROOT_ID = None
    supports_group_chat = True
    __slots__ = \
    username password hub name server
    status buddies disconnecting logging_on sockets
    version keep_alive_thread gtc_state num_groups
    num_buddies allow_unknown_contacts last_buddy
    forward_list reverse_list allow_list block_list
    pending_list list_types groups root_group
    status_to_code code_to_status self_info
    versions props m_buddies icon_requests
    icon_obj _ft remote_alias
    auth_type = 'SSO'
    # flags: 1=Forward, 2=Allow, 4=Block, 8=Reverse and 16=Pending
    list_types = \
    {'FL'    :    'forward_list',
     'RL'    :    'reverse_list',
     'AL'    :    'allow_list',
     'BL'    :    'block_list',
     'PL'    :    'pending_list',
     'HL'    :    'hidden_list', }
    status_to_code = \
    {'available' :    'NLN',
     'online'    :    'NLN',
     'busy'      :    'BSY',
     'idle'      :    'IDL',
     'brb'       :    'BRB',
     'away'      :    'AWY',
     'phone'     :    'PHN',
     'lunch'     :    'LUN',
     'invisible' :    'HDN',
     'offline'   :    'FLN'}
    code_to_status = util.dictreverse(status_to_code)
     'be right back': 'BRB',
     'on the phone':  'PHN',
     'out to lunch':  'LUN',
    client_id = msn.MSNClientID.IM_DEFAULT_CAPABILITIES
    #client_id = 0
    require_connected = require_connected
    message_formatting = 'simple'
    def SOCKTYPES(self):
        scks = []
        if common.pref('msn.socket.use_direct', True) and not self.use_http_only:
        if common.pref('msn.socket.use_http', True) or self.use_http_only:
        return scks
    def email_hint(cls, contact):
        return # the passport IS an email
    def __init__(self, username, password, user,
                 server, login_as = "invisible", remote_alias = None,
                 use_http_only = False, allow_unknown_contacts = False, **extra_args):
        protocol.__init__(self, username, password, user)
        remote_alias = remote_alias or username
        if '@' not in self.username:
            self.username = self.username + ''
        self.use_http_only = use_http_only
        self.__socktypes = self.SOCKTYPES[:]
        self._conn_args = ((), {})
        self.server, self.status = server, login_as
        self.allow_unknown_contacts = allow_unknown_contacts
        self.mail = 0
        self.groups_to_add = {}
        self.buddies = msn.MSNBuddies.MSNBuddies(self)
        self.m_buddies = msn.MSNBuddies.MSNBuddies(self) # mobile buddies
        self.circle_buddies = msn.MSNBuddies.CircleBuddies(self)
        self.self_buddy = self.get_buddy(self.username)
        self.self_buddy.client_id = self.client_id
        self.remote_alias = remote_alias
        self.sockets = {}
        self.icon_requests = []
        self.version = None
        self.keep_alive_thread = util.ResetTimer(0, lambda:None)
        self.rl_notify_user = False
        #init groups
        self.root_group = Group('__Root__', self, None)
        self.groups = {'__Root__' : self.root_group, None : self.root_group}
        self.disconnecting = False
        self.add_observer(self.state_change, 'state')
        self._old_status = None
        #init lists
        self.forward_list, \
        self.reverse_list, \
        self.allow_list, \
        self.block_list, \
        self.pending_list, \
        self.hidden_list, \
        self.self_info = set(), set(), set(), set(), set(), set(), dict(),
        self.hub = user.getThreadsafeInstance()
        self._ns_class = msn.Notification
        self._sb_class = None
        self.ns = None
        self.__authenticating = False
        self._conversations = []
        self._waiting_for_sb = []
        self.waiting_for_auth = set()
        self.status_message = ''
        self._requesting_icons = set()
        self._pending_icon_requests = {}
        self.self_buddy.status = self.status
#    def init_p2p(self):
#        self.P2PHandler = P2P.P2PHandler.P2PHandler(self)
#        # Registers P2P application types
#        import msn.P2P.P2PApplication
#        import msn.P2P.P2PActivity
#        import msn.P2P.P2PObjectTransfer
#        import msn.P2P.P2PFileTransfer
#        bridges = [
#                   P2P.Bridges.UdpBridge,
#                   P2P.Bridges.TcpBridge,
#                   ]
#        for b in bridges:
#            self._p2p_manager.register_bridge(b.bridge_name, b)
#    def get_bridge_names(self):
#        return self._p2p_manager.get_bridge_names()
#    def get_bridge_class(self, name):
#        return self._p2p_manager.get_bridge_class(name)
    def caps(self):
        'Returns the MSN capability list.'
        from common import caps
        return [caps.INFO, caps.IM, caps.FILES, caps.EMAIL, caps.SMS, caps.BLOCKABLE, caps.VIDEO]
    connected = property(lambda self: self.state == self.Statuses.ONLINE)
    def __repr__(self):
        return '<%s %s (%s)>' % (type(self).__name__, self.username, self.state)
    def buddy_dictionaries(self):
        return [self.buddies, self.m_buddies, self.circle_buddies]
    @action(lambda self, *a, **k:
            not any((self.disconnecting,))
            and self.state != self.Statuses.ONLINE)
    def Connect(self, *a, **k):
        Begin the connection process for this protocol
        assert self.ns is None
        self._conn_args = (a, k)
        invisible = k.get('invisible', False)
        self.set_status('invisible' if invisible else 'available', on_ns = False)
        if not self.__socktypes:
  'No more socket types to try.')
        self.__socktype = self.__socktypes.pop(0)
        self.ns = self._ns_class(self.__socktype, self.server, self.self_buddy)
        self.ns.protocol = self
"self.ns is now: %r", self.ns)
        self.ns.bind('on_connect', self._on_ns_connect)
        self.ns.bind('on_conn_error', self._on_ns_error)
        self.ns.bind('on_require_auth', self._on_ns_needs_auth)
        self.ns.bind('on_receive_version', self._store_new_version)
        if self.ns.needs_login_timer():
            login_timeout = common.pref('msn.login.timeout', default = 60, type = int)
            self._login_check_timer = util.ResetTimer(login_timeout, self._check_login)
  "Starting login timer for %r", self)
    def change_state(self, newstate):
        lct = getattr(self, '_login_check_timer', None)
        if lct is not None:
        return protocol.change_state(self, newstate)
    def _store_new_version(self, num):
        self.version = 'MSNP%d' % num"Got target protocol version: %r", self.version)
        exec('from msn.p%(num)s import Notification, Switchboard' % locals())
        self._sb_class = Switchboard
        self._ns_class = Notification
    def _cancel_login_check_timer(self):
        timer, self._login_check_timer = getattr(self, '_login_check_timer', None), None
        if timer is not None:
  "Stopping login timer for %r", self)
    def _check_login(self):
        if self.state not in (self.Statuses.ONLINE, self.Statuses.OFFLINE):
            self._on_ns_error(self.ns, Exception('MSN login took more than a minute'))
    def _check_offline(self):
        if (self.ns is None or not self.ns.connected()) and not self._conversations:
            self.set_offline(getattr(self, '_future_offline_reason', self.Reasons.NONE))
  '%r is offline', self)
            self.disconnecting = False
  '%r not offline yet: ns=%r, conversations=%r', self, self.ns, self._conversations)
            if not self.disconnecting:
            for conv in self._conversations:
                conv.exit(force_close = True)
            if self.ns is not None and self.ns.connected():
    def swap_ns(self):
        self.ns.unbind('on_connect', self._on_ns_connect)
        self.ns.unbind('on_conn_error', self._on_ns_error)
        self.ns.unbind('on_require_auth', self._on_ns_needs_auth)
        self.ns.unbind('on_receive_version', self._store_new_version)
        socket = self.ns.unhook_socket()
        if self.disconnecting:
        xfrcount = getattr(self.ns, '_xfrcount', 0)
        self.ns = self._ns_class(socket, buddy = self.self_buddy)
        self.ns.protocol = self"self.ns is now: %r", self.ns)
        self.ns._auth_type = self.auth_type
        self.ns._username = self.username
        self.ns._password = self.password
        self.ns._xfrcount = xfrcount
        if not self.ns.needs_login_timer():
        self.ns._sb_class = self._sb_class
        self.ns.bind('on_require_auth', self._on_ns_needs_auth)
        self.ns.bind('on_conn_error', self.on_conn_error)
        self.ns.bind('disconnect', self._on_ns_disconnect)
    def P2PHandler(self):
        return self.ns.P2PHandler
    def _on_ns_error(self, ns, error):
        log.error('MSN Disconnecting. Received the following error from transport: %s', error)
    def _clean_up_ns(self):
        if self.ns is not None:
            ns, self.ns = self.ns, None
            if getattr(ns, 'protocol', None) is not None:
                del ns.protocol
    def on_conn_fail(self):
        self.__authenticating = False
        if self.__socktypes:
            conn_args, conn_kwargs = self._conn_args
            self.Connect(*conn_args, **conn_kwargs)
    def on_conn_error(self, ns, error = None):'Got connection error: %r', error)
    def _on_ns_disconnect(self):
        log.debug('NS disconnected')
    def _on_ns_connect(self, socket):
        self.__socktypes[:] = []
        self._conn_args = ((), {})
        self.ns.unbind('on_connect', self._on_ns_connect)
    def _on_ns_needs_auth(self, ver = None):
        if ver is not None:
            assert str(ver) in self.version
        #self.ns.unbind('on_require_auth', self._on_ns_needs_auth)
        self.ns.bind('on_auth_challenge', self.do_tweener_auth)
        self.ns.authenticate(self.username, self.password, self.auth_type)
    def appid(self):
        return self.ns.client_chl_id
    def appcode(self):
        return self.ns.client_chl_code
    def get_token(self, domain):
        return self.ns.get_token(domain)
    def set_token(self, domain, token):
        return self.ns.set_token(domain, token)
    def do_tweener_auth(self, auth_type, twn_args):
        self.ns.unbind('on_auth_challenge', self.do_tweener_auth)
        if self.__authenticating:
        self.__authenticating = True
        if not self.ns:
        tweener = self.get_authorizer(auth_type)'authorizer is: %r', tweener)
        tweener(self.username, self.password, twn_args,
                success = self.finish_auth, error = self.auth_error)
    def get_authorizer(self, auth_type):
        return self.ns.get_authorizer(auth_type)
    def finish_auth(self, *ticket):'Finishing authorization process')
        if self.ns:
            if self.disconnecting and self.ns is None:
            self.ns.bind('on_auth_success', self._on_ns_authorize)
    def _on_ns_authorize(self):
        self.ns.unbind('on_require_auth', self._on_ns_needs_auth)
        self.ns.unbind('on_auth_success', self._on_ns_authorize)
    def _bind_ns_events(self):
        evt_table = (
            ('on_recv_profile', self.on_recv_profile),
            ('on_rl_notify', self.got_rlnotify_behavior),
            ('on_blist_privacy', self.got_blp),
            ('recv_prop', self.set_prop),
            ('recv_contact', self.on_contact_recv),
            ('recv_status', self.on_set_status),
            ('recv_clientid', self.on_set_client_id),
            ('group_receive', self.on_recv_group),
            ('on_contact_add', self.on_contact_add),
            ('contact_online_initial', self.on_buddy_status),
            ('contact_online', self.on_buddy_status),
            ('contact_offline', self.on_buddy_status),
            ('contact_alias', self.on_contact_alias_changed),
            ('contact_remove', self.on_contact_remove),
            ('contact_list_details', self.on_blist_info),
            ('contact_id_recv', self.on_contact_id),
            ('contact_status_msg', self.on_buddy_status_msg),
            ('challenge', self.on_challenge),
            ('challenge_success', self.on_challenge_success),
            ('other_user', self.on_other_user),
            ('connection_close', self.on_conn_close),
            ('initial_mail', self.on_init_mail),
            ('subsequent_mail', self.on_more_mail),
            ('group_remove', self.on_group_remove),
            ('group_add', self.on_group_add),
            ('group_rename', self.on_group_name_changed),
            ('ping_response', self.on_ping_response),
            ('switchboard_invite', self.on_switchboard_invite),
            ('switchboard_request', self.on_switchboard_request),
            ('sb_request_error', self.on_switchboard_error),
            ('recv_sms', self.on_recv_sms),
            ('contact_icon_info', self.on_bicon_info),
            ('contact_role_info', self.on_buddy_role_info),
            ('contact_cid_recv', self.on_contact_cid),
            ('contact_profile_update', self.get_profile_for_cid),
            ('received_oims', self.on_received_oims),
            ('soap_info', self.on_soap_info),
            ('contact_btype', self.on_contact_type),
            ('on_connect', self.on_connect),
            ('fed_message', self.on_federated_msg),
            ('buddy_authed', self.on_authorize_buddy),
            ('needs_status_message', self.on_needs_status_message),
            ('needs_self_buddy', self.on_needs_self_buddy),
            ('on_circle_member_joined', self.on_circle_member_joined),
            ('circle_roster_recv', self.on_circle_roster_recv),
            ('circle_roster_remove', self.on_circle_roster_remove),
        events =
        bind = self.ns.bind
        for name, callback in evt_table:
            if name in events:
                bind(name, callback)
                log.warning('Can\'t bind event %s to %r', name, self.ns)
    def auth_error(self, e):
        log.error('%s exception: %s', self, e)
            raise e
        except (urllib2.URLError, socketmodule.error):
            reason = self.Reasons.CONN_FAIL
        except util.xml_tag.SOAPException:
            log.debug('SOAPException when trying to authenticate: %r', e.t._to_xml(pretty = False))
            reason = self.Reasons.BAD_PASSWORD
        except Exception:
            log.error("Failed to authenticate %r: %r", self, e)
            reason = self.Reasons.BAD_PASSWORD
        return True
    def on_init_mail(self, newmail):
        self.setnotifyif('mail', newmail)
    def on_more_mail(self, count):
        self.setnotifyif('mail', self.mail + count)
    def on_conn_close(self):'Connection closed. Calling disconnect...')
        if not self.disconnecting:
    def on_other_user(self):
        log.error('Logged in from another location. Disconnecting...')
    def on_recv_profile(self, prof):
        for k in prof:
  [k] = prof[k]
        self.ip ='clientip', util.myip())
        self.port, = struct.unpack('H', struct.pack
                                   ('!H', int( ('clientport', self.server[-1]))))
    def got_rlnotify_behavior(self, value):
        This is the GTC property. It specifies whether the user should be notified
        of reverse-list additions, or if they should happen silently
        self.rl_notify_user = value
    def got_blp(self, value):
        if self.version > 'MSNP12':
    def set_blist_privacy(self, allow_unknowns):
        self.ns.send_blp('AL' if allow_unknowns else 'BL')
        self.setnotify('allow_unknown_contacts', allow_unknowns)
        self.account.allow_unknown_contacts, old = self.allow_unknown_contacts, self.account.allow_unknown_contacts
        if old != self.account.allow_unknown_contacts:
    def request_sb(self, callback = None):
        if self.ns is not None:
            # Must return a value such that bool(v) == True but v itself cannot be True, due to callsback semantics
            return 1
            return False
    #@action(lambda self, *a, **k: not(self.state == self.Statuses.OFFLINE or self.disconnecting))
    def Disconnect(self, reason = None):
        Disconnect from all servers
        if self.ns is not None:
            if getattr(self.ns, 'protocol', None) is not None:
                del self.ns.protocol
        if reason is None:
            reason = self.offline_reason
        elif isinstance(reason, Exception):
            reason = self.Reasons.CONN_FAIL
        if not self.disconnecting:
            self.disconnecting = True
            self._future_offline_reason = reason
            if self.keep_alive_thread is not None:
            if hasattr(self, 'slp_call_master'):
            if self._conversations:
                for conv in self._conversations[:]:
                    conv.exit(force_close = True)
    def reconnect_ns(self, server, version):
    def authenticate(self, *args):
        self.do_tweener_auth(self.username, self.password, args,
                             success = self.ns.complete_auth,
                             error = self._auth_error)
    def _auth_error(self, errmsg):
        log.error('msn had an auth error: %r, %r' , type(errmsg), errmsg)
        self.logging_on = False
        raise Exception(errmsg)
    def on_authenticate(self):
    def on_recv_group(self, name, id):
        log.debug('on_recv_group: name=%r, id=%r', name, id)
        if id in self.groups:
            new_group = self.groups[id]
            new_group = Group(name, self, id)
        rglist = list(self.root_group)
        if new_group not in rglist:
        self.groups[id] = new_group
        num_groups = len(self.groups) - 1
        if hasattr(self, 'num_groups'):
            log.debug('Got group %d/%d', num_groups, self.num_groups)
            if num_groups == self.num_groups:
      'Got all groups!')
    def on_blist_info(self, num_buddies, num_groups):'Got buddy list information: %d buddies, %d groups', num_buddies, num_groups)
        self.num_buddies = num_buddies
        self.num_groups = num_groups
    def on_soap_info(self, name, typ, soap, role):
        raise Exception'Got soap info for buddy: %r', (name, typ, soap, role))
        if typ == 'Passport':
            d = self.buddies
            t = 1
        elif typ == 'Email':
            d = self.buddies
            t = 32
        elif typ == 'Phone':
            d = self.m_buddies
            t = 4
        d[name].mships[role] = soap
        d[name]._btype = t
    def has_buddy_on_list(self, buddy):
        return in set( for x in self.forward_list)
    def on_contact_add(self, name, id, flags, groups, soap = None):
        buddy = self.on_contact_id(name, id)
        if soap is not None:
            buddy.contactsoap = soap
        if isint(flags):
            ltypes = self.apply_list_flags(flags, buddy)
            ltypes = self.apply_list_types(flags, buddy)
#        if buddy is self.self_buddy:
#            log.error('Not putting self buddy in a group, that\'s silly')
#            return buddy
        root_contact = msn.Contact(buddy, self.ROOT_ID)
        if 'FL' in ltypes:
            if groups:
                added = []
                if root_contact in self.root_group:
                for id in groups:
                    g = self.groups[id]
                    c = msn.Contact(buddy, id)
                    #if c not in g:
      '%s is in groups: %r',, added)
                if buddy in self.forward_list and root_contact not in self.root_group:
        return buddy
    def get_authorization_for(self, buddy):
        bname = getattr(buddy, 'name', buddy)
        buddy = self.get_buddy(bname)
        if buddy not in self.waiting_for_auth:
            buddy.pending_auth = False
            self.hub.authorize_buddy(self, buddy, message = getattr(self.ns, 'get_auth_message_for', lambda b: u'')(buddy))
    def on_contact_recv(self, name, flags, groups, soap = None, id = None):
        log.debug('on_contact_recv: name=%s, flags=%s, groups=%s', name, flags, groups)
        if self.state == self.Statuses.LOADING_CONTACT_LIST:
            setattr(self, '_recieved_contacts', getattr(self, '_recieved_contacts', 0) + 1)
            buddy = self.on_contact_add(name, name or id, flags, groups, soap)
        except Exception:
            log.error('Error processing buddy: name = %r, flags = %r, groups = %r, soap = %r, id = %r',
                      name, flags, groups, soap, id)
            if hasattr(self, 'num_buddies'):
                self.num_buddies -= 1
            buddy = None
        return buddy
    def on_contact_type(self, name, btype):
        import msn.AddressBook as MSNAB
        buddies = {MSNAB.ClientType.PassportMember : self.buddies,
                   MSNAB.ClientType.PhoneMember : self.m_buddies,
                   MSNAB.ClientType.EmailMember : self.buddies}.get(MSNAB.ClientType(btype), None)
        if buddies is None:
        buddy = buddies[name]
        buddy._btype = int(btype)
    def _check_connected(self):
        num_buddies = getattr(self, '_recieved_contacts', 0)
        if self.state != self.Statuses.ONLINE:
            if not hasattr(self, 'num_buddies'):
            log.debug('Got buddy %d/%d', num_buddies, self.num_buddies)
            if num_buddies >= self.num_buddies:
      'Got all buddies!')
                if (len(self.groups) - 1) >= self.num_groups:
          'Got all buddies and groups.')
                    if self.version <= 'MSNP12':
    def on_load_contact_list(self):
    def on_set_status(self, newstatus):
        sta = self.code_to_status[newstatus]
        if sta == 'online':
            sta = 'available'
        for x in (self, self.self_buddy):
            x.setnotifyif('status', sta)
    def on_set_client_id(self, client_id):
        for x in (self, self.self_buddy):
            x.setnotifyif('client_id', client_id)
    def on_buddy_status(self, name, nick, status, id):
        log.debug('Got status change for %r (%r). newstatus=%r, id=%r', name, nick, status, id)
        buddy = self.get_buddy(name)
        if nick is None:
            nick = buddy.alias
            self.on_contact_alias_changed(name, nick)
        log.debug('Got buddy %r for status change', buddy)
        sta = self.code_to_status[status]
        if sta == 'online':
            sta = 'available'
        buddy.status = sta
        buddy.client_id = id
        buddy.notify('status'); buddy.notify('client_id')
    def on_buddy_status_msg(self, bname, message):'Got Status Message for %s: %r', bname, message)
        b = self.get_buddy(bname)
        b.status_message = message
    def on_challenge(self, nonce):
    def on_challenge_success(self):
        yay still connected
    def on_contact_id(self, name, guid):
'Got ID for %r: %r', name, guid)
        if not name:
            for buddy in (self.buddies.values() + self.m_buddies.values()):
                if str(getattr(buddy, 'guid', None)) == str(guid):
                    return buddy
                raise ValueError("No buddy with %r was found and no name was supplied." % guid)
        if name.startswith('tel:'):
            name = name[4:]
        buddy = self.get_buddy(name)
        if not guid or guid == name:
            return buddy
            if not isinstance(guid, self.ns.cid_class):
                _guid = self.ns.cid_class(guid)
                _guid = guid
            buddy.guid = _guid
        except (ValueError, AttributeError, TypeError), e:
  '%r is not a valid ID. (error was %r)', guid, e)
        return buddy
    def on_contact_cid(self, name, cid):
        self.get_buddy(name).CID = cid
    def get_buddy_by_id(self, id_name):
        if id_name not in self.buddies:
            possibles = filter(lambda x: (str( == str(id_name)) or ( == id_name),
                               self.buddies.values() +
            if len(possibles) > 1:
                log.warning('Found multiple buddies with id %r', id_name)
            if possibles:
                return possibles[0]
        if isinstance(id_name, basestring):
            if not util.is_email(id_name):
                if id_name.startswith('tel:'):
                    return self.m_buddies[id_name[4:]]
                elif id_name.startswith('fed:'):
                    raise Exception
                    assert False, (type(id_name), id_name)
            assert False, (type(id_name), id_name)
        assert util.is_email(id_name), (type(id_name), id_name)
        return self.buddies[id_name]
    def on_contact_remove(self, name, l_id, g_id):
'Contact has been removed: name=%s, lid=%s, gid=%s', name, l_id, g_id)
        buddy = self.get_buddy_by_id(name)
        l_id = MSNAB.MSNList(l_id)
        l = getattr(self, self.list_types.get(l_id.role))
        if not g_id or g_id not in self.groups:
            # Contact is being removed from block, allow, reverse, or pending list
            if buddy in l:
      'removing buddy %s from list %s', name, l_id)
            if l_id == MSNAB.MSNList.Forward:
                root_contact = msn.Contact(buddy, self.ROOT_ID)
                if root_contact in self.root_group:
        # Contact being removed from forward list -- need to deal with groups
        groups = [self.groups[g_id]]
'Removing %r from groups %r', buddy, groups)
        for group in groups:
            contact = msn.Contact(buddy,
            except ValueError, e:
      '%r wasn\'t in %r, ignoring. (exception was: %r)', buddy, group, e)
        if not self.groups_containing_buddy(buddy) and l_id == 'FL':
  '%r has been removed from all groups. Removing from forward list.', buddy)
            self.ns._remove_buddy(l_id, buddy, None, service = buddy.service)
    def groups_containing_buddy(self, buddy):
        groups = []
        for group in self.groups.values():
            c = msn.Contact(buddy,
            if c in group:
        return groups
    def on_group_remove(self, g_id):
        group = self.groups.pop(g_id, None)
        if group is not None:
            for contact in list(group):
                if not self.groups_containing_buddy(contact.buddy):
                    if None in self.groups:
                        self.groups[self.ROOT_ID].append(msn.Contact(contact.buddy, self.ROOT_ID))
            group[:] = []
    def on_group_add(self, name, id):
        new_group = Group(name, self, id)
        self.groups[id] = new_group
        self.root_group.insert(0, new_group)
        return new_group
    def on_contact_alias_changed(self, name, nick):
        self.get_buddy(name).setnotifyif('remote_alias', nick)
    def on_group_name_changed(self, id, name):
        g = self.groups[id] = name
    def on_ping_response(self, next = sentinel):
        if next is sentinel or not next:
        next += 5
        log.debug('next ping in %d seconds', next)
    def on_switchboard_request(self, server, cookie):'Got response for switchboard request, creating %r', self._sb_class)
        f = self._waiting_for_sb.pop(0)
        f.success(self.make_sb(server = server, cookie = cookie))
    def make_sb(self, server = None, sessionid = None, cookie = None):
        if self.version < 'MSNP21':
            socktype = self.__socktype
            socktype = self.ns
        sb = self._sb_class(socktype, server = server, sessionid = sessionid, cookie = cookie)
        return sb
    def make_sb_adapter(self, to_invite = ()):
        if self.version < 'MSNP21':
            return msn.NSSBAdapter(self.ns, to_invite = to_invite)
            return self._sb_class(self.ns, to_invite = to_invite)
    def on_switchboard_error(self, emsg):'Received error for switchboard request: %r', emsg)
        cb = self._waiting_for_sb.pop(0)
    def on_switchboard_invite(self, name, session = None, server = None, auth_type = None, cookie = None):
        # verify user ('name') is OK to talk to
        # find conversation
        # if not found:
        sb = self.make_sb(server = server, sessionid = session, cookie = cookie)
        con = self.convo_for(name, sb)
        if not con.connected():
    def on_federated_msg(self, bname, msg):
    def set_status(self, status = None, callback = None, on_ns = True):
        status = status or self.status
        if status == 'online' and status not in self.status_to_code:
            status = 'available'
        if status not in self.code_to_status:
            code = self.status_to_code.get(status, 'AWY')
            code = status
'setting status to %s (%s)', status, code)
        if self.status == 'invisible' and status != 'invisible': # If moving from invis to not invis, enable icons again
            for buddy in self.buddies.values():
                buddy.icon_disabled = False
        self.status = self.code_to_status[code]
        self.self_buddy._got_presence = True
        if on_ns:
            self.ns._set_status(code, self.client_id, callback)
    def connect_socket(self, sock_id, server_tuple, callback = None):
        connect_socket(sock_id, server, connect_func, *args)
        Create a socket, store it as sockets[sock_id], connect it
        to server, and call connect_func(*args) when it has connected.
        if sock_id.startswith('sb'):
            sock_id = server_tuple
            sock_type = 'sb'
            sock_type = 'ns'
        if sock_id not in self.sockets:
            host, port = server_tuple
  'opening %r', sock_id)
            self.sockets[sock_id] = self.__socktype(self, (host, port), callback = callback)
            log.fatal("something's broken! see MSNP.connect_socket")
            sck = self.sockets[sock_id]
            assert (sck.server == server_tuple), sock_id
    def close_socket(self, socket):
        This function is called when a socket is closed from the other end.
        If socket is in sockets, it is removed.
        If we not connected to any sockets after this, an alert is sent
        to the GUI.
        log.debug('closing socket %r', socket)
        for k in self.sockets.keys():
            if self.sockets[k] is socket:
                del self.sockets[k]
        if (len(self.sockets) == 0 or (self.sockets.get('NS', None) in (socket, None))) and \
           (self.state != self.Statuses.OFFLINE) and \
           (not self.disconnecting):
            log.debug('no sockets left, disconnecting')
            if self.state == self.Statuses.CONNECTING:
                raisin = self.Reasons.CONN_FAIL
                raisin = self.Reasons.CONN_LOST
            #raise msn.GeneralException("%s was disconnected!" % self.username)
    def close_sock_id(self, sock_id):
        if sock_id in self.sockets:
#            log.warning('closing and deleting %s', sock_id)
#            try:
#                self.sockets[sock_id].close_when_done()
#                del self.sockets[sock_id]
#            except KeyError:
#                if not self.state != self.state['Disconnecting']: raise
    def apply_list_flags(self, flags, buddy = None):
        Figure out the list flags for specified buddy (or self.last_buddy)
        and put them in the appropriate lists.
        buddy = buddy or self.last_buddy
        # flags: 1=Forward, 2=Allow, 4=Block, 8=Reverse and 16=Pending
        lists = []
        if flags & 1:
        if flags & 2:
        if flags & 4:
        if flags & 8:
        if flags & 16:
            buddy.pending_auth = True
        return self.apply_list_types(lists, buddy)
    def apply_list_types(self, types, buddy = None):
        buddy = try_this(lambda: buddy or self.last_buddy, buddy)
        if buddy is None:
        log.debug('Putting %r in the following lists: %r', buddy, types)
        for l in types:
            #log.debug('   adding %r to %r', buddy, self.list_types[l])
            getattr(self, self.list_types[l]).add(buddy)
        #log.debug('   done')
        if self.state == self.Statuses.ONLINE:
            if buddy in self.reverse_list and not (buddy in self.block_list or buddy in self.allow_list):
                #log.debug("Buddy in reverse list but not block or allow. NOT Adding to pending.")
#                log.debug("Buddy in reverse list but not block or allow. Adding to pending.")
#                self.pending_list.add(buddy)
#                buddy.pending_auth = True
        if buddy in self.pending_list and (buddy in self.allow_list or buddy in self.block_list):
            log.debug("Buddy was in pending list but was already in %s list. Not asking for auth.",
                      ('block' if buddy in self.block_list else 'allow'))
            buddy.pending_auth = False
        for buddy in self.pending_list:
            #print 'not doing auth thingy', buddy
  '%r is in pending list (apply_list_types). Asking for authorization...', buddy)
        return types
    def get_buddy_by_sms(self, sms):
        is_sms = validate_sms(sms)
        if not is_sms:
            return None
        if sms in self.m_buddies:
            return self.m_buddies[sms]
        for buddy in self.buddies.values():
            if buddy.sms == sms:
                return buddy
        return self.m_buddies[sms]
    def get_buddy(self, name):
        if isinstance(name, msn.MSNBuddy.MSNBuddy):
            return name
        name = ''.join(name.split()).encode('utf-8')
        is_sms = validate_sms(name)
        if name.startswith('tel:'):
            name = name[4:]
        sms_buddy = self.get_buddy_by_sms(name)
        if sms_buddy is not None:
            return sms_buddy
        if is_circle_name(name):
            buddy = self.circle_buddies.get(name, None)
            ns_circle = self.ns.GetCircle(name)
            if buddy is None:
                buddy = self.circle_buddies[name] = msn.MSNBuddy.CircleBuddy.from_msnab(ns_circle, self)
                if ns_circle is not None:
            return buddy
        if name.startswith('fed:'):
            raise Exception
            if not util.is_email(name):
                raise AssertionError('Bad buddy name! %r' % name)
        except AssertionError:
            import traceback;traceback.print_exc()
        return self.buddies[name]
    def on_buddy_role_info(self, name, l_ids, role_id):
        b = self.get_buddy(name)
        self.apply_list_types(l_ids, b)
        for role in l_ids:
            b.role_ids[role] = role_id
    def get_list_flags(self, b):
        buddy = self.get_buddy(getattr(b, 'name', b))
        l_flags = (b in self.forward_list) * 1
        l_flags |= (b in self.allow_list) * 2
        l_flags |= (b in self.block_list) * 4
        # check if b on allow and block lists, if so, don't
        # report as in block list
        if l_flags & 6 >= 6: l_flags -= 4
        return l_flags
    def on_connect(self):'on_connect called. setting buddy icon and requesting profiles')
        if self.ns is not None:
            self.ns.unbind('on_connect', self.on_connect)
        if self.state == self.Statuses.ONLINE:
        for buddy in self.buddies.values():
#        if self.rl_notify_user:
#            for buddy in self.pending_list:
#                #print 'not doing auth thingy', buddy
#      '%r is in pending list (on_connect). Asking for authorization...', buddy)
#                self.get_authorization_for(buddy)
        #for buddy in self.buddies.values():
        #    buddy.icon_disabled = False
'Setting display name')
        self.set_display_name(self.remote_alias)'    done setting display name')
        self.set_status()'on_connect done')
    def clear_unknown_statuses(self):
        for buddy in (self.buddies.values() + self.m_buddies.values()):
            if buddy.status != 'unknown':
            changed = False
            if not buddy._got_presence:
                buddy.status_message = ''
                changed = True
            if buddy._status == 'unknown':
                buddy._got_presence = True
                buddy.status_message = ''
                buddy.status = 'offline'
                changed = True
            if changed: buddy.notify('status')
'Set all unknown buddies to offline')
    def set_message_object(self, messageobj, callback = None):
        if hasattr(self.ns, 'set_message_object') and getattr(messageobj, 'media', None) is not None:
  'MSN.set_status_object: setting CurrentMedia')
            self.ns.set_message_object(messageobj, callback = callback)
  'MSN.set_status_object: setting PSM')
            return protocol.set_message_object(self, messageobj, callback = callback)
    def set_message(self, message, status, format = None, callback = None):
        self.status = status
        self.status_message = message
        self.set_status(status, callback = callback)
        self.set_status_message(message, callback = callback)
    def on_needs_status_message(self, callable):
    def on_needs_self_buddy(self, callable):
        self.self_buddy._got_presence = True
    def set_invisible(self, invis = True):
        if invis:
            sta = 'HDN'
            sta = 'NLN'
    def stop_keepalive_thread(self):
        kat, self.keep_alive_thread = getattr(self, 'keep_alive_thread', None), None
        if kat is not None:
    def send_keepalive(self):
        ns = self.ns
        if ns is not None and ns.connected():
            log.error('NS is not connected but keepalive timer is still notifying')
    def state_change(self, obj, attr, old, new):
        assert attr is 'state'
        log.debug('%r was %s', self, old)
        if self.state == self.Statuses.ONLINE:
            self.keep_alive_thread = util.RepeatTimer(5, self.send_keepalive)
        if self.state == self.Statuses.OFFLINE:
    def set_prop(self, buddy, prp_type, val, args = ()):'Got %r for %r', prp_type, buddy)
        bname = getattr(buddy, 'name', buddy)
        buddy = self.get_buddy(bname)
        attr = self.ns.props[prp_type.lower()]
        if isinstance(val, basestring):
            val = val.decode('url').decode('fuzzy utf8')
        buddy.setnotifyif(attr, val)
        if attr == 'phone_mobile' and args:
            args = list(args)
                enabled = int(args.pop(0))
                if buddy.allow_mobile is None:
                    buddy.allow_mobile = ('N', 'Y')[enabled]
            except Exception:
    def buddy_for_cid(self, cid):
        for buddy in self.buddies.values():
            if int(buddy.CID) == int(cid):
                return buddy
        assert False, 'CID %d not found! My buddies are: %r' % (cid, self.buddies)
    def get_profile_for_cid(self, cid):
        buddy = self.buddy_for_cid(cid)
    def set_profile(self, *a, **k):
    def set_idle(self, since = 0):
        if since:
            self._old_status = self.status
            if self._old_status is not None:
            self._old_status = 'idle'
    def group_for_name(self, groupname):
        res = None
        for group in self.groups.values():
            if == groupname:
                res = group
        #log.debug('Group for name %s found %r', groupname, res)
        return res
    def group_for(self, contact):
        gid =[-1]
        if gid is None:
            return None
        return self.groups[gid].name
    get_group = group_for_name
    def get_groups(self):
        'Returns a list of group names.'
        return [ for g in self.root_group if type(g) is Group]
    def get_ticket(self, key = None):
        if key is None:
            return self.ticket
        return msn.util.fmt_to_dict('&', '=')(self.ticket)[key]
    def join_chat(self, convo, room_name = None, server = None, callback = None):
        if convo.type == 'sb':
            return callback.success(convo)
    def search_for_contact(self):
    def address_book(self):
    def mobile_settings(self):
    def my_profile(self):
    def save_contact_list(self):
    def load_contact_list(self):
    def contact_list_to_file(self):
        xml_str = '<?xml version="1.0"?>'
        messenger = util.xml_tag.tag('messenger')
        messenger.service = util.xml_tag.tag('service', name = '.NET Messenger Service')
        for bname in self.buddies:
            messenger.service.contactlist += util.xml_tag.tag('contact', bname)
        return xml_str + messenger._to_xml()
    def file_to_contact_list(self, f_obj):
        s =
        t = util.xml_tag.tag(s)
        if isinstance(t.service, list):
            messenger = filter(lambda x: x['name'] == '.NET Messenger Service',
            messenger = messenger[0] if messenger else None
        elif t.service['name'] == '.NET Messenger Service':
            messenger = t.service
            messenger = None
        if messenger is None: return []
        contacts = messenger.contactlist
        added_contacts = []
        for contact in contacts:
            bname = str(contact)
            b = self.get_buddy(bname)
            # add them all but only if they're not already in the list
            if b not in self.forward_list:
        # let caller know which were added
        return added_contacts
    def on_received_oims(self, oims = None):
        if oims is None:
            oims = self.oims
        while oims:
            oim = oims.pop(0)
            b = self.get_buddy(
            conv = self.convo_for(
            conv.received_message(b, unicode(oim.msg), offline = True, timestamp = oim.time, content_type = 'text/plain')
    def on_recv_sms(self, phone, message):
        b = self.get_buddy(phone)
        c = self.convo_for(b)
        c.on_message_recv(, message, sms = True)
    def on_bicon_info(self, name, msnobj):
        buddy = self.get_buddy(name)
        if isinstance(msnobj, basestring):
            buddy.icon_disabled = False
            buddy.setnotifyif('icon_hash', msnobj)
            buddy.msn_obj = msnobj
            if msnobj is None:
                buddy.icon_disabled = True
                buddy.setnotifyif('icon_hash', '')
            elif int(msnobj.type) == 3:
                buddy.icon_disabled = False
                buddy._icon_disabled_until = 0
                buddy.setnotifyif('icon_hash', msnobj.sha1d.decode('base64'))
    ### Features not supported by MSN
    def send_direct_IM_req(self, *args):
        log.warning('MSN does not support send_direct_IM_req yet')
        print '%s%s' % (util.get_func_name(), repr(args))
    def send_buddy_list_request(self, *args):
        log.warning('MSN does not support send_buddy_list_request yet')
        print '%s%s' % (util.get_func_name(), repr(args))
    ### End unsupported features
    def _get_default_p2p_transport(self, bname, callback = None):
        return self.convo_for(bname, callback = callback)
#   Determine common signature for the following functions
    def chat_with(self, buddy):
        ###TODO: Use conversation manager?
        Chat with a buddy. Ensures that a conversation exists that has buddy in it
        (or at least, one that is capable of inviting buddy when appropriate).
        @param buddy: The C{buddy} to chat with
        @type  buddy: L{msn.Buddy}
    def convo_for(self, bname, sb = None, callback = None):
        Returns the conversation for a buddy with name bname, creating it if necessary
        @param bname: name of the buddy to get a conversation for.
        @type  bname: basestring (passport)
        @rtype:   L{msn.Conversation}
        @returns: A conversation for the buddy
        if sb is None:
            return self._find_sb(bname, sb, callback)
            return self._create_conv(bname, sb, callback)
    def find_conv(self, bname):
        is_circle = is_circle_name(bname)
        buddy = self.get_buddy(bname)
        for conv in self._conversations[:]:
            if conv.ischat and not is_circle:
            if conv.ischat and is_circle and == conv._chatbuddy:
                return conv
            if == getattr(conv, '_chat_target_name', getattr(conv, '_chatbuddy',
                return conv
            if ==
                if len(conv._clean_list()) == 1:
                    return conv
            if buddy in conv.room_list:
                return conv
            elif bname in conv._clean_list():
                return conv
        return None
    def _find_sb(self, bname, sb, callback):'Finding conversation for %s', bname)
        bname = getattr(bname, 'name', bname)
        buddy = self.get_buddy(bname)
        conv = self.find_conv(bname)
        if bname ==
            if conv is None:
                return self._create_conv(bname, sb, callback)
                return conv
        if conv is not None:
            if conv.ischat:
                return conv
            if buddy in conv.room_list:
                if sb is not None:
                    conv.connect(callback = callback)
                return conv
            elif bname in conv._clean_list():
      "%r already in %r's clean_list. disconnecting %r", bname, conv, conv)
                conv = None
                conv = None
        return self._create_conv(bname, sb, callback)
    def rejoin_chat(self, old_conversation, callback = None):
        bud = old_conversation._chatbuddy
        conf = self.convo_for(bud, callback = callback)
        connect = getattr(conf, 'connect', None)
        if connect is not None:
    def make_chat_and_invite(self, buddies_to_invite, convo = None, room_name = None, server = None, notify = False, callback = None):
        if convo is None:
            buds = filter(lambda b: b != self.self_buddy, buddies_to_invite)
            conf = self._create_conv_invite(buds, callback = callback)
            conf.connect() # connect now explicitly
            if notify:
                from common import profile
            buds = filter(lambda b: b != self.self_buddy and b not in convo.room_list, buddies_to_invite)
            for b in buds:
                self.invite_to_chat(b, convo)
    def get_conversation_class(self):
        if self.version < 'MSNP21':
            return msn.Conversation
            import msn.p21.MSNP21Conversation as p21
            return p21.MSNP21Conversation
    def _create_conv(self, bname, sb, connect_cb):
        return self._create_conv_invite((bname,), sb = sb, success = connect_cb)
    def _create_conv_invite(self, buddies, sb = None, callback = None):
        buddies = tuple(getattr(b, 'name', b) for b in buddies)'_create_conv_invite: buddies=%r, connect_cb=%r', buddies, callback)
        c = self.get_conversation_class()(self, switchboard = sb, to_invite = buddies)
        c._connect_cb = callback
        return c
    def register_conv(self, c):
        if c not in self._conversations:
    def unregister_conv(self, c):'removing %r from %r', c, self._conversations)
            while c in self._conversations:
        except ValueError, e:
        if not self._conversations and self.disconnecting:
    def add_buddy(self, bname, g_id, pos = 0, service = None, callback = None):
        add_buddy(bname, gname, pos=0)
        Used to add a buddy to a group
        @param bname:    passport of the buddy to add
        @type  bname:    basestring (passport)
        @param gname:    id of the group to add
        @type  gname:    L{msn.GroupId}
        @param   pos:    Ignored. Present for interface adherence. (Not supported by protocol)
        @type    pos:    int
        @default pos:    0
        log.debug('add_buddy(%r, %r, %r, %r, %r)', bname, g_id, pos, service, callback)
        if bname == and self.version < "MSNP21":
            log.error('Not adding self buddy, that\'s silly')
        group = self.groups[g_id]
        buddy = self.get_buddy(bname)
        def setstatus(*a, **k):
            if buddy.status == 'unknown':
                buddy.setnotifyif('status', 'offline')
        real_success = callback.success
        real_success += setstatus
        callback.success = Delegate()
        callback.success += lambda * a, **k: real_success(group)
        if buddy not in self.forward_list:
            success = lambda * a, **k: self.add_buddy(bname, g_id, pos, service = service, callback = callback)
  'Adding %s to forward list and allow list', buddy)
            self.ns._add_buddy_to_list(, service = service, success = success, error = callback.error)
            self.ns._add_buddy('AL',,, None, service = service)
        #check if they're in the group already
        if msn.Contact(buddy, in group:
            print 'buddy in group already!'
            return callback.success()
        if == None:
            g_id = None
'Adding %s to group %r', buddy, g_id)
        self.ns._add_buddy_to_group(,, g_id, service = service or buddy.type, callback = callback)
#    def on_add_buddy(self, buddy, group):
#        '''
#        on_add_buddy(buddy, group)
#        Called when a buddy is successfully added to a group
#        @param buddy:    Buddy object added to group
#        @type  buddy:    L{msn.Buddy}
#        @param group:    Group object buddy was added to
#        @type  group:    L{msn.Group}
#        '''
#        pass
    def remove_buddy(self, buddy_id, callback = None):
        remove_buddy(contact, group)
        Remove contact from group
        @param contact: the contact to remove from group
        @type  contact: L{msn.Contact}
        @param group:   the group to remove contact from
        @type  group:   L{msn.Group}
        bname, gid = buddy_id
        if bname == and self.version < 'MSNP21':
            log.error("Not going to try to remove self buddy, that's silly")
        if gid == None:
            gid = None
        buddy = self.get_buddy(bname)
        bid =
        if gid is None:
            return self.ns._remove_buddy('FL', buddy, gid, service = buddy.service)
        # Ugh, fake group. This is really just a display hack so pretend we actually succeeded and just take it out.
        if gid == 'Root':
            g = self.get_group(gid)
            b = get([_b for _b in g if == buddy_id], 0, None)
            if b is not None:
                print 'removing %r from fake group' % (b,)
            buddy = b.buddy
            if not self.groups_containing_buddy(buddy):
                return self.ns._remove_buddy('FL', buddy, None, service = buddy.service, callback = callback)
                return callback.success()
        return self.ns._remove_buddy_from_group(bname, bid, gid, service = buddy.service, callback = callback)
    def on_remove_buddy(self, contact, group):
        on_remove_buddy(contact, group)
        contact has been removed from group
        @param contact: the contact that was removed from group
        @type  contact: L{msn.Contact}
        @param group:   the group contact was removed from
        @type  group:   L{msn.Group}
    def authorize_buddy(self, bname, authorize = True, username_added = None, callback = None):
        authorize_buddy(bname, authorize=True)
        Authorize a buddy (or block them), removing them from the pending list.
        @param bname:     name of the buddy to authorize
        @type  bname:     basestring (passport)
        @param   authorize: authorize them?
        @type    authorize: bool
        @default authorize: True
        buddy = self.get_buddy(bname)
        self.ns._authorize_buddy(buddy, authorize, callback = callback)
    def on_authorize_buddy(self, buddy, authed):
        on_authorize_buddy(buddy, authed)
        Called when a buddy is authorized (or de-authorized)
        @param buddy:   buddy who was (de)authorized
        @type  buddy:   L{msn.Buddy}
        @param authed:  True if they were authorized, False otherwise
        @type  authed:  bool
    def block_buddy(self, buddy, block = True, callback = None):
        block_buddy(buddy, block=True)
        Block buddy if block is True else unblock
        @param buddy:  the buddy to block/unblock
        @type  buddy:  L{msn.Buddy}
        @param   block:  Block them?
        @type    block:  bool
        @default block:  True
        if block: return self.ns._block_buddy  (buddy, callback = callback)
        else:     return self.ns._unblock_buddy(buddy, callback = callback)
    def unblock_buddy(self, buddy, callback = None):
        Unblock buddy. Equivalent to block_buddy(buddy, False)
        @param buddy: the buddy to unblock
        @type  buddy: msn.Buddy
        return self.block_buddy(buddy, False, callback = callback)
    def on_block_buddy(self, bid, blocked):
        on_block_buddy(buddy, blocked)
        Called when a buddy is blocked or un-blocked
        @param buddy:   buddy who was (un-)blocked
        @type  buddy:   L{msn.Buddy
        @param blocked:  True if they were blocked, False otherwise
        @type  blocked:  bool
        self.get_buddy(bid).notify('blocked', not blocked, blocked)
    def move_buddy(self, contact, to_groupname, from_groupname = None, pos = 0, callback = None):
        move_buddy(contact, to_groupname, from_groupid=None, pos=0)
        Move a buddy to group with name to_groupname, creating if necessary. The buddy
        is moved from group with id from_groupid (if it is provided), else the root group
        (or is added if buddy is not in root group and from_groupid is not provided)
        @param contact:       Contact to be moved
        @type  contact:       L{msn.Contact}
        @param to_groupname:  Name of the group to move contact to
        @type  to_groupname:  basestring
        @param from_groupid:  GroupId of the group the contact is moving from
        @type  from_groupid:  L{msn.GroupId}
        @param   pos:        Ignored. Present for interface adherence. (Not supported by protocol)
        @type    pos:        int
        @default pos:        0
        togroup = self.get_group(to_groupname)
        fromgroup = self.get_group(from_groupname)
        buddy = self.get_buddy(
        if == and self.version < 'MSNP21':
            log.error("Not going to try to move self buddy, that's silly")
        if fromgroup and ((fromgroup is not self.root_group) or from_groupname is not None):
            def success(*a, **k):
                self.remove_buddy(, callback = callback)
            success = callback.success
                       success = success,
                       error = callback.error)
    def set_display_name(self, new_alias, callback = None):
        Set the friendly name other clients will see.
        @param new_alias: the new alias to set
        @type  new_alias: basestring
        return self.ns._set_display_name(new_alias or self.username, callback)
    def on_set_display_name(self, alias):
        Called when display name has been set.
        @param alias: the C{alias} that was set
        @type  alias: basestring
'Got response for display name set')
    def set_remote_alias(self, buddy, new_alias, callback = None):
        set_remote_alias(buddy, new_alias)
        Set the remote alias of a buddy.
        @param buddy:     The C{buddy} whose alias to set
        @type  buddy:     L{msn.Buddy}
        @param new_alias: Alias to set for C{buddy}
        @type  new_alias: basestring
        return self.ns._set_remote_alias(buddy, new_alias, callback)
    def on_set_remote_alias(self, buddy, alias):
        on_set_remote_alias(buddy, alias)
        Called when the remote C{alias} of C{buddy} has been set.
        @param buddy: The buddy whose alias was set
        @type  buddy; L{msn.Buddy}
        @param alias: The alias that was set
        @type  alias: basestring
    def add_group(self, gname, callback = None):
        add_group(gname, callback)
        Add a group of name C{gname} to the buddy list
        @param gname: name of group to add
        @type  gname: basestring
#        self.groups_to_add[gname] = callback.success
#        callback.success = lambda group: (log.debug('received response for adding group %r: %r', gname, group)
        return self.ns._add_group(gname, callback = callback)
# This is unused currently
#    def on_add_group(self, group):
#        '''
#        on_add_group(group)
#        Called when a group is added to the buddylist
#        @param group: The C{group} object that was added
#        @type  group: L{msn.Group}
#        '''
#        f = self.groups_to_add.pop(, lambda g:None)
#        f(group)
    def remove_group(self, group_id, callback = None):
        Remove group from the buddylist, pushing any buddies in it to the root group
        @param group: The C{group} to be removed
        @type  group: L{msn.Group}
        from util import CallCounter
        from util.callbacks import Callback
        group = self.groups[group_id]
        if group_id in ('Root', None):
  'It\'s a fake root group! Removing all buddies from it and then wiping it out.')
            for contact in list(group):
                if len(self.groups_containing_buddy(contact.buddy)) == 1:
                    self.remove_buddy((, None))
            group[:] = []
            return callback.success()
'Going to remove group %r with id %r', group, group_id)
        if not len(group):
  '    that group is already empty. Removing....')
            return self.ns._remove_group(group_id, callback = callback)
'    that group has %d things in it, going to make a call counter and remove all of them seperately', len(group))
        cc = CallCounter(len(group), lambda * a, **k: self.ns._remove_group(group_id, callback = callback))
        cb = Callback(success = (lambda * a, **k: cc()), error = callback.error)
        for contact in list(group):
            __, gid =
            bid = str(contact.buddy.guid)
            name =
  '    Removing buddy %r (name=%r, id=%r) from group %r (id=%r)', contact.buddy, name, bid, group, group_id)
            if gid is None:
                self.ns._remove_buddy('FL', contact.buddy, None, service = contact.buddy.service, callback = cb)
                self.ns._remove_buddy_from_group(name, bid, gid, service = contact.buddy.service, callback = cb)
    def on_remove_group(self, group):
        Called when a group is removed from the buddylist
        @param group: The C{group} that was removed
        @type  group: L{msn.Group}
        self.groups.pop(, None)
        if group in self.root_group:
    def set_status_message(self, message, callback = None):
        Sets status message to C{message}
        @param message: Status message to set.
        @type  message: basestring
        return self.ns._set_status_message(message, callback = callback)
    def on_set_status_message(self, message):
        Called when status message is successfully set
        @param message: the message that has been set
        @type  message: basestring
    def get_profile(self, buddy, callback = None):
        Retrieve buddy's profile
        @param buddy: The buddy whose profile to get
        @type  buddy: L{msn.Buddy}
        return self.ns._get_profile(buddy, callback)
    def on_get_profile(self, buddy, profile):
        Called when a buddy's profile is retrieved
        @param buddy:   The buddy whose profile was retrieved
        @type  buddy:   L{msn.Buddy}
        @param profile: The profile that was retrieved
        @type  profile: The raw data that was received
    def rename_group(self, groupid, name, callback = None):
        rename_group(group, name)
        Change the name of a group
        @param group: Group to be renamed
        @type  group: L{msn.Group}
        @param name:  new name of the group
        @type  name:  basestring
        return self.ns._rename_group(groupid, name, callback = callback)
    def on_rename_group(self, groupid, name):
        on_rename_group(group, name)
        Called when a group has been renamed
        @param group: Group that was renamed
        @type  group: L{msn.Group}
        @param name:  The current name of the group
        @type  name:  basestring
        self.groups[groupid].setnotifyif('name', name)
    def set_buddy_icon(self, icon = None, callback = None):
        Sets the client's buddy icon to icon
        If icon is None, there will be an attempt to retreive the cached icon
        for self_buddy from disk. If it is not found, then the icon is cleared.
        This function resizes and re-formats the icon to a 96x96 png, as per
        MSN "spec". It will be saved to disk temporarily for this operation.
        @param   icon: The icon to set
        @type    icon: NoneType or string (binary data)
        @default icon: None
        import os.path
        import io'setting buddy icon: %d bytes', len(icon or ''))
        pth = icon_path_for(self.self_buddy)
        icon_data = icon
        if icon_data is None:
            if os.path.exists(pth):
                icon_file = open(pth, 'rb')
                icon_file = None
            icon_file = io.BytesIO(icon_data)
        if icon_file is None:
            self.icon_obj = None
            icon_data = None
        if (icon_file, icon_data) != (None, None): # make sure both aren't None
            if icon_file is not None:
                import wx
                icon_img = wx.ImageFromString(
                if not icon_img.IsOk(): return
                if (icon_img.Size.x, icon_img.Size.y) > (96, 96):
                    icon_img.Rescale(96, 96, wx.IMAGE_QUALITY_HIGH)
                    if not icon_img.IsOk(): return
                if not os.path.exists(os.path.split(pth)[0]):
                temp_fn = os.path.join(os.path.split(pth)[0], 'temp_resize')
                icon_img.SaveFile(temp_fn, wx.BITMAP_TYPE_PNG)
                with open(temp_fn, 'rb') as f:
                    icon_data =
            if icon_data is not None:
                hash = hashlib.sha1(icon_data).digest()
                self.self_buddy.cache_icon(icon_data, hash)
        sta = self.status_to_code.get(self.status, 'AWY')
        cli = self.client_id
        return self.ns._set_buddy_icon(sta, cli, icon_data, callback)
    def on_set_buddy_icon(self, icon):
        Called when the buddy icon has been set
        @param data: The image data the icon has been set to
        @type  data: string (binary data)
    def get_buddy_icon(self, name, callback = None):
        Get the buddy icon for buddy with name C{name}
        @param name: The name of the buddy whose icon to get
        @type  name: string (passport)
        buddy = self.get_buddy(name)
        if buddy is self.self_buddy:
            log.debug("Not requesting self-buddy icon")
        if not
            log.debug("Not requesting offline buddy icon")
        if getattr(buddy, 'icon_disabled', False) and getattr(buddy, '_icon_disabled_until', 0xffffffff) > time.time():
            log.debug("Not requesting disabled buddy icon")
        bname =
'Adding %r to pending icon requests', bname)
        self._pending_icon_requests[bname] = callback
    def _process_icon_request(self):
        if len(self._requesting_icons) >= common.pref('msn.max_icon_requests', type = int, default = 2):
  'Too many active buddy icon requests')
            bname, callback = self._pending_icon_requests.popitem()
        except KeyError:
  'No pending buddy icon requests')
        buddy = self.get_buddy(bname)
        if bname in self._requesting_icons:
  'already requesting icon for %r, trying then next one.', buddy)
            return self._process_icon_request()
        def _request_resolved():
            self._pending_icon_requests.pop(bname, None)
            if buddy._getting_image:
        def on_icon_receive(objtrans, data):
            if data is not None:
                buddy.cache_icon(data, hashlib.sha1(data).digest())
      'Got buddy icon for %r', buddy)
        callback.success += on_icon_receive
        def cancel_get(objtrans = None, by_who = None):
  'Failed to get buddy icon for %r: transfer object: %r', buddy, objtrans)
            if not buddy.icon_disabled:
                buddy.icon_disabled = True
                # Don't try again for 10 minutes
                buddy._icon_disabled_until = time.time() + (10 * 60)
            buddy.icon_bitmap = None
            buddy._cached_hash = None
            buddy._getting_image = False
        callback.error += cancel_get
        failsafe_timer = util.Timer(60, _request_resolved)
        self._requesting_icons.add(bname)'Requesting icon for %r', buddy)
        self.P2PHandler.RequestMsnObject(getattr(buddy, 'contact', buddy), buddy.msn_obj, callback = callback)
    def on_get_buddy_icon(self, buddy, icon):
        on_get_buddy_icon(buddy, icon)
        @param buddy: The buddy object whose icon we got
        @type  buddy: L{msn.Buddy}
        @param icon:  The icon data we found for buddy
        @type  icon:  string (binary data)
    def send_file(self, buddy, fileinfo, callback = None):
        send_file(buddy, filepath)
        Begins a file transfer session with buddy to send file located at
        filepath to them.
        @param buddy:    The buddy to send the file to
        @type  buddy:    L{msn.Buddy}
        @param filepath: Absolute path to the file
        @type  filepath: string (filepath)
        bname = get(buddy, 'name', buddy)
        buddy = self.get_buddy(bname)
        if buddy == self.self_buddy:
        return self.P2PHandler.SendFile(, fileinfo.path, fileinfo.obj)
    def on_send_file(self, buddy, filepath):
        on_send_file(buddy, filepath)
        @param buddy:    The buddy the file was sent to
        @type  buddy:    L{msn.Buddy}
        @param filepath: the local address of the file
        @type  filepath: string (filepath)
    def send_sms(self, phone, message, callback = None):
        send_sma(phone, message)
        @param phone:   The phone number to send the message to
        @type phone:    string (Phone number - all digits, may start with '+')
        @param message: The message to send
        @type message:  string (may have length limitations)
        #TODO: See revision 6350 for reference
#        print 'Totally not sending sms to %s (message was %s)' % (phone, message)
#        return
        self.ns._send_sms(phone, message, callback)
#        for convo in self.conversations:
#            if convo.type == 'sms' or phone in (, convo.buddy.phone_mobile):
#                convo.buddy_says(convo.buddy, message)
#                break
#        else:
#            #somehow we sent an sms without a conversation?!
#            pass
    def on_send_sms(self, phone, message):
        on_send_sms(phone, message)
        @param phone:   Phone number message was sent to
        @type phone:    string
        @param message: The message that was sent
        @type message:  string
    def add_to_block(self, buddy, callback = None):
        if isinstance(buddy, basestring):
            buddy = self.get_buddy(buddy)
        self.ns.add_to_block(buddy, callback = callback)
    def rem_from_block(self, buddy, callback = None):
        if isinstance(buddy, basestring):
            buddy = self.get_buddy(buddy)
        self.ns.rem_from_block(buddy, callback = callback)
    def add_to_allow(self, buddy, callback = None):
        if isinstance(buddy, basestring):
            buddy = self.get_buddy(buddy)
        self.ns.add_to_allow(buddy, callback = callback)
    def rem_from_allow(self, buddy, callback = None):
        if isinstance(buddy, basestring):
            buddy = self.get_buddy(buddy)
        self.ns.rem_from_allow(buddy, callback = callback)
    def add_new_buddy(self, buddyname, groupname, service = None, alias = None):
        if service == 'yahoo' and self.version < 'MSNP14':
            return False
        if service == 'yahoo' and not '@yahoo' in buddyname:
            buddyname = buddyname + ''
        return protocol.add_new_buddy(self, buddyname, groupname, service, alias)
    def get_local_sockname(self):
        return self.ns.get_local_sockname()
    def allow_message(self, buddy, mobj):
        super = protocol.allow_message(self, buddy, mobj)
        if super: # Don't want to catch None or False here
            return super
        if buddy is None:
            return True
        if buddy.blocked:
            return False
        if super is None:
            return True
    def get_contact_info(self, name):
        contact_list = getattr(self.ns, 'contact_list', None)
        if contact_list is None:
            return self.get_buddy(name)
            return contact_list.GetContact(name)
    def get_machine_guid(self):
        return self.ns.get_machine_guid()
    def on_circle_member_joined(self, circle_id, buddy_name):
        members = self.circle_buddies[circle_id].Members
        if buddy_name not in members:
"Got new member for circle: %r in %r", buddy_name, circle_id)
    def on_circle_roster_remove(self, circle_id, ctype, name):
        circle = self.circle_buddies[circle_id]
        if name in circle.Members:
        if name in circle.Pending:
        convo = self.find_conv(circle_id)
        bname = name.split(':', 1)[-1]
        if convo is not None:
    def on_circle_roster_recv(self, circle_id, ctype, names, pending_names, nonpending_names, full):
        circle = self.circle_buddies[circle_id]
        if full:
            circle.Members = names
            circle.Pending = pending_names
            circle.Members = list(set(circle.Members) | set(names))
            circle.Pending = list(set(circle.Pending) | set(pending_names))
        conv = self.find_conv(circle_id)
        for name in nonpending_names:
            if name in circle.Pending:
        if conv is not None:
            bnames_in_room = [ for x in conv.room_list]
            log.debug("Names in room: %r / circle names: %r", bnames_in_room, circle.buddy_names)
            for name in circle.buddy_names:
                if name not in bnames_in_room:
        my_id = '1:%s' %
        from common import netcall
        def on_yes():
            netcall(lambda: (common.profile.on_entered_chat
                             (convo = self.accept_circle_invite(circle_id = circle_id, ctype = ctype))))
        def on_no():
            netcall(lambda: self.reject_circle_invite(circle_id = circle_id, ctype = ctype))
        log.debug("Got names for circle %r roster: %r", circle_id, names)
        if my_id in pending_names and full and len(names) > 1:
            # on chat invite
            common.profile.on_chat_invite(protocol = self,
                                          buddy = None,
                                          message = None,
                                          room_name = circle_id,
                                          on_yes = on_yes,
                                          on_no = on_no)
        elif my_id in pending_names and full and len(names) == 1:
            # This is the result from our room creation. "accept" it
    def accept_circle_invite(self, circle_id, ctype):
        return self.convo_for(circle_id)
    def reject_circle_invite(self, circle_id, ctype):
    @action(lambda self, *a, **k: (self.state == self.Statuses.ONLINE) or None)
    def CreateCircle(self):
        hooks.notify("digsby.msn.circle_create_prompt", success = self.DoCreateCircle)
    def DoCreateCircle(self, name):
    def LeaveCircle(self, circle_id_pair):
        circleId, _gid = circle_id_pair
        circle_contact = self.ns.GetCircle(circleId)
        circle_buddy = self.circle_buddies.get(circle_contact.account)
        abid = circle_contact.abid
        admins = circle_buddy.get_role_names('Admin')
        if len(admins) == 1 and in admins:
            url_cid = ''.join(abid.split('-'))[-16:]
            self.hub.launchurl('' % url_cid)
    def appear_offline_to(self, name):
    def appear_online_to(self, name):
    def OnInvitationReceived(self, session):
        import msn.P2P.P2PFileTransfer as FT
        activity = session.App"OnInvitationReceived(session = %r)", session)
        if activity.EufGuid == FT.FileTransfer.EufGuid:
            self.hub.on_file_request(self, activity)