Repository: line/line-bot-sdk-python

Issue:
Messaging API update for June 2020
News: https://developers.line.biz/en/news/2020/06/10/messaging-api-update-june-2020/
New endpoints are added to the Messaging API

- [ ] [Get group summary](https://developers.line.biz/en/reference/messaging-api/#get-group-summary)
- [ ] [Get members in group count](https://developers.line.biz/en/reference/messaging-api/#get-members-group-count)
- [ ] [Get members in room count](https://developers.line.biz/en/reference/messaging-api/#get-members-room-count)

Discussion:
louis70109: > LGTM with some nits. Thank you for your contribution!

Thanks for your careful review 🙂
louis70109: > Sorry for leaving a comment after approving.
> 
> Could you add a description of new methods to README?

Ok🙂
louis70109: I have already updated README,
please check 😁

Patch:
diff --git a/README.rst b/README.rst
index ccf2c2c..368f99a 100644
--- a/README.rst
+++ b/README.rst
@@ -196,6 +196,20 @@ https://developers.line.biz/en/reference/messaging-api/#get-profile
     print(profile.picture_url)
     print(profile.status_message)
 
+get\_group\_summary(self, group\_id, timeout=None)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Gets the group ID, group name, and group icon URL of a group
+where the LINE Official Account is a member.
+
+https://developers.line.biz/en/reference/messaging-api/#get-group-summary
+
+.. code:: python
+
+    summary = line_bot_api.get_group_summary(group_id)
+    print(summary.group_id)
+    print(summary.group_name)
+    print(summary.picture_url)
+
 get\_group\_member\_profile(self, group\_id, user\_id, timeout=None)
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
@@ -259,6 +273,30 @@ https://developers.line.biz/en/reference/messaging-api/#get-room-member-user-ids
     print(member_ids_res.member_ids)
     print(member_ids_res.next)
 
+get\_group\_members\_count(self, group\_id, timeout=None)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Gets the count of members in a group.
+
+https://developers.line.biz/en/reference/messaging-api/#get-members-group-count
+
+.. code:: python
+
+    group_count = line_bot_api.get_group_members_count(group_id)
+    print(group_count)
+
+get\_room\_members\_count(self, room\_id, timeout=None)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Gets the count of members in a room.
+
+https://developers.line.biz/en/reference/messaging-api/#get-members-room-count
+
+.. code:: python
+
+    room_count = line_bot_api.get_room_members_count(room_id)
+    print(room_count)
+
 get\_message\_content(self, message\_id, timeout=None)
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
diff --git a/linebot/api.py b/linebot/api.py
index 77f2034..afa337d 100644
--- a/linebot/api.py
+++ b/linebot/api.py
@@ -30,6 +30,7 @@ from .models import (
     InsightMessageEventResponse, BroadcastResponse, NarrowcastResponse,
     MessageProgressNarrowcastResponse,
 )
+from .models.responses import Group
 
 
 class LineBotApi(object):
@@ -386,6 +387,76 @@ class LineBotApi(object):
 
         return Profile.new_from_json_dict(response.json)
 
+    def get_group_summary(self, group_id, timeout=None):
+        """Call get group summary API.
+
+        https://developers.line.biz/en/reference/messaging-api/#get-group-summary
+
+        Gets the group ID, group name, and group icon URL of a group
+        where the LINE Official Account is a member.
+
+        :param str group_id: Group 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)
+        :rtype: :py:class:`linebot.models.responses.Group`
+        :return: Profile instance
+        """
+        response = self._get(
+            '/v2/bot/group/{group_id}/summary'.format(group_id=group_id),
+            timeout=timeout
+        )
+
+        return Group.new_from_json_dict(response.json)
+
+    def get_group_members_count(self, group_id, timeout=None):
+        """Call get members in group count API.
+
+        https://developers.line.biz/en/reference/messaging-api/#get-members-group-count
+
+        Gets the count of members in a group.
+
+        :param str group_id: Group 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)
+        :rtype: :py:class:`linebot.models.responses.Group`
+        :return: Profile instance
+        """
+        response = self._get(
+            '/v2/bot/group/{group_id}/members/count'.format(group_id=group_id),
+            timeout=timeout
+        )
+
+        return response.json.get('count')
+
+    def get_room_members_count(self, room_id, timeout=None):
+        """Call get members in room count API.
+
+        https://developers.line.biz/en/reference/messaging-api/#get-members-room-count
+
+        Gets the count of members in a room.
+
+        :param str room_id: Room 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)
+        :rtype: :py:class:`linebot.models.responses.Group`
+        :return: Profile instance
+        """
+        response = self._get(
+            '/v2/bot/room/{room_id}/members/count'.format(room_id=room_id),
+            timeout=timeout
+        )
+
+        return response.json.get('count')
+
     def get_group_member_profile(self, group_id, user_id, timeout=None):
         """Call get group member profile API.
 
diff --git a/linebot/models/flex_message.py b/linebot/models/flex_message.py
index afccc3e..bd95169 100644
--- a/linebot/models/flex_message.py
+++ b/linebot/models/flex_message.py
@@ -109,7 +109,12 @@ class BubbleContainer(FlexContainer):
         self.size = size
         self.direction = direction
         self.header = self.get_or_new_from_json_dict(header, BoxComponent)
-        self.hero = self.get_or_new_from_json_dict(hero, ImageComponent)
+        self.hero = self.get_or_new_from_json_dict_with_types(
+            hero, {
+                'image': ImageComponent,
+                'box': BoxComponent
+            }
+        )
         self.body = self.get_or_new_from_json_dict(body, BoxComponent)
         self.footer = self.get_or_new_from_json_dict(footer, BoxComponent)
         self.styles = self.get_or_new_from_json_dict(styles, BubbleStyle)
diff --git a/linebot/models/responses.py b/linebot/models/responses.py
index 850f94b..936c60a 100644
--- a/linebot/models/responses.py
+++ b/linebot/models/responses.py
@@ -65,6 +65,27 @@ class Profile(Base):
         self.language = language
 
 
+class Group(Base):
+    """Group.
+
+    https://developers.line.biz/en/reference/messaging-api/#get-group-id-response
+    """
+
+    def __init__(self, group_id=None, group_name=None, picture_url=None, **kwargs):
+        """__init__ method.
+
+        :param str group_id
+        :param str group_name
+        :param str picture_url
+        :param kwargs:
+        """
+        super(Group, self).__init__(**kwargs)
+
+        self.group_id = group_id
+        self.group_name = group_name
+        self.picture_url = picture_url
+
+
 class MemberIds(Base):
     """MemberIds.
 


Test patch:
diff --git a/tests/api/test_get_group.py b/tests/api/test_get_group.py
new file mode 100644
index 0000000..967d8be
--- /dev/null
+++ b/tests/api/test_get_group.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+
+#  Licensed under the Apache License, Version 2.0 (the "License"); you may
+#  not use this file except in compliance with the License. You may obtain
+#  a copy of the License at
+#
+#       https://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#  License for the specific language governing permissions and limitations
+#  under the License.
+
+from __future__ import unicode_literals, absolute_import
+
+import unittest
+
+import responses
+
+from linebot import (
+    LineBotApi
+)
+
+
+class TestLineBotApi(unittest.TestCase):
+    def setUp(self):
+        self.tested = LineBotApi('channel_secret')
+
+    @responses.activate
+    def test_get_group_summary(self):
+        responses.add(
+            responses.GET,
+            LineBotApi.DEFAULT_API_ENDPOINT + '/v2/bot/group/group_id/summary',
+            json={
+                "groupId": "Ca56f94637c...",
+                "groupName": "Group name",
+                "pictureUrl": "https://example.com/abcdefghijklmn"
+            },
+            status=200
+        )
+
+        group = self.tested.get_group_summary('group_id')
+
+        request = responses.calls[0].request
+        self.assertEqual(request.method, 'GET')
+        self.assertEqual(
+            request.url,
+            LineBotApi.DEFAULT_API_ENDPOINT + '/v2/bot/group/group_id/summary')
+        self.assertEqual(group.group_id, 'Ca56f94637c...')
+        self.assertEqual(group.group_name, 'Group name')
+        self.assertEqual(group.picture_url, 'https://example.com/abcdefghijklmn')
+
+    @responses.activate
+    def test_get_group_members_count(self):
+        responses.add(
+            responses.GET,
+            LineBotApi.DEFAULT_API_ENDPOINT + '/v2/bot/group/group_id/members/count',
+            json={"count": 3},
+            status=200
+        )
+
+        count = self.tested.get_group_members_count('group_id')
+
+        request = responses.calls[0].request
+        self.assertEqual(request.method, 'GET')
+        self.assertEqual(
+            request.url,
+            LineBotApi.DEFAULT_API_ENDPOINT + '/v2/bot/group/group_id/members/count')
+        self.assertEqual(count, 3)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/api/test_get_room.py b/tests/api/test_get_room.py
new file mode 100644
index 0000000..5174f00
--- /dev/null
+++ b/tests/api/test_get_room.py
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+
+#  Licensed under the Apache License, Version 2.0 (the "License"); you may
+#  not use this file except in compliance with the License. You may obtain
+#  a copy of the License at
+#
+#       https://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#  License for the specific language governing permissions and limitations
+#  under the License.
+
+from __future__ import unicode_literals, absolute_import
+
+import unittest
+
+import responses
+
+from linebot import (
+    LineBotApi
+)
+
+
+class TestLineBotApi(unittest.TestCase):
+    def setUp(self):
+        self.tested = LineBotApi('channel_secret')
+
+    @responses.activate
+    def test_get_room_members_count(self):
+        responses.add(
+            responses.GET,
+            LineBotApi.DEFAULT_API_ENDPOINT + '/v2/bot/room/room_id/members/count',
+            json={"count": 3},
+            status=200
+        )
+
+        count = self.tested.get_room_members_count('room_id')
+
+        request = responses.calls[0].request
+        self.assertEqual(request.method, 'GET')
+        self.assertEqual(
+            request.url,
+            LineBotApi.DEFAULT_API_ENDPOINT + '/v2/bot/room/room_id/members/count')
+        self.assertEqual(count, 3)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/models/test_flex_message.py b/tests/models/test_flex_message.py
index ad752fc..9fe7c29 100644
--- a/tests/models/test_flex_message.py
+++ b/tests/models/test_flex_message.py
@@ -61,8 +61,6 @@ class TestFlexMessage(SerializeTestCase):
             'header':
                 BoxComponent(layout='vertical',
                              contents=[TextComponent(text='Header text')]),
-            'hero':
-                ImageComponent(uri='https://example.com/flex/images/image.jpg'),
             'body':
                 BoxComponent(layout='vertical',
                              contents=[TextComponent(text='Body text')]),
@@ -79,10 +77,17 @@ class TestFlexMessage(SerializeTestCase):
                                       separator_color='#00ffff')
                 )
         }
-        self.assertEqual(
-            self.serialize_as_dict(arg, type=self.BUBBLE),
-            BubbleContainer(**arg).as_json_dict()
-        )
+        heros = [
+            ImageComponent(uri='https://example.com/flex/images/image.jpg'),
+            BoxComponent(layout='vertical',
+                         contents=[TextComponent(text='Body text')]),
+        ]
+        for hero in heros:
+            arg['hero'] = hero
+            self.assertEqual(
+                self.serialize_as_dict(arg, type=self.BUBBLE),
+                BubbleContainer(**arg).as_json_dict()
+            )
 
     def test_bubble_style(self):
         arg = {