| Server IP : 182.53.201.61 / Your IP : 216.73.217.175 Web Server : Apache/2.2.15 (Fedora) System : Linux km10.dyndns.org 2.6.31.5-127.fc12.i686.PAE #1 SMP Sat Nov 7 21:25:57 EST 2009 i686 User : apache ( 48) PHP Version : 5.3.3 Disable Function : NONE MySQL : ON | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : ON Directory : /usr/lib/python2.6/site-packages/papyon/msnp/ |
Upload File : |
# -*- coding: utf-8 -*-
#
# papyon - a python client library for Msn
#
# Copyright (C) 2005-2007 Ali Sabil <ali.sabil@gmail.com>
# Copyright (C) 2005-2006 Ole André Vadla Ravnås <oleavr@gmail.com>
# Copyright (C) 2007 Johann Prieur <johann.prieur@gmail.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""Notification protocol Implementation
Implements the protocol used to communicate with the Notification Server."""
from base import BaseProtocol, ProtocolState
from message import Message
from constants import ProtocolConstant
from challenge import _msn_challenge
import papyon
from papyon.gnet.message.HTTP import HTTPMessage
from papyon.util.queue import PriorityQueue, LastElementQueue
from papyon.util.decorator import throttled
import papyon.util.element_tree as ElementTree
import papyon.profile as profile
import papyon.service.SingleSignOn as SSO
import papyon.service.AddressBook as AB
import papyon.service.OfflineIM as OIM
import hashlib
import time
import logging
import urllib
import gobject
import xml.sax.saxutils as xml_utils
__all__ = ['NotificationProtocol']
logger = logging.getLogger('papyon.protocol.notification')
class NotificationProtocol(BaseProtocol, gobject.GObject):
"""Protocol used to communicate with the Notification Server
@undocumented: do_get_property, do_set_property
@group Handlers: _handle_*, _default_handler, _error_handler
@ivar state: the current protocol state
@type state: integer
@see L{base.ProtocolState}"""
__gsignals__ = {
"authentication-failed" : (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE,
()),
"disconnected-by-other" : (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE,
()),
"server-down" : (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE,
()),
"buddy-notification-received" : (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE,
(object, object,)),
"mail-received" : (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE,
(object,)),
"switchboard-invitation-received" : (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE,
(object, object)),
"unmanaged-message-received" : (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE,
(object, object)),
}
__gproperties__ = {
"state": (gobject.TYPE_INT,
"State",
"The state of the communication with the server.",
0, 6, ProtocolState.CLOSED,
gobject.PARAM_READABLE)
}
def __init__(self, client, transport, proxies={}, version=15):
"""Initializer
@param client: the parent instance of L{client.Client}
@type client: L{client.Client}
@param transport: The transport to use to speak the protocol
@type transport: L{transport.BaseTransport}
@param proxies: a dictonary mapping the proxy type to a
L{gnet.proxy.ProxyInfos} instance
@type proxies: {type: string, proxy:L{gnet.proxy.ProxyInfos}}
"""
BaseProtocol.__init__(self, client, transport, proxies)
gobject.GObject.__init__(self)
self.__state = ProtocolState.CLOSED
self._protocol_version = version
self._url_callbacks = {} # tr_id=>callback
# Properties ------------------------------------------------------------
def __get_state(self):
return self.__state
def __set_state(self, state):
self.__state = state
self.notify("state")
state = property(__get_state)
_state = property(__get_state, __set_state)
def do_get_property(self, pspec):
if pspec.name == "state":
return self.__state
else:
raise AttributeError, "unknown property %s" % pspec.name
def do_set_property(self, pspec, value):
raise AttributeError, "unknown property %s" % pspec.name
# Public API -------------------------------------------------------------
@throttled(2000, LastElementQueue())
def set_presence(self, presence, client_id=0, msn_object=None):
"""Publish the new user presence.
@param presence: the new presence
@type presence: string L{profile.Presence}"""
if msn_object == None:
msn_object = ""
if presence == profile.Presence.OFFLINE:
self._client.logout()
else:
if msn_object:
self._client._msn_object_store.publish(msn_object)
self._send_command('CHG',
(presence, str(client_id), urllib.quote(str(msn_object))))
@throttled(2000, LastElementQueue())
def set_display_name(self, display_name):
"""Sets the new display name
@param display_name: the new friendly name
@type display_name: string"""
self._send_command('PRP',
('MFN', urllib.quote(display_name)))
@throttled(2000, LastElementQueue())
def set_personal_message(self, personal_message='', current_media=None,
signature_sound=None):
"""Sets the new personal message
@param personal_message: the new personal message
@type personal_message: string"""
cm = ''
if current_media is not None:
cm ='\\0Music\\01\\0{0} - {1}\\0%s\\0%s\\0\\0' % \
(xml_utils.escape(current_media[0]),
xml_utils.escape(current_media[1]))
if signature_sound is not None:
signature_sound = xml_utils.escape(signature_sound)
ss = '<SignatureSound>%s</SignatureSound>' % signature_sound
message = xml_utils.escape(personal_message)
pm = '<Data>'\
'<PSM>%s</PSM>'\
'<CurrentMedia>%s</CurrentMedia>'\
'<MachineGuid>%s</MachineGuid>'\
'</Data>' % (message, cm, self._client.machine_guid.upper())
self._send_command('UUX', payload=pm)
self._client.profile._server_property_changed("personal-message",
personal_message)
if current_media is not None:
self._client.profile._server_property_changed("current-media",
current_media)
@throttled(2000, LastElementQueue())
def set_end_point_name(self, name="Papyon", idle=False):
ep = '<EndpointData>'\
'<Capabilities>%s</Capabilities>'\
'</EndpointData>' % self._client.profile.client_id
name = xml_utils.escape(name)
pep = '<PrivateEndpointData>'\
'<EpName>%s</EpName>'\
'<Idle>%s</Idle>'\
'<State>%s</State>'\
'<ClientType>%i</ClientType>'\
'</PrivateEndpointData>' % (name, str(idle).lower(),
self._client.profile.presence, self._client.client_type)
self._send_command('UUX', payload=ep)
self._send_command('UUX', payload=pep)
def signoff(self):
"""Logout from the server"""
self._send_command('OUT')
self._transport.lose_connection()
@throttled(7600, list())
def request_switchboard(self, priority, callback, *callback_args):
self.__switchboard_callbacks.add((callback, callback_args), priority)
self._send_command('XFR', ('SB',))
def add_contact_to_membership(self, account,
network_id=profile.NetworkID.MSN,
membership=profile.Membership.FORWARD):
"""Add a contact to a given membership.
@param account: the contact identifier
@type account: string
@param network_id: the contact network
@type network_id: integer
@see L{papyon.profile.NetworkID}
@param membership: the list to be added to
@type membership: integer
@see L{papyon.profile.Membership}"""
if network_id == profile.NetworkID.MOBILE:
payload = '<ml><t><c n="tel:%s" l="%d" /></t></ml>' % \
(account, membership)
self._send_command("ADL", payload=payload)
else:
user, domain = account.split("@", 1)
payload = '<ml><d n="%s"><c n="%s" l="%d" t="%d"/></d></ml>' % \
(domain, user, membership, network_id)
self._send_command("ADL", payload=payload)
def remove_contact_from_membership(self, account,
network_id=profile.NetworkID.MSN,
membership=profile.Membership.FORWARD):
"""Remove a contact from a given membership.
@param account: the contact identifier
@type account: string
@param network_id: the contact network
@type network_id: integer
@see L{papyon.profile.NetworkID}
@param membership: the list to be added to
@type membership: integer
@see L{papyon.profile.Membership}"""
if network_id == profile.NetworkID.MOBILE:
payload = '<ml><t><c n="tel:%s" l="%d" /></t></ml>' % \
(account, membership)
self._send_command("RML", payload=payload)
else:
user, domain = account.split("@", 1)
payload = '<ml><d n="%s"><c n="%s" l="%d" t="%d"/></d></ml>' % \
(domain, user, membership, network_id)
self._send_command("RML", payload=payload)
def send_user_notification(self, message, contact, type):
self._send_command("UUN", (contact.account, type), message)
def send_unmanaged_message(self, contact, message):
content_type = message.content_type[0]
if content_type == 'text/x-msnmsgr-datacast':
message_type = 3
elif content_type == 'text/x-msmsgscontrol':
message_type = 2
else:
message_type = 1
self._send_command('UUM',
(contact.account, contact.network_id, message_type),
payload=message)
def send_url_request(self, url_command_args, callback):
tr_id = self._send_command('URL', url_command_args)
self._url_callbacks[tr_id] = callback
def _parse_account(self, command, idx=0):
if self._protocol_version >= 18:
temp = command.arguments[idx].split(":")
network_id = int(temp[0])
account = temp[1]
else:
account = command.arguments[idx]
idx += 1
network_id = int(command.arguments[idx])
idx += 1
return idx, network_id, account
# Handlers ---------------------------------------------------------------
# --------- Connection ---------------------------------------------------
def _handle_VER(self, command):
self._protocol_version = int(command.arguments[0].lstrip('MSNP'))
self._send_command('CVR',
ProtocolConstant.CVR + (self._client.profile.account,))
def _handle_CVR(self, command):
method = 'SSO'
self._send_command('USR',
(method, 'I', self._client.profile.account))
def _handle_XFR(self, command):
if command.arguments[0] == 'NS':
try:
host, port = command.arguments[1].split(":", 1)
port = int(port)
except ValueError:
host = command.arguments[1]
port = self._transport.server[1]
logger.debug("<-> Redirecting to " + command.arguments[1])
self._transport.reset_connection((host,port))
else: # connect to a switchboard
try:
host, port = command.arguments[1].split(":", 1)
port = int(port)
except ValueError:
host = command.arguments[1]
port = self._transport.server[1]
session_id = command.arguments[3]
callback, callback_args = self.__switchboard_callbacks.pop(0)
callback(((host, port), session_id, None), *callback_args)
def _handle_USR(self, command):
args_len = len(command.arguments)
# MSNP15 have only 4 params for final USR
assert(args_len == 3 or args_len == 4), \
"Received USR with invalid number of params : " + str(command)
if command.arguments[0] == "OK":
self._state = ProtocolState.AUTHENTICATED
# we need to authenticate with a passport server
elif command.arguments[1] == "S":
self._state = ProtocolState.AUTHENTICATING
if command.arguments[0] == "SSO":
self._client._sso.RequestMultipleSecurityTokens(
(self._sso_cb, command.arguments[3]),
(lambda *args: self.emit("authentication-failed"),),
SSO.LiveService.MESSENGER_CLEAR)
self._client.address_book.connect("notify::state",
self._address_book_state_changed_cb)
self._client.address_book.connect("messenger-contact-added",
self._address_book_contact_added_cb)
self._client.address_book.connect("contact-accepted",
self._address_book_contact_accepted_cb)
self._client.address_book.connect("contact-rejected",
self._address_book_contact_rejected_cb)
self._client.address_book.connect("contact-deleted",
self._address_book_contact_deleted_cb)
self._client.address_book.connect("contact-blocked",
self._address_book_contact_blocked_cb)
self._client.address_book.connect("contact-unblocked",
self._address_book_contact_unblocked_cb)
self._client.address_book.connect("contact-allowed",
self._address_book_contact_allowed_cb)
self._client.address_book.connect("contact-disallowed",
self._address_book_contact_disallowed_cb)
elif command.arguments[0] == "TWN":
raise NotImplementedError, "Missing Implementation, please fix"
def _handle_SBS(self, command): # unknown command
pass
def _handle_OUT(self, command):
reason = None
if len(command.arguments) > 0:
reason = command.arguments[0]
if reason == "OTH":
self.emit("disconnected-by-other")
elif reason == "SSD":
self.emit("server-down")
else:
self._transport.lose_connection()
# --------- Presence & Privacy -------------------------------------------
def _handle_BLP(self, command):
self._client.profile._server_property_changed("privacy",
command.arguments[0])
def _handle_CHG(self, command):
self._client.profile._server_property_changed("presence",
command.arguments[0])
if len(command.arguments) > 2:
if command.arguments[2] != '0':
msn_object = papyon.p2p.MSNObject.parse(self._client,
urllib.unquote(command.arguments[2]))
else:
msn_object = None
self._client.profile._server_property_changed("msn_object", msn_object)
else:
self._client.profile._server_property_changed("msn_object", None)
def _handle_ILN(self,command):
self._handle_NLN(command)
def _handle_FLN(self,command):
idx, network_id, account = self._parse_account(command)
contacts = self._client.address_book.contacts.\
search_by_network_id(network_id).\
search_by_account(account)
if len(contacts) == 0:
logger.warning("Contact (network_id=%d) %s not found" % \
(network_id, account))
for contact in contacts:
contact._server_property_changed("presence",
profile.Presence.OFFLINE)
def _handle_NLN(self,command):
idx, network_id, account = self._parse_account(command, 1)
contacts = self._client.address_book.contacts.\
search_by_network_id(network_id).\
search_by_account(account)
if len(contacts) == 0:
logger.warning("Contact (network_id=%d) %s not found" % \
(network_id, account))
presence = command.arguments[0]
display_name = urllib.unquote(command.arguments[idx])
idx += 1
capabilities = command.arguments[idx]
idx += 1
msn_object = None
icon_url = None
if len(command.arguments) > idx:
if command.arguments[idx] != '0':
msn_object = papyon.p2p.MSNObject.parse(self._client,
urllib.unquote(command.arguments[idx]))
idx += 1
if len(command.arguments) > idx:
icon_url = command.arguments[idx]
for contact in contacts:
contact._server_property_changed("presence", presence)
contact._server_property_changed("display-name", display_name)
contact._server_property_changed("client-capabilities", capabilities)
contact._server_property_changed("msn-object", msn_object)
if icon_url is not None:
contact._server_attribute_changed('icon_url', icon_url)
# --------- Display name and co ------------------------------------------
def _handle_PRP(self, command):
ctype = command.arguments[0]
if len(command.arguments) < 2: return
if ctype == 'MFN':
self._client.profile._server_property_changed('display-name',
urllib.unquote(command.arguments[1]))
# TODO: add support for other stuff
def _handle_UUX(self, command):
pass
def _handle_UBN(self,command): # contact infos
if not command.payload:
return
type = int(command.arguments[1])
self.emit("buddy-notification-received", type, command)
def _handle_UBX(self,command): # contact infos
if not command.payload:
return
idx, network_id, account = self._parse_account(command)
try:
tree = ElementTree.fromstring(command.payload)
except:
logger.error("Invalid XML data in received UBX command")
return
cm = tree.find("./CurrentMedia")
if cm is not None and cm.text is not None:
parts = cm.text.split('\\0')
if parts[1] == 'Music' and parts[2] == '1':
cm = (parts[4].encode("utf-8"), parts[5].encode("utf-8"))
elif parts[2] == '0':
cm = None
else:
cm = None
pm = tree.find("./PSM")
if pm is not None and pm.text is not None:
pm = pm.text.encode("utf-8")
else:
pm = ""
ss = tree.find("./SignatureSound")
if ss is not None and ss.text is not None:
ss = ss.text.encode("utf-8")
else:
ss = None
contacts = self._client.address_book.contacts.\
search_by_network_id(network_id).\
search_by_account(account)
if len(contacts) == 0:
logger.warning("Contact (network_id=%d) %s not found" % \
(network_id, account))
for contact in contacts:
contact._server_property_changed("current-media", cm)
contact._server_property_changed("personal-message", pm)
contact._server_property_changed("signature-sound", ss)
def _handle_UUN(self,command): # UBN acknowledgment
pass
# --------- Contact List -------------------------------------------------
def _handle_ADL(self, command):
if command.transaction_id == 0: # incoming ADL from the server
self._client.address_book.check_pending_invitations()
if len(command.arguments) > 0 and command.arguments[0] == "OK":
if self._state != ProtocolState.OPEN: # Initial ADL
self._state = ProtocolState.OPEN
self._transport.enable_ping()
else: # contact Added
pass
def _handle_RML(self, command):
pass
# --------- Messages -----------------------------------------------------
def _handle_MSG(self, command):
message = Message(None, command.payload)
content_type = message.content_type
if content_type[0] == 'text/x-msmsgsprofile':
profile = {}
lines = command.payload.split("\r\n")
for line in lines:
line = line.strip()
if line:
name, value = line.split(":", 1)
profile[name] = value.strip()
self._client.profile._server_property_changed("profile", profile)
self._send_command("BLP",
(self._client.profile.privacy,))
self._state = ProtocolState.SYNCHRONIZING
self._client.address_book.sync()
elif content_type[0] in \
('text/x-msmsgsinitialmdatanotification', \
'text/x-msmsgsoimnotification'):
if self._client.oim_box is not None:
self._client.oim_box._state = \
OIM.OfflineMessagesBoxState.NOT_SYNCHRONIZED
m = HTTPMessage()
m.parse(message.body)
mail_data = m.get_header('Mail-Data').strip()
if mail_data == 'too-large':
mail_data = None
self._client.oim_box.sync(mail_data)
if mail_data and \
content_type[0] == 'text/x-msmsgsinitialmdatanotification':
#Initial mail
start = mail_data.find('<IU>') + 4
end = mail_data.find('</IU>')
if start < end:
mailbox_unread = int(mail_data[start:end])
self._client.mailbox._initial_set(mailbox_unread)
elif content_type[0] == 'text/x-msmsgsinitialemailnotification':
#Initial mail (obsolete by MSNP11)
pass
elif content_type[0] == 'text/x-msmsgsemailnotification':
#New mail
m = HTTPMessage()
m.parse(message.body)
name = m.get_header('From')
address = m.get_header('From-Addr')
subject = m.get_header('Subject')
message_url = m.get_header('Message-URL')
post_url = m.get_header('Post-URL')
post_id = m.get_header('id')
dest = m.get_header('Dest-Folder')
if dest == 'ACTIVE':
self._client.mailbox._unread_mail_increased(1)
build = self._build_url_post_data
post_url, form_data = build(message_url, post_url, post_id)
self._client.mailbox._new_mail(name, address, subject,
post_url, form_data)
elif content_type[0] == 'text/x-msmsgsactivemailnotification':
#Movement of unread mail
m = HTTPMessage()
m.parse(message.body)
src = m.get_header('Src-Folder')
dest = m.get_header('Dest-Folder')
delta = int(m.get_header('Message-Delta'))
if src == 'ACTIVE':
self._client.mailbox._unread_mail_decreased(delta)
elif dest == 'ACTIVE':
self._client.mailbox._unread_mail_increased(delta)
def _handle_UBM(self, command):
idx, network_id, account = self._parse_account(command)
contacts = self._client.address_book.contacts.\
search_by_network_id(network_id).\
search_by_account(account)
if len(contacts) == 0:
logger.warning("Contact (network_id=%d) %s not found" % \
(network_id, account))
else:
contact = contacts[0]
message = Message(contact, command.payload)
self.emit("unmanaged-message-received", contact, message)
# --------- Urls ---------------------------------------------------------
def _build_url_post_data(self,
message_url="/cgi-bin/HoTMaiL",
post_url='https://loginnet.passport.com/ppsecure/md5auth.srf?',
post_id='2'):
profile = self._client.profile.profile
account = self._client.profile.account
password = str(self._client.profile.password)
sl = str(int(time.time()) - int(profile['LoginTime']))
sid = profile['sid']
auth = profile['MSPAuth']
creds = hashlib.md5(auth + sl + password).hexdigest()
post_data = dict([
('mode', 'ttl'),
('login', account.split('@')[0]),
('username', account),
('sid', sid),
('kv', ''),
('id', post_id),
('sl', sl),
('rru', message_url),
('auth', auth),
('creds', creds),
('svc', 'mail'),
('js', 'yes')])
return (post_url, post_data)
def _handle_URL(self, command):
tr_id = command.transaction_id
if tr_id in self._url_callbacks:
message_url, post_url, post_id = command.arguments
post_url, form_dict = self._build_url_post_data(message_url,
post_url, post_id)
callback = self._url_callbacks[tr_id]
del self._url_callbacks[tr_id]
callback(post_url, form_dict)
# --------- Invitation ---------------------------------------------------
def _handle_RNG(self,command):
session_id = command.arguments[0]
host, port = command.arguments[1].split(':',1)
port = int(port)
key = command.arguments[3]
account = command.arguments[4]
display_name = urllib.unquote(command.arguments[5])
session = ((host, port), session_id, key)
inviter = (account, display_name)
self.emit("switchboard-invitation-received", session, inviter)
# --------- Challenge ----------------------------------------------------
def _handle_QNG(self,command):
pass
def _handle_QRY(self,command):
pass
def _handle_CHL(self,command):
response = _msn_challenge(command.arguments[0])
self._send_command('QRY',
(ProtocolConstant.PRODUCT_ID,), payload=response)
# callbacks --------------------------------------------------------------
def _connect_cb(self, transport):
self.__switchboard_callbacks = PriorityQueue()
self._state = ProtocolState.OPENING
versions = []
for version in ProtocolConstant.VER:
if version <= self._protocol_version:
versions.append("MSNP%i" % version)
self._send_command('VER', versions)
def _disconnect_cb(self, transport, reason):
self._state = ProtocolState.CLOSED
def _sso_cb(self, tokens, nonce):
if self._state != ProtocolState.AUTHENTICATING:
return
clear_token = tokens[SSO.LiveService.MESSENGER_CLEAR]
token = clear_token.security_token
blob = clear_token.mbi_crypt(nonce)
if self._protocol_version >= 16:
arguments = ("SSO", "S", token, blob, "{%s}" %
self._client.machine_guid.upper())
else:
arguments = ("SSO", "S", token, blob)
self._send_command("USR", arguments)
def _address_book_state_changed_cb(self, address_book, pspec):
MAX_PAYLOAD_SIZE = 7500
if self._state != ProtocolState.SYNCHRONIZING:
return
if address_book.state != AB.AddressBookState.SYNCHRONIZED:
return
self._client.profile._server_property_changed("display-name",
address_book.profile.display_name)
contacts = address_book.contacts.group_by_domain()
payloads = ['<ml l="1">']
mask = ~(profile.Membership.REVERSE | profile.Membership.PENDING)
for domain, contacts in contacts.iteritems():
payloads[-1] += '<d n="%s">' % domain
for contact in contacts:
user = contact.account.split("@", 1)[0]
lists = contact.memberships & mask
if lists == profile.Membership.NONE:
continue
network_id = contact.network_id
node = '<c n="%s" l="%d" t="%d"/>' % (user, lists, network_id)
size = len(payloads[-1]) + len(node) + len('</d></ml>')
if size >= MAX_PAYLOAD_SIZE:
payloads[-1] += '</d></ml>'
payloads.append('<ml l="1"><d n="%s">' % domain)
payloads[-1] += node
payloads[-1] += '</d>'
payloads[-1] += '</ml>'
import re
pattern = re.compile ('<d n="[^"]+"></d>')
for payload in payloads:
payload = pattern.sub('', payload)
self._send_command("ADL", payload=payload)
self._state = ProtocolState.SYNCHRONIZED
def _add_contact_to_membership(self, contact, membership):
self.add_contact_to_membership(contact.account, contact.network_id,
membership)
def _remove_contact_from_membership(self, contact, membership):
self.remove_contact_from_membership(contact.account,
contact.network_id, membership)
def _address_book_contact_added_cb(self, address_book, contact):
if contact.is_member(profile.Membership.ALLOW):
self._add_contact_to_membership(contact, profile.Membership.ALLOW)
self._add_contact_to_membership(contact, profile.Membership.FORWARD)
if contact.network_id != profile.NetworkID.MOBILE:
account, domain = contact.account.split('@', 1)
payload = '<ml l="2"><d n="%s"><c n="%s"/></d></ml>'% \
(domain, account)
self._send_command("FQY", payload=payload)
def _address_book_contact_deleted_cb(self, address_book, contact):
self._remove_contact_from_membership(contact, profile.Membership.FORWARD)
def _address_book_contact_accepted_cb(self, address_book, contact):
mask = ~(profile.Membership.REVERSE | profile.Membership.PENDING)
memberships = contact.memberships & mask
if memberships:
self._add_contact_to_membership(contact, memberships)
def _address_book_contact_rejected_cb(self, address_book, contact):
mask = ~(profile.Membership.REVERSE | profile.Membership.PENDING)
memberships = contact.memberships & mask
if memberships:
self._add_contact_to_membership(contact, memberships)
def _address_book_contact_blocked_cb(self, address_book, contact):
self._remove_contact_from_membership(contact, profile.Membership.ALLOW)
self._add_contact_to_membership(contact, profile.Membership.BLOCK)
def _address_book_contact_unblocked_cb(self, address_book, contact):
self._remove_contact_from_membership(contact, profile.Membership.BLOCK)
self._add_contact_to_membership(contact, profile.Membership.ALLOW)
def _address_book_contact_allowed_cb(self, address_book, contact):
self._add_contact_to_membership(contact, profile.Membership.ALLOW)
def _address_book_contact_disallowed_cb(self, address_book, contact):
self._remove_contact_from_membership(contact, profile.Membership.ALLOW)