import React, { useEffect, useState } from 'react'; import { Button, Form, Header, Message, Segment } from 'semantic-ui-react'; import { useParams } from 'react-router-dom'; import { API, showError, showSuccess } from '../../helpers'; import { CHANNEL_OPTIONS } from '../../constants'; import axios from 'axios'; const EditChannel = () => { const params = useParams(); const channelId = params.id; const isEditing = channelId !== undefined; const [loading, setLoading] = useState(isEditing); const originInputs = { type: 'none', name: '', description: '', secret: '', app_id: '', account_id: '', url: '', other: '', corp_id: '', // only for corp_app agent_id: '', // only for corp_app }; const [inputs, setInputs] = useState(originInputs); const { type, name, description, secret, app_id, account_id, url, other } = inputs; const handleInputChange = (e, { name, value }) => { setInputs((inputs) => ({ ...inputs, [name]: value })); }; const loadChannel = async () => { let res = await API.get(`/api/channel/${channelId}`); const { success, message, data } = res.data; if (success) { if (data.type === 'corp_app') { const [corp_id, agent_id] = data.app_id.split('|'); data.corp_id = corp_id; data.agent_id = agent_id; } setInputs(data); } else { showError(message); } setLoading(false); }; useEffect(() => { if (isEditing) { loadChannel().then(); } }, []); const submit = async () => { if (!name) return; let res = undefined; let localInputs = { ...inputs }; switch (inputs.type) { case 'corp_app': localInputs.app_id = `${inputs.corp_id}|${inputs.agent_id}`; break; case 'bark': if (localInputs.url === '') { localInputs.url = 'https://api.day.app'; } break; case 'one_bot': if (localInputs.url.endsWith('/')) { localInputs.url = localInputs.url.slice(0, -1); } break; case 'group': let channels = localInputs.app_id.split('|'); let targets = localInputs.account_id.split('|'); if (localInputs.account_id === '') { for (let i = 0; i < channels.length - 1; i++) { localInputs.account_id += '|'; } } else if (channels.length !== targets.length) { showError( '群组通道的子通道数量与目标数量不匹配,对于不需要指定的目标请直接留空' ); return; } break; case 'custom': if (!localInputs.url.startsWith('https://')) { showError('自定义通道的 URL 必须以 https:// 开头!'); return; } try { JSON.parse(localInputs.other); }catch (e) { showError('JSON 格式错误:' + e.message); return; } break; } if (isEditing) { res = await API.put(`/api/channel/`, { ...localInputs, id: parseInt(channelId), }); } else { res = await API.post(`/api/channel`, localInputs); } const { success, message } = res.data; if (success) { if (isEditing) { showSuccess('通道信息更新成功!'); } else { showSuccess('通道创建成功!'); setInputs(originInputs); } } else { showError(message); } }; const getTelegramChatId = async () => { if (inputs.telegram_bot_token === '') { showError('请先输入 Telegram 机器人令牌!'); return; } let res = await axios.get( `https://api.telegram.org/bot${inputs.secret}/getUpdates` ); const { ok } = res.data; if (ok) { let result = res.data.result; if (result.length === 0) { showError(`请先向你的机器人发送一条任意消息!`); } else { let id = result[0]?.message?.chat?.id; id = id.toString(); setInputs((inputs) => ({ ...inputs, account_id: id })); showSuccess('会话 ID 获取成功!'); } } else { showError(`发生错误:${res.description}`); } }; const renderChannelForm = () => { switch (type) { case 'email': return ( <> 邮件推送方式(email)需要设置邮箱,请前往个人设置页面绑定邮箱地址,之后系统将自动为你创建邮箱推送通道。 ); case 'test': return ( <> 通过微信测试号进行推送,点击前往配置: 微信公众平台接口测试帐号
需要新增测试模板,模板标题推荐填写为「消息推送」,模板内容填写为:
标题:{' {{'}title.DATA{'}}'}
描述:{' {{'}description.DATA{'}}'}
内容:{' {{'}content.DATA{'}}'}
); case 'tencent_alarm': return ( <> 通过腾讯云自定义消息告警进行推送, 配置教程 ); case 'corp_app': return ( <> 通过企业微信应用号进行推送,点击前往配置: 企业微信应用管理

注意,企业微信要求配置可信 IP,步骤:应用管理 -> 自建 -> 创建应用 -> 应用设置页面下拉中找到「企业可信 IP」,点击配置 -> 设置可信域名 -> 在「可调用 JS-SDK、跳转小程序的可信域名」下面填写一个域名,然后点击「申请校验域名」,根据提示完成校验 -> 之后填写服务器 IP 地址(此 IP 地址是消息推送服务所部署在的服务器的 IP 地址,未必是上面校验域名中记录的 IP 地址)。
); case 'corp': return ( <> 通过企业微信群机器人进行推送,配置流程:选择一个群聊 -> 设置 -> 群机器人 -> 添加 -> 新建 -> 输入名字,点击添加 -> 点击复制 Webhook 地址 ); case 'lark': return ( <> 通过飞书群机器人进行推送,飞书桌面客户端的配置流程:选择一个群聊 -> 设置 -> 群机器人 -> 添加机器人 -> 自定义机器人 -> 添加( 注意选中「签名校验」)。具体参见: 飞书开放文档 ); case 'ding': return ( <> 通过钉钉群机器人进行推送,钉钉桌面客户端的配置流程:选择一个群聊 -> 群设置 -> 智能群助手 -> 添加机器人(点击右侧齿轮图标) -> 自定义 -> 添加( 注意选中「加密」)。具体参见: 钉钉开放文档 ); case 'bark': return ( <> 通过 Bark 进行推送,下载 Bark 后按提示注册设备,之后会看到一个 URL,例如 https://api.day.app/wrsVSDRANDOM/Body Text ,其中 wrsVSDRANDOM 就是你的推送 key。 ); case 'client': return ( <> 通过 WebSocket 客户端进行推送,可以使用官方客户端实现,或者根据协议自行实现。官方客户端 详见此处 ); case 'telegram': return ( <> 通过 Telegram 机器人进行消息推送。首先向 {' '} Bot Father{' '} 申请创建一个新的机器人,之后在下方输入获取到的令牌,然后点击你的机器人,随便发送一条消息,之后点击下方的「获取会话 ID」按钮,系统将自动为你填写会话 ID,最后点击保存按钮保存设置即可。 ); case 'discord': return ( <> 通过 Discord 群机器人进行推送,配置流程:选择一个 channel -> 设置 -> 整合 -> 创建 Webhook -> 点击复制 Webhook URL ); case 'one_bot': return ( <> 通过 OneBot 协议进行推送,可以使用{' '} cqhttp {' '} 等实现。 利用 OneBot 协议可以实现推送 QQ 消息。
注意,如果推送目标是群号则前面必须加上群号前缀,例如 group_123456789。
); case 'group': return ( <> 对渠道进行分组,然后在推送时选择分组进行推送,可以实现一次性推送到多个渠道的功能。

推送目标如若不填,则使用子渠道的默认推送目标。如果填写,请务必全部按顺序填写,对于不需要指定的直接留空即可,例如{' '} 123456789||@wechat,两个连续的分隔符表示跳过该渠道。
); case 'lark_app': return ( <> 通过飞书自建应用进行推送,点击前往配置: 飞书开放平台
需要为应用添加机器人能力:应用能力->添加应用能力—>机器人。
需要为应用添加消息发送权限:开发配置->权限管理->权限配置->搜索「获取与发送单聊、群组消息」->开通权限。
注意,添加完成权限后需要发布版本提交审核才能见效。
注意,推送目标的格式为: 类型:ID ,详见飞书 开发文档 中查询参数一节。
); case 'custom': return ( <> 自定义推送,目前仅支持 POST 请求,请求体为 JSON 格式。
支持以下模板变量:$title$description$content$url$to
这个页面给出了常见的第三方平台的配置实例,你可以参考这些示例进行配置。
注意,为了防止攻击者利用本功能访问内部网络,也为了你的信息安全,请求地址必须使用 HTTPS 协议。
); case 'none': return ( <> 仅保存消息,不做推送,可以在 Web 端查看,需要用户具有消息持久化的权限。 ); default: return ( <> 未知通道类型! ); } }; return ( <>
{isEditing ? '更新通道配置' : '新建消息通道'}
{renderChannelForm()}
); }; export default EditChannel;