facebook_business/adobjects/helpers/customaudiencemixin.py (184 lines of code) (raw):

# Copyright 2014 Facebook, Inc. # You are hereby granted a non-exclusive, worldwide, royalty-free license to # use, copy, modify, and distribute this software in source code or binary # form for use in connection with the web services and APIs provided by # Facebook. # As with any software that integrates with the Facebook platform, your use # of this software is subject to the Facebook Developer Principles and # Policies [http://developers.facebook.com/policy/]. This copyright notice # shall be included in all copies or substantial portions of the software. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. from facebook_business.exceptions import FacebookBadObjectError import hashlib import six import re class CustomAudienceMixin: class Schema(object): uid = 'UID' email_hash = 'EMAIL_SHA256' phone_hash = 'PHONE_SHA256' mobile_advertiser_id = 'MOBILE_ADVERTISER_ID' class MultiKeySchema(object): extern_id = 'EXTERN_ID' email = 'EMAIL' phone = 'PHONE' gen = 'GEN' doby = 'DOBY' dobm = 'DOBM' dobd = 'DOBD' ln = 'LN' fn = 'FN' fi = 'FI' ct = 'CT' st = 'ST' zip = 'ZIP' madid = 'MADID' country = 'COUNTRY' appuid = 'APPUID' @classmethod def format_params(cls, schema, users, is_raw=False, app_ids=None, pre_hashed=None, session=None): hashed_users = [] if schema in (cls.Schema.phone_hash, cls.Schema.email_hash, cls.Schema.mobile_advertiser_id): for user in users: if schema == cls.Schema.email_hash: user = user.strip(" \t\r\n\0\x0B.").lower() if isinstance(user, six.text_type) and not(pre_hashed) and schema != cls.Schema.mobile_advertiser_id: user = user.encode('utf8') # required for hashlib # for mobile_advertiser_id, don't hash it if pre_hashed or schema == cls.Schema.mobile_advertiser_id: hashed_users.append(user) else: hashed_users.append(hashlib.sha256(user).hexdigest()) elif isinstance(schema, list): # SDK will support only single PII if not is_raw: raise FacebookBadObjectError( "Please send single PIIs i.e. is_raw should be true. " + "The combining of the keys will be done internally.", ) # users is array of array for user in users: if len(schema) != len(user): raise FacebookBadObjectError( "Number of keys in each list in the data should " + "match the number of keys specified in scheme", ) break # If the keys are already hashed then send as it is if pre_hashed: hashed_users.append(user) else: counter = 0 hashed_user = [] for key in user: key = key.strip(" \t\r\n\0\x0B.").lower() key = cls.normalize_key(schema[counter], str(key)) if schema[counter] != \ cls.Schema.MultiKeySchema.extern_id: if isinstance(key, six.text_type): key = key.encode('utf8') key = hashlib.sha256(key).hexdigest() counter = counter + 1 hashed_user.append(key) hashed_users.append(hashed_user) payload = { 'schema': schema, 'is_raw': is_raw, 'data': hashed_users or users, } if schema == cls.Schema.uid: if not app_ids: raise FacebookBadObjectError( "Custom Audiences with type " + cls.Schema.uid + "require at least one app_id", ) if app_ids: payload['app_ids'] = app_ids params = { 'payload': payload, } if session: params['session'] = session return params @classmethod def normalize_key(cls, key_name, key_value=None): """ Normalize the value based on the key """ if key_value is None: return key_value if(key_name == cls.Schema.MultiKeySchema.extern_id or key_name == cls.Schema.MultiKeySchema.email or key_name == cls.Schema.MultiKeySchema.madid or key_name == cls.Schema.MultiKeySchema.appuid): return key_value if(key_name == cls.Schema.MultiKeySchema.phone): key_value = re.sub(r'[^0-9]', '', key_value) return key_value if(key_name == cls.Schema.MultiKeySchema.gen): key_value = key_value.strip()[:1] return key_value if(key_name == cls.Schema.MultiKeySchema.doby): key_value = re.sub(r'[^0-9]', '', key_value) return key_value if(key_name == cls.Schema.MultiKeySchema.dobm or key_name == cls.Schema.MultiKeySchema.dobd): key_value = re.sub(r'[^0-9]', '', key_value) if len(key_value) == 1: key_value = '0' + key_value return key_value if(key_name == cls.Schema.MultiKeySchema.ln or key_name == cls.Schema.MultiKeySchema.fn or key_name == cls.Schema.MultiKeySchema.ct or key_name == cls.Schema.MultiKeySchema.fi or key_name == cls.Schema.MultiKeySchema.st): key_value = re.sub(r'[^a-zA-Z]', '', key_value) return key_value if(key_name == cls.Schema.MultiKeySchema.zip): key_value = re.split('-', key_value)[0] return key_value if(key_name == cls.Schema.MultiKeySchema.country): key_value = re.sub(r'[^a-zA-Z]', '', key_value)[:2] return key_value def add_users(self, schema, users, is_raw=False, app_ids=None, pre_hashed=None, session=None): """Adds users to this CustomAudience. Args: schema: A CustomAudience.Schema value specifying the type of values in the users list. users: A list of identities respecting the schema specified. Returns: The FacebookResponse object. """ return self.get_api_assured().call( 'POST', (self.get_id_assured(), 'users'), params=self.format_params( schema, users, is_raw, app_ids, pre_hashed, session, ), ) def remove_users(self, schema, users, is_raw=False, app_ids=None, pre_hashed=None, session=None): """Deletes users from this CustomAudience. Args: schema: A CustomAudience.Schema value specifying the type of values in the users list. users: A list of identities respecting the schema specified. Returns: The FacebookResponse object. """ return self.get_api_assured().call( 'DELETE', (self.get_id_assured(), 'users'), params=self.format_params( schema, users, is_raw, app_ids, pre_hashed, session, ), ) def share_audience(self, account_ids): """Shares this CustomAudience with the specified account_ids. Args: account_ids: A list of account ids. Returns: The FacebookResponse object. """ return self.get_api_assured().call( 'POST', (self.get_id_assured(), 'adaccounts'), params={'adaccounts': account_ids}, ) def unshare_audience(self, account_ids): """Unshares this CustomAudience with the specified account_ids. Args: account_ids: A list of account ids. Returns: The FacebookResponse object. """ return self.get_api_assured().call( 'DELETE', (self.get_id_assured(), 'adaccounts'), params={'adaccounts': account_ids}, )