Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
kimwz committed Aug 26, 2016
2 parents ed11050 + 649d6ae commit 59f1a36
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 25 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -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
-----

Expand Down
32 changes: 31 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)


Expand Down Expand Up @@ -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
Expand All @@ -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),
Expand All @@ -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
Expand Down Expand Up @@ -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',
Expand All @@ -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

Expand Down
8 changes: 7 additions & 1 deletion example/fbpage.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
from fbmq import Page
from config import CONFIG

page = Page(CONFIG['FACEBOOK_TOKEN'])
page = Page(CONFIG['FACEBOOK_TOKEN'])


@page.after_send
def after_send(payload, response):
print('AFTER_SEND : ' + payload.to_json())
print('RESPONSE : ' + response.text)
8 changes: 6 additions & 2 deletions example/messenger.py
Original file line number Diff line number Diff line change
@@ -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 = {}
Expand Down Expand Up @@ -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):
Expand Down
4 changes: 2 additions & 2 deletions fbmq/__init__.py
Original file line number Diff line number Diff line change
@@ -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
51 changes: 39 additions & 12 deletions fbmq/fbmq.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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:
Expand Down Expand Up @@ -96,41 +111,50 @@ 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

payload = Payload(recipient=Recipient(id=recipient_id),
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)

Expand Down Expand Up @@ -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:
Expand Down
7 changes: 6 additions & 1 deletion fbmq/payload.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
12 changes: 6 additions & 6 deletions tests/test_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 = """
Expand All @@ -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):
Expand All @@ -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 = """
Expand All @@ -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):
Expand Down

0 comments on commit 59f1a36

Please sign in to comment.