Repository: line/line-bot-sdk-python
Pull request #170 (2019-06-05 11:02:53)

Implement new richmenu API
https://github.com/line/line-bot-sdk-python/issues/150


Patch:
diff --git a/README.rst b/README.rst
index 2d8772c..5d31df1 100644
--- a/README.rst
+++ b/README.rst
@@ -320,6 +320,17 @@ https://developers.line.me/en/docs/messaging-api/reference/#link-rich-menu-to-us
 
     line_bot_api.link_rich_menu_to_user(user_id, rich_menu_id)
 
+link\_rich\_menu\_to\_users(self, user\_ids, rich\_menu\_id, timeout=None)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Links a rich menu to multiple users.
+
+https://developers.line.biz/en/reference/messaging-api/#link-rich-menu-to-users
+
+.. code:: python
+
+    line_bot_api.link_rich_menu_to_users(<user_ids>, <rich_menu_id>)
+
 unlink\_rich\_menu\_from\_user(self, user\_id, timeout=None)
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
@@ -331,6 +342,17 @@ https://developers.line.me/en/docs/messaging-api/reference/#unlink-rich-menu-fro
 
     line_bot_api.unlink_rich_menu_from_user(user_id)
 
+unlink\_rich\_menu\_from\_users(self, user\_ids, timeout=None)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Unlinks rich menus from multiple users.
+
+https://developers.line.biz/en/reference/messaging-api/#unlink-rich-menu-from-users
+
+.. code:: python
+
+    line_bot_api.unlink_rich_menu_from_users(<user_ids>)
+
 get\_rich\_menu\_image(self, rich\_menu\_id, timeout=None)
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
@@ -370,6 +392,39 @@ https://developers.line.me/en/docs/messaging-api/reference/#get-rich-menu-list
     for rich_menu in rich_menu_list:
         print(rich_menu.rich_menu_id)
 
+set\_default\_rich\_menu(self, rich\_menu\_id, timeout=None)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Sets the default rich menu.
+
+https://developers.line.biz/en/reference/messaging-api/#set-default-rich-menu
+
+.. code:: python
+
+    line_bot_api.set_default_rich_menu(<rich_menu_id>)
+
+get\_default\_rich\_menu(self, timeout=None)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Gets the ID of the default rich menu set with the Messaging API.
+
+https://developers.line.biz/en/reference/messaging-api/#get-default-rich-menu-id
+
+.. code:: python
+
+    line_bot_api.get_default_rich_menu()
+
+cancel\_default\_rich\_menu(self, timeout=None)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Cancels the default rich menu set with the Messaging API.
+
+https://developers.line.biz/en/reference/messaging-api/#cancel-default-rich-menu
+
+.. code:: python
+
+    line_bot_api.cancel_default_rich_menu()
+
 ※ Error handling
 ^^^^^^^^^^^^^^^^
 
diff --git a/linebot/api.py b/linebot/api.py
index ce2e11f..b4343a0 100644
--- a/linebot/api.py
+++ b/linebot/api.py
@@ -498,8 +498,12 @@ class LineBotApi(object):
 
         https://developers.line.me/en/docs/messaging-api/reference/#link-rich-menu-to-user
 
-        :param str user_id: ID of an uploaded rich menu
-        :param str rich_menu_id: ID of the user
+        :param str user_id: ID of the user
+        :param str rich_menu_id: ID of an uploaded rich menu
+        :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._post(
@@ -510,6 +514,30 @@ class LineBotApi(object):
             timeout=timeout
         )
 
+    def link_rich_menu_to_users(self, user_ids, rich_menu_id, timeout=None):
+        """Links a rich menu to multiple users.
+
+        https://developers.line.biz/en/reference/messaging-api/#link-rich-menu-to-users
+
+        :param user_ids: user IDs
+            Max: 150 users
+        :type user_ids: list[str]
+        :param str rich_menu_id: ID of an uploaded rich menu
+        :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._post(
+            '/v2/bot/richmenu/bulk/link',
+            data=json.dumps({
+                'userIds': user_ids,
+                'richMenuId': rich_menu_id,
+            }),
+            timeout=timeout
+        )
+
     def unlink_rich_menu_from_user(self, user_id, timeout=None):
         """Call unlink rich menu from user API.
 
@@ -527,6 +555,28 @@ class LineBotApi(object):
             timeout=timeout
         )
 
+    def unlink_rich_menu_from_users(self, user_ids, timeout=None):
+        """Unlinks rich menus from multiple users.
+
+        https://developers.line.biz/en/reference/messaging-api/#unlink-rich-menu-from-users
+
+        :param user_ids: user IDs
+            Max: 150 users
+        :type user_ids: list[str]
+        :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._post(
+            '/v2/bot/richmenu/bulk/unlink',
+            data=json.dumps({
+                'userIds': user_ids,
+            }),
+            timeout=timeout
+        )
+
     def get_rich_menu_image(self, rich_menu_id, timeout=None):
         """Call download rich menu image API.
 
@@ -595,6 +645,59 @@ class LineBotApi(object):
 
         return result
 
+    def set_default_rich_menu(self, rich_menu_id, timeout=None):
+        """Set the default rich menu.
+
+        https://developers.line.biz/en/reference/messaging-api/#set-default-rich-menu
+
+        :param str rich_menu_id: ID of an uploaded rich menu
+        :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._post(
+            '/v2/bot/user/all/richmenu/{rich_menu_id}'.format(
+                rich_menu_id=rich_menu_id,
+            ),
+            timeout=timeout
+        )
+
+    def get_default_rich_menu(self, timeout=None):
+        """Get the ID of the default rich menu set with the Messaging API.
+
+        https://developers.line.biz/en/reference/messaging-api/#get-default-rich-menu-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)
+        """
+        response = self._get(
+            '/v2/bot/user/all/richmenu',
+            timeout=timeout
+        )
+
+        return response.json.get('richMenuId')
+
+    def cancel_default_rich_menu(self, timeout=None):
+        """Cancel the default rich menu set with the Messaging API.
+
+        https://developers.line.biz/en/reference/messaging-api/#cancel-default-rich-menu
+
+        :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/user/all/richmenu',
+            timeout=timeout
+        )
+
     def get_message_quota(self, timeout=None):
         """Call Get the target limit for additional messages.
 


Test patch:
diff --git a/tests/api/test_rich_menu.py b/tests/api/test_rich_menu.py
index 3654a24..383758f 100644
--- a/tests/api/test_rich_menu.py
+++ b/tests/api/test_rich_menu.py
@@ -14,6 +14,7 @@
 
 from __future__ import unicode_literals, absolute_import
 
+import json
 import unittest
 
 import responses
@@ -283,6 +284,110 @@ class TestLineBotApi(unittest.TestCase):
         self.assertEqual(rich_menus[0].areas[0].action.type, 'postback')
         self.assertEqual(rich_menus[0].areas[0].action.data, 'action=buy&itemid=123')
 
+    @responses.activate
+    def test_link_rich_menu_to_users(self):
+        responses.add(
+            responses.POST,
+            LineBotApi.DEFAULT_API_ENDPOINT +
+            '/v2/bot/richmenu/bulk/link',
+            json={}, status=202
+        )
+
+        self.tested.link_rich_menu_to_users(['user_id1', 'user_id2'], 'rich_menu_id')
+
+        request = responses.calls[0].request
+        self.assertEqual(request.method, 'POST')
+        self.assertEqual(
+            request.url,
+            LineBotApi.DEFAULT_API_ENDPOINT + '/v2/bot/richmenu/bulk/link'
+        )
+        self.assertEqual(
+            json.loads(request.body),
+            {
+                "richMenuId": "rich_menu_id",
+                "userIds": ["user_id1", "user_id2"],
+            }
+        )
+
+    @responses.activate
+    def test_unlink_rich_menu_to_users(self):
+        responses.add(
+            responses.POST,
+            LineBotApi.DEFAULT_API_ENDPOINT +
+            '/v2/bot/richmenu/bulk/unlink',
+            json={}, status=202
+        )
+
+        self.tested.unlink_rich_menu_from_users(['user_id1', 'user_id2'], 'rich_menu_id')
+
+        request = responses.calls[0].request
+        self.assertEqual(request.method, 'POST')
+        self.assertEqual(
+            request.url,
+            LineBotApi.DEFAULT_API_ENDPOINT + '/v2/bot/richmenu/bulk/unlink'
+        )
+        self.assertEqual(
+            json.loads(request.body),
+            {
+                "userIds": ["user_id1", "user_id2"],
+            }
+        )
+
+    @responses.activate
+    def test_set_default_rich_menu(self):
+        responses.add(
+            responses.POST,
+            LineBotApi.DEFAULT_API_ENDPOINT +
+            '/v2/bot/user/all/richmenu/rich_menu_id',
+            json={}, status=200
+        )
+
+        self.tested.set_default_rich_menu('rich_menu_id')
+
+        request = responses.calls[0].request
+        self.assertEqual(request.method, 'POST')
+        self.assertEqual(
+            request.url,
+            LineBotApi.DEFAULT_API_ENDPOINT + '/v2/bot/user/all/richmenu/rich_menu_id'
+        )
+
+    @responses.activate
+    def test_get_default_rich_menu(self):
+        responses.add(
+            responses.GET,
+            LineBotApi.DEFAULT_API_ENDPOINT +
+            '/v2/bot/user/all/richmenu',
+            json={"richMenuId": "richMenuId"}, status=200
+        )
+
+        result = self.tested.get_default_rich_menu()
+
+        request = responses.calls[0].request
+        self.assertEqual(request.method, 'GET')
+        self.assertEqual(
+            request.url,
+            LineBotApi.DEFAULT_API_ENDPOINT + '/v2/bot/user/all/richmenu'
+        )
+        self.assertEqual(result, "richMenuId")
+
+    @responses.activate
+    def test_cancel_default_rich_menu(self):
+        responses.add(
+            responses.DELETE,
+            LineBotApi.DEFAULT_API_ENDPOINT +
+            '/v2/bot/user/all/richmenu',
+            json={}, status=200
+        )
+
+        self.tested.cancel_default_rich_menu()
+
+        request = responses.calls[0].request
+        self.assertEqual(request.method, 'DELETE')
+        self.assertEqual(
+            request.url,
+            LineBotApi.DEFAULT_API_ENDPOINT + '/v2/bot/user/all/richmenu'
+        )
+
 
 if __name__ == '__main__':
     unittest.main()