@goodandready/dsh-messenger-gateway 0.3.20 → 0.3.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -2
- package/README.ru.md +10 -0
- package/README.zh.md +10 -0
- package/lib/adapters/discord.js +73 -11
- package/lib/adapters/slack.js +78 -12
- package/lib/adapters/telegram.js +712 -708
- package/lib/alerts.js +10 -10
- package/lib/artifacts.js +117 -117
- package/lib/ask.js +151 -151
- package/lib/client.js +424 -105
- package/lib/commands.js +74 -70
- package/lib/config.js +104 -104
- package/lib/content-guard.js +2 -2
- package/lib/documents.js +9 -9
- package/lib/file-manager.js +9 -9
- package/lib/gateway.js +1772 -1539
- package/lib/index.js +532 -500
- package/lib/locales/en.js +123 -0
- package/lib/locales/index.js +21 -0
- package/lib/locales/zh.js +123 -0
- package/lib/models.js +1 -1
- package/lib/personas.js +67 -14
- package/lib/photos.js +2 -2
- package/lib/scheduler.js +29 -12
- package/lib/stream.js +4 -2
- package/lib/text.js +1 -1
- package/lib/updater.js +304 -0
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -114,109 +114,183 @@ window.__ModuleLoader__.load({
|
|
|
114
114
|
'stats.errors': 'Delivery Errors',
|
|
115
115
|
'stats.uptime': 'Gateway Uptime',
|
|
116
116
|
'stats.active_chats': 'Active Chats in Memory',
|
|
117
|
+
// Updater
|
|
118
|
+
'updater.title': 'Plugin Self-Updater',
|
|
119
|
+
'updater.desc': 'Check npmjs registry for updates and upgrade in-place.',
|
|
120
|
+
'updater.current': 'Current version: {version}',
|
|
121
|
+
'updater.checking': 'Checking for updates…',
|
|
122
|
+
'updater.available': 'Update available: v{version}',
|
|
123
|
+
'updater.upToDate': 'Up to date',
|
|
124
|
+
'updater.btn_check': 'Check for Updates',
|
|
125
|
+
'updater.btn_update': 'Update to v{version}',
|
|
126
|
+
'updater.updating': 'Updating…',
|
|
127
|
+
'updater.success': 'Updated to v{version}! Please restart DSH service if required.',
|
|
128
|
+
'updater.failed': 'Update failed: {error}',
|
|
129
|
+
'updater.check_failed': 'Update check failed: {error}',
|
|
130
|
+
// Discord
|
|
131
|
+
discord: 'Discord Gateway',
|
|
132
|
+
discordSub: 'Discord bot and outbound webhook delivery settings.',
|
|
133
|
+
enableDiscord: 'Enable Discord',
|
|
134
|
+
disableDiscord: 'Disable Discord',
|
|
135
|
+
discordToken: 'Discord Bot Token',
|
|
136
|
+
discordTokenHint: 'From Discord Developer Portal. Leave empty to keep existing.',
|
|
137
|
+
discordWebhook: 'Discord Webhook URL',
|
|
138
|
+
discordWebhookHint: 'Incoming webhook for sending messages/notifications.',
|
|
139
|
+
discordChannels: 'Allowed Discord Channel IDs',
|
|
140
|
+
discordChannelsHint: 'Comma-separated channel IDs. Leave empty to allow all configured.',
|
|
141
|
+
// Slack
|
|
142
|
+
slack: 'Slack Gateway',
|
|
143
|
+
slackSub: 'Slack bot and incoming webhook settings.',
|
|
144
|
+
enableSlack: 'Enable Slack',
|
|
145
|
+
disableSlack: 'Disable Slack',
|
|
146
|
+
slackToken: 'Slack Bot Token (xoxb-…)',
|
|
147
|
+
slackTokenHint: 'Bot User OAuth Token. Leave empty to keep existing.',
|
|
148
|
+
slackWebhook: 'Slack Webhook URL',
|
|
149
|
+
slackWebhookHint: 'Incoming webhook URL for channel notifications.',
|
|
150
|
+
slackDefaultChannel: 'Slack Default Channel',
|
|
151
|
+
slackDefaultChannelHint: 'Default channel or member ID for outbound messages (e.g. #general or C12345).',
|
|
152
|
+
// Cron badge
|
|
153
|
+
'badge.cron_count': 'Cron: {count}',
|
|
117
154
|
}
|
|
118
155
|
|
|
119
|
-
const
|
|
156
|
+
const zh = {
|
|
120
157
|
title: 'Messenger gateway',
|
|
121
|
-
description: 'Telegram
|
|
122
|
-
'settings.loading': '
|
|
123
|
-
'settings.retry': '
|
|
124
|
-
'settings.unavailable': '
|
|
125
|
-
'header.title': '
|
|
126
|
-
'header.sub': '
|
|
127
|
-
'badge.online': 'Telegram
|
|
128
|
-
'badge.running': '
|
|
129
|
-
'badge.stopped': '
|
|
130
|
-
'badge.token_ok': '
|
|
131
|
-
'badge.token_missing': '
|
|
132
|
-
'badge.pairing_count': '
|
|
133
|
-
'badge.transport': '
|
|
134
|
-
save: '
|
|
135
|
-
saved: '
|
|
136
|
-
saving: '
|
|
137
|
-
showAdvanced: '⚙️
|
|
138
|
-
hideAdvanced: '
|
|
139
|
-
telegram: '
|
|
140
|
-
telegramSub: '
|
|
141
|
-
agent: '
|
|
142
|
-
agentSub: '
|
|
143
|
-
media: '
|
|
144
|
-
mediaSub: '
|
|
145
|
-
tts: '
|
|
146
|
-
ttsSub: '
|
|
147
|
-
advanced: '
|
|
148
|
-
botToken: '
|
|
149
|
-
botTokenHint: '
|
|
150
|
-
allowedIds: '
|
|
151
|
-
allowedIdsHint: 'ID
|
|
152
|
-
textFormat: '
|
|
153
|
-
textFormatHtml: 'Markdown → Telegram HTML (
|
|
154
|
-
textFormatPlain: '
|
|
155
|
-
disableTelegram: '
|
|
156
|
-
enableTelegram: '
|
|
157
|
-
provider: '
|
|
158
|
-
providerHint: '
|
|
159
|
-
model: '
|
|
160
|
-
modelHint: '
|
|
161
|
-
photoOnly: '
|
|
162
|
-
photoPrompt: '
|
|
163
|
-
photoRun: '
|
|
164
|
-
instruction: '
|
|
165
|
-
instructionHint: '
|
|
166
|
-
instructionPlaceholder: '
|
|
167
|
-
maxMessageLength: '
|
|
168
|
-
turnTimeout: '
|
|
169
|
-
maxDocMb: '
|
|
170
|
-
maxImageMb: '
|
|
171
|
-
maxTextKb: '
|
|
172
|
-
ttsEnable: '
|
|
173
|
-
ttsMaxChars: '
|
|
174
|
-
pollTimeout: '
|
|
175
|
-
pollInterval: '
|
|
176
|
-
idleTimeout: '
|
|
177
|
-
pairing: '
|
|
178
|
-
pairingSub: '
|
|
179
|
-
pairingEmpty: '
|
|
180
|
-
pairingApprove: '
|
|
181
|
-
pairingReject: '
|
|
182
|
-
pairingRefresh: '
|
|
183
|
-
groupsEnable: '
|
|
184
|
-
groupMention: '
|
|
185
|
-
reactionsEnable: '
|
|
186
|
-
progressEnable: '
|
|
187
|
-
statusIndicator: '
|
|
188
|
-
statusIndicatorHint: '
|
|
189
|
-
transport: '
|
|
190
|
-
transportPoll: '
|
|
158
|
+
description: 'Telegram 机器人:文本、语音、图片、文档和交互式询问。',
|
|
159
|
+
'settings.loading': '正在加载 Messenger gateway 设置…',
|
|
160
|
+
'settings.retry': '重试',
|
|
161
|
+
'settings.unavailable': '设置不可用(主机命名空间未就绪)。',
|
|
162
|
+
'header.title': 'Telegram 与多消息平台网关',
|
|
163
|
+
'header.sub': 'DeepSeek Harness 与 Telegram 之间的桥梁:会话、转向提示、论坛话题、内联按钮和可选语音。',
|
|
164
|
+
'badge.online': 'Telegram 在线 (@{username})',
|
|
165
|
+
'badge.running': '运行中',
|
|
166
|
+
'badge.stopped': '已停止',
|
|
167
|
+
'badge.token_ok': '令牌有效 ✓',
|
|
168
|
+
'badge.token_missing': '缺少令牌',
|
|
169
|
+
'badge.pairing_count': '配对: {count}',
|
|
170
|
+
'badge.transport': '传输方式: {mode}',
|
|
171
|
+
save: '保存更改',
|
|
172
|
+
saved: '设置保存成功',
|
|
173
|
+
saving: '保存中…',
|
|
174
|
+
showAdvanced: '⚙️ 高级连接与轮询设置',
|
|
175
|
+
hideAdvanced: '隐藏高级设置',
|
|
176
|
+
telegram: 'Telegram 机器人设置',
|
|
177
|
+
telegramSub: '主机器人凭证、白名单与消息发送设置。',
|
|
178
|
+
agent: '智能体与推理行为',
|
|
179
|
+
agentSub: '模型路由、附加指令与轮次超时设置。',
|
|
180
|
+
media: '媒体与文件限制',
|
|
181
|
+
mediaSub: '文档提取与图片上传限制。',
|
|
182
|
+
tts: '语音回复 (dsh-tts)',
|
|
183
|
+
ttsSub: '为 Telegram 语音消息合成语音回复。',
|
|
184
|
+
advanced: '连接与轮询内部设置',
|
|
185
|
+
botToken: 'Telegram 机器人令牌',
|
|
186
|
+
botTokenHint: '从 @BotFather 获取。留空保持现有配置。',
|
|
187
|
+
allowedIds: '允许的 Telegram 用户 ID',
|
|
188
|
+
allowedIdsHint: '逗号分隔的用户 ID 列表。留空允许所有用户(启用配对请求)。',
|
|
189
|
+
textFormat: '文本格式化渲染',
|
|
190
|
+
textFormatHtml: 'Markdown → Telegram HTML (推荐)',
|
|
191
|
+
textFormatPlain: '纯文本 (无格式化)',
|
|
192
|
+
disableTelegram: '停用 Telegram 机器人',
|
|
193
|
+
enableTelegram: '启用 Telegram 机器人',
|
|
194
|
+
provider: '提供商覆盖',
|
|
195
|
+
providerHint: '留空使用 DSH 默认提供商',
|
|
196
|
+
model: '模型覆盖',
|
|
197
|
+
modelHint: '留空使用 DSH 默认模型',
|
|
198
|
+
photoOnly: '无附言图片处理方式',
|
|
199
|
+
photoPrompt: '等待问题 (推荐)',
|
|
200
|
+
photoRun: '立即回复',
|
|
201
|
+
instruction: '额外智能体指令',
|
|
202
|
+
instructionHint: '添加到每条来自网关的消息前',
|
|
203
|
+
instructionPlaceholder: '留空 = 内置指令',
|
|
204
|
+
maxMessageLength: '最大回复长度 (字符)',
|
|
205
|
+
turnTimeout: '智能体轮次超时 (秒)',
|
|
206
|
+
maxDocMb: '最大文档大小 (MB)',
|
|
207
|
+
maxImageMb: '最大图片大小 (MB)',
|
|
208
|
+
maxTextKb: '文档提取最大文本量 (KB)',
|
|
209
|
+
ttsEnable: '启用语音回复 (需 dsh-tts)',
|
|
210
|
+
ttsMaxChars: '每条语音消息最大字数',
|
|
211
|
+
pollTimeout: '长轮询超时 (秒)',
|
|
212
|
+
pollInterval: '长轮询间隔 (毫秒)',
|
|
213
|
+
idleTimeout: '空闲会话超时 (秒)',
|
|
214
|
+
pairing: '配对请求与授权',
|
|
215
|
+
pairingSub: '受限白名单下授权新用户。',
|
|
216
|
+
pairingEmpty: '暂无待处理配对码',
|
|
217
|
+
pairingApprove: '批准',
|
|
218
|
+
pairingReject: '拒绝',
|
|
219
|
+
pairingRefresh: '刷新列表',
|
|
220
|
+
groupsEnable: '在群组和超级群组中响应',
|
|
221
|
+
groupMention: '群组中需 @提及、回复或 /命令',
|
|
222
|
+
reactionsEnable: '处理中添加 👀 表情反应',
|
|
223
|
+
progressEnable: '进度状态提示 (思考中 → 删除 → 最终回复)',
|
|
224
|
+
statusIndicator: '机器人状态描述指示器 (Online/Offline)',
|
|
225
|
+
statusIndicatorHint: '通过 Telegram API 设置机器人简介(所有人可见)。',
|
|
226
|
+
transport: 'Telegram 传输模式',
|
|
227
|
+
transportPoll: '长轮询 (getUpdates)',
|
|
191
228
|
transportWebhook: 'Webhook (HTTPS)',
|
|
192
|
-
webhookUrl: '
|
|
193
|
-
webhookUrlHint: '
|
|
194
|
-
webhookSecret: '
|
|
195
|
-
webhookSecretHint: '
|
|
196
|
-
sessionScope: '
|
|
197
|
-
sessionScopeChat: '
|
|
198
|
-
sessionScopeUser: '
|
|
199
|
-
voiceMode: '
|
|
200
|
-
voiceModeMirror: '
|
|
201
|
-
voiceModeAlways: '
|
|
202
|
-
voiceModeOff: '
|
|
203
|
-
notifyBridge: '
|
|
204
|
-
notifyBridgeHint: '
|
|
205
|
-
notifyHome: '
|
|
206
|
-
quickActions: '
|
|
207
|
-
artifactPreviews: '
|
|
208
|
-
'diag.title': '⚡
|
|
209
|
-
'diag.desc': '
|
|
210
|
-
'diag.smoke_btn': '
|
|
211
|
-
'diag.smoke_testing': '
|
|
212
|
-
'diag.smoke_ok': '
|
|
213
|
-
'diag.smoke_err': '
|
|
214
|
-
'stats.title': '📊
|
|
215
|
-
'stats.desc': '
|
|
216
|
-
'stats.sent': '
|
|
217
|
-
'stats.errors': '
|
|
218
|
-
'stats.uptime': '
|
|
219
|
-
'stats.active_chats': '
|
|
229
|
+
webhookUrl: '公开 Webhook HTTPS 地址',
|
|
230
|
+
webhookUrlHint: 'Telegram 投递更新的公开端点',
|
|
231
|
+
webhookSecret: 'Webhook 密钥令牌',
|
|
232
|
+
webhookSecretHint: '留空保持当前配置的密钥',
|
|
233
|
+
sessionScope: '群组会话范围',
|
|
234
|
+
sessionScopeChat: '每个聊天/话题共享',
|
|
235
|
+
sessionScopeUser: '每用户独立会话 (推荐)',
|
|
236
|
+
voiceMode: '语音回复模式',
|
|
237
|
+
voiceModeMirror: '镜像输入语音 (仅语音输入时回复语音)',
|
|
238
|
+
voiceModeAlways: '始终合成语音回复',
|
|
239
|
+
voiceModeOff: '关闭 (除非通过 /voice 启用)',
|
|
240
|
+
notifyBridge: '通知网桥 → Telegram Home',
|
|
241
|
+
notifyBridgeHint: '将 Web 会话的 task_done/error 事件转发至指定频道/话题',
|
|
242
|
+
notifyHome: '目标频道名称 (Home)',
|
|
243
|
+
quickActions: '快捷操作键盘 (/new, /stop, /voice, /status)',
|
|
244
|
+
artifactPreviews: '在 Telegram 中预览图表与表格',
|
|
245
|
+
'diag.title': '⚡ 诊断与连通性检查 (Smoke test)',
|
|
246
|
+
'diag.desc': '检查 Telegram Bot API 连接、测量延迟并验证机器人名称。',
|
|
247
|
+
'diag.smoke_btn': '测试 Telegram 连接 (Ping)',
|
|
248
|
+
'diag.smoke_testing': '正在连接 Telegram API…',
|
|
249
|
+
'diag.smoke_ok': 'Telegram API 连接成功!延迟: {latency} 毫秒 (@{username}, ID: {id})',
|
|
250
|
+
'diag.smoke_err': '连通性检查失败: {error}',
|
|
251
|
+
'stats.title': '📊 网关遥测与计数器',
|
|
252
|
+
'stats.desc': '运行中网关进程的实时统计。',
|
|
253
|
+
'stats.sent': '已发送消息',
|
|
254
|
+
'stats.errors': '发送错误数',
|
|
255
|
+
'stats.uptime': '运行时间',
|
|
256
|
+
'stats.active_chats': '内存中活跃聊天数',
|
|
257
|
+
// Updater
|
|
258
|
+
'updater.title': '插件自更新',
|
|
259
|
+
'updater.desc': '从 npmjs 仓库检查更新并直接就地升级。',
|
|
260
|
+
'updater.current': '当前版本: {version}',
|
|
261
|
+
'updater.checking': '正在检查更新…',
|
|
262
|
+
'updater.available': '发现新版本: v{version}',
|
|
263
|
+
'updater.upToDate': '已是最新版本',
|
|
264
|
+
'updater.btn_check': '检查更新',
|
|
265
|
+
'updater.btn_update': '更新至 v{version}',
|
|
266
|
+
'updater.updating': '正在更新…',
|
|
267
|
+
'updater.success': '已更新至 v{version}!如需生效请重启 DSH 服务。',
|
|
268
|
+
'updater.failed': '更新失败: {error}',
|
|
269
|
+
'updater.check_failed': '检查更新失败: {error}',
|
|
270
|
+
// Discord
|
|
271
|
+
discord: 'Discord 网关',
|
|
272
|
+
discordSub: 'Discord 机器人与出站 Webhook 设置。',
|
|
273
|
+
enableDiscord: '启用 Discord',
|
|
274
|
+
disableDiscord: '停用 Discord',
|
|
275
|
+
discordToken: 'Discord 机器人令牌',
|
|
276
|
+
discordTokenHint: '从 Discord 开发者平台获取。留空保持现有配置。',
|
|
277
|
+
discordWebhook: 'Discord Webhook URL',
|
|
278
|
+
discordWebhookHint: '用于发送消息或通知的传入 Webhook。',
|
|
279
|
+
discordChannels: '允许的 Discord 频道 ID',
|
|
280
|
+
discordChannelsHint: '逗号分隔的频道 ID。留空允许所有已配置频道。',
|
|
281
|
+
// Slack
|
|
282
|
+
slack: 'Slack 网关',
|
|
283
|
+
slackSub: 'Slack 机器人与 Webhook 设置。',
|
|
284
|
+
enableSlack: '启用 Slack',
|
|
285
|
+
disableSlack: '停用 Slack',
|
|
286
|
+
slackToken: 'Slack 机器人令牌 (xoxb-…)',
|
|
287
|
+
slackTokenHint: 'Bot 用户 OAuth 令牌。留空保持现有配置。',
|
|
288
|
+
slackWebhook: 'Slack Webhook URL',
|
|
289
|
+
slackWebhookHint: '用于频道通知的传入 Webhook URL。',
|
|
290
|
+
slackDefaultChannel: 'Slack 默认频道',
|
|
291
|
+
slackDefaultChannelHint: '出站消息的默认频道或成员 ID (例如 #general 或 C12345)。',
|
|
292
|
+
// Cron badge
|
|
293
|
+
'badge.cron_count': '定时任务: {count}',
|
|
220
294
|
}
|
|
221
295
|
|
|
222
296
|
function useActiveLocale(ctx) {
|
|
@@ -234,7 +308,7 @@ window.__ModuleLoader__.load({
|
|
|
234
308
|
}
|
|
235
309
|
|
|
236
310
|
function makeT(locale) {
|
|
237
|
-
const DICT = String(locale || '').startsWith('
|
|
311
|
+
const DICT = String(locale || '').startsWith('zh') ? zh : en
|
|
238
312
|
return (key, params) => {
|
|
239
313
|
let text = DICT[key] || en[key] || key
|
|
240
314
|
if (params && typeof params === 'object') {
|
|
@@ -369,11 +443,13 @@ window.__ModuleLoader__.load({
|
|
|
369
443
|
)
|
|
370
444
|
}
|
|
371
445
|
|
|
372
|
-
const SETTINGS_KEYS = ['enabled', 'telegram', 'agent', 'media', 'tts']
|
|
446
|
+
const SETTINGS_KEYS = ['enabled', 'telegram', 'discord', 'slack', 'agent', 'media', 'tts']
|
|
373
447
|
|
|
374
448
|
function draftFromStored(s) {
|
|
375
449
|
const src = (s && typeof s === 'object') ? s : {}
|
|
376
450
|
const t = src.telegram || {}
|
|
451
|
+
const d = src.discord || {}
|
|
452
|
+
const sl = src.slack || {}
|
|
377
453
|
const a = src.agent || {}
|
|
378
454
|
const m = src.media || {}
|
|
379
455
|
const tts = src.tts || {}
|
|
@@ -411,6 +487,18 @@ window.__ModuleLoader__.load({
|
|
|
411
487
|
botToken: t.botToken || '',
|
|
412
488
|
webhookSecret: t.webhookSecret || '',
|
|
413
489
|
},
|
|
490
|
+
discord: {
|
|
491
|
+
enabled: d.enabled === true,
|
|
492
|
+
botToken: d.botToken || '',
|
|
493
|
+
webhookUrl: d.webhookUrl || '',
|
|
494
|
+
allowedChannelIds: Array.isArray(d.allowedChannelIds) ? d.allowedChannelIds : [],
|
|
495
|
+
},
|
|
496
|
+
slack: {
|
|
497
|
+
enabled: sl.enabled === true,
|
|
498
|
+
botToken: sl.botToken || '',
|
|
499
|
+
webhookUrl: sl.webhookUrl || '',
|
|
500
|
+
defaultChannel: sl.defaultChannel || '',
|
|
501
|
+
},
|
|
414
502
|
agent: {
|
|
415
503
|
provider: a.provider || '',
|
|
416
504
|
model: a.model || '',
|
|
@@ -441,6 +529,18 @@ window.__ModuleLoader__.load({
|
|
|
441
529
|
const [token, setToken] = React.useState('')
|
|
442
530
|
const [webhookSecret, setWebhookSecret] = React.useState('')
|
|
443
531
|
const [allowText, setAllowText] = React.useState('')
|
|
532
|
+
const [discordToken, setDiscordToken] = React.useState('')
|
|
533
|
+
const [slackToken, setSlackToken] = React.useState('')
|
|
534
|
+
const [discordChannelsText, setDiscordChannelsText] = React.useState('')
|
|
535
|
+
const [updateState, setUpdateState] = React.useState({
|
|
536
|
+
checking: false,
|
|
537
|
+
updating: false,
|
|
538
|
+
currentVersion: '',
|
|
539
|
+
latestVersion: '',
|
|
540
|
+
updateAvailable: false,
|
|
541
|
+
error: '',
|
|
542
|
+
notice: '',
|
|
543
|
+
})
|
|
444
544
|
const [pending, setPending] = React.useState([])
|
|
445
545
|
const [status, setStatus] = React.useState(null)
|
|
446
546
|
const [smokeResult, setSmokeResult] = React.useState(null)
|
|
@@ -472,6 +572,7 @@ window.__ModuleLoader__.load({
|
|
|
472
572
|
if (snapStatus === 'ready' && cfg === null) {
|
|
473
573
|
setCfg(draftFromStored(stored))
|
|
474
574
|
setAllowText(idsToText(stored?.telegram?.allowedUserIds))
|
|
575
|
+
setDiscordChannelsText(idsToText(stored?.discord?.allowedChannelIds))
|
|
475
576
|
}
|
|
476
577
|
}, [snapStatus, stored, cfg])
|
|
477
578
|
|
|
@@ -503,6 +604,61 @@ window.__ModuleLoader__.load({
|
|
|
503
604
|
return () => clearInterval(timer)
|
|
504
605
|
}, [loadStatus, loadPairing])
|
|
505
606
|
|
|
607
|
+
const loadUpdateStatus = React.useCallback(async () => {
|
|
608
|
+
setUpdateState((s) => ({ ...s, checking: true, error: '' }))
|
|
609
|
+
try {
|
|
610
|
+
const res = await fetch(`${ROUTE_PREFIX}/update`, { cache: 'no-store' })
|
|
611
|
+
const data = await res.json().catch(() => ({}))
|
|
612
|
+
if (res.ok && data.ok) {
|
|
613
|
+
setUpdateState((s) => ({
|
|
614
|
+
...s,
|
|
615
|
+
checking: false,
|
|
616
|
+
currentVersion: data.currentVersion || '',
|
|
617
|
+
latestVersion: data.latestVersion || '',
|
|
618
|
+
updateAvailable: !!data.updateAvailable,
|
|
619
|
+
}))
|
|
620
|
+
} else {
|
|
621
|
+
setUpdateState((s) => ({ ...s, checking: false, error: data.error || `HTTP ${res.status}` }))
|
|
622
|
+
}
|
|
623
|
+
} catch (e) {
|
|
624
|
+
setUpdateState((s) => ({ ...s, checking: false, error: String(e.message || e) }))
|
|
625
|
+
}
|
|
626
|
+
}, [])
|
|
627
|
+
|
|
628
|
+
const handleTriggerUpdate = async () => {
|
|
629
|
+
if (updateState.updating) return
|
|
630
|
+
setUpdateState((s) => ({ ...s, updating: true, error: '', notice: '' }))
|
|
631
|
+
try {
|
|
632
|
+
const res = await fetch(`${ROUTE_PREFIX}/update`, {
|
|
633
|
+
method: 'POST',
|
|
634
|
+
credentials: 'same-origin',
|
|
635
|
+
headers: {
|
|
636
|
+
'Content-Type': 'application/json',
|
|
637
|
+
'x-dsh-plugin-update': '1',
|
|
638
|
+
},
|
|
639
|
+
})
|
|
640
|
+
const data = await res.json().catch(() => ({}))
|
|
641
|
+
if (!res.ok || !data.ok) throw new Error(data.error || `HTTP ${res.status}`)
|
|
642
|
+
setUpdateState((s) => ({
|
|
643
|
+
...s,
|
|
644
|
+
updating: false,
|
|
645
|
+
updateAvailable: false,
|
|
646
|
+
currentVersion: data.updatedVersion || s.latestVersion,
|
|
647
|
+
notice: t('updater.success', { version: data.updatedVersion || s.latestVersion }),
|
|
648
|
+
}))
|
|
649
|
+
} catch (e) {
|
|
650
|
+
setUpdateState((s) => ({
|
|
651
|
+
...s,
|
|
652
|
+
updating: false,
|
|
653
|
+
error: t('updater.failed', { error: String(e.message || e) }),
|
|
654
|
+
}))
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
React.useEffect(() => {
|
|
659
|
+
loadUpdateStatus()
|
|
660
|
+
}, [loadUpdateStatus])
|
|
661
|
+
|
|
506
662
|
const handleSmoke = async () => {
|
|
507
663
|
setBusy('smoke')
|
|
508
664
|
setErr('')
|
|
@@ -531,6 +687,8 @@ window.__ModuleLoader__.load({
|
|
|
531
687
|
...base,
|
|
532
688
|
...patch,
|
|
533
689
|
telegram: { ...base.telegram, ...(patch.telegram || {}) },
|
|
690
|
+
discord: { ...base.discord, ...(patch.discord || {}) },
|
|
691
|
+
slack: { ...base.slack, ...(patch.slack || {}) },
|
|
534
692
|
tts: { ...base.tts, ...(patch.tts || {}) },
|
|
535
693
|
agent: { ...base.agent, ...(patch.agent || {}) },
|
|
536
694
|
media: { ...base.media, ...(patch.media || {}) },
|
|
@@ -546,6 +704,9 @@ window.__ModuleLoader__.load({
|
|
|
546
704
|
if (token.trim()) next.telegram = { ...next.telegram, botToken: token.trim() }
|
|
547
705
|
if (webhookSecret.trim()) next.telegram = { ...next.telegram, webhookSecret: webhookSecret.trim() }
|
|
548
706
|
next.telegram.allowedUserIds = textToIds(allowText)
|
|
707
|
+
if (discordToken.trim()) next.discord = { ...next.discord, botToken: discordToken.trim() }
|
|
708
|
+
if (slackToken.trim()) next.slack = { ...next.slack, botToken: slackToken.trim() }
|
|
709
|
+
next.discord.allowedChannelIds = textToIds(discordChannelsText)
|
|
549
710
|
|
|
550
711
|
const broken = []
|
|
551
712
|
for (const key of SETTINGS_KEYS) {
|
|
@@ -564,6 +725,8 @@ window.__ModuleLoader__.load({
|
|
|
564
725
|
setCfg(next)
|
|
565
726
|
setToken('')
|
|
566
727
|
setWebhookSecret('')
|
|
728
|
+
setDiscordToken('')
|
|
729
|
+
setSlackToken('')
|
|
567
730
|
setMsg(t('saved'))
|
|
568
731
|
setTimeout(() => setMsg(''), 3000)
|
|
569
732
|
await loadPairing()
|
|
@@ -628,6 +791,13 @@ window.__ModuleLoader__.load({
|
|
|
628
791
|
{ className: 'msgw-badge msgw-badge-warn' },
|
|
629
792
|
t('badge.pairing_count', { count: pendingCount })
|
|
630
793
|
)
|
|
794
|
+
: null,
|
|
795
|
+
(status?.scheduledCount > 0)
|
|
796
|
+
? React.createElement(
|
|
797
|
+
'span',
|
|
798
|
+
{ className: 'msgw-badge msgw-badge-ok' },
|
|
799
|
+
t('badge.cron_count', { count: status.scheduledCount })
|
|
800
|
+
)
|
|
631
801
|
: null
|
|
632
802
|
),
|
|
633
803
|
React.createElement('div', { className: 'msgw-page-sub' }, t('header.sub'))
|
|
@@ -637,6 +807,60 @@ window.__ModuleLoader__.load({
|
|
|
637
807
|
err ? React.createElement('div', { className: 'msgw-alert-err' }, err) : null,
|
|
638
808
|
msg ? React.createElement('div', { className: 'msgw-alert-ok' }, msg) : null,
|
|
639
809
|
|
|
810
|
+
// Self-Updater Card
|
|
811
|
+
React.createElement(
|
|
812
|
+
'div',
|
|
813
|
+
{ className: 'msgw-section-card' },
|
|
814
|
+
React.createElement(
|
|
815
|
+
'div',
|
|
816
|
+
{ className: 'msgw-section-title' },
|
|
817
|
+
React.createElement('span', null, `🚀 ${t('updater.title')}`),
|
|
818
|
+
React.createElement(
|
|
819
|
+
'div',
|
|
820
|
+
{ className: 'msgw-row' },
|
|
821
|
+
React.createElement(
|
|
822
|
+
'button',
|
|
823
|
+
{
|
|
824
|
+
type: 'button',
|
|
825
|
+
className: 'msgw-btn msgw-btn-mini',
|
|
826
|
+
disabled: updateState.checking || updateState.updating,
|
|
827
|
+
onClick: loadUpdateStatus,
|
|
828
|
+
},
|
|
829
|
+
updateState.checking ? t('updater.checking') : t('updater.btn_check')
|
|
830
|
+
),
|
|
831
|
+
updateState.updateAvailable
|
|
832
|
+
? React.createElement(
|
|
833
|
+
'button',
|
|
834
|
+
{
|
|
835
|
+
type: 'button',
|
|
836
|
+
className: 'msgw-btn msgw-btn-mini msgw-btn-primary',
|
|
837
|
+
disabled: updateState.updating,
|
|
838
|
+
onClick: handleTriggerUpdate,
|
|
839
|
+
},
|
|
840
|
+
updateState.updating ? t('updater.updating') : t('updater.btn_update', { version: updateState.latestVersion })
|
|
841
|
+
)
|
|
842
|
+
: null
|
|
843
|
+
)
|
|
844
|
+
),
|
|
845
|
+
React.createElement('div', { className: 'msgw-section-desc' }, t('updater.desc')),
|
|
846
|
+
React.createElement(
|
|
847
|
+
'div',
|
|
848
|
+
{ className: 'msgw-row', style: { fontSize: '13px' } },
|
|
849
|
+
React.createElement('span', { style: { color: 'var(--dsw-alias-label-secondary)' } },
|
|
850
|
+
t('updater.current', { version: updateState.currentVersion || status?.version || '...' })
|
|
851
|
+
),
|
|
852
|
+
updateState.checking
|
|
853
|
+
? React.createElement('span', { className: 'msgw-badge msgw-badge-warn' }, t('updater.checking'))
|
|
854
|
+
: updateState.updateAvailable
|
|
855
|
+
? React.createElement('span', { className: 'msgw-badge msgw-badge-warn' }, t('updater.available', { version: updateState.latestVersion }))
|
|
856
|
+
: updateState.currentVersion && !updateState.error
|
|
857
|
+
? React.createElement('span', { className: 'msgw-badge msgw-badge-ok' }, `✓ ${t('updater.upToDate')}`)
|
|
858
|
+
: null
|
|
859
|
+
),
|
|
860
|
+
updateState.notice ? React.createElement('div', { className: 'msgw-alert-ok' }, updateState.notice) : null,
|
|
861
|
+
updateState.error ? React.createElement('div', { className: 'msgw-alert-err' }, updateState.error) : null
|
|
862
|
+
),
|
|
863
|
+
|
|
640
864
|
// Card 1: Telegram Connection & Bot
|
|
641
865
|
React.createElement(
|
|
642
866
|
'div',
|
|
@@ -949,6 +1173,101 @@ window.__ModuleLoader__.load({
|
|
|
949
1173
|
)
|
|
950
1174
|
),
|
|
951
1175
|
|
|
1176
|
+
// Card: Discord Gateway
|
|
1177
|
+
React.createElement(
|
|
1178
|
+
'div',
|
|
1179
|
+
{ className: 'msgw-section-card' },
|
|
1180
|
+
React.createElement(
|
|
1181
|
+
'div',
|
|
1182
|
+
{ className: 'msgw-section-title' },
|
|
1183
|
+
React.createElement('span', null, `🎮 ${t('discord')}`),
|
|
1184
|
+
React.createElement(
|
|
1185
|
+
'button',
|
|
1186
|
+
{
|
|
1187
|
+
type: 'button',
|
|
1188
|
+
className: `msgw-btn msgw-btn-mini ${cfg.discord?.enabled ? 'msgw-btn-danger' : 'msgw-btn-primary'}`,
|
|
1189
|
+
disabled: !!busy,
|
|
1190
|
+
onClick: () => save({ discord: { enabled: !cfg.discord?.enabled } }),
|
|
1191
|
+
},
|
|
1192
|
+
cfg.discord?.enabled ? t('disableDiscord') : t('enableDiscord')
|
|
1193
|
+
)
|
|
1194
|
+
),
|
|
1195
|
+
React.createElement('div', { className: 'msgw-section-desc' }, t('discordSub')),
|
|
1196
|
+
React.createElement(Field, { label: t('discordToken'), hint: t('discordTokenHint') },
|
|
1197
|
+
React.createElement('input', {
|
|
1198
|
+
type: 'password',
|
|
1199
|
+
className: 'msgw-input',
|
|
1200
|
+
value: discordToken,
|
|
1201
|
+
placeholder: (status?.config?.discord?.botTokenConfigured || cfg.discord?.botTokenConfigured) ? '••••••••••••••••' : 'Discord Bot Token',
|
|
1202
|
+
onChange: (e) => setDiscordToken(e.target.value),
|
|
1203
|
+
})
|
|
1204
|
+
),
|
|
1205
|
+
React.createElement(Field, { label: t('discordWebhook'), hint: t('discordWebhookHint') },
|
|
1206
|
+
React.createElement('input', {
|
|
1207
|
+
className: 'msgw-input',
|
|
1208
|
+
value: cfg.discord?.webhookUrl || '',
|
|
1209
|
+
placeholder: 'https://discord.com/api/webhooks/...',
|
|
1210
|
+
onChange: (e) => setCfg({ ...cfg, discord: { ...cfg.discord, webhookUrl: e.target.value } }),
|
|
1211
|
+
})
|
|
1212
|
+
),
|
|
1213
|
+
React.createElement(Field, { label: t('discordChannels'), hint: t('discordChannelsHint') },
|
|
1214
|
+
React.createElement('textarea', {
|
|
1215
|
+
className: 'msgw-textarea',
|
|
1216
|
+
value: discordChannelsText,
|
|
1217
|
+
onChange: (e) => setDiscordChannelsText(e.target.value),
|
|
1218
|
+
rows: 2,
|
|
1219
|
+
placeholder: '123456789012345678, 987654321098765432',
|
|
1220
|
+
})
|
|
1221
|
+
)
|
|
1222
|
+
),
|
|
1223
|
+
|
|
1224
|
+
// Card: Slack Gateway
|
|
1225
|
+
React.createElement(
|
|
1226
|
+
'div',
|
|
1227
|
+
{ className: 'msgw-section-card' },
|
|
1228
|
+
React.createElement(
|
|
1229
|
+
'div',
|
|
1230
|
+
{ className: 'msgw-section-title' },
|
|
1231
|
+
React.createElement('span', null, `💼 ${t('slack')}`),
|
|
1232
|
+
React.createElement(
|
|
1233
|
+
'button',
|
|
1234
|
+
{
|
|
1235
|
+
type: 'button',
|
|
1236
|
+
className: `msgw-btn msgw-btn-mini ${cfg.slack?.enabled ? 'msgw-btn-danger' : 'msgw-btn-primary'}`,
|
|
1237
|
+
disabled: !!busy,
|
|
1238
|
+
onClick: () => save({ slack: { enabled: !cfg.slack?.enabled } }),
|
|
1239
|
+
},
|
|
1240
|
+
cfg.slack?.enabled ? t('disableSlack') : t('enableSlack')
|
|
1241
|
+
)
|
|
1242
|
+
),
|
|
1243
|
+
React.createElement('div', { className: 'msgw-section-desc' }, t('slackSub')),
|
|
1244
|
+
React.createElement(Field, { label: t('slackToken'), hint: t('slackTokenHint') },
|
|
1245
|
+
React.createElement('input', {
|
|
1246
|
+
type: 'password',
|
|
1247
|
+
className: 'msgw-input',
|
|
1248
|
+
value: slackToken,
|
|
1249
|
+
placeholder: (status?.config?.slack?.botTokenConfigured || cfg.slack?.botTokenConfigured) ? '••••••••••••••••' : 'xoxb-...',
|
|
1250
|
+
onChange: (e) => setSlackToken(e.target.value),
|
|
1251
|
+
})
|
|
1252
|
+
),
|
|
1253
|
+
React.createElement(Field, { label: t('slackWebhook'), hint: t('slackWebhookHint') },
|
|
1254
|
+
React.createElement('input', {
|
|
1255
|
+
className: 'msgw-input',
|
|
1256
|
+
value: cfg.slack?.webhookUrl || '',
|
|
1257
|
+
placeholder: 'https://hooks.slack.com/services/...',
|
|
1258
|
+
onChange: (e) => setCfg({ ...cfg, slack: { ...cfg.slack, webhookUrl: e.target.value } }),
|
|
1259
|
+
})
|
|
1260
|
+
),
|
|
1261
|
+
React.createElement(Field, { label: t('slackDefaultChannel'), hint: t('slackDefaultChannelHint') },
|
|
1262
|
+
React.createElement('input', {
|
|
1263
|
+
className: 'msgw-input',
|
|
1264
|
+
value: cfg.slack?.defaultChannel || '',
|
|
1265
|
+
placeholder: '#general or C12345678',
|
|
1266
|
+
onChange: (e) => setCfg({ ...cfg, slack: { ...cfg.slack, defaultChannel: e.target.value } }),
|
|
1267
|
+
})
|
|
1268
|
+
)
|
|
1269
|
+
),
|
|
1270
|
+
|
|
952
1271
|
// Card 6: Pairing Requests
|
|
953
1272
|
React.createElement(
|
|
954
1273
|
'div',
|
|
@@ -1243,20 +1562,20 @@ window.__ModuleLoader__.load({
|
|
|
1243
1562
|
try {
|
|
1244
1563
|
const loc = ctx?.get?.('locale') || ctx?.locale
|
|
1245
1564
|
if (loc?.register) {
|
|
1246
|
-
loc.register(NS, { en,
|
|
1565
|
+
loc.register(NS, { en, zh })
|
|
1247
1566
|
}
|
|
1248
1567
|
} catch (taken) {
|
|
1249
|
-
console.warn('[dsh-messenger-gateway]
|
|
1568
|
+
console.warn('[dsh-messenger-gateway] dictionary already registered:', taken && taken.message)
|
|
1250
1569
|
}
|
|
1251
1570
|
}, 'dsh-messenger-gateway: dictionaries')
|
|
1252
1571
|
} else {
|
|
1253
1572
|
const loc = ctx?.get?.('locale') || ctx?.locale
|
|
1254
1573
|
if (loc?.register) {
|
|
1255
|
-
loc.register(NS, { en,
|
|
1574
|
+
loc.register(NS, { en, zh })
|
|
1256
1575
|
}
|
|
1257
1576
|
}
|
|
1258
1577
|
} catch (err) {
|
|
1259
|
-
console.warn('[dsh-messenger-gateway]
|
|
1578
|
+
console.warn('[dsh-messenger-gateway] dictionary registration error:', err && err.message)
|
|
1260
1579
|
}
|
|
1261
1580
|
|
|
1262
1581
|
if (typeof ctx.effect === 'function') {
|