snapshot of https://github.com/zulip/zulip.git @ 48c69911c7dd4e03b20552eb3bd77ad8151c1357 (2026-09-15)
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
This commit is contained in:
0
zerver/webhooks/dialogflow/__init__.py
Normal file
0
zerver/webhooks/dialogflow/__init__.py
Normal file
27
zerver/webhooks/dialogflow/doc.md
Normal file
27
zerver/webhooks/dialogflow/doc.md
Normal file
@@ -0,0 +1,27 @@
|
||||
Get personal message notifications in Zulip for the results of your
|
||||
Dialogflow queries!
|
||||
|
||||
1. {!create-channel.md!}
|
||||
|
||||
1. {!create-an-incoming-webhook.md!}
|
||||
|
||||
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 Dialogflow bot using the bot's API key and your
|
||||
Zulip email. The webhook URL should look like:
|
||||
|
||||
`{{api_url}}?api_key=BOT'S_API_KEY&email=foo@example.com`
|
||||
|
||||
Modify the parameters of the URL above where `api_key` is the API key of your Zulip bot
|
||||
and `email` is your Zulip email.
|
||||
|
||||
1. Go to the **Fulfillment** settings of your Dialogflow app and enable **Webhooks**.
|
||||
Set **URL** to the URL constructed above.
|
||||
Go to **Intents** and at the bottom, check the box **Use webhook**
|
||||
under **Fulfillment**.
|
||||
|
||||
{!congrats.md!}
|
||||
|
||||

|
||||
45
zerver/webhooks/dialogflow/fixtures/alternate_result.json
Normal file
45
zerver/webhooks/dialogflow/fixtures/alternate_result.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"id": "69316a96-cce1-4322-9bdc-d94e8298f0d2",
|
||||
"timestamp": "2018-01-12T12:22:31.283Z",
|
||||
"lang": "en",
|
||||
"result": {
|
||||
"source": "agent",
|
||||
"resolvedQuery": "what's the weather in New Delhi",
|
||||
"action": "yahooWeatherForecast",
|
||||
"actionIncomplete": false,
|
||||
"parameters": {
|
||||
"geo-city": "New Delhi"
|
||||
},
|
||||
"contexts": [],
|
||||
"metadata": {
|
||||
"intentId": "a61266c2-b6af-4dc8-a7b9-72dfb11b72d8",
|
||||
"webhookUsed": "true",
|
||||
"webhookForSlotFillingUsed": "false",
|
||||
"webhookResponseTime": 115,
|
||||
"intentName": "weather-intent"
|
||||
},
|
||||
"fulfillment": {
|
||||
"speech": "",
|
||||
"source": "apiai-weather-webhook-sample",
|
||||
"displayText": "",
|
||||
"messages": [
|
||||
{
|
||||
"type": 0,
|
||||
"speech": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
"score": 0.7099999785423279
|
||||
},
|
||||
"alternateResult": {
|
||||
"fulfillment": {
|
||||
"speech": "Weather in New Delhi is nice!"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"code": 200,
|
||||
"errorType": "success",
|
||||
"webhookTimedOut": false
|
||||
},
|
||||
"sessionId": "2507496e-8028-4be5-ac52-1624f752b188"
|
||||
}
|
||||
37
zerver/webhooks/dialogflow/fixtures/default.json
Normal file
37
zerver/webhooks/dialogflow/fixtures/default.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"id": "1faaa328-dad8-4532-be7e-c4103dfdbf40",
|
||||
"timestamp": "2018-01-12T12:09:15.699Z",
|
||||
"lang": "en",
|
||||
"result": {
|
||||
"source": "agent",
|
||||
"resolvedQuery": "how is the weather in Sunnyvale",
|
||||
"action": "",
|
||||
"actionIncomplete": false,
|
||||
"parameters": {
|
||||
"geo-city": "Sunnyvale"
|
||||
},
|
||||
"contexts": [],
|
||||
"metadata": {
|
||||
"intentId": "a61266c2-b6af-4dc8-a7b9-72dfb11b72d8",
|
||||
"webhookUsed": "false",
|
||||
"webhookForSlotFillingUsed": "false",
|
||||
"intentName": "weather-intent"
|
||||
},
|
||||
"fulfillment": {
|
||||
"speech": "The weather sure looks great !",
|
||||
"messages": [
|
||||
{
|
||||
"type": 0,
|
||||
"speech": "The weather sure looks great !"
|
||||
}
|
||||
]
|
||||
},
|
||||
"score": 1
|
||||
},
|
||||
"status": {
|
||||
"code": 200,
|
||||
"errorType": "success",
|
||||
"webhookTimedOut": false
|
||||
},
|
||||
"sessionId": "2507496e-8028-4be5-ac52-1624f752b188"
|
||||
}
|
||||
14
zerver/webhooks/dialogflow/fixtures/error_status.json
Normal file
14
zerver/webhooks/dialogflow/fixtures/error_status.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"id": "69316a96-cce1-4322-9bdc-d94e8298f0d2",
|
||||
"timestamp": "2018-01-12T12:22:31.283Z",
|
||||
"lang": "en",
|
||||
"result": {
|
||||
"resolvedQuery": "what's the weather in New Delhi"
|
||||
},
|
||||
"status": {
|
||||
"code": 403,
|
||||
"errorType": "fail",
|
||||
"errorDetails": "Access Denied"
|
||||
},
|
||||
"sessionId": "2507496e-8028-4be5-ac52-1624f752b188"
|
||||
}
|
||||
45
zerver/webhooks/dialogflow/fixtures/exception.json
Normal file
45
zerver/webhooks/dialogflow/fixtures/exception.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"id": "69316a96-cce1-4322-9bdc-d94e8298f0d2",
|
||||
"timestamp": "2018-01-12T12:22:31.283Z",
|
||||
"lang": "en",
|
||||
"result": {
|
||||
"source": "agent",
|
||||
"resolvedQuery": "what's the weather in New Delhi",
|
||||
"action": "yahooWeatherForecast",
|
||||
"actionIncomplete": false,
|
||||
"parameters": {
|
||||
"geo-city": "New Delhi"
|
||||
},
|
||||
"contexts": [],
|
||||
"metadata": {
|
||||
"intentId": "a61266c2-b6af-4dc8-a7b9-72dfb11b72d8",
|
||||
"webhookUsed": "true",
|
||||
"webhookForSlotFillingUsed": "false",
|
||||
"webhookResponseTime": 115,
|
||||
"intentName": "weather-intent"
|
||||
},
|
||||
"fulfillment": {
|
||||
"speech": "",
|
||||
"source": "apiai-weather-webhook-sample",
|
||||
"displayText": "",
|
||||
"messages": [
|
||||
{
|
||||
"type": 0,
|
||||
"speech": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
"score": 0.7099999785423279
|
||||
},
|
||||
"alternateResult": {
|
||||
"fulfillment": {
|
||||
"speech": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"code": 200,
|
||||
"errorType": "success",
|
||||
"webhookTimedOut": false
|
||||
},
|
||||
"sessionId": "2507496e-8028-4be5-ac52-1624f752b188"
|
||||
}
|
||||
40
zerver/webhooks/dialogflow/fixtures/weather_app.json
Normal file
40
zerver/webhooks/dialogflow/fixtures/weather_app.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"id": "69316a96-cce1-4322-9bdc-d94e8298f0d2",
|
||||
"timestamp": "2018-01-12T12:22:31.283Z",
|
||||
"lang": "en",
|
||||
"result": {
|
||||
"source": "agent",
|
||||
"resolvedQuery": "what's the weather in New Delhi",
|
||||
"action": "yahooWeatherForecast",
|
||||
"actionIncomplete": false,
|
||||
"parameters": {
|
||||
"geo-city": "New Delhi"
|
||||
},
|
||||
"contexts": [],
|
||||
"metadata": {
|
||||
"intentId": "a61266c2-b6af-4dc8-a7b9-72dfb11b72d8",
|
||||
"webhookUsed": "true",
|
||||
"webhookForSlotFillingUsed": "false",
|
||||
"webhookResponseTime": 115,
|
||||
"intentName": "weather-intent"
|
||||
},
|
||||
"fulfillment": {
|
||||
"speech": "Today the weather in Delhi: Sunny, And the temperature is 65 F",
|
||||
"source": "apiai-weather-webhook-sample",
|
||||
"displayText": "Today the weather in Delhi: Sunny, And the temperature is 65 F",
|
||||
"messages": [
|
||||
{
|
||||
"type": 0,
|
||||
"speech": "Today the weather in Delhi: Sunny, And the temperature is 65 F"
|
||||
}
|
||||
]
|
||||
},
|
||||
"score": 0.7099999785423279
|
||||
},
|
||||
"status": {
|
||||
"code": 200,
|
||||
"errorType": "success",
|
||||
"webhookTimedOut": false
|
||||
},
|
||||
"sessionId": "2507496e-8028-4be5-ac52-1624f752b188"
|
||||
}
|
||||
45
zerver/webhooks/dialogflow/tests.py
Normal file
45
zerver/webhooks/dialogflow/tests.py
Normal file
@@ -0,0 +1,45 @@
|
||||
from zerver.lib.test_classes import WebhookTestCase
|
||||
|
||||
|
||||
class DialogflowHookTests(WebhookTestCase):
|
||||
URL_TEMPLATE = "/api/v1/external/dialogflow?api_key={api_key}&email=AARON@zulip.com"
|
||||
|
||||
def test_dialogflow_default(self) -> None:
|
||||
email = self.example_user("aaron").email
|
||||
self.url = self.build_webhook_url(
|
||||
email=email,
|
||||
username="aaron",
|
||||
user_ip="127.0.0.1",
|
||||
)
|
||||
expected_message = "The weather sure looks great !"
|
||||
self.send_and_test_private_message("default", expected_message)
|
||||
|
||||
def test_dialogflow_alternate_result(self) -> None:
|
||||
email = self.example_user("aaron").email
|
||||
self.url = self.build_webhook_url(
|
||||
email=email,
|
||||
username="aaron",
|
||||
user_ip="127.0.0.1",
|
||||
)
|
||||
expected_message = "Weather in New Delhi is nice!"
|
||||
self.send_and_test_private_message("alternate_result", expected_message)
|
||||
|
||||
def test_dialogflow_error_status(self) -> None:
|
||||
email = self.example_user("aaron").email
|
||||
self.url = self.build_webhook_url(
|
||||
email=email,
|
||||
username="aaron",
|
||||
user_ip="127.0.0.1",
|
||||
)
|
||||
expected_message = "403 - Access Denied"
|
||||
self.send_and_test_private_message("error_status", expected_message)
|
||||
|
||||
def test_dialogflow_exception(self) -> None:
|
||||
email = self.example_user("aaron").email
|
||||
self.url = self.build_webhook_url(
|
||||
email=email,
|
||||
username="aaron",
|
||||
user_ip="127.0.0.1",
|
||||
)
|
||||
expected_message = "Dialogflow couldn't process your query."
|
||||
self.send_and_test_private_message("exception", expected_message)
|
||||
45
zerver/webhooks/dialogflow/view.py
Normal file
45
zerver/webhooks/dialogflow/view.py
Normal file
@@ -0,0 +1,45 @@
|
||||
# Webhooks for external integrations.
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
|
||||
from zerver.actions.message_send import check_send_private_message
|
||||
from zerver.decorator import webhook_view
|
||||
from zerver.lib.request import RequestNotes
|
||||
from zerver.lib.response import json_success
|
||||
from zerver.lib.typed_endpoint import JsonBodyPayload, typed_endpoint
|
||||
from zerver.lib.validator import WildValue, check_int, check_string
|
||||
from zerver.models import UserProfile
|
||||
from zerver.models.users import get_user
|
||||
|
||||
|
||||
@webhook_view("Dialogflow")
|
||||
@typed_endpoint
|
||||
def api_dialogflow_webhook(
|
||||
request: HttpRequest,
|
||||
user_profile: UserProfile,
|
||||
*,
|
||||
payload: JsonBodyPayload[WildValue],
|
||||
email: str,
|
||||
) -> HttpResponse:
|
||||
status = payload["status"]["code"].tame(check_int)
|
||||
|
||||
if status == 200:
|
||||
result = payload["result"]["fulfillment"]["speech"].tame(check_string)
|
||||
if not result:
|
||||
alternate_result = payload["alternateResult"]["fulfillment"]["speech"].tame(
|
||||
check_string
|
||||
)
|
||||
if not alternate_result:
|
||||
body = "Dialogflow couldn't process your query."
|
||||
else:
|
||||
body = alternate_result
|
||||
else:
|
||||
body = result
|
||||
else:
|
||||
error_status = payload["status"]["errorDetails"].tame(check_string)
|
||||
body = f"{status} - {error_status}"
|
||||
|
||||
receiving_user = get_user(email, user_profile.realm)
|
||||
client = RequestNotes.get_notes(request).client
|
||||
assert client is not None
|
||||
check_send_private_message(user_profile, client, receiving_user, body)
|
||||
return json_success(request)
|
||||
Reference in New Issue
Block a user