diff --git a/README.rst b/README.rst index b7deac724..0280f4505 100644 --- a/README.rst +++ b/README.rst @@ -145,7 +145,7 @@ get\_group\_member\_profile(self, group\_id, user\_id, timeout=None) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Gets the user profile of a member of a group that the bot is in. This can be -the user ID of a user who has not added the bot as a friend or has blocked +the user ID of a user who has not added the bot as a friend or has blocked the bot. https://devdocs.line.me/en/#get-group-room-member-profile @@ -577,6 +577,10 @@ Event - reply\_token - postback: Postback - data + - params: Params + - date + - time + - datetime - BeaconEvent - type - timestamp diff --git a/linebot/models/__init__.py b/linebot/models/__init__.py index fcc4dc17a..a25aabefa 100644 --- a/linebot/models/__init__.py +++ b/linebot/models/__init__.py @@ -31,6 +31,7 @@ PostbackEvent, BeaconEvent, Postback, + Params, Beacon, ) from .imagemap import ( # noqa @@ -79,6 +80,7 @@ PostbackTemplateAction, MessageTemplateAction, URITemplateAction, + DatetimePickerTemplateAction, ImageCarouselTemplate, ImageCarouselColumn, ) diff --git a/linebot/models/events.py b/linebot/models/events.py index 65711b89f..0e4463f6c 100644 --- a/linebot/models/events.py +++ b/linebot/models/events.py @@ -265,15 +265,54 @@ class Postback(Base): https://devdocs.line.me/en/#postback-event """ - def __init__(self, data=None, **kwargs): + def __init__(self, data=None, params=None, **kwargs): """__init__ method. - :param str data: + :param str data: Postback data + :param params: JSON object with the date and time + selected by a user through a datetime picker action. + Only returned for postback actions via the datetime picker. + :type params: T <= :py:class:`linebot.models.events.Params` :param kwargs: """ super(Postback, self).__init__(**kwargs) self.data = data + self.params = self.get_or_new_from_json_dict( + params, Params + ) + + +class Params(Base): + """Params. + + Object with the date and time selected by a user + through a datetime picker action. + The format for the full-date, time-hour, and time-minute + as shown below follow the RFC3339 protocol. + + https://devdocs.line.me/en/#postback-params-object + """ + + def __init__(self, date=None, time=None, datetime=None, **kwargs): + """__init__ method. + + :param str date: Date selected by user. + Only included in the date mode. + Format: full-date + :param str time: Time selected by the user. + Only included in the time mode. + Format: time-hour":"time-minute + :param str datetime: Date and time selected by the user. + Only included in the datetime mode. + Format: full-date"T"time-hour":"time-minute + :param kwargs: + """ + super(Params, self).__init__(**kwargs) + + self.date = date + self.time = time + self.datetime = datetime class Beacon(Base): diff --git a/linebot/models/template.py b/linebot/models/template.py index 671db7bf7..f569321fc 100644 --- a/linebot/models/template.py +++ b/linebot/models/template.py @@ -29,7 +29,8 @@ def _get_action(action): action, { 'postback': PostbackTemplateAction, 'message': MessageTemplateAction, - 'uri': URITemplateAction + 'uri': URITemplateAction, + 'datetimepicker': DatetimePickerTemplateAction, } ) return action_obj @@ -361,3 +362,43 @@ def __init__(self, label=None, uri=None, **kwargs): self.type = 'uri' self.label = label self.uri = uri + + +class DatetimePickerTemplateAction(TemplateAction): + """DatetimePickerTemplateAction. + + https://devdocs.line.me/en/#template-messages + + When this action is tapped, a postback event is returned via webhook + with the date and time selected by the user from the date and time selection dialog. + """ + + def __init__(self, label=None, data=None, mode=None, + initial=None, max=None, min=None, **kwargs): + """__init__ method. + + :param str label: Label for the action + Max: 20 characters + :param str data: String returned via webhook + in the postback.data property of the postback event + Max: 300 characters + :param str mode: Action mode + date: Pick date + time: Pick time + datetime: Pick date and time + :param str initial: Initial value of date or time + :param str max: Largest date or time value that can be selected. + Must be greater than the min value. + :param str min: Smallest date or time value that can be selected. + Must be less than the max value. + :param kwargs: + """ + super(DatetimePickerTemplateAction, self).__init__(**kwargs) + + self.type = 'datetimepicker' + self.label = label + self.data = data + self.mode = mode + self.initial = initial + self.max = max + self.min = min diff --git a/tests/api/test_send_template_message.py b/tests/api/test_send_template_message.py index d46d24f9a..9a92ed472 100644 --- a/tests/api/test_send_template_message.py +++ b/tests/api/test_send_template_message.py @@ -24,13 +24,17 @@ ) from linebot.models import ( TemplateSendMessage, ButtonsTemplate, - PostbackTemplateAction, MessageTemplateAction, URITemplateAction, + PostbackTemplateAction, MessageTemplateAction, + URITemplateAction, DatetimePickerTemplateAction, ConfirmTemplate, CarouselTemplate, CarouselColumn, ImageCarouselTemplate, ImageCarouselColumn ) class TestLineBotApi(unittest.TestCase): + + maxDiff = None + def setUp(self): self.tested = LineBotApi('channel_secret') @@ -161,6 +165,37 @@ def setUp(self): uri='http://example.com/2' ) ] + ), + CarouselColumn( + thumbnail_image_url='https://example.com' + '/item3.jpg', + title='this is menu3', text='description3', + actions=[ + DatetimePickerTemplateAction( + label="datetime picker date", + data="action=sell&itemid=2&mode=date", + mode="date", + initial="2013-04-01", + min="2011-06-23", + max="2017-09-08" + ), + DatetimePickerTemplateAction( + label="datetime picker time", + data="action=sell&itemid=2&mode=time", + mode="time", + initial="10:00", + min="00:00", + max="23:59" + ), + DatetimePickerTemplateAction( + label="datetime picker datetime", + data="action=sell&itemid=2&mode=datetime", + mode="datetime", + initial="2013-04-01T10:00", + min="2011-06-23T00:00", + max="2017-09-08T23:59" + ) + ] ) ] ) @@ -219,6 +254,41 @@ def setUp(self): "uri": "http://example.com/2" } ] + }, + { + "thumbnailImageUrl": + "https://example.com/item3.jpg", + "title": "this is menu3", + "text": "description3", + "actions": [ + { + "type": "datetimepicker", + "label": "datetime picker date", + "data": "action=sell&itemid=2&mode=date", + "mode": "date", + "initial": "2013-04-01", + "min": "2011-06-23", + "max": "2017-09-08" + }, + { + "type": "datetimepicker", + "label": "datetime picker time", + "data": "action=sell&itemid=2&mode=time", + "mode": "time", + "initial": "10:00", + "min": "00:00", + "max": "23:59" + }, + { + "type": "datetimepicker", + "label": "datetime picker datetime", + "data": "action=sell&itemid=2&mode=datetime", + "mode": "datetime", + "initial": "2013-04-01T10:00", + "min": "2011-06-23T00:00", + "max": "2017-09-08T23:59" + } + ] } ] } diff --git a/tests/test_webhook.py b/tests/test_webhook.py index 930a98b1b..f27756369 100644 --- a/tests/test_webhook.py +++ b/tests/test_webhook.py @@ -27,6 +27,7 @@ TextMessage, ImageMessage, VideoMessage, AudioMessage, LocationMessage, StickerMessage, SourceUser, SourceRoom, SourceGroup, + Params, ) @@ -195,6 +196,7 @@ def test_parse(self): self.assertEqual(events[10].source.user_id, 'U206d25c2ea6bd87c17655609a1c37cb8') self.assertEqual(events[10].source.sender_id, 'U206d25c2ea6bd87c17655609a1c37cb8') self.assertEqual(events[10].postback.data, 'action=buyItem&itemId=123123&color=red') + self.assertEqual(events[10].postback.params, None) # BeaconEvent, SourceUser self.assertIsInstance(events[11], BeaconEvent) @@ -254,6 +256,45 @@ def test_parse(self): self.assertEqual(events[14].message.type, 'text') self.assertEqual(events[14].message.text, 'Hello, world') + # PostbackEvent, SourceUser, with date params + self.assertIsInstance(events[15], PostbackEvent) + self.assertEqual(events[15].reply_token, 'nHuyWiB7yP5Zw52FIkcQobQuGDXCTA') + self.assertEqual(events[15].type, 'postback') + self.assertEqual(events[15].timestamp, 1462629479859) + self.assertIsInstance(events[15].source, SourceUser) + self.assertEqual(events[15].source.type, 'user') + self.assertEqual(events[15].source.user_id, 'U206d25c2ea6bd87c17655609a1c37cb8') + self.assertEqual(events[15].source.sender_id, 'U206d25c2ea6bd87c17655609a1c37cb8') + self.assertEqual(events[15].postback.data, 'action=buyItem&itemId=123123&color=red') + self.assertIsInstance(events[15].postback.params, Params) + self.assertEqual(events[15].postback.params.date, '2013-04-01') + + # PostbackEvent, SourceUser, with date params + self.assertIsInstance(events[16], PostbackEvent) + self.assertEqual(events[16].reply_token, 'nHuyWiB7yP5Zw52FIkcQobQuGDXCTA') + self.assertEqual(events[16].type, 'postback') + self.assertEqual(events[16].timestamp, 1462629479859) + self.assertIsInstance(events[16].source, SourceUser) + self.assertEqual(events[16].source.type, 'user') + self.assertEqual(events[16].source.user_id, 'U206d25c2ea6bd87c17655609a1c37cb8') + self.assertEqual(events[16].source.sender_id, 'U206d25c2ea6bd87c17655609a1c37cb8') + self.assertEqual(events[16].postback.data, 'action=buyItem&itemId=123123&color=red') + self.assertIsInstance(events[15].postback.params, Params) + self.assertEqual(events[16].postback.params.time, '10:00') + + # PostbackEvent, SourceUser, with date params + self.assertIsInstance(events[17], PostbackEvent) + self.assertEqual(events[17].reply_token, 'nHuyWiB7yP5Zw52FIkcQobQuGDXCTA') + self.assertEqual(events[17].type, 'postback') + self.assertEqual(events[17].timestamp, 1462629479859) + self.assertIsInstance(events[17].source, SourceUser) + self.assertEqual(events[17].source.type, 'user') + self.assertEqual(events[17].source.user_id, 'U206d25c2ea6bd87c17655609a1c37cb8') + self.assertEqual(events[17].source.sender_id, 'U206d25c2ea6bd87c17655609a1c37cb8') + self.assertEqual(events[17].postback.data, 'action=buyItem&itemId=123123&color=red') + self.assertIsInstance(events[15].postback.params, Params) + self.assertEqual(events[17].postback.params.datetime, '2013-04-01T10:00') + class TestWebhookHandler(unittest.TestCase): def setUp(self): @@ -321,6 +362,11 @@ def test_handler(self): self.assertEqual(self.calls[10], '6 postback') self.assertEqual(self.calls[11], '7 beacon') self.assertEqual(self.calls[12], '7 beacon') + self.assertEqual(self.calls[13], '1 message_text') + self.assertEqual(self.calls[14], '1 message_text') + self.assertEqual(self.calls[15], '6 postback') + self.assertEqual(self.calls[16], '6 postback') + self.assertEqual(self.calls[17], '6 postback') if __name__ == '__main__': diff --git a/tests/text/webhook.json b/tests/text/webhook.json index cad941556..7a0bea8dd 100644 --- a/tests/text/webhook.json +++ b/tests/text/webhook.json @@ -187,6 +187,51 @@ "type": "text", "text": "Hello, world" } + }, + { + "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", + "type": "postback", + "timestamp": 1462629479859, + "source": { + "type": "user", + "userId": "U206d25c2ea6bd87c17655609a1c37cb8" + }, + "postback": { + "data": "action=buyItem&itemId=123123&color=red", + "params": { + "date": "2013-04-01" + } + } + }, + { + "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", + "type": "postback", + "timestamp": 1462629479859, + "source": { + "type": "user", + "userId": "U206d25c2ea6bd87c17655609a1c37cb8" + }, + "postback": { + "data": "action=buyItem&itemId=123123&color=red", + "params": { + "time": "10:00" + } + } + }, + { + "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", + "type": "postback", + "timestamp": 1462629479859, + "source": { + "type": "user", + "userId": "U206d25c2ea6bd87c17655609a1c37cb8" + }, + "postback": { + "data": "action=buyItem&itemId=123123&color=red", + "params": { + "datetime": "2013-04-01T10:00" + } + } } ] }