diff --git a/examples/managing-audiences/test_audienceGroup.py b/examples/managing-audiences/test_audienceGroup.py new file mode 100644 index 000000000..4ebbe6f6a --- /dev/null +++ b/examples/managing-audiences/test_audienceGroup.py @@ -0,0 +1,153 @@ +# -*- coding: utf-8 -*- +import logging +from linebot import LineBotApi as LineBotApi_ori + +logging.basicConfig( + level=logging.DEBUG, + format='%(asctime)s %(levelname)s %(message)s', + datefmt='%Y-%m-%d %H:%M', + handlers=[logging.FileHandler('my.log', 'w', 'utf-8'), ] +) + +channel_access_token = 'xxxxxxx' + +tester = [ + "Utesttestesttesttesttestestest18", + "Utesttestesttesttesttestestest61", + "Utesttestesttesttesttestestest59", + "Utesttestesttesttesttestestest89", + "Utesttestesttesttesttestestest79", + "Utesttestesttesttesttestestest8c", + "Utesttestesttesttesttestestestc3", + "Utesttestesttesttesttestestest29", + "Utesttestesttesttesttestestest03", +] + +line_ids = { + "tester": tester, + "tester1": tester[:5], + "測試": tester[:4], + "tester2": tester[:3], + "tester3": tester[:2], + "tester john": tester[:1], +} + +request_id = "xxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx" + + +class LineBotApi(LineBotApi_ori): + def create_audience_group(self, audience_group_name, audiences, + is_ifa=False, mode="normal", timeout=None): + if mode in ["force", "append"]: + audience_group_id = self.get_audience_gid_by_name(audience_group_name) + if audience_group_id is not None and mode == "force": + self.delete_audience_group(audience_group_id) + elif audience_group_id is not None and mode == "append": + self.add_audiences_to_audience_group(audience_group_id, audiences) + return audience_group_id + return super(LineBotApi, self).create_audience_group( + audience_group_name, audiences, is_ifa, timeout) + + def get_audience_gid_by_name(self, description, timeout=None): + audience_groups = super(LineBotApi, self).get_audience_group_list( + description=description, timeout=timeout) + for audience_group in audience_groups: + if audience_group.description.encode('utf-8') == description: + return audience_group.audience_group_id + else: + return None + + def delete_audience_groups(self, audienceGroupIds): + for audienceGroupId in audienceGroupIds: + if audienceGroupId is not None: + self.delete_audience_group(audienceGroupId) + + +def initialize_env(): + _audience_group_id = [line_bot_api.get_audience_gid_by_name(gn) for gn in line_ids.keys()] + _audience_group_id += [line_bot_api.get_audience_gid_by_name(gn + "_new") + for gn in line_ids.keys()] + _audience_group_id += [line_bot_api.get_audience_gid_by_name(gn) for gn in + ['test_impression_based', 'test_click_based']] + line_bot_api.delete_audience_groups(_audience_group_id) + + +def add_audience_groups_cust(mode="normal"): + _audience_group_ids = [] + for audience_name, line_id in line_ids.items(): + if len(line_id) > 0: + audience_ids = [{"Id": _id} for _id in line_id] + new_gid = line_bot_api.create_audience_group(audience_name, audience_ids, mode=mode) + _audience_group_ids.append(new_gid) + return _audience_group_ids + + +def main(): + initialize_env() + to_be_delete = [] + + # Test create_imp_audience_group + _audience_group_id = line_bot_api.create_imp_audience_group( + 'test_impression_based', request_id) + to_be_delete.append(_audience_group_id) + logging.info("create_imp_audience_group result :\n{}".format(_audience_group_id)) + + # Test create_click_audience_group + _audience_group_id = line_bot_api.create_click_audience_group('test_click_based', request_id) + to_be_delete.append(_audience_group_id) + logging.info("create_click_audience_group result :\n{}".format(_audience_group_id)) + + # Test create_audience_group (normal) + _audience_group_ids = add_audience_groups_cust() + logging.info("create_audience_group(normal) result :\n{}".format(_audience_group_ids)) + + # Test create_audience_group (force) + _audience_group_ids = add_audience_groups_cust("force") + logging.info("create_audience_group(force) result :\n{}".format(_audience_group_ids)) + + # Test create_audience_group (append) + _audience_group_ids = add_audience_groups_cust("append") + logging.info("create_audience_group(append) result :\n{}".format(_audience_group_ids)) + + # Test get_audience_group_list + _audience_group_ids = [line_bot_api.get_audience_gid_by_name(gn) for gn in line_ids.keys()] + logging.info("get_audience_group_list result :\n{}".format(_audience_group_ids)) + + # Test rename_audience_group + [line_bot_api.rename_audience_group(gid, gn + "_new") + for gn, gid in zip(line_ids.keys(), _audience_group_ids)] + + # Test add_audiences_to_audience_group + [line_bot_api.add_audiences_to_audience_group(gid, [{"Id": _id} for _id in tester[-3:]], + upload_description='test_{}'.format(gid)) + for gid in _audience_group_ids] + + # Test list all audience groups + _audience_groups = line_bot_api.get_audience_group(_audience_group_ids[1]) + logging.info("Get audience group by 1st id :\n{}".format(_audience_groups)) + + # Test get_audience_group + _audience_groups = [line_bot_api.get_audience_group(gid) for gid in _audience_group_ids] + logging.info("get_audience_group result :\n{}".format(_audience_groups)) + + # Test delete_audience_group + [line_bot_api.delete_audience_group(gid) for gid in _audience_group_ids] + + # Test delete_audience_groups + line_bot_api.delete_audience_groups(to_be_delete) + + # Test change_audience_group_authority_level + line_bot_api.change_audience_group_authority_level('PUBLIC') + + # Test get_audience_group_authority_level + _result = line_bot_api.get_audience_group_authority_level() + logging.info("change_audience_group_authority_level result :\n{}".format(_result)) + + # Test list all audience groups + _audience_groups = line_bot_api.get_audience_group_list() + logging.info("All audience groups :\n{}".format(_audience_groups)) + + +if __name__ == '__main__': + line_bot_api = LineBotApi(channel_access_token) + main() diff --git a/linebot/api.py b/linebot/api.py index 8bc5df08b..a8d54fa9f 100644 --- a/linebot/api.py +++ b/linebot/api.py @@ -29,6 +29,8 @@ InsightMessageDeliveryResponse, InsightFollowersResponse, InsightDemographicResponse, InsightMessageEventResponse, BroadcastResponse, NarrowcastResponse, MessageProgressNarrowcastResponse, BotInfo, GetWebhookResponse, TestWebhookResponse, + AudienceGroup, ClickAudienceGroup, ImpAudienceGroup, GetAuthorityLevel, Audience, + CreateAudienceGroup ) from .models.responses import Group, UserIds @@ -1148,6 +1150,258 @@ def get_bot_info(self, timeout=None): return BotInfo.new_from_json_dict(response.json) + def create_audience_group(self, audience_group_name, audiences=[], + is_ifa=False, timeout=None): + """Create an audience group. + + https://developers.line.biz/en/reference/messaging-api/#create-upload-audience-group + + :param str audience_group_name: The audience's name + :param list audiences: An array of user IDs or IFAs + :param bool is_ifa: true | false + :return: audience group id + """ + if audiences: + audiences = [Audience.new_from_json_dict(audience) for audience in audiences] + response = self._post( + '/v2/bot/audienceGroup/upload', + data=json.dumps({ + "description": audience_group_name, + "isIfaAudience": is_ifa, + "audiences": [audience.as_json_dict() for audience in audiences], + }), + timeout=timeout + ) + + return CreateAudienceGroup.new_from_json_dict(response.json) + + def get_audience_group(self, audience_group_id, timeout=None): + """Get the object of audience group. + + https://developers.line.biz/en/reference/messaging-api/#get-audience-group + + :param str audience_group_id: The audience ID + :param timeout: (optional) How long to wait for the server + to send data before giving up, as a float, + or a (connect timeout, read timeout) float tuple. + Default is self.http_client.timeout + :type timeout: float | tuple(float, float) + :return: AudienceGroup instance + """ + response = self._get( + '/v2/bot/audienceGroup/{audience_group_id}'.format( + audience_group_id=audience_group_id), + timeout=timeout + ) + + return AudienceGroup.new_from_json_dict(response.json) + + def get_audience_group_list(self, page=1, description=None, status=None, size=20, + include_external_public_group=None, create_route=None, + timeout=None): + """Get data for more than one audience. + + https://developers.line.biz/en/reference/messaging-api/#get-audience-groups + + :param int page: The page to return when getting (paginated) results. Must be 1 or higher + :param str description: The name of the audience(s) to return + :param str status: IN_PROGRESS | READY | FAILED | EXPIRED + :param int size: The number of audiences per page. Default: 20, Max: 40 + :param bool include_external_public_group: true | false + :param str create_route: How the audience was created. + :type create_route: OA_MANAGER | MESSAGING_API + :return: AudienceGroup instance + """ + params = {} + if page: + params["page"] = page + if description: + params["description"] = description + if status: + params["status"] = status + if size: + params["size"] = size + if include_external_public_group: + params["includesExternalPublicGroup"] = include_external_public_group + if create_route: + params["createRoute"] = create_route + response = self._get( + '/v2/bot/audienceGroup/list?', + params=params, + timeout=timeout + ) + result = [] + for audience_group in response.json.get('audienceGroups', []): + result.append(AudienceGroup.new_from_json_dict(audience_group)) + if response.json.get('hasNextPage', False): + result += self.get_audience_group_list(page + 1, description, status, size, + include_external_public_group, + create_route, timeout) + return result + + def delete_audience_group(self, audience_group_id, timeout=None): + """Delete an existing audience. + + https://developers.line.biz/en/reference/messaging-api/#delete-audience-group + + :param str audience_group_id: The audience ID + :param timeout: (optional) How long to wait for the server + to send data before giving up, as a float, + or a (connect timeout, read timeout) float tuple. + Default is self.http_client.timeout + :type timeout: float | tuple(float, float) + """ + self._delete( + '/v2/bot/audienceGroup/{}'.format(audience_group_id), + timeout=timeout + ) + + def rename_audience_group(self, audience_group_id, description, timeout=None): + """Modify the name of an existing audience. + + https://developers.line.biz/en/reference/messaging-api/#set-description-audience-group + + :param str audience_group_id: The audience ID + :param str description: The new audience's name + :param timeout: (optional) How long to wait for the server + to send data before giving up, as a float, + or a (connect timeout, read timeout) float tuple. + Default is self.http_client.timeout + :type timeout: float | tuple(float, float) + """ + self._put( + '/v2/bot/audienceGroup/{audience_group_id}/updateDescription'.format( + audience_group_id=audience_group_id), + data=json.dumps({ + "description": description, + }), + timeout=timeout + ) + + return '' + + def add_audiences_to_audience_group(self, audience_group_id, audiences, + upload_description=None, timeout=None): + """Add new user IDs or IFAs to an audience for uploading user IDs. + + https://developers.line.biz/en/reference/messaging-api/#update-upload-audience-group + + :param str audience_group_id: The audience ID + :param list audiences: An array of user IDs or IFAs + :param timeout: (optional) How long to wait for the server + to send data before giving up, as a float, + or a (connect timeout, read timeout) float tuple. + Default is self.http_client.timeout + :param bool is_ifa: If this is false (default), recipients are specified by user IDs. + If true, recipients must be specified by IFAs. + :param str upload_description: The description to register for the job + :type timeout: float | tuple(float, float) + """ + if audiences: + audiences = [Audience.new_from_json_dict(audience) for audience in audiences] + response = self._put( + '/v2/bot/audienceGroup/upload', + data=json.dumps({ + "audienceGroupId": audience_group_id, + "audiences": [audience.as_json_dict() for audience in audiences], + "uploadDescription": upload_description, + }), + timeout=timeout + ) + + return response.json + + def get_audience_group_authority_level(self, timeout=None): + """Get the authority level of the audience. + + https://developers.line.biz/en/reference/messaging-api/#get-authority-level + + :param timeout: (optional) How long to wait for the server + to send data before giving up, as a float, + or a (connect timeout, read timeout) float tuple. + Default is self.http_client.timeout + :type timeout: float | tuple(float, float) + :return: json + """ + response = self._get( + '/v2/bot/audienceGroup/authorityLevel', + timeout=timeout + ) + + return GetAuthorityLevel.new_from_json_dict(response.json) + + def change_audience_group_authority_level(self, authority_level='PUBLIC', timeout=None): + """Change the authority level of all audiences created in the same channel. + + https://developers.line.biz/en/reference/messaging-api/#change-authority-level + + :param str authority_level: PUBLIC | PRIVATE. + """ + self._put( + '/v2/bot/audienceGroup/authorityLevel', + data=json.dumps({ + "authorityLevel": authority_level, + }), + timeout=timeout + ) + + return '' + + def create_click_audience_group(self, description, request_id, + click_url=None, timeout=None): + """Create an audience for click-based retargeting. + + https://developers.line.biz/en/reference/messaging-api/#create-click-audience-group + + :param str description: The audience's name. Audience names must be unique. + :param str request_id: The request ID of a message sent in the past 60 days. + :param str click_url: The URL clicked by the user. + If empty, users who clicked any URL in the message are added to the list of recipients. + :param timeout: (optional) How long to wait for the server + to send data before giving up, as a float, + or a (connect timeout, read timeout) float tuple. + Default is self.http_client.timeout + :type timeout: float | tuple(float, float) + :return: ClickAudienceGroup instance + """ + response = self._post( + '/v2/bot/audienceGroup/click', + data=json.dumps({ + "description": description, + "requestId": request_id, + "clickUrl": click_url, + }), + timeout=timeout + ) + + return ClickAudienceGroup.new_from_json_dict(response.json) + + def create_imp_audience_group(self, description, request_id, + timeout=None): + """Create an audience for impression-based retargeting. + + https://developers.line.biz/en/reference/messaging-api/#create-imp-audience-group + + :param str description: The audience's name. Audience names must be unique. + :param str request_id: The request ID of a message sent in the past 60 days. + :param timeout: (optional) How long to wait for the server + to send data before giving up, as a float, + or a (connect timeout, read timeout) float tuple. + Default is self.http_client.timeout + :type timeout: float | tuple(float, float) + :return: ImpAudienceGroup instance + """ + response = self._post( + '/v2/bot/audienceGroup/imp', + data=json.dumps({ + "description": description, + "requestId": request_id, + }), + timeout=timeout + ) + + return ImpAudienceGroup.new_from_json_dict(response.json) + def set_webhook_endpoint(self, webhook_endpoint, timeout=None): """Set the webhook endpoint URL. diff --git a/linebot/models/__init__.py b/linebot/models/__init__.py index 9f7bc0ab1..ff04d51ca 100644 --- a/linebot/models/__init__.py +++ b/linebot/models/__init__.py @@ -101,6 +101,7 @@ MessageStatistics, MessageInsight, ClickInsight, + JobInsight, ) from .limit import ( # noqa @@ -153,6 +154,12 @@ BotInfo, GetWebhookResponse, TestWebhookResponse, + AudienceGroup, + ClickAudienceGroup, + ImpAudienceGroup, + GetAuthorityLevel, + Audience, + CreateAudienceGroup, ) from .rich_menu import ( # noqa RichMenu, diff --git a/linebot/models/insight.py b/linebot/models/insight.py index 57126a61f..5b1369221 100644 --- a/linebot/models/insight.py +++ b/linebot/models/insight.py @@ -212,3 +212,33 @@ def __init__(self, seq=None, url=None, click=None, unique_click=None, self.click = click self.unique_click = unique_click self.unique_click_of_request = unique_click_of_request + + +class JobInsight(Base): + """ClickInsight.""" + + def __init__(self, audience_group_job_id=None, audience_group_id=None, description=None, + type=None, job_status=None, failed_type=None, audience_count=None, + created=None, **kwargs): + """__init__ method. + + :param int audience_group_job_id: A job ID. + :param int audience_group_id: An audience ID. + :param str description: The job's description. + :param str type: The job's type. One of: 'DIFF_ADD' + :param str job_status: The job's status. One of: 'QUEUED', 'WORKING', 'FINISHED', 'FAILED' + :param str failed_type: The reason why the operation failed. This is only included when + jobs[].jobStatus is FAILED. + :param int audience_count: The number of accounts (recipients) that were added or removed. + :param int created: When the job was created (in UNIX time). + :param kwargs: + """ + super(JobInsight, self).__init__(**kwargs) + self.audience_group_job_id = audience_group_job_id + self.audience_group_id = audience_group_id + self.description = description + self.type = type + self.job_status = job_status + self.failed_type = failed_type + self.audience_count = audience_count + self.created = created diff --git a/linebot/models/responses.py b/linebot/models/responses.py index 7593daa43..5227ed036 100644 --- a/linebot/models/responses.py +++ b/linebot/models/responses.py @@ -18,9 +18,9 @@ from .base import Base from .insight import ( - SubscriptionPeriodInsight, AppTypeInsight, - AgeInsight, GenderInsight, AreaInsight, - MessageInsight, ClickInsight, MessageStatistics, + SubscriptionPeriodInsight, AppTypeInsight, AgeInsight, + GenderInsight, AreaInsight, MessageInsight, ClickInsight, + MessageStatistics, JobInsight, ) from .rich_menu import RichMenuSize, RichMenuArea @@ -574,6 +574,213 @@ def __init__(self, success=None, timestamp=None, status_code=None, self.detail = detail +class AudienceGroup(Base): + """AudienceGroups. + + https://developers.line.biz/en/reference/messaging-api/#get-audience-group + """ + + def __init__(self, audience_group_id=None, type=None, description=None, status=None, + audience_count=None, created=None, is_ifa_audience=None, permission=None, + create_route=None, request_id=None, failed_type=None, click_url=None, + jobs=None, **kwargs): + """__init__ method. + + :param int audience_group_id: The audience ID. + :param str type: The audience type. One of `UPLOAD` or `CLICK` or `IMP` or `CHAT_TAG` + or `FRIEND_PATH`. + :param str description: The audience's name. + :param str status: The audience's status. One of `IN_PROGRESS` or `READY` or `FAILED` + or `EXPIRED`. + :param int audience_count: The number of valid recipients. + :param int created: When the audience was created (in UNIX time). + :param bool is_ifa_audience: The value specified when creating an audience for uploading + user IDs to indicate the type of accounts that will be given as recipients. + :param str permission: Audience's update permission. Audiences linked to the same channel + will be READ_WRITE. + :param str create_route: How the audience was created. If omitted, + you will get all audiences. + :param str request_id: The request ID that was specified when the audience was created. + :param str failed_type: The reason why the operation failed. This is only included when + status is FAILED. One of `AUDIENCE_GROUP_AUDIENCE_INSUFFICIENT` or `INTERNAL_ERROR` + :param str click_url: The URL that was specified when the audience was created. + This is only included when type is CLICK + :param jobs: An array of jobs. This array is used to keep track of each attempt to + add new user IDs or IFAs to an audience for uploading user IDs. + :param kwargs: + """ + super(AudienceGroup, self).__init__(**kwargs) + + self.audience_group_id = audience_group_id + self.type = type + self.description = description + self.status = status + self.audience_count = audience_count + self.created = created + self.is_ifa_audience = is_ifa_audience + self.permission = permission + self.create_route = create_route + self.request_id = request_id + self.failed_type = failed_type + self.click_url = click_url + if jobs: + self.jobs = [self.get_or_new_from_json_dict(job, JobInsight) for job in jobs] + + +class ClickAudienceGroup(Base): + """ClickAudienceGroup. + + https://developers.line.biz/en/reference/messaging-api/#create-click-audience-group + """ + + def __init__(self, audience_group_id=None, create_route=None, type=None, description=None, + created=None, permission=None, expire_timestamp=None, is_ifa_audience=None, + request_id=None, click_url=None, **kwargs): + """__init__ method. + + :param int audience_group_id: The audience ID. + :param str create_route: How the audience was created. If omitted, + you will get all audiences. + :param str type: The audience type. One of `UPLOAD` or `CLICK` or `IMP` or `CHAT_TAG` + or `FRIEND_PATH`. + :param str description: The audience's name. + :param int created: When the audience was created (in UNIX time). + :param str permission: Audience's update permission. Audiences linked to the same channel + will be READ_WRITE. + :param int expire_timestamp: Time of audience expiration. Only returned for specific + audiences. + :param bool is_ifa_audience: The value specified when creating an audience for uploading + user IDs to indicate the type of accounts that will be given as recipients. + :param str request_id: The request ID that was specified when the audience was created. + :param str click_url: The URL that was specified when the audience was created. + This is only included when type is CLICK + :param kwargs: + """ + super(ClickAudienceGroup, self).__init__(**kwargs) + + self.audience_group_id = audience_group_id + self.create_route = create_route + self.type = type + self.description = description + self.created = created + self.permission = permission + self.expire_timestamp = expire_timestamp + self.is_ifa_audience = is_ifa_audience + self.request_id = request_id + self.click_url = click_url + + +class CreateAudienceGroup(Base): + """ClickAudienceGroup. + + https://developers.line.biz/en/reference/messaging-api/#create-upload-audience-group + """ + + def __init__(self, audience_group_id=None, create_route=None, type=None, description=None, + created=None, permission=None, expire_timestamp=None, is_ifa_audience=None, + **kwargs): + """__init__ method. + + :param int audience_group_id: The audience ID. + :param str create_route: How the audience was created. If omitted, + you will get all audiences. + :param str type: The audience type. One of `UPLOAD` or `CLICK` or `IMP` or `CHAT_TAG` + or `FRIEND_PATH`. + :param str description: The audience's name. + :param int created: When the audience was created (in UNIX time). + :param str permission: Audience's update permission. Audiences linked to the same channel + will be READ_WRITE. + :param int expire_timestamp: Time of audience expiration. Only returned for specific + audiences. + :param bool is_ifa_audience: The value specified when creating an audience for uploading + user IDs to indicate the type of accounts that will be given as recipients. + :param kwargs: + """ + super(CreateAudienceGroup, self).__init__(**kwargs) + + self.audience_group_id = audience_group_id + self.create_route = create_route + self.type = type + self.description = description + self.created = created + self.permission = permission + self.expire_timestamp = expire_timestamp + self.is_ifa_audience = is_ifa_audience + + +class ImpAudienceGroup(Base): + """ImpAudienceGroup. + + https://developers.line.biz/en/reference/messaging-api/#create-imp-audience-group + """ + + def __init__(self, audience_group_id=None, create_route=None, type=None, description=None, + created=None, permission=None, expire_timestamp=None, is_ifa_audience=None, + request_id=None, **kwargs): + """__init__ method. + + :param int audience_group_id: The audience ID. + :param str create_route: How the audience was created. If omitted, + you will get all audiences. + :param str type: The audience type. One of `UPLOAD` or `CLICK` or `IMP` or `CHAT_TAG` + or `FRIEND_PATH`. + :param str description: The audience's name. + :param int created: When the audience was created (in UNIX time). + :param str permission: Audience's update permission. Audiences linked to the same channel + will be READ_WRITE. + :param int expire_timestamp: Time of audience expiration. Only returned for specific + audiences. + :param bool is_ifa_audience: The value specified when creating an audience for uploading + user IDs to indicate the type of accounts that will be given as recipients. + :param str request_id: The request ID that was specified when the audience was created. + :param kwargs: + """ + super(ImpAudienceGroup, self).__init__(**kwargs) + + self.audience_group_id = audience_group_id + self.create_route = create_route + self.type = type + self.description = description + self.created = created + self.permission = permission + self.expire_timestamp = expire_timestamp + self.is_ifa_audience = is_ifa_audience + self.request_id = request_id + + +class GetAuthorityLevel(Base): + """GetAuthorityLevel. + + https://developers.line.biz/en/reference/messaging-api/#get-authority-level + """ + + def __init__(self, authority_level=None, **kwargs): + """__init__ method. + + :param str authority_level: The authority level for all audiences linked to a channel. + :param kwargs: + """ + super(GetAuthorityLevel, self).__init__(**kwargs) + + self.authority_level = authority_level + + +class Audience(Base): + """Audience. + + https://developers.line.biz/en/reference/messaging-api/#update-upload-audience-group + """ + + def __init__(self, id=None, **kwargs): + """__init__ method. + + :param str audience_id: A user ID or IFA. + :param kwargs: + """ + super(Audience, self).__init__(**kwargs) + self.id = id + + class UserIds(Base): """UserIds.