diff --git a/CHANGELOG b/CHANGELOG index 1a3be05..8a82831 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,14 @@ FBMQ Changelog Latest ------ +1.7.0 +----- + +* Fix, trigger message_handler, postback_handler even if custom callbacks are used +* Feature : Add a callback function in page.send +* Feature : Add a page option 'after_send' (support decorator @page.after_send) +* Feature : Support notification type (e.g page.send(notification_type=NotificationType.SILENT_PUSH)) + 1.6.0 ----- diff --git a/README.md b/README.md index 8f86f75..a769b50 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,9 @@ Facebook messenger platform api full features are supported * [button callback](#button-callback) * [generic](#template--generic) * [receipt](#template--receipt) + * [options](#options) + * [notification type](#notification-type) + * [callback](#callback) * [Example](#example) @@ -53,6 +56,10 @@ def message_handler(event): message = event['message'] page.send(sender_id, "thank you! your message is '%s'" % message.get('text')) + +@page.after_send +def after_send(payload, response): + print("complete") ``` ### handlers @@ -72,8 +79,12 @@ A spec in detail - https://developers.facebook.com/docs/messenger-platform/webho `@page.handle_account_linking` - This callback will occur when the Linked Account or Unlink Account call-to-action have been tapped. +`@page.after_send` - This callback will occur when page.send function has been called. + #### if you don't need a decorator ```python +page = fbmq.Page(PAGE_ACCESS_TOKEN, after_send=after_send) + @app.route('/webhook', methods=['POST']) def webhook(): page.handle_webhook(request.get_data(as_text=True), @@ -85,6 +96,9 @@ def message_handler(event): message = event['message'] page.send(sender_id, "thank you! your message is '%s'" % message.get('text')) + +def after_send(payload, response): + print("complete") ``` # Send a message @@ -247,7 +261,7 @@ page.send(recipient_id, Template.Generic([ adjustment = Template.ReceiptAdjustment(name="New Customer Discount", amount=-50) - fbpage.send(recipient_id, Template.Receipt(recipient_name='Peter Chang', + page.send(recipient_id, Template.Receipt(recipient_name='Peter Chang', order_number='1234', currency='USD', payment_method='Visa 1234', @@ -257,8 +271,24 @@ page.send(recipient_id, Template.Generic([ summary=summary, adjustments=[adjustment])) ``` +### Options + +##### notification type +support notification_type as a option +`NotificationType.REGULAR (default)`, `NotificationType.SILENT_PUSH`, `NotificationType.NO_PUSH` +``` +page.send(recipient_id, 'hello', notification_type=NotificationType.NO_PUSH) +``` +##### callback +you can set a callback function to each `page.send` +``` +def callback(payload, response): + print('response : ' + response.text) + +page.send(recipient_id, 'hello', callback=callback) +``` # Example diff --git a/example/fbpage.py b/example/fbpage.py index dcac706..b57313a 100644 --- a/example/fbpage.py +++ b/example/fbpage.py @@ -1,4 +1,10 @@ from fbmq import Page from config import CONFIG -page = Page(CONFIG['FACEBOOK_TOKEN']) \ No newline at end of file +page = Page(CONFIG['FACEBOOK_TOKEN']) + + +@page.after_send +def after_send(payload, response): + print('AFTER_SEND : ' + payload.to_json()) + print('RESPONSE : ' + response.text) \ No newline at end of file diff --git a/example/messenger.py b/example/messenger.py index 6e37c84..5e6d8e6 100644 --- a/example/messenger.py +++ b/example/messenger.py @@ -1,6 +1,6 @@ import json from config import CONFIG -from fbmq import Attachment, Template, QuickReply +from fbmq import Attachment, Template, QuickReply, NotificationType from fbpage import page USER_SEQ = {} @@ -136,7 +136,11 @@ def send_message(recipient_id, text): if text in special_keywords: special_keywords[text](recipient_id) else: - page.send(recipient_id, text) + page.send(recipient_id, text, callback=send_text_callback, notification_type=NotificationType.REGULAR) + + +def send_text_callback(payload, response): + print("SEND CALLBACK") def send_image(recipient): diff --git a/fbmq/__init__.py b/fbmq/__init__.py index f56bba1..9d9fd2d 100644 --- a/fbmq/__init__.py +++ b/fbmq/__init__.py @@ -1,5 +1,5 @@ -__version__ = '1.6.0' +__version__ = '1.7.0' -from .fbmq import QuickReply, Page +from .fbmq import * from . import attachment as Attachment from . import template as Template \ No newline at end of file diff --git a/fbmq/fbmq.py b/fbmq/fbmq.py index 6ef8d7b..b73330e 100644 --- a/fbmq/fbmq.py +++ b/fbmq/fbmq.py @@ -5,9 +5,23 @@ from .payload import * +# I agree with him : http://stackoverflow.com/a/36937/3843242 +class NotificationType: + REGULAR = 'REGULAR' + SILENT_PUSH = 'SILENT_PUSH' + NO_PUSH = 'NO_PUSH' + + +class SenderAction: + TYPING_ON='typing_on' + TYPING_OFF='typing_off' + MARK_SEEN='mark_seen' + + class Page(object): - def __init__(self, page_access_token): + def __init__(self, page_access_token, **options): self.page_access_token = page_access_token + self._after_send = options.pop('after_send', None) # webhook_handlers contains optin, message, echo, delivery, postback, read, account_linking. # these are only set by decorators @@ -19,6 +33,8 @@ def __init__(self, page_access_token): _quick_reply_callbacks_key_regex = {} _button_callbacks_key_regex = {} + _after_send = None + def _call_handler(self, name, func, *args, **kwargs): if func is not None: func(*args, **kwargs) @@ -45,17 +61,16 @@ def handle_webhook(self, payload, optin=None, message=None, echo=None, delivery= elif 'message' in event: if event.get("message", {}).get("is_echo"): self._call_handler('echo', echo, event) - elif self.is_quick_reply(event) and self.has_quick_reply_callback(event): - self.call_quick_reply_callback(event) else: self._call_handler('message', message, event) + if self.is_quick_reply(event) and self.has_quick_reply_callback(event): + self.call_quick_reply_callback(event) elif 'delivery' in event: self._call_handler('delivery', delivery, event) elif 'postback' in event: + self._call_handler('postback', postback, event) if self.has_postback_callback(event): self.call_postback_callback(event) - else: - self._call_handler('postback', postback, event) elif 'read' in event: self._call_handler('read', read, event) elif 'account_linking' in event: @@ -96,15 +111,23 @@ def _fetch_page_info(self): self._page_id = data['id'] self._page_name = data['name'] - def _send(self, payload): + def _send(self, payload, callback=None): r = requests.post("https://graph.facebook.com/v2.6/me/messages", params={"access_token": self.page_access_token}, data=payload.to_json(), headers={'Content-type': 'application/json'}) + if r.status_code != requests.codes.ok: print(r.text) - def send(self, recipient_id, message, quick_replies=None, metadata=None): + if self._after_send is not None: + self._after_send(payload=payload, response=r) + + if callback is not None: + callback(payload=payload, response=r) + + def send(self, recipient_id, message, quick_replies=None, metadata=None, + notification_type=None, callback=None): text = message if isinstance(message, str) else None attachment = message if not isinstance(message, str) else None @@ -112,25 +135,26 @@ def send(self, recipient_id, message, quick_replies=None, metadata=None): message=Message(text=text, attachment=attachment, quick_replies=quick_replies, - metadata=metadata)) + metadata=metadata), + notification_type=notification_type) - self._send(payload) + self._send(payload, callback=callback) def typing_on(self, recipient_id): payload = Payload(recipient=Recipient(id=recipient_id), - sender_action='typing_on') + sender_action=SenderAction.TYPING_ON) self._send(payload) def typing_off(self, recipient_id): payload = Payload(recipient=Recipient(id=recipient_id), - sender_action='typing_off') + sender_action=SenderAction.TYPING_OFF) self._send(payload) def mark_seen(self, recipient_id): payload = Payload(recipient=Recipient(id=recipient_id), - sender_action='mark_seen') + sender_action=SenderAction.MARK_SEEN) self._send(payload) @@ -159,6 +183,9 @@ def handle_read(self, func): def handle_account_linking(self, func): self._webhook_handlers['account_linking'] = func + def after_send(self, func): + self._after_send = func + def callback_quick_reply(self, payloads=None): def wrapper(func): if payloads is None: diff --git a/fbmq/payload.py b/fbmq/payload.py index b8cf70c..f565416 100644 --- a/fbmq/payload.py +++ b/fbmq/payload.py @@ -6,9 +6,14 @@ def __init__(self, recipient, message=None, sender_action=None, notification_typ self.recipient = recipient self.message = message if sender_action is not None and sender_action not in ['typing_off', 'typing_on', 'mark_seen']: - raise ValueError('invalud sender_action') + raise ValueError('invalid sender_action : it must be one of "typing_off","typing_on","mark_seen"') self.sender_action = sender_action + + if notification_type is not None \ + and notification_type not in ['REGULAR', 'SILENT_PUSH', 'NO_PUSH']: + raise ValueError('invalid notification_type : it must be one of "REGULAR","SILENT_PUSH","NO_PUSH"') + self.notification_type = notification_type def to_json(self): diff --git a/tests/test_page.py b/tests/test_page.py index 199f14b..e47ee54 100644 --- a/tests/test_page.py +++ b/tests/test_page.py @@ -14,14 +14,14 @@ def setUp(self): self.page._fetch_page_info = mock.MagicMock() def test_send(self): - self.page.send(12345, "hello world", quick_replies=[{'title': 'Yes', 'payload': 'YES'}]) + self.page.send(12345, "hello world", quick_replies=[{'title': 'Yes', 'payload': 'YES'}], callback=1) self.page._send.assert_called_once_with('{"message": {"attachment": null, "metadata": null, ' '"quick_replies": ' '[{"content_type": "text", "payload": "YES", "title": "Yes"}], ' '"text": "hello world"},' ' "notification_type": null, ' '"recipient": {"id": 12345, "phone_number": null}, ' - '"sender_action": null}') + '"sender_action": null}', callback=1) def test_typingon(self): self.page.typing_on(1004) @@ -265,7 +265,7 @@ def button_callback(payload, event): self.page.handle_webhook(payload, postback=handler1) - self.assertEquals(0, counter1.call_count) + self.assertEquals(1, counter1.call_count) self.assertEquals(1, counter2.call_count) payload = """ @@ -276,7 +276,7 @@ def button_callback(payload, event): }]} """ self.page.handle_webhook(payload, postback=handler1) - self.assertEquals(1, counter1.call_count) + self.assertEquals(2, counter1.call_count) self.assertEquals(1, counter2.call_count) def test_handle_webhook_quickreply_callback(self): @@ -299,7 +299,7 @@ def button_callback(payload, event): self.page.handle_webhook(payload, postback=handler1) - self.assertEquals(0, counter1.call_count) + self.assertEquals(1, counter1.call_count) self.assertEquals(1, counter2.call_count) payload = """ @@ -309,7 +309,7 @@ def button_callback(payload, event): "message":{"quick_reply":{"payload":"PICK_COMEDY"},"mid":"mid.1472028637817:ae2763cc036a664b43","seq":834,"text":"Action"}}]}]} """ self.page.handle_webhook(payload, postback=handler1) - self.assertEquals(1, counter1.call_count) + self.assertEquals(2, counter1.call_count) self.assertEquals(1, counter2.call_count) def test_callback_regex_pattern(self):