From 686886383967e424842f083d2c79dbaaf60c8424 Mon Sep 17 00:00:00 2001 From: Eugene Krevenets Date: Sat, 21 Jan 2017 01:37:36 +0100 Subject: [PATCH 1/7] double text limit from 320 to 640 --- botstory/integrations/fb/validate.py | 4 ++-- botstory/integrations/fb/validate_test.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/botstory/integrations/fb/validate.py b/botstory/integrations/fb/validate.py index 27f22c1..3cdf5b3 100644 --- a/botstory/integrations/fb/validate.py +++ b/botstory/integrations/fb/validate.py @@ -42,8 +42,8 @@ def send_text_message(text, options): :param options: :return: """ - if len(text) > 320: - raise Invalid('send message text should not exceed 320 character limit') + if len(text) > 640: + raise Invalid('send message text should not exceed 640 character limit') if isinstance(options, list): if len(options) > 10: diff --git a/botstory/integrations/fb/validate_test.py b/botstory/integrations/fb/validate_test.py index b13799d..c369ed0 100644 --- a/botstory/integrations/fb/validate_test.py +++ b/botstory/integrations/fb/validate_test.py @@ -68,7 +68,7 @@ async def test_validate_persistent_menu(mocker, menu, invalid_message): @pytest.mark.parametrize('text,options,invalid_message', [ ('hi there!', None, False), - ('very long message ' * 20, None, 'send message text should not exceed 320 character limit'), + ('very long message ' * 40, None, 'send message text should not exceed 640 character limit'), ('short message', [{ 'content_type': 'text', From 21327a77da6a41301e2d35ce401de0fb92a7cb27 Mon Sep 17 00:00:00 2001 From: Eugene Krevenets Date: Sat, 21 Jan 2017 02:00:47 +0100 Subject: [PATCH 2/7] remove obsolte argument --- botstory/integrations/fb/messenger.py | 1 - 1 file changed, 1 deletion(-) diff --git a/botstory/integrations/fb/messenger.py b/botstory/integrations/fb/messenger.py index 1eda490..fd1ffd5 100644 --- a/botstory/integrations/fb/messenger.py +++ b/botstory/integrations/fb/messenger.py @@ -82,7 +82,6 @@ async def send_text_message(self, recipient, text, options=None): """ async send message to the facebook user (recipient) - :param session: :param recipient: :param text: :param options: From 56296891dab72a721d1e69251f2917be7a83f77c Mon Sep 17 00:00:00 2001 From: Eugene Krevenets Date: Wed, 25 Jan 2017 23:43:30 +0100 Subject: [PATCH 3/7] add special exception for breaking lenght message limits --- botstory/integrations/fb/messenger.py | 11 +++++++++++ botstory/integrations/fb/validate.py | 6 +++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/botstory/integrations/fb/messenger.py b/botstory/integrations/fb/messenger.py index fd1ffd5..935a223 100644 --- a/botstory/integrations/fb/messenger.py +++ b/botstory/integrations/fb/messenger.py @@ -91,6 +91,17 @@ async def send_text_message(self, recipient, text, options=None): try: validate.send_text_message(text, options) + except validate.ExceedLengthException as i: + # TODO: take first part of message show option `more` + # store last part until user press `more` + + # TODO: register dynamic `quick answer` handler + # with the rest of message + + # TODO: or handle it on application level? + # motivation: if we're working with dynamic data or endless + # we could just pass text. It should be generator + logger.warn(str(i)) except validate.Invalid as i: logger.warn(str(i)) diff --git a/botstory/integrations/fb/validate.py b/botstory/integrations/fb/validate.py index 3cdf5b3..a7f5441 100644 --- a/botstory/integrations/fb/validate.py +++ b/botstory/integrations/fb/validate.py @@ -2,6 +2,10 @@ class Invalid(BaseException): pass +class ExceedLengthException(Invalid): + pass + + def greeting_text(message): """ more: https://developers.facebook.com/docs/messenger-platform/thread-settings/greeting-text @@ -43,7 +47,7 @@ def send_text_message(text, options): :return: """ if len(text) > 640: - raise Invalid('send message text should not exceed 640 character limit') + raise ExceedLengthException('send message text should not exceed 640 character limit') if isinstance(options, list): if len(options) > 10: From 5aac5538a95ca13045d9c9e55d6b7e57eb076c2a Mon Sep 17 00:00:00 2001 From: Eugene Krevenets Date: Wed, 25 Jan 2017 23:48:54 +0100 Subject: [PATCH 4/7] rename options to quick_replies --- botstory/chat.py | 8 ++++---- botstory/integrations/fb/messenger.py | 12 ++++++------ botstory/integrations/fb/messenger_test.py | 2 +- botstory/integrations/fb/validate.py | 10 +++++----- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/botstory/chat.py b/botstory/chat.py index f217edc..a9ca179 100644 --- a/botstory/chat.py +++ b/botstory/chat.py @@ -10,18 +10,18 @@ class Chat: def __init__(self): self.interfaces = {} - async def ask(self, body, options=None, user=None): + async def ask(self, body, quick_replies=None, user=None): """ - simple ask with predefined options + simple ask with predefined quick replies :param body: - :param options: (optional) in form of + :param quick_replies: (optional) in form of {'title': , 'payload': } :param user: :return: """ await self.send_text_message_to_all_interfaces( - recipient=user, text=body, options=options) + recipient=user, text=body, quick_replies=quick_replies) return any.Any() # TODO: move to middlewares/location/location.py and make async diff --git a/botstory/integrations/fb/messenger.py b/botstory/integrations/fb/messenger.py index 935a223..d817aba 100644 --- a/botstory/integrations/fb/messenger.py +++ b/botstory/integrations/fb/messenger.py @@ -78,19 +78,19 @@ def add_users(self, users): logger.debug(users) self.users = users - async def send_text_message(self, recipient, text, options=None): + async def send_text_message(self, recipient, text, quick_replies=None): """ async send message to the facebook user (recipient) :param recipient: :param text: - :param options: + :param quick_replies: :return: """ try: - validate.send_text_message(text, options) + validate.send_text_message(text, quick_replies) except validate.ExceedLengthException as i: # TODO: take first part of message show option `more` # store last part until user press `more` @@ -105,14 +105,14 @@ async def send_text_message(self, recipient, text, options=None): except validate.Invalid as i: logger.warn(str(i)) - if not options: - options = [] + if not quick_replies: + quick_replies = [] message = { 'text': text, } - quick_replies = [{**reply, 'content_type': 'text'} for reply in options] + quick_replies = [{**reply, 'content_type': 'text'} for reply in quick_replies] if len(quick_replies) > 0: message['quick_replies'] = quick_replies diff --git a/botstory/integrations/fb/messenger_test.py b/botstory/integrations/fb/messenger_test.py index 4982047..e390935 100644 --- a/botstory/integrations/fb/messenger_test.py +++ b/botstory/integrations/fb/messenger_test.py @@ -31,7 +31,7 @@ async def test_send_text_message(): await story.start() await interface.send_text_message( - recipient=user, text='hi!', options=None + recipient=user, text='hi!', quick_replies=None ) mock_http.post.assert_called_with( diff --git a/botstory/integrations/fb/validate.py b/botstory/integrations/fb/validate.py index a7f5441..3805884 100644 --- a/botstory/integrations/fb/validate.py +++ b/botstory/integrations/fb/validate.py @@ -35,7 +35,7 @@ def persistent_menu(menu): raise Invalid('menu item payload should not exceed 1000 characters') -def send_text_message(text, options): +def send_text_message(text, quick_replies): """ more: https://developers.facebook.com/docs/messenger-platform/send-api-reference/text-message @@ -43,17 +43,17 @@ def send_text_message(text, options): https://developers.facebook.com/docs/messenger-platform/send-api-reference/quick-replies :param text: - :param options: + :param quick_replies: :return: """ if len(text) > 640: raise ExceedLengthException('send message text should not exceed 640 character limit') - if isinstance(options, list): - if len(options) > 10: + if isinstance(quick_replies, list): + if len(quick_replies) > 10: raise Invalid('send message quick replies should not exceed 10 limit') - for item in options: + for item in quick_replies: if len(item['title']) > 20: raise Invalid('send message quick replies title should not exceed 20 character limit') if 'content_type' not in item: From db20c477c0694b3487bbc049148cda6e00012d84 Mon Sep 17 00:00:00 2001 From: Eugene Krevenets Date: Thu, 26 Jan 2017 01:06:52 +0100 Subject: [PATCH 5/7] chat.say can has options --- botstory/chat.py | 4 ++-- botstory/chat_test.py | 1 + botstory/integrations/fb/messenger.py | 5 ++++- botstory/story.py | 4 ++-- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/botstory/chat.py b/botstory/chat.py index a9ca179..2ddc4ec 100644 --- a/botstory/chat.py +++ b/botstory/chat.py @@ -36,7 +36,7 @@ def ask_location(self, body, user): # 4 ask details once we not sure return [location.Any(), text.Any()] - async def say(self, body, user): + async def say(self, body, user, options): """ say something to user @@ -45,7 +45,7 @@ async def say(self, body, user): :return: """ return await self.send_text_message_to_all_interfaces( - recipient=user, text=body) + recipient=user, text=body, options=options) async def send_text_message_to_all_interfaces(self, *args, **kwargs): """ diff --git a/botstory/chat_test.py b/botstory/chat_test.py index 8b4b67f..5fa36ad 100644 --- a/botstory/chat_test.py +++ b/botstory/chat_test.py @@ -46,6 +46,7 @@ async def then(message): mock_interface.send_text_message.assert_called_once_with( recipient=user, text='Nice to see you!', + options=None, ) diff --git a/botstory/integrations/fb/messenger.py b/botstory/integrations/fb/messenger.py index d817aba..491051e 100644 --- a/botstory/integrations/fb/messenger.py +++ b/botstory/integrations/fb/messenger.py @@ -78,13 +78,16 @@ def add_users(self, users): logger.debug(users) self.users = users - async def send_text_message(self, recipient, text, quick_replies=None): + async def send_text_message(self, recipient, text, + quick_replies=None, + options=None): """ async send message to the facebook user (recipient) :param recipient: :param text: :param quick_replies: + :param options :return: """ diff --git a/botstory/story.py b/botstory/story.py index b70d8c1..2b32d69 100644 --- a/botstory/story.py +++ b/botstory/story.py @@ -62,8 +62,8 @@ def case(self, default=forking.Undefined, equal_to=forking.Undefined, match=fork async def ask(self, body, options=None, user=None): return await self.chat.ask(body, options, user) - async def say(self, body, user): - return await self.chat.say(body, user) + async def say(self, body, user, options=None): + return await self.chat.say(body, user, options) def use(self, middleware): """ From e11bfc55af9e31f946951fc4be4e3276c5120f3e Mon Sep 17 00:00:00 2001 From: Eugene Krevenets Date: Thu, 26 Jan 2017 01:18:15 +0100 Subject: [PATCH 6/7] could cut message if it exceed length limits --- botstory/integrations/fb/messenger.py | 3 ++ botstory/integrations/fb/messenger_test.py | 36 ++++++++++++++++++++++ botstory/integrations/fb/validate.py | 9 ++++-- 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/botstory/integrations/fb/messenger.py b/botstory/integrations/fb/messenger.py index 491051e..cb6a268 100644 --- a/botstory/integrations/fb/messenger.py +++ b/botstory/integrations/fb/messenger.py @@ -105,6 +105,9 @@ async def send_text_message(self, recipient, text, # motivation: if we're working with dynamic data or endless # we could just pass text. It should be generator logger.warn(str(i)) + + if options and options.get('overflow', None) == 'cut': + text = text[:i.limit] except validate.Invalid as i: logger.warn(str(i)) diff --git a/botstory/integrations/fb/messenger_test.py b/botstory/integrations/fb/messenger_test.py index e390935..0675ba4 100644 --- a/botstory/integrations/fb/messenger_test.py +++ b/botstory/integrations/fb/messenger_test.py @@ -49,6 +49,42 @@ async def test_send_text_message(): } ) +@pytest.mark.asyncio +async def test_truncate_long_message(): + user = utils.build_fake_user() + + global story + story = Story() + + interface = story.use(messenger.FBInterface(page_access_token='qwerty1')) + mock_http = story.use(mockhttp.MockHttpInterface()) + + await story.start() + + very_long_message = 'very_long_message' * 100 + await interface.send_text_message( + recipient=user, + text=very_long_message, + quick_replies=None, + options={ + 'overflow': 'cut' + } + ) + + mock_http.post.assert_called_with( + 'https://graph.facebook.com/v2.6/me/messages/', + params={ + 'access_token': 'qwerty1', + }, + json={ + 'message': { + 'text': very_long_message[:640], + }, + 'recipient': { + 'id': user['facebook_user_id'], + }, + } + ) @pytest.mark.asyncio async def test_integration(): diff --git a/botstory/integrations/fb/validate.py b/botstory/integrations/fb/validate.py index 3805884..fd67119 100644 --- a/botstory/integrations/fb/validate.py +++ b/botstory/integrations/fb/validate.py @@ -3,7 +3,9 @@ class Invalid(BaseException): class ExceedLengthException(Invalid): - pass + def __init__(self, *args, limit=None, **kwargs): + super().__init__(*args, **kwargs) + self.limit = limit def greeting_text(message): @@ -47,7 +49,10 @@ def send_text_message(text, quick_replies): :return: """ if len(text) > 640: - raise ExceedLengthException('send message text should not exceed 640 character limit') + raise ExceedLengthException( + 'send message text should not exceed 640 character limit', + limit=640, + ) if isinstance(quick_replies, list): if len(quick_replies) > 10: From d049a85fbce08679be54e4b1fa001687a330e37e Mon Sep 17 00:00:00 2001 From: Eugene Krevenets Date: Thu, 26 Jan 2017 01:27:18 +0100 Subject: [PATCH 7/7] trunkate with ellipsis by default --- botstory/integrations/fb/messenger.py | 2 ++ botstory/integrations/fb/messenger_test.py | 37 ++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/botstory/integrations/fb/messenger.py b/botstory/integrations/fb/messenger.py index cb6a268..2b42ea0 100644 --- a/botstory/integrations/fb/messenger.py +++ b/botstory/integrations/fb/messenger.py @@ -108,6 +108,8 @@ async def send_text_message(self, recipient, text, if options and options.get('overflow', None) == 'cut': text = text[:i.limit] + else: + text = text[:i.limit - 2] + '\u2026' except validate.Invalid as i: logger.warn(str(i)) diff --git a/botstory/integrations/fb/messenger_test.py b/botstory/integrations/fb/messenger_test.py index 0675ba4..4bd7ee8 100644 --- a/botstory/integrations/fb/messenger_test.py +++ b/botstory/integrations/fb/messenger_test.py @@ -49,6 +49,7 @@ async def test_send_text_message(): } ) + @pytest.mark.asyncio async def test_truncate_long_message(): user = utils.build_fake_user() @@ -86,6 +87,42 @@ async def test_truncate_long_message(): } ) + +@pytest.mark.asyncio +async def test_truncate_with_ellipsis_long_message_by_default(): + user = utils.build_fake_user() + + global story + story = Story() + + interface = story.use(messenger.FBInterface(page_access_token='qwerty1')) + mock_http = story.use(mockhttp.MockHttpInterface()) + + await story.start() + + very_long_message = 'very_long_message' * 100 + await interface.send_text_message( + recipient=user, + text=very_long_message, + quick_replies=None, + ) + + mock_http.post.assert_called_with( + 'https://graph.facebook.com/v2.6/me/messages/', + params={ + 'access_token': 'qwerty1', + }, + json={ + 'message': { + 'text': very_long_message[:638] + '\u2026', + }, + 'recipient': { + 'id': user['facebook_user_id'], + }, + } + ) + + @pytest.mark.asyncio async def test_integration(): user = utils.build_fake_user()