Skip to content

Commit

Permalink
Merge pull request #120 from botstory/feature/track-facebook-limits
Browse files Browse the repository at this point in the history
Feature/track facebook limits
  • Loading branch information
hyzhak committed Jan 26, 2017
2 parents 6246b16 + d049a85 commit 4f507d9
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 24 deletions.
12 changes: 6 additions & 6 deletions botstory/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -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': <message>, 'payload': <any json>}
: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
Expand All @@ -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
Expand All @@ -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):
"""
Expand Down
1 change: 1 addition & 0 deletions botstory/chat_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)


Expand Down
32 changes: 25 additions & 7 deletions botstory/integrations/fb/messenger.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,31 +78,49 @@ 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,
options=None):
"""
async send message to the facebook user (recipient)
:param session:
:param recipient:
:param text:
:param options:
:param quick_replies:
:param options
: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`

# 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))

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))

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

Expand Down
75 changes: 74 additions & 1 deletion botstory/integrations/fb/messenger_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -50,6 +50,79 @@ 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_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()
Expand Down
23 changes: 16 additions & 7 deletions botstory/integrations/fb/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ class Invalid(BaseException):
pass


class ExceedLengthException(Invalid):
def __init__(self, *args, limit=None, **kwargs):
super().__init__(*args, **kwargs)
self.limit = limit


def greeting_text(message):
"""
more: https://developers.facebook.com/docs/messenger-platform/thread-settings/greeting-text
Expand Down Expand Up @@ -31,25 +37,28 @@ 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
and
https://developers.facebook.com/docs/messenger-platform/send-api-reference/quick-replies
:param text:
:param options:
:param quick_replies:
:return:
"""
if len(text) > 320:
raise Invalid('send message text should not exceed 320 character limit')
if len(text) > 640:
raise ExceedLengthException(
'send message text should not exceed 640 character limit',
limit=640,
)

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:
Expand Down
2 changes: 1 addition & 1 deletion botstory/integrations/fb/validate_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
4 changes: 2 additions & 2 deletions botstory/story.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand Down

0 comments on commit 4f507d9

Please sign in to comment.