webhooks: Remove the Facebook integration.
Rishi and I decided that it makes sense to get rid of the Facebook integration for a few reasons, some of which are: * The setup process is too complicated on Facebook's end. The users will surely have to browse Facebook's huge API reference before even having a vague idea of what they want. * Slack chooses not to have a Facebook integration, but relies on Zapier for it. Zaps that integrate with Facebook are much more streamlined and the setup process isn't as much of a pain. Zapier's Facebook Zaps are much more fine-tuned and there are different Zaps for different parts of the FB API, a luxury that would likely span 2K+ lines of code on our end if we were to implement it from scratch. So, I think we should relegate integration with Facebook to Zapier as well! * After thoroughly testing the setup process, we concluded that the person who submitted the FB integration didn't really test it thoroughly because there were some gaping holes in the docs (missing steps, user permissions, etc.).
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 549 B |
|
@ -373,7 +373,6 @@ WEBHOOK_INTEGRATIONS = [
|
||||||
WebhookIntegration('zendesk', ['customer-support']),
|
WebhookIntegration('zendesk', ['customer-support']),
|
||||||
WebhookIntegration('gci', ['misc'], display_name='Google Code-in',
|
WebhookIntegration('gci', ['misc'], display_name='Google Code-in',
|
||||||
stream_name='gci'),
|
stream_name='gci'),
|
||||||
WebhookIntegration('facebook', ['communication'], display_name='Facebook')
|
|
||||||
] # type: List[WebhookIntegration]
|
] # type: List[WebhookIntegration]
|
||||||
|
|
||||||
INTEGRATIONS = {
|
INTEGRATIONS = {
|
||||||
|
|
|
@ -1,75 +0,0 @@
|
||||||
{!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**:
|
|
||||||
|
|
||||||
![](/static/images/help/bot_types.png)
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
![](/static/images/integrations/facebook/001.png)
|
|
||||||
|
|
||||||
Then, fill in the following form to create a new Facebook app:
|
|
||||||
|
|
||||||
![](/static/images/integrations/facebook/002.png)
|
|
||||||
|
|
||||||
Next, under **Webhooks**, click on **Set up**:
|
|
||||||
|
|
||||||
![](/static/images/integrations/facebook/003.png)
|
|
||||||
|
|
||||||
Choose a category for the webhook:
|
|
||||||
|
|
||||||
![](/static/images/integrations/facebook/004.png)
|
|
||||||
|
|
||||||
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:
|
|
||||||
|
|
||||||
![](/static/images/integrations/facebook/005.png)
|
|
||||||
|
|
||||||
Finally, click **Subscribe** and **Test** in the **feed** row, like so:
|
|
||||||
|
|
||||||
![](/static/images/integrations/facebook/006.png)
|
|
||||||
|
|
||||||
Click on **Send to My Server** and a test message will be sent to your Zulip server.
|
|
||||||
|
|
||||||
![](/static/images/integrations/facebook/007.png)
|
|
||||||
|
|
||||||
{!congrats.md!}
|
|
||||||
|
|
||||||
![](/static/images/integrations/facebook/008.png)
|
|
||||||
|
|
||||||
**This integration is not created by, affiliated with, or supported by Facebook, Inc.**
|
|
|
@ -1,23 +0,0 @@
|
||||||
{
|
|
||||||
"entry":[
|
|
||||||
{
|
|
||||||
"changes":[
|
|
||||||
{
|
|
||||||
"field":"plugin_comment",
|
|
||||||
"value":{
|
|
||||||
"created_time":"2018-01-02T16:53:38+0000",
|
|
||||||
"message":"Test Comment",
|
|
||||||
"from":{
|
|
||||||
"name":"Test User",
|
|
||||||
"id":"4444444444"
|
|
||||||
},
|
|
||||||
"id":"4444444444_4444444444"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"id":"0",
|
|
||||||
"time":1514912018
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"object":"application"
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
{
|
|
||||||
"entry":[
|
|
||||||
{
|
|
||||||
"changes":[
|
|
||||||
{
|
|
||||||
"field":"plugin_comment_reply",
|
|
||||||
"value":{
|
|
||||||
"created_time":"2018-01-03T10:45:42+0000",
|
|
||||||
"message":"Test Comment",
|
|
||||||
"from":{
|
|
||||||
"name":"Test User",
|
|
||||||
"id":"4444444444"
|
|
||||||
},
|
|
||||||
"id":"4444444444_4444444444",
|
|
||||||
"parent":{
|
|
||||||
"created_time":"2017-12-22T20:59:02+0000",
|
|
||||||
"message":"Test Parent Comment",
|
|
||||||
"from":{
|
|
||||||
"name":"Test User 1",
|
|
||||||
"id":"4444444444"
|
|
||||||
},
|
|
||||||
"id":"4444444444_44444444"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"id":"0",
|
|
||||||
"time":1514976343
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"object":"application"
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
{
|
|
||||||
"entry":[
|
|
||||||
{
|
|
||||||
"changes":[
|
|
||||||
{
|
|
||||||
"field":"conversations",
|
|
||||||
"value":{
|
|
||||||
"thread_id":"t_mid.14833205540:9182a4e489",
|
|
||||||
"page_id":4444444
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"id":"0",
|
|
||||||
"time":1514911876
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"object":"page"
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
{
|
|
||||||
"entry":[
|
|
||||||
{
|
|
||||||
"changes":[
|
|
||||||
{
|
|
||||||
"field":"website"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"id":"0",
|
|
||||||
"time":1514911950
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"object":"page"
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
{
|
|
||||||
"entry": [
|
|
||||||
{
|
|
||||||
"changes": [
|
|
||||||
{
|
|
||||||
"field": "ads_management",
|
|
||||||
"value": {
|
|
||||||
"target_ids": [
|
|
||||||
"123123123123123",
|
|
||||||
"321321321321321"
|
|
||||||
],
|
|
||||||
"verb": "granted"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"id": "0",
|
|
||||||
"time": 1515252883,
|
|
||||||
"uid": "0"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"object": "permissions"
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
{
|
|
||||||
"entry": [
|
|
||||||
{
|
|
||||||
"changes": [
|
|
||||||
{
|
|
||||||
"field": "manage_pages",
|
|
||||||
"value": {
|
|
||||||
"target_ids": [
|
|
||||||
"123123123123123",
|
|
||||||
"321321321321321"
|
|
||||||
],
|
|
||||||
"verb": "granted"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"id": "0",
|
|
||||||
"time": 1515254831,
|
|
||||||
"uid": "0"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"object": "permissions"
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
{
|
|
||||||
"entry":[
|
|
||||||
{
|
|
||||||
"time":1514911552,
|
|
||||||
"changes":[
|
|
||||||
{
|
|
||||||
"field":"email",
|
|
||||||
"value":"example_email@facebook.com"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"id":"0",
|
|
||||||
"uid":"0"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"object":"user"
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
{
|
|
||||||
"entry":[
|
|
||||||
{
|
|
||||||
"time":1514911601,
|
|
||||||
"changes":[
|
|
||||||
{
|
|
||||||
"field":"feed"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"id":"0",
|
|
||||||
"uid":"0"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"object":"user"
|
|
||||||
}
|
|
|
@ -1,110 +0,0 @@
|
||||||
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)
|
|
|
@ -1,116 +0,0 @@
|
||||||
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]
|
|