webhooks: Add Facebook integration.
BIN
static/images/integrations/facebook/001.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
static/images/integrations/facebook/002.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
static/images/integrations/facebook/003.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
static/images/integrations/facebook/004.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
static/images/integrations/facebook/005.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
static/images/integrations/facebook/006.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
static/images/integrations/facebook/007.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
static/images/integrations/facebook/008.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
1
static/images/integrations/logos/facebook.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="266.893" height="266.895"><path fill="#3C5A99" d="M248.082 262.307c7.854 0 14.223-6.369 14.223-14.225V18.812c0-7.857-6.368-14.224-14.223-14.224H18.812c-7.857 0-14.224 6.367-14.224 14.224v229.27c0 7.855 6.366 14.225 14.224 14.225h229.27z"/><path fill="#FFF" d="M182.409 262.307v-99.803h33.499l5.016-38.895h-38.515V98.777c0-11.261 3.127-18.935 19.275-18.935l20.596-.009V45.045c-3.562-.474-15.788-1.533-30.012-1.533-29.695 0-50.025 18.126-50.025 51.413v28.684h-33.585v38.895h33.585v99.803h40.166z"/></svg>
|
||||
|
After Width: | Height: | Size: 549 B |
@@ -368,7 +368,8 @@ WEBHOOK_INTEGRATIONS = [
|
||||
WebhookIntegration('zapier', ['meta-integration']),
|
||||
WebhookIntegration('zendesk', ['customer-support']),
|
||||
WebhookIntegration('gci', ['misc'], display_name='Google Code-in',
|
||||
stream_name='gci')
|
||||
stream_name='gci'),
|
||||
WebhookIntegration('facebook', ['communication'], display_name='Facebook')
|
||||
] # type: List[WebhookIntegration]
|
||||
|
||||
INTEGRATIONS = {
|
||||
|
||||
0
zerver/webhooks/facebook/__init__.py
Normal file
75
zerver/webhooks/facebook/doc.md
Normal file
@@ -0,0 +1,75 @@
|
||||
{!create-stream.md!}
|
||||
|
||||
Next, on your {{ settings_html|safe }},
|
||||
[create a bot](/help/add-a-bot-or-integration) for
|
||||
{{ integration_display_name }}. Make sure that you select
|
||||
**Incoming webhook** as the **Bot type**:
|
||||
|
||||

|
||||
|
||||
The API key for an incoming webhook bot cannot be used to read messages out
|
||||
of Zulip. Thus, using an incoming webhook bot lowers the security risk of
|
||||
exposing the bot's API key to a third-party service.
|
||||
|
||||
Construct the URL for the {{ integration_display_name }}
|
||||
bot using the bot's API key and the desired stream name:
|
||||
|
||||
`{{ api_url }}{{ integration_url }}?api_key=abcdefgh&stream={{ recommended_stream_name }}&token=sampletoken`
|
||||
|
||||
Modify the parameters of the URL above, where `api_key` is the API key
|
||||
of your Zulip bot, and `stream` is the stream name you want the
|
||||
notifications sent to.
|
||||
|
||||
`token` is an arbitrary string of your choosing that can be used to confirm to your
|
||||
server that the request is valid. This string will be included in Facebook's
|
||||
incoming payloads each time they send your server a verification request.
|
||||
|
||||
{!append-stream-name.md!}
|
||||
|
||||
### Configuring the webhook
|
||||
|
||||
Sign In to the following URL: <https://developers.facebook.com/apps/>
|
||||
|
||||
Next, click on **+ Add a New App** button.
|
||||
|
||||

|
||||
|
||||
Then, fill in the following form to create a new Facebook app:
|
||||
|
||||

|
||||
|
||||
Next, under **Webhooks**, click on **Set up**:
|
||||
|
||||

|
||||
|
||||
Choose a category for the webhook:
|
||||
|
||||

|
||||
|
||||
This guide explains how to subscribe to a "feed" in the **User** category.
|
||||
|
||||
Select the **User** category, and click on **Subscribe to this topic**.
|
||||
Fill in the **Edit User Subscription** form as follows:
|
||||
|
||||
1. **Callback URL**: enter the webhook URL created above.
|
||||
2. **Verify Token**: enter the token you chose above. For instance, in this example you may enter **sampletoken**
|
||||
3. Activate the **Include Values** option.
|
||||
4. Click on **Verify and Save**.
|
||||
|
||||
The resulting form would look like:
|
||||
|
||||

|
||||
|
||||
Finally, click **Subscribe** and **Test** in the **feed** row, like so:
|
||||
|
||||

|
||||
|
||||
Click on **Send to My Server** and a test message will be sent to your Zulip server.
|
||||
|
||||

|
||||
|
||||
{!congrats.md!}
|
||||
|
||||

|
||||
|
||||
**This integration is not created by, affiliated with, or supported by Facebook, Inc.**
|
||||
@@ -1,13 +1,22 @@
|
||||
{
|
||||
"entry":[
|
||||
{
|
||||
"time":1514912190,
|
||||
"id":"0",
|
||||
"changed_fields":[
|
||||
"ads_management"
|
||||
],
|
||||
"uid":"0"
|
||||
}
|
||||
],
|
||||
"object":"permissions"
|
||||
"entry": [
|
||||
{
|
||||
"changes": [
|
||||
{
|
||||
"field": "ads_management",
|
||||
"value": {
|
||||
"target_ids": [
|
||||
"123123123123123",
|
||||
"321321321321321"
|
||||
],
|
||||
"verb": "granted"
|
||||
}
|
||||
}
|
||||
],
|
||||
"id": "0",
|
||||
"time": 1515252883,
|
||||
"uid": "0"
|
||||
}
|
||||
],
|
||||
"object": "permissions"
|
||||
}
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
{
|
||||
"entry":[
|
||||
{
|
||||
"time":1514912232,
|
||||
"id":"0",
|
||||
"changed_fields":[
|
||||
"manage_pages"
|
||||
],
|
||||
"uid":"0"
|
||||
}
|
||||
],
|
||||
"object":"permissions"
|
||||
"entry": [
|
||||
{
|
||||
"changes": [
|
||||
{
|
||||
"field": "manage_pages",
|
||||
"value": {
|
||||
"target_ids": [
|
||||
"123123123123123",
|
||||
"321321321321321"
|
||||
],
|
||||
"verb": "granted"
|
||||
}
|
||||
}
|
||||
],
|
||||
"id": "0",
|
||||
"time": 1515254831,
|
||||
"uid": "0"
|
||||
}
|
||||
],
|
||||
"object": "permissions"
|
||||
}
|
||||
|
||||
110
zerver/webhooks/facebook/tests.py
Normal file
@@ -0,0 +1,110 @@
|
||||
from typing import Optional, Text
|
||||
|
||||
from zerver.lib.test_classes import WebhookTestCase
|
||||
|
||||
class FacebookTests(WebhookTestCase):
|
||||
STREAM_NAME = 'Facebook'
|
||||
URL_TEMPLATE = "/api/v1/external/facebook?api_key={api_key}&stream={stream}&token=aaaa"
|
||||
FIXTURE_DIR_NAME = 'facebook'
|
||||
|
||||
def test_application_plugin_comment(self) -> None:
|
||||
expected_subject = u'application notification'
|
||||
expected_message = u'**plugin_comment** received'\
|
||||
u'\n**Test User:**'\
|
||||
u'\n```quote'\
|
||||
u'\nTest Comment'\
|
||||
u'\n```'
|
||||
self.send_and_test_stream_message('application_plugin_comment',
|
||||
expected_subject, expected_message)
|
||||
|
||||
def test_application_plugin_comment_reply(self) -> None:
|
||||
expected_subject = u'application notification'
|
||||
expected_message = u'**plugin_comment_reply** received'\
|
||||
u'\n**Test User 1:** (Parent)'\
|
||||
u'\n```quote'\
|
||||
u'\nTest Parent Comment'\
|
||||
u'\n```'\
|
||||
u'\n**Test User:**'\
|
||||
u'\n```quote'\
|
||||
u'\n```quote'\
|
||||
u'\nTest Comment'\
|
||||
u'\n```'\
|
||||
u'\n```'
|
||||
self.send_and_test_stream_message('application_plugin_comment_reply',
|
||||
expected_subject, expected_message)
|
||||
|
||||
def test_page_conversations(self) -> None:
|
||||
expected_subject = u'page notification'
|
||||
expected_message = u'Updated **conversations**'\
|
||||
u'\n[Open conversations...](https://www.facebook.com/'\
|
||||
u'4444444/t_mid.14833205540:9182a4e489)'
|
||||
self.send_and_test_stream_message('page_conversations',
|
||||
expected_subject, expected_message)
|
||||
|
||||
def test_page_website_test(self) -> None:
|
||||
expected_subject = u'page notification'
|
||||
expected_message = u'Changed **website**'
|
||||
self.send_and_test_stream_message('page_website',
|
||||
expected_subject, expected_message)
|
||||
|
||||
def test_permissions_ads_management(self) -> None:
|
||||
expected_subject = u'permissions notification'
|
||||
expected_message = u'**ads_management permission** changed'\
|
||||
u'\n* granted'\
|
||||
u'\n * 123123123123123'\
|
||||
u'\n * 321321321321321'
|
||||
self.send_and_test_stream_message('permissions_ads_management',
|
||||
expected_subject, expected_message)
|
||||
|
||||
def test_permissions_manage_pages(self) -> None:
|
||||
expected_subject = u'permissions notification'
|
||||
expected_message = u'**manage_pages permission** changed'\
|
||||
u'\n* granted'\
|
||||
u'\n * 123123123123123'\
|
||||
u'\n * 321321321321321'
|
||||
self.send_and_test_stream_message('permissions_manage_pages',
|
||||
expected_subject, expected_message)
|
||||
|
||||
def test_user_email(self) -> None:
|
||||
expected_subject = u'user notification'
|
||||
expected_message = u'Changed **email**'\
|
||||
u'\nTo: *example_email@facebook.com*'
|
||||
self.send_and_test_stream_message('user_email',
|
||||
expected_subject, expected_message)
|
||||
|
||||
def test_user_feed(self) -> None:
|
||||
expected_subject = u'user notification'
|
||||
expected_message = u'Changed **feed**'
|
||||
self.send_and_test_stream_message('user_feed',
|
||||
expected_subject, expected_message)
|
||||
|
||||
def test_webhook_verify_request(self) -> None:
|
||||
self.subscribe(self.test_user, self.STREAM_NAME)
|
||||
get_params = {'stream_name': self.STREAM_NAME,
|
||||
'hub.challenge': '9B2SVL4orbt5DxLMqJHI6pOTipTqingt2YFMIO0g06E',
|
||||
'api_key': self.test_user.api_key,
|
||||
'hub.mode': 'subscribe',
|
||||
'hub.verify_token': 'aaaa',
|
||||
'token': 'aaaa'}
|
||||
result = self.client_get(self.url, get_params)
|
||||
self.assert_in_response('9B2SVL4orbt5DxLMqJHI6pOTipTqingt2YFMIO0g06E', result)
|
||||
|
||||
def test_error_webhook_verify_request_wrong_token(self) -> None:
|
||||
self.subscribe(self.test_user, self.STREAM_NAME)
|
||||
get_params = {'stream_name': self.STREAM_NAME,
|
||||
'hub.challenge': '9B2SVL4orbt5DxLMqJHI6pOTipTqingt2YFMIO0g06E',
|
||||
'api_key': self.test_user.api_key,
|
||||
'hub.mode': 'subscribe',
|
||||
'hub.verify_token': 'aaaa',
|
||||
'token': 'wrong_token'}
|
||||
result = self.client_get(self.url, get_params)
|
||||
self.assert_in_response('Error: Token is wrong', result)
|
||||
|
||||
def test_error_webhook_verify_request_unsupported_method(self) -> None:
|
||||
self.subscribe(self.test_user, self.STREAM_NAME)
|
||||
get_params = {'stream_name': self.STREAM_NAME,
|
||||
'api_key': self.test_user.api_key,
|
||||
'hub.mode': 'unsupported_method',
|
||||
'token': 'aaaa'}
|
||||
result = self.client_get(self.url, get_params)
|
||||
self.assert_in_response('Error: Unsupported method', result)
|
||||
116
zerver/webhooks/facebook/view.py
Normal file
@@ -0,0 +1,116 @@
|
||||
from typing import Any, Dict, Optional, Text
|
||||
|
||||
from django.http import HttpRequest, HttpResponse, QueryDict
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from zerver.decorator import api_key_only_webhook_view
|
||||
from zerver.lib.actions import check_send_stream_message, create_stream_if_needed
|
||||
from zerver.lib.request import REQ, has_request_variables
|
||||
from zerver.lib.response import json_success, json_error
|
||||
from zerver.models import UserProfile
|
||||
import json
|
||||
|
||||
class UnknownEventType(Exception):
|
||||
pass
|
||||
|
||||
def user_event(payload: Dict[Text, Any]) -> Text:
|
||||
field = payload['entry'][0]['changes'][0]['field']
|
||||
message = "Changed **{field}**".format(field=field)
|
||||
if field == "email":
|
||||
if payload['entry'][0]['changes'][0]['value'] is not None:
|
||||
message = message + '\nTo: *{email}*'.format(
|
||||
email=payload['entry'][0]['changes'][0]['value']
|
||||
)
|
||||
return message
|
||||
|
||||
def page_event(payload: Dict[Text, Any]) -> Text:
|
||||
field = payload['entry'][0]['changes'][0]['field']
|
||||
message = ''
|
||||
if field == 'conversations':
|
||||
message = message + 'Updated **conversations**'
|
||||
message = message + '\n[Open conversations...](https://www.facebook.com/'\
|
||||
'{page_id}/{thread_id})'.format(
|
||||
page_id=payload['entry'][0]['changes'][0]['value']['page_id'],
|
||||
thread_id=payload['entry'][0]['changes'][0]['value']['thread_id']
|
||||
)
|
||||
elif field == 'website':
|
||||
message = message + 'Changed **website**'
|
||||
return message
|
||||
|
||||
def permissions_event(payload: Dict[Text, Any]) -> Text:
|
||||
field = payload['entry'][0]['changes'][0]['field']
|
||||
message = '**{field} permission** changed'.format(field=field)
|
||||
if field == 'ads_management':
|
||||
message = message + '\n* {verb}'.format(
|
||||
verb=payload['entry'][0]['changes'][0]['value']['verb']
|
||||
)
|
||||
for id in payload['entry'][0]['changes'][0]['value']['target_ids']:
|
||||
message = message + '\n * {id}'.format(id=id)
|
||||
elif field == 'manage_pages':
|
||||
message = message + '\n* {verb}'.format(
|
||||
verb=payload['entry'][0]['changes'][0]['value']['verb']
|
||||
)
|
||||
for id in payload['entry'][0]['changes'][0]['value']['target_ids']:
|
||||
message = message + '\n * {id}'.format(id=id)
|
||||
return message
|
||||
|
||||
def application_event(payload: Dict[Text, Any]) -> Text:
|
||||
field = payload['entry'][0]['changes'][0]['field']
|
||||
message = '**{field}** received'.format(field=field)
|
||||
if field == 'plugin_comment':
|
||||
message = message + '\n**{msg_user}:**\n```quote\n{message}\n```'.format(
|
||||
msg_user=payload['entry'][0]['changes'][0]['value']['from']['name'],
|
||||
message=payload['entry'][0]['changes'][0]['value']['message']
|
||||
)
|
||||
if field == 'plugin_comment_reply':
|
||||
message = message + '\n**{prt_msg_user}:** (Parent)\n'\
|
||||
'```quote\n{prt_message}\n```'.format(
|
||||
prt_msg_user=payload['entry'][0]['changes'][0]['value']['parent']['from']['name'],
|
||||
prt_message=payload['entry'][0]['changes'][0]['value']['parent']['message']
|
||||
)
|
||||
message = message + '\n**{cld_msg_user}:**\n```quote\n'\
|
||||
'```quote\n{cld_message}\n```\n```'.format(
|
||||
cld_msg_user=payload['entry'][0]['changes'][0]['value']['from']['name'],
|
||||
cld_message=payload['entry'][0]['changes'][0]['value']['message']
|
||||
)
|
||||
return message
|
||||
|
||||
@api_key_only_webhook_view("Facebook")
|
||||
@has_request_variables
|
||||
def api_facebook_webhook(request: HttpRequest, user_profile: UserProfile,
|
||||
stream: Text=REQ(default='Facebook'), token: Text=REQ()) -> HttpResponse:
|
||||
|
||||
if request.method == 'GET': # facebook webhook verify
|
||||
if request.GET.get("hub.mode") == 'subscribe':
|
||||
if request.GET.get('hub.verify_token') == token:
|
||||
return HttpResponse(request.GET.get('hub.challenge'))
|
||||
else:
|
||||
return json_error(_('Error: Token is wrong'))
|
||||
return json_error(_('Error: Unsupported method'))
|
||||
|
||||
payload = json.loads(request.body.decode("UTF-8"))
|
||||
event = get_event(payload)
|
||||
if event is not None:
|
||||
body = get_body_based_on_event(event)(payload)
|
||||
subject = event + " notification"
|
||||
check_send_stream_message(user_profile, request.client,
|
||||
stream, subject, body)
|
||||
return json_success()
|
||||
|
||||
# This integration doesn't support instant_workflow, instagram
|
||||
# and certificate_transparency event.
|
||||
EVENTS_FUNCTION_MAPPER = {
|
||||
'user': user_event,
|
||||
'page': page_event,
|
||||
'permissions': permissions_event,
|
||||
'application': application_event
|
||||
}
|
||||
|
||||
def get_event(payload: Dict[Text, Any]) -> Optional[Text]:
|
||||
event = payload['object']
|
||||
if event in EVENTS_FUNCTION_MAPPER:
|
||||
return event
|
||||
raise UnknownEventType(u"OEvent '{}' is unknown and cannot be handled".format(event)) # nocoverage
|
||||
|
||||
def get_body_based_on_event(event: Text) -> Any:
|
||||
return EVENTS_FUNCTION_MAPPER[event]
|
||||