├── .gitignore ├── .travis.yml ├── CONTRIBUTORS.md ├── LICENSE ├── README.md ├── examples └── echo_bot.py ├── pymessenger ├── __init__.py ├── bot.py └── utils.py ├── requirements.txt ├── setup.cfg ├── setup.py └── test ├── __init__.py └── bot_test.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | *.swn 4 | dist/ 5 | test.py 6 | 7 | .idea/ 8 | 9 | # Created by https://www.gitignore.io/api/python 10 | 11 | ### Python ### 12 | # Byte-compiled / optimized / DLL files 13 | __pycache__ 14 | *.py[cod] 15 | *$py.class 16 | 17 | # C extensions 18 | *.so 19 | 20 | # Distribution / packaging 21 | .Python 22 | env/ 23 | build/ 24 | develop-eggs/ 25 | dist/ 26 | downloads/ 27 | eggs/ 28 | .eggs/ 29 | lib/ 30 | lib64/ 31 | parts/ 32 | sdist/ 33 | var/ 34 | *.egg-info/ 35 | .installed.cfg 36 | *.egg 37 | 38 | # PyInstaller 39 | # Usually these files are written by a python script from a template 40 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 41 | *.manifest 42 | *.spec 43 | 44 | # Installer logs 45 | pip-log.txt 46 | pip-delete-this-directory.txt 47 | 48 | # Unit test / coverage reports 49 | htmlcov/ 50 | .tox/ 51 | .coverage 52 | .coverage.* 53 | .cache 54 | nosetests.xml 55 | coverage.xml 56 | *,cover 57 | .hypothesis/ 58 | 59 | # Translations 60 | *.mo 61 | *.pot 62 | 63 | # Django stuff: 64 | *.log 65 | local_settings.py 66 | 67 | # Flask instance folder 68 | instance/ 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # IPython Notebook 80 | .ipynb_checkpoints 81 | 82 | # pyenv 83 | .python-version 84 | 85 | # celery beat schedule file 86 | celerybeat-schedule 87 | 88 | # dotenv 89 | .env 90 | 91 | # virtualenv 92 | venv/ 93 | ENV/ 94 | 95 | # Spyder project settings 96 | .spyderproject 97 | 98 | # Rope project settings 99 | .ropeproject 100 | 101 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - '3.5' 4 | install: pip install -r requirements.txt 5 | script: py.test 6 | env: 7 | global: 8 | - secure: k2CJPjd9ejNNAGpS9j71/cICFjD8K2v6BKUokjR02l80ghmeGuksQDTxeMuGQjQ2f46S9V5mpv73griKO5O6zTlYS0PciLVGqh/DmVhEaFmCMWEPmnHwYXAbbOz4ZlzahYoku8rkALjKuW4eqSXjgIYhH6AynFbw4Qfkow1gGCK6+hSTSJMeMfwY8if2nAj5N4jLY+GkolD9r2Vtdkh6F22RE+8bm27uzMFDngX7pplhByjkRkP4Cp5VPg+oAbt7A/ogyvwz4gEjBo0jD/zx4P+tXtgIp6FZfvzHqDrHUH/NhhU0UcVZfoG6MaSfEsz7zJuFNdFfBjZlOFIrp8ehdrBptkAAt4V+7l4HyWhqjQdj2BgY/2jgGtn2DpikNY4rnAR7Ld3FzklxICDWpEHY/QD0AessVCMeR6KvhePWYnSSFKNA6pQ2dAyQCccT+X0k6RPA3v4MbrzTL3iIk4XLR0Eb3r8Tnt29VFRNHPY0akkLv0TI4gKZrFx2ofUpjW5th1+Fk7g8QxdPT6iynR3eJSuLFD/9O014dDMvbZkKTMpYKaGAyatbpbc7tB3NrCglLF8y0fV3yaAXENb6gIH9zYqZVKGSPPXbaj1DU1YvaiycqvsyJdRggJpu/p2h71LSBZSFqs/mhArUBnrb2ZeBD/r0mtqu9CIrIuWdSYt0haQ= 9 | - secure: blCr8zAOjJhNNaLXN9fDa4il+T3SHpwmuat8yQrVQdfyKGs2QlePDrhhVwLe9JjOBq1AaRUemgaZdtk2qdXY3kVXH40VAOgIHxhSCAvVitzTN5WBsL2Jk9DbV+W5fwhbGxTYk9aFoZHYAzmTwcakIQ4iBw3ZxCoz8jIaQVHo3G1PI05gNoHsoghePvdDXcVxVa5PdklbSm+iZ2E1xbTEmXebfmLk/9BfiC3NdlIkX0HEzRyOioNVmgzvurrIScDFo/gBowAEGrYvobe3kkr4t8xqeAly/ryHwHE82H63aNcF1kAT+n5w4Ps+QsJP8EUCMXeXKfFNZD+8Rh6dyF5OZRuDyrtBhIsllV8SkBApj+gkxWuVcxfKi5y3UH75pFsD9mNA9kqIriGsvEi8+HMfHJWWBx/DM38hFzKwn3Tge8QVbRXpY0Tz5utgnkb1yDej4bxUwFR115j1Im5+rpZE37GjVXC9FAuQ+6Wzp1vzV/NrRofLEQ3jT6nfW6biBl+WbhqBHi5/jAj1Tv25b4sBxRpd1C2FpJg/WQ7ZcMF1xlSnTf2SKf2ZlBrFxRcAG2t4KZdohna6vfdreOpXq4cbB98JSa9jxDTWl/6rEKQVqAL0E8MZ5oWPw+r3aWl6dxCHltNy9j8cEtBJTtOgmCW0Z7Lc8TMAiBA/tmu7A79nP9o= 10 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | Special thanks to the following people who has contributed to this project! 2 | 3 | * [tbsf](http://github.com/tbsf) 4 | * [h4ck3rk3y](http://github.com/h4ck3rk3y) 5 | * [bildzeitung](https://github.com/bildzeitung) 6 | * [Cretezy](https://github.com/Cretezy) 7 | * [Onyenanu](https://github.com/madewithkode) 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 David Chua 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pymessenger [![Build Status](https://travis-ci.org/davidchua/pymessenger.svg?branch=master)](https://travis-ci.org/davidchua/pymessenger) 2 | 3 | Python Wrapper for [Facebook Messenger Platform](https://developers.facebook.com/docs/messenger-platform). 4 | 5 | __Disclaimer__: This wrapper is __NOT__ an official wrapper and do not attempt to represent Facebook in anyway. 6 | 7 | ### About 8 | 9 | This wrapper has the following functions: 10 | 11 | * send_text_message(recipient_id, message) 12 | * send_message(recipient_id, message) 13 | * send_generic_message(recipient_id, elements) 14 | * send_button_message(recipient_id, text, buttons) 15 | * send_attachment(recipient_id, attachment_type, attachment_path) 16 | * send_attachment_url(recipient_id, attachment_type, attachment_url) 17 | * send_image(recipient_id, image_path) 18 | * send_image_url(recipient_id, image_url) 19 | * send_audio(recipient_id, audio_path) 20 | * send_audio_url(recipient_id, audio_url) 21 | * send_video(recipient_id, video_path) 22 | * send_video_url(recipient_id, video_url) 23 | * send_file(recipient_id, file_path) 24 | * send_file_url(recipient_id, file_url) 25 | * send_action(recipient_id, action) 26 | * send_raw(payload) 27 | * get_user_info(recipient_id) 28 | * set_get_started(gs_obj) 29 | * set_persistent_menu(pm_obj) 30 | * remove_get_started() 31 | * remove_persistent_menu() 32 | 33 | You can see the code/documentation for there in [bot.py](pymessenger/bot.py). 34 | 35 | The functions return the full JSON body of the actual API call to Facebook. 36 | 37 | ### Register for an Access Token 38 | 39 | You'll need to setup a [Facebook App](https://developers.facebook.com/apps/), Facebook Page, get the Page Access Token and link the App to the Page before you can really start to use the Send/Receive service. 40 | 41 | [This quickstart guide should help](https://developers.facebook.com/docs/messenger-platform/quickstart) 42 | 43 | ### Installation 44 | 45 | ```bash 46 | pip install pymessenger 47 | ``` 48 | 49 | ### Usage 50 | 51 | ```python 52 | from pymessenger.bot import Bot 53 | 54 | bot = Bot(, [optional: app_secret]) 55 | bot.send_text_message(recipient_id, message) 56 | ``` 57 | 58 | __Note__: From Facebook regarding User IDs 59 | 60 | > These ids are page-scoped. These ids differ from those returned from Facebook Login apps which are app-scoped. You must use ids retrieved from a Messenger integration for this page in order to function properly. 61 | 62 | > If `app_secret` is initialized, an app_secret_proof will be generated and send with every request. 63 | > Appsecret Proofs helps further secure your client access tokens. You can find out more on the [Facebook Docs](https://developers.facebook.com/docs/graph-api/securing-requests#appsecret_proof) 64 | 65 | 66 | ##### Sending a generic template message: 67 | 68 | > [Generic Template Messages](https://developers.facebook.com/docs/messenger-platform/implementation#receive_message) allows you to add cool elements like images, text all in a single bubble. 69 | 70 | 71 | ```python 72 | from pymessenger.bot import Bot 73 | bot = Bot() 74 | elements = [] 75 | element = Element(title="test", image_url="", subtitle="subtitle", item_url="http://arsenal.com") 76 | elements.append(element) 77 | 78 | bot.send_generic_message(recipient_id, elements) 79 | ``` 80 | 81 | Output: 82 | 83 | ![Generic Bot Output](https://cloud.githubusercontent.com/assets/68039/14519266/4c7033b2-0250-11e6-81a3-f85f3809d86c.png) 84 | 85 | ##### Sending an image/video/file using an URL: 86 | 87 | ```python 88 | from pymessenger.bot import Bot 89 | bot = Bot() 90 | image_url = "http://url/to/image.png" 91 | bot.send_image_url(recipient_id, image_url) 92 | ``` 93 | 94 | ### Todo 95 | 96 | * Structured Messages 97 | * Receipt Messages 98 | * Quick Replies 99 | * Airlines 100 | * Tests! 101 | 102 | ### Example 103 | 104 | ![Screenshot of Echo Facebook Bot](https://cloud.githubusercontent.com/assets/68039/14516627/905c84ae-0237-11e6-918e-2c2ae9352f7d.png) 105 | 106 | You can find an example of an Echo Facebook Bot in ```examples/``` 107 | 108 | 109 | -------------------------------------------------------------------------------- /examples/echo_bot.py: -------------------------------------------------------------------------------- 1 | """ 2 | This bot listens to port 5002 for incoming connections from Facebook. It takes 3 | in any messages that the bot receives and echos it back. 4 | """ 5 | from flask import Flask, request 6 | from pymessenger.bot import Bot 7 | 8 | app = Flask(__name__) 9 | 10 | ACCESS_TOKEN = "" 11 | VERIFY_TOKEN = "" 12 | bot = Bot(ACCESS_TOKEN) 13 | 14 | 15 | @app.route("/", methods=['GET', 'POST']) 16 | def hello(): 17 | if request.method == 'GET': 18 | if request.args.get("hub.verify_token") == VERIFY_TOKEN: 19 | return request.args.get("hub.challenge") 20 | else: 21 | return 'Invalid verification token' 22 | 23 | if request.method == 'POST': 24 | output = request.get_json() 25 | for event in output['entry']: 26 | messaging = event['messaging'] 27 | for x in messaging: 28 | if x.get('message'): 29 | recipient_id = x['sender']['id'] 30 | if x['message'].get('text'): 31 | message = x['message']['text'] 32 | bot.send_text_message(recipient_id, message) 33 | if x['message'].get('attachments'): 34 | for att in x['message'].get('attachments'): 35 | bot.send_attachment_url(recipient_id, att['type'], att['payload']['url']) 36 | else: 37 | pass 38 | return "Success" 39 | 40 | 41 | if __name__ == "__main__": 42 | app.run(port=5002, debug=True) 43 | -------------------------------------------------------------------------------- /pymessenger/__init__.py: -------------------------------------------------------------------------------- 1 | import json 2 | import six 3 | 4 | from .bot import Bot 5 | 6 | 7 | class Element(dict): 8 | __acceptable_keys = ['title', 'item_url', 'image_url', 'subtitle', 'buttons'] 9 | 10 | def __init__(self, *args, **kwargs): 11 | if six.PY2: 12 | kwargs = {k: v for k, v in kwargs.iteritems() if k in self.__acceptable_keys} 13 | else: 14 | kwargs = {k: v for k, v in kwargs.items() if k in self.__acceptable_keys} 15 | super(Element, self).__init__(*args, **kwargs) 16 | 17 | def to_json(self): 18 | return json.dumps({k: v for k, v in self.iteritems() if k in self.__acceptable_keys}) 19 | 20 | 21 | class Button(dict): 22 | # TODO: Decide if this should do more 23 | pass 24 | 25 | # class Receipt: 26 | # pass 27 | -------------------------------------------------------------------------------- /pymessenger/bot.py: -------------------------------------------------------------------------------- 1 | import os 2 | from enum import Enum 3 | 4 | import requests 5 | from requests_toolbelt import MultipartEncoder 6 | 7 | from pymessenger import utils 8 | 9 | DEFAULT_API_VERSION = 2.6 10 | 11 | 12 | class NotificationType(Enum): 13 | regular = "REGULAR" 14 | silent_push = "SILENT_PUSH" 15 | no_push = "NO_PUSH" 16 | 17 | 18 | class Bot: 19 | def __init__(self, access_token, **kwargs): 20 | """ 21 | @required: 22 | access_token 23 | @optional: 24 | api_version 25 | app_secret 26 | """ 27 | 28 | self.api_version = kwargs.get('api_version') or DEFAULT_API_VERSION 29 | self.app_secret = kwargs.get('app_secret') 30 | self.graph_url = 'https://graph.facebook.com/v{0}'.format(self.api_version) 31 | self.access_token = access_token 32 | 33 | @property 34 | def auth_args(self): 35 | if not hasattr(self, '_auth_args'): 36 | auth = { 37 | 'access_token': self.access_token 38 | } 39 | if self.app_secret is not None: 40 | appsecret_proof = utils.generate_appsecret_proof(self.access_token, self.app_secret) 41 | auth['appsecret_proof'] = appsecret_proof 42 | self._auth_args = auth 43 | return self._auth_args 44 | 45 | def send_recipient(self, recipient_id, payload, notification_type=NotificationType.regular): 46 | payload['recipient'] = { 47 | 'id': recipient_id 48 | } 49 | payload['notification_type'] = notification_type.value 50 | return self.send_raw(payload) 51 | 52 | def send_message(self, recipient_id, message, notification_type=NotificationType.regular): 53 | return self.send_recipient(recipient_id, { 54 | 'message': message 55 | }, notification_type) 56 | 57 | def send_attachment(self, recipient_id, attachment_type, attachment_path, 58 | notification_type=NotificationType.regular): 59 | """Send an attachment to the specified recipient using local path. 60 | Input: 61 | recipient_id: recipient id to send to 62 | attachment_type: type of attachment (image, video, audio, file) 63 | attachment_path: Path of attachment 64 | Output: 65 | Response from API as 66 | """ 67 | payload = { 68 | 'recipient': { 69 | { 70 | 'id': recipient_id 71 | } 72 | }, 73 | 'notification_type': notification_type, 74 | 'message': { 75 | { 76 | 'attachment': { 77 | 'type': attachment_type, 78 | 'payload': {} 79 | } 80 | } 81 | }, 82 | 'filedata': (os.path.basename(attachment_path), open(attachment_path, 'rb')) 83 | } 84 | multipart_data = MultipartEncoder(payload) 85 | multipart_header = { 86 | 'Content-Type': multipart_data.content_type 87 | } 88 | return requests.post(self.graph_url, data=multipart_data, 89 | params=self.auth_args, headers=multipart_header).json() 90 | 91 | def send_attachment_url(self, recipient_id, attachment_type, attachment_url, 92 | notification_type=NotificationType.regular): 93 | """Send an attachment to the specified recipient using URL. 94 | Input: 95 | recipient_id: recipient id to send to 96 | attachment_type: type of attachment (image, video, audio, file) 97 | attachment_url: URL of attachment 98 | Output: 99 | Response from API as 100 | """ 101 | return self.send_message(recipient_id, { 102 | 'attachment': { 103 | 'type': attachment_type, 104 | 'payload': { 105 | 'url': attachment_url 106 | } 107 | } 108 | }, notification_type) 109 | 110 | def send_text_message(self, recipient_id, message, notification_type=NotificationType.regular): 111 | """Send text messages to the specified recipient. 112 | https://developers.facebook.com/docs/messenger-platform/send-api-reference/text-message 113 | Input: 114 | recipient_id: recipient id to send to 115 | message: message to send 116 | Output: 117 | Response from API as 118 | """ 119 | return self.send_message(recipient_id, { 120 | 'text': message 121 | }, notification_type) 122 | 123 | def send_generic_message(self, recipient_id, elements, notification_type=NotificationType.regular): 124 | """Send generic messages to the specified recipient. 125 | https://developers.facebook.com/docs/messenger-platform/send-api-reference/generic-template 126 | Input: 127 | recipient_id: recipient id to send to 128 | elements: generic message elements to send 129 | Output: 130 | Response from API as 131 | """ 132 | return self.send_message(recipient_id, { 133 | "attachment": { 134 | "type": "template", 135 | "payload": { 136 | "template_type": "generic", 137 | "elements": elements 138 | } 139 | } 140 | }, notification_type) 141 | 142 | def send_button_message(self, recipient_id, text, buttons, notification_type=NotificationType.regular): 143 | """Send text messages to the specified recipient. 144 | https://developers.facebook.com/docs/messenger-platform/send-api-reference/button-template 145 | Input: 146 | recipient_id: recipient id to send to 147 | text: text of message to send 148 | buttons: buttons to send 149 | Output: 150 | Response from API as 151 | """ 152 | return self.send_message(recipient_id, { 153 | "attachment": { 154 | "type": "template", 155 | "payload": { 156 | "template_type": "button", 157 | "text": text, 158 | "buttons": buttons 159 | } 160 | } 161 | }, notification_type) 162 | 163 | def send_action(self, recipient_id, action, notification_type=NotificationType.regular): 164 | """Send typing indicators or send read receipts to the specified recipient. 165 | https://developers.facebook.com/docs/messenger-platform/send-api-reference/sender-actions 166 | 167 | Input: 168 | recipient_id: recipient id to send to 169 | action: action type (mark_seen, typing_on, typing_off) 170 | Output: 171 | Response from API as 172 | """ 173 | return self.send_recipient(recipient_id, { 174 | 'sender_action': action 175 | }, notification_type) 176 | 177 | def send_image(self, recipient_id, image_path, notification_type=NotificationType.regular): 178 | """Send an image to the specified recipient. 179 | Image must be PNG or JPEG or GIF (more might be supported). 180 | https://developers.facebook.com/docs/messenger-platform/send-api-reference/image-attachment 181 | Input: 182 | recipient_id: recipient id to send to 183 | image_path: path to image to be sent 184 | Output: 185 | Response from API as 186 | """ 187 | return self.send_attachment(recipient_id, "image", image_path, notification_type) 188 | 189 | def send_image_url(self, recipient_id, image_url, notification_type=NotificationType.regular): 190 | """Send an image to specified recipient using URL. 191 | Image must be PNG or JPEG or GIF (more might be supported). 192 | https://developers.facebook.com/docs/messenger-platform/send-api-reference/image-attachment 193 | Input: 194 | recipient_id: recipient id to send to 195 | image_url: url of image to be sent 196 | Output: 197 | Response from API as 198 | """ 199 | return self.send_attachment_url(recipient_id, "image", image_url, notification_type) 200 | 201 | def send_audio(self, recipient_id, audio_path, notification_type=NotificationType.regular): 202 | """Send audio to the specified recipient. 203 | Audio must be MP3 or WAV 204 | https://developers.facebook.com/docs/messenger-platform/send-api-reference/audio-attachment 205 | Input: 206 | recipient_id: recipient id to send to 207 | audio_path: path to audio to be sent 208 | Output: 209 | Response from API as 210 | """ 211 | return self.send_attachment(recipient_id, "audio", audio_path, notification_type) 212 | 213 | def send_audio_url(self, recipient_id, audio_url, notification_type=NotificationType.regular): 214 | """Send audio to specified recipient using URL. 215 | Audio must be MP3 or WAV 216 | https://developers.facebook.com/docs/messenger-platform/send-api-reference/audio-attachment 217 | Input: 218 | recipient_id: recipient id to send to 219 | audio_url: url of audio to be sent 220 | Output: 221 | Response from API as 222 | """ 223 | return self.send_attachment_url(recipient_id, "audio", audio_url, notification_type) 224 | 225 | def send_video(self, recipient_id, video_path, notification_type=NotificationType.regular): 226 | """Send video to the specified recipient. 227 | Video should be MP4 or MOV, but supports more (https://www.facebook.com/help/218673814818907). 228 | https://developers.facebook.com/docs/messenger-platform/send-api-reference/video-attachment 229 | Input: 230 | recipient_id: recipient id to send to 231 | video_path: path to video to be sent 232 | Output: 233 | Response from API as 234 | """ 235 | return self.send_attachment(recipient_id, "video", video_path, notification_type) 236 | 237 | def send_video_url(self, recipient_id, video_url, notification_type=NotificationType.regular): 238 | """Send video to specified recipient using URL. 239 | Video should be MP4 or MOV, but supports more (https://www.facebook.com/help/218673814818907). 240 | https://developers.facebook.com/docs/messenger-platform/send-api-reference/video-attachment 241 | Input: 242 | recipient_id: recipient id to send to 243 | video_url: url of video to be sent 244 | Output: 245 | Response from API as 246 | """ 247 | return self.send_attachment_url(recipient_id, "video", video_url, notification_type) 248 | 249 | def send_file(self, recipient_id, file_path, notification_type=NotificationType.regular): 250 | """Send file to the specified recipient. 251 | https://developers.facebook.com/docs/messenger-platform/send-api-reference/file-attachment 252 | Input: 253 | recipient_id: recipient id to send to 254 | file_path: path to file to be sent 255 | Output: 256 | Response from API as 257 | """ 258 | return self.send_attachment(recipient_id, "file", file_path, notification_type) 259 | 260 | def send_file_url(self, recipient_id, file_url, notification_type=NotificationType.regular): 261 | """Send file to the specified recipient. 262 | https://developers.facebook.com/docs/messenger-platform/send-api-reference/file-attachment 263 | Input: 264 | recipient_id: recipient id to send to 265 | file_url: url of file to be sent 266 | Output: 267 | Response from API as 268 | """ 269 | return self.send_attachment_url(recipient_id, "file", file_url, notification_type) 270 | 271 | def get_user_info(self, recipient_id, fields=None): 272 | """Getting information about the user 273 | https://developers.facebook.com/docs/messenger-platform/user-profile 274 | Input: 275 | recipient_id: recipient id to send to 276 | Output: 277 | Response from API as 278 | """ 279 | params = {} 280 | if fields is not None and isinstance(fields, (list, tuple)): 281 | params['fields'] = ",".join(fields) 282 | 283 | params.update(self.auth_args) 284 | 285 | request_endpoint = '{0}/{1}'.format(self.graph_url, recipient_id) 286 | response = requests.get(request_endpoint, params=params) 287 | if response.status_code == 200: 288 | return response.json() 289 | 290 | return None 291 | 292 | def send_raw(self, payload): 293 | request_endpoint = '{0}/me/messages'.format(self.graph_url) 294 | response = requests.post( 295 | request_endpoint, 296 | params=self.auth_args, 297 | json=payload 298 | ) 299 | result = response.json() 300 | return result 301 | 302 | def _send_payload(self, payload): 303 | """ Deprecated, use send_raw instead """ 304 | return self.send_raw(payload) 305 | 306 | def set_get_started(self, gs_obj): 307 | """Set a get started button shown on welcome screen for first time users 308 | https://developers.facebook.com/docs/messenger-platform/reference/messenger-profile-api/get-started-button 309 | Input: 310 | gs_obj: Your formatted get_started object as described by the API docs 311 | Output: 312 | Response from API as 313 | """ 314 | request_endpoint = '{0}/me/messenger_profile'.format(self.graph_url) 315 | response = requests.post( 316 | request_endpoint, 317 | params = self.auth_args, 318 | json = gs_obj 319 | ) 320 | result = response.json() 321 | return result 322 | 323 | def set_persistent_menu(self, pm_obj): 324 | """Set a persistent_menu that stays same for every user. Before you can use this, make sure to have set a get started button. 325 | https://developers.facebook.com/docs/messenger-platform/reference/messenger-profile-api/persistent-menu 326 | Input: 327 | pm_obj: Your formatted persistent menu object as described by the API docs 328 | Output: 329 | Response from API as 330 | """ 331 | request_endpoint = '{0}/me/messenger_profile'.format(self.graph_url) 332 | response = requests.post( 333 | request_endpoint, 334 | params = self.auth_args, 335 | json = pm_obj 336 | ) 337 | result = response.json() 338 | return result 339 | 340 | def remove_get_started(self): 341 | """delete get started button. 342 | https://developers.facebook.com/docs/messenger-platform/reference/messenger-profile-api/#delete 343 | Output: 344 | Response from API as 345 | """ 346 | delete_obj = {"fields": ["get_started"]} 347 | request_endpoint = '{0}/me/messenger_profile'.format(self.graph_url) 348 | response = requests.delete( 349 | request_endpoint, 350 | params = self.auth_args, 351 | json = delete_obj 352 | ) 353 | result = response.json() 354 | return result 355 | 356 | def remove_persistent_menu(self): 357 | """delete persistent menu. 358 | https://developers.facebook.com/docs/messenger-platform/reference/messenger-profile-api/#delete 359 | Output: 360 | Response from API as 361 | """ 362 | delete_obj = {"fields": ["persistent_menu"]} 363 | request_endpoint = '{0}/me/messenger_profile'.format(self.graph_url) 364 | response = requests.delete( 365 | request_endpoint, 366 | params = self.auth_args, 367 | json = delete_obj 368 | ) 369 | result = response.json() 370 | return result 371 | -------------------------------------------------------------------------------- /pymessenger/utils.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import hmac 3 | import six 4 | 5 | 6 | def validate_hub_signature(app_secret, request_payload, hub_signature_header): 7 | """ 8 | @inputs: 9 | app_secret: Secret Key for application 10 | request_payload: request body 11 | hub_signature_header: X-Hub-Signature header sent with request 12 | @outputs: 13 | boolean indicated that hub signature is validated 14 | """ 15 | try: 16 | hash_method, hub_signature = hub_signature_header.split('=') 17 | except: 18 | pass 19 | else: 20 | digest_module = getattr(hashlib, hash_method) 21 | hmac_object = hmac.new(str(app_secret), unicode(request_payload), digest_module) 22 | generated_hash = hmac_object.hexdigest() 23 | if hub_signature == generated_hash: 24 | return True 25 | return False 26 | 27 | 28 | def generate_appsecret_proof(access_token, app_secret): 29 | """ 30 | @inputs: 31 | access_token: page access token 32 | app_secret_token: app secret key 33 | @outputs: 34 | appsecret_proof: HMAC-SHA256 hash of page access token 35 | using app_secret as the key 36 | """ 37 | if six.PY2: 38 | hmac_object = hmac.new(str(app_secret), unicode(access_token), hashlib.sha256) 39 | else: 40 | hmac_object = hmac.new(bytearray(app_secret, 'utf8'), str(access_token).encode('utf8'), hashlib.sha256) 41 | generated_hash = hmac_object.hexdigest() 42 | return generated_hash 43 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pytest 2 | requests 3 | requests-toolbelt 4 | six 5 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | installation_requirements = [ 4 | 'requests', 5 | 'requests-toolbelt', 6 | 'six' 7 | ] 8 | 9 | try: 10 | import enum 11 | del enum 12 | except ImportError: 13 | installation_requirements.append('enum') 14 | 15 | setup( 16 | name='pymessenger', 17 | packages=['pymessenger'], 18 | version='1.0.0', 19 | install_requires=installation_requirements, 20 | description="Python Wrapper for Facebook Messenger Platform", 21 | author='David Chua', 22 | author_email='zhchua@gmail.com', 23 | url='https://github.com/davidchua/pymessenger', 24 | download_url='https://github.com/davidchua/pymessenger/tarball/1.0.0', 25 | keywords=['facebook messenger', 'python', 'wrapper', 'bot', 'messenger bot'], 26 | classifiers=[], 27 | ) 28 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | sys.path.append(os.path.realpath(os.path.dirname(__file__) + "/..")) 5 | -------------------------------------------------------------------------------- /test/bot_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from pymessenger.bot import Bot 4 | from pymessenger import Element, Button 5 | 6 | TOKEN = os.environ.get('TOKEN') 7 | APP_SECRET = os.environ.get('APP_SECRET') 8 | 9 | bot = Bot(TOKEN, app_secret=APP_SECRET) 10 | 11 | recipient_id = os.environ.get('RECIPIENT_ID') 12 | 13 | 14 | def test_wrong_format_message(): 15 | result = bot.send_text_message(recipient_id, {'text': "its a test"}) 16 | assert type(result) is dict 17 | assert result.get('message_id') is None 18 | 19 | 20 | def test_text_message(): 21 | result = bot.send_text_message(recipient_id, "test") 22 | assert type(result) is dict 23 | assert result.get('message_id') is not None 24 | assert result.get('recipient_id') is not None 25 | 26 | 27 | def test_elements(): 28 | image_url = 'https://lh4.googleusercontent.com/-dZ2LhrpNpxs/AAAAAAAAAAI/AAAAAAAA1os/qrf-VeTVJrg/s0-c-k-no-ns/photo.jpg' 29 | elements = [] 30 | element = Element(title="Arsenal", image_url=image_url, subtitle="Click to go to Arsenal website.", 31 | item_url="http://arsenal.com") 32 | elements.append(element) 33 | result = bot.send_generic_message(recipient_id, elements) 34 | assert type(result) is dict 35 | assert result.get('message_id') is not None 36 | assert result.get('recipient_id') is not None 37 | 38 | 39 | def test_image_url(): 40 | image_url = 'https://lh4.googleusercontent.com/-dZ2LhrpNpxs/AAAAAAAAAAI/AAAAAAAA1os/qrf-VeTVJrg/s0-c-k-no-ns/photo.jpg' 41 | result = bot.send_image_url(recipient_id, image_url) 42 | assert type(result) is dict 43 | assert result.get('message_id') is not None 44 | assert result.get('recipient_id') is not None 45 | 46 | def test_image_gif_url(): 47 | image_url = 'https://media.giphy.com/media/rl0FOxdz7CcxO/giphy.gif' 48 | result = bot.send_image_url(recipient_id, image_url) 49 | assert type(result) is dict 50 | assert result.get('message_id') is not None 51 | assert result.get('recipient_id') is not None 52 | 53 | 54 | def test_button_message(): 55 | buttons = [] 56 | button = Button(title='Arsenal', type='web_url', url='http://arsenal.com') 57 | buttons.append(button) 58 | button = Button(title='Other', type='postback', payload='other') 59 | buttons.append(button) 60 | text = 'Select' 61 | result = bot.send_button_message(recipient_id, text, buttons) 62 | assert type(result) is dict 63 | assert result.get('message_id') is not None 64 | assert result.get('recipient_id') is not None 65 | 66 | 67 | def test_fields_blank(): 68 | user_profile = bot.get_user_info(recipient_id) 69 | assert user_profile is not None 70 | 71 | 72 | def test_fields(): 73 | fields = ['first_name', 'last_name'] 74 | user_profile = bot.get_user_info(recipient_id, fields=fields) 75 | assert user_profile is not None 76 | assert len(user_profile.keys()) == len(fields) 77 | --------------------------------------------------------------------------------