ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Zulip Outgoing Webhook 负载格式全解析:原生 Zulip 格式与 Slack 兼容格式的字段、转换规则与源码实现

Zulip Outgoing Webhook 负载格式全解析:原生 Zulip 格式与 Slack 兼容格式的字段、转换规则与源码实现 Zulip Outgoing Webhook 负载格式全解析原生 Zulip 格式与 Slack 兼容格式的字段、转换规则与源码实现【免费下载链接】zulipZulip server and web application. Open-source team chat that helps teams stay productive and focused.项目地址: https://gitcode.com/GitHub_Trending/zu/zulipZulip 的 outgoing webhook 允许在特定消息被发送时将消息内容以 HTTP 请求的形式推送给外部服务bot 服务器或第三方系统是实现聊天机器人与外部集成的核心机制。本篇以仓库文档 api_docs/outgoing-webhook-payload.md 为主线完整讲解 Zulip 原生 JSON 负载与 Slack 兼容表单负载的字段含义、Zulip 服务器到 Slack 格式的字段映射关系、响应约定并结合 zerver/lib/outgoing_webhook.py 与 zerver/tests/test_outgoing_webhook_interfaces.py 的源码与测试帮助你精确理解收到的 payload 结构并正确编写可被 Zulip 识别的响应。Outgoing webhook 的两种负载格式总览Zulip 的 outgoing webhook 支持两种负载格式原生 Zulip 格式Zulip format服务器以 JSON 形式 POST 一个包含data、message、token、trigger、bot_email、bot_full_name字段的对象适合为 Zulip 从零编写的集成。Slack 兼容格式Slack-compatible format服务器把 Zulip 消息翻译成 Slack outgoing webhook API 的形式参数application/x-www-form-urlencoded 风格有助于将已有的 Slack 集成直接移植到 Zulip也能让许多已经支持 Slack outgoing webhook 的第三方系统开箱即用。两种格式由 bot 在创建/编辑时选择的 interface 类型决定。从 zerver/models/bots.py 可以看到两种 interface 的标识符GENERIC_INTERFACE GenericService与SLACK_INTERFACE SlackOutgoingWebhookService它们分别对应 zerver/lib/outgoing_webhook.py 中的GenericOutgoingWebhookService与SlackOutgoingWebhookService两个实现类。接口分发逻辑位于get_service_interface_classzerver/lib/outgoing_webhook.py未知的 interface 名会回退到通用的 Zulip 原生格式。原生 Zulip 格式Zulip format服务器 POST 的请求负载结构当消息触发 outgoing webhook 时Zulip 服务器会向 bot 配置的 URL 发送一个 JSON 请求。该负载的完整字段定义记录在 OpenAPI 规范 zerver/openapi/zulip.yaml 的/zulip-outgoing-webhook端点中并作为文档页面中{generate_code_example|/zulip-outgoing-webhook:post|fixture}与{generate_return_values_table|zulip.yaml|/zulip-outgoing-webhook:post}两个占位符的实际渲染来源。字段说明如下字段类型说明bot_emailstringbot 用户的邮箱bot_full_namestringbot 用户的完整显示名datastring消息内容使用原始的 Zulip-flavored Markdown未渲染为 HTMLtriggerstring触发本次 outgoing webhook 通知的消息特征可能的值包括direct_message和mention。变更Zulip 8.0feature level 201起触发器private_message被重命名为direct_messagetokenstring一串字母数字字符用于认证 webhook 请求每个 bot 用户使用固定的 token。创建 bot 时下载的zuliprc文件中可以找到该 tokenmessageobject触发消息的详细信息字典格式与GET /messages接口返回的消息一致MessagesBaseschema并额外包含rendered_content消息渲染后的 HTML 内容message子对象中常见的字段包括id、sender_id、sender_email、sender_full_name、sender_realm_str、content原始 Markdown、rendered_content渲染 HTML、content_type、display_recipient频道名或私信参与者、stream_id、typestream或private、timestamp、client、avatar_url、reactions、submessages、topic_links、recipient_id、is_me_message等。OpenAPI 规范中给出的完整 JSON 负载示例如下{ data: **Outgoing webhook test** Zulip is the world’s most productive group chat!, trigger: mention, token: xvOzfurIutdRRVLzpXrIIHXJvNfaJLJ0, message: { subject: Verona2, sender_email: iagozulip.com, timestamp: 1527876931, client: website, submessages: [], recipient_id: 20, topic_links: [], sender_full_name: Iago, avatar_url: https://secure.gravatar.com/avatar/1f4f1575bf002ae562fea8fc4b861b09?didenticonversion1, rendered_content: pspan class\user-mention\>request_data { data: event[command], message: message_dict, bot_email: self.user_profile.email, bot_full_name: self.user_profile.full_name, token: self.token, trigger: event[trigger], }然后通过带超时与自定义 User-Agent 的会话以self.session.post(base_url, jsonrequest_data)发送。其中event[trigger]的取值由消息类型决定详见下文触发器的判定一节。测试 zerver/tests/test_outgoing_webhook_interfaces.py 中的test_make_request会构造一条内容为**test**的频道消息断言请求负载与预期一致并调用validate_against_openapi_schema校验其符合/zulip-outgoing-webhook的 OpenAPI schema同时验证负载中的bot_full_name、data、token等字段以及wide_message_dict未被意外修改。触发器的判定触发器trigger并非随意取值而是由服务器按消息场景计算得出。核心逻辑位于 zerver/actions/message_send.py 的get_message_triggered_bot_events为避免死循环机器人sender.is_bot发送的消息不会生成消息触发的 bot 事件频道消息中如果 bot 出现在实际被提及的用户集合mentioned_user_ids中则触发器为mention私信单人或群组私信中如果 bot 是消息的实际接收者active_user_ids则触发器为NotificationTriggers.DIRECT_MESSAGE即direct_message出现在代码块等非真实提及场景中的 bot 会被过滤掉注释中特别强调message_triggered_bot_tuples可能包含并未真正被提及的 bot。NotificationTriggers枚举定义在 zerver/models/scheduled_jobs.py除了DIRECT_MESSAGE direct_message外还包含MENTION mentioned、TOPIC_WILDCARD_MENTION、STREAM_WILDCARD_MENTION等值——注意负载中实际使用的频道提及触发器字符串是mention见message_send.py第 638 行而 OpenAPI 文档同时声明可能值为direct_message与mention。服务器如何消费你的响应process_success_responsezerver/lib/outgoing_webhook.py解析 bot 返回的 JSON 响应响应必须是合法 JSON否则抛出Invalid JSON in response错误为兼容zulip_botserver2021-05 之前版本返回的json.dumps()空的 JSON 字符串会被当作无需回复处理响应若不是 JSON 对象dict视为非法格式随后调用对应 service 的process_success提取回复内容。GenericOutgoingWebhookService.process_successzerver/lib/outgoing_webhook.py支持三种响应形式{response_not_required: true}—— 无需回复不发送任何消息{response_string: ...}—— 已废弃的回复字段作为content使用{content: ..., widget_content: {...}}—— 标准回复content为 Markdown 文本widget_content可选用于发送交互式小组件会以 JSON 字符串形式传给send_response_message。以上行为均有测试覆盖test_process_successzerver/tests/test_outgoing_webhook_interfaces.py验证了response_not_required、response_string、contentwidget_content与空响应四种情形test_process_success_response同文件第 39-67 行则验证了合法 JSON、非法 JSON 两种路径。最终回复消息通过send_response_messagezerver/lib/outgoing_webhook.py以 bot 的身份发送回原频道/私信并沿用原主题topic使用OutgoingWebhookResponse作为 client。错误处理与失败通知do_rest_callzerver/lib/outgoing_webhook.py统一处理请求执行与异常请求默认超时时间为settings.OUTGOING_WEBHOOK_TIMEOUT_SECONDS秒超时或连接错误ConnectionError、ChunkedEncodingError会通过request_retry进入outgoing_webhooks队列重试并在最终失败后通知 bot 所有者Bot is unavailable2xx 状态码进入process_success_response解析非 2xx 状态码如 407 表示配置的 URL 是私有或受限网络会向 bot 所有者发送包含状态码与响应内容的通知其他RequestException异常会记录日志、向频道回发失败消息并通知 bot 所有者。notify_bot_ownerzerver/lib/outgoing_webhook.py会向 bot 所有者发送私信内容包括触发消息链接、异常类型、失败原因、状态码及原始响应内容以repr形式包裹在代码块中。Slack 兼容格式Slack-compatible format设计动机该格式兼容 Slack 的 outgoing webhook API即 Slack 文档中的 legacy custom integration post data 参数集合。它的价值在于已有 Slack 集成的移植几乎可以零改动运行在 Zulip 上且大量第三方系统已经支持 Slack outgoing webhook因此可以直接对接 Zulip。字段转换映射表下面是文档中给出的完整映射表说明 Zulip 服务器如何把一条 Zulip 消息翻译成 Slack 兼容的 webhook 格式名称说明token一串字母数字字符可用于认证 webhook 请求每个 bot 用户使用固定的 tokenteam_idZulip 组织realm的 ID前缀 Tteam_domainZulip 组织的主机名hostnamechannel_id频道 ID前缀 Cchannel_name频道名称thread_ts消息发送时的时间戳timestamp消息发送时的时间戳user_id消息发送者的用户 ID前缀 Uuser_name发送者的全名text消息内容Markdown 格式trigger_word触发方式service_idbot 用户的 ID源码级字段映射实现SlackOutgoingWebhookService.make_requestzerver/lib/outgoing_webhook.py是上述映射的实际实现。构造的请求数据是一个元组列表list of tuples而非 JSONrequest_data [ (token, self.token), (team_id, fT{realm.id}), (team_domain, realm.host), (channel_id, fC{event[message][stream_id]}), (channel_name, event[message][display_recipient]), (thread_ts, event[message][timestamp]), (timestamp, event[message][timestamp]), (user_id, fU{event[message][sender_id]}), (user_name, event[message][sender_full_name]), (text, event[command]), (trigger_word, event[trigger]), (service_id, event[user_profile_id]), ] return self.session.post(base_url, datarequest_data)要点使用datarequest_data而非json发送因此负载以表单参数形式application/x-www-form-urlencoded提交team_id为 realm ID 加T前缀channel_id为 stream ID 加C前缀user_id为 sender ID 加U前缀team_domain是 realm 的主机名如zulip.example.com并非 Slack 式的短域名thread_ts与timestamp都取消息发送时间戳text为原始命令内容event[command]未渲染 Markdowntrigger_word取event[trigger]即mention或direct_messageservice_id取event[user_profile_id]bot 用户的 ID。源码注释中第 121-134 行保留了 Slack 官方文档对 legacy outgoing webhooks POST 数据的参考示例字段顺序与之保持一致。文档给出的完整负载示例元组列表形式[(token, v9fpCdldZIej2bco3uoUvGp06PowKFOf), (team_id, T1512), (team_domain, zulip.example.com), (channel_id, C123), (channel_name, integrations), (thread_ts, 1532078950), (timestamp, 1532078950), (user_id, U21), (user_name, Full Name), (text, **test**), (trigger_word, mention), (service_id, 27)]行为限制不支持私信SlackOutgoingWebhookService.make_request开头有一段关键检查zerver/lib/outgoing_webhook.py如果触发消息是私信event[message][type] privateSlack 兼容格式不支持服务器不会发送任何请求而是调用fail_with_message在频道里回发Failure! Slack outgoing webhooks dont support direct messages.。测试test_make_request_private_messagezerver/tests/test_outgoing_webhook_interfaces.py验证了该行为session.post不会被调用返回None且fail_with_message被触发。字段映射的测试验证test_make_request_stream_messagezerver/tests/test_outgoing_webhook_interfaces.py对频道消息事件逐项断言了 12 个字段的取值例如token abcdef、team_id T2、team_domain zulip.testserver、channel_id C123、channel_name integrations、user_id U21、user_name Sample User、text **test**、trigger_word mention、service_id 12与文档示例和源码实现完全一致。Slack 兼容格式的响应处理SlackOutgoingWebhookService.process_successzerver/lib/outgoing_webhook.py只认一个字段若响应 JSON 中存在text则将其作为回复内容发送回 Zulip否则不回复。也就是说返回{text: ...}即可让 bot 在 Zulip 中回消息。对应测试test_process_successzerver/tests/test_outgoing_webhook_interfaces.py。响应约定与常见错误排查综合两种格式bot 服务器的响应遵循以下约定成功请求如果返回了数据Zulip 会解析其中的回复内容Zulip 原生格式解析content/response_stringSlack 兼容格式解析text如果没有返回数据或返回response_not_required: true/ 空 JSON 字符串则返回空白响应不发送任何消息。失败请求Zulip 会把服务器返回的失败原因或异常消息反馈到频道/通知 bot 所有者。常见排查点响应必须是合法 JSON 且为对象否则会触发Invalid JSON in response/Invalid response format错误请求超时或连接错误会触发队列重试可检查OUTGOING_WEBHOOK_TIMEOUT_SECONDS配置与 bot 服务器进程状态非 2xx 状态码会触发失败消息与所有者通知其中 407 有专门提示URL configured for the webhook is for a private or disallowed networkSlack 兼容格式不适用于私信触发私信场景请改用 Zulip 原生格式bot 发送的消息不会再次触发 outgoing webhook防止死循环。结语理解 outgoing webhook 的负载格式是正确编写 Zulip 机器人后端的第一步Zulip 原生格式提供结构化的 JSON 消息数据含data、trigger、token与完整的message对象适合深度定制集成Slack 兼容格式则通过元组列表形式的表单参数复刻 Slack legacy outgoing webhook 协议便于移植既有集成。两者的字段构造、触发判定、响应解析与失败通知均可在 zerver/lib/outgoing_webhook.py 与 zerver/tests/test_outgoing_webhook_interfaces.py 中找到对应的源码与测试依据字段级规范以 zerver/openapi/zulip.yaml 中的/zulip-outgoing-webhook定义为最终权威。按照本文的字段映射与响应约定你可以在任意语言/框架中实现自己的 bot 服务端点。【免费下载链接】zulipZulip server and web application. Open-source team chat that helps teams stay productive and focused.项目地址: https://gitcode.com/GitHub_Trending/zu/zulip创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表