@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/README.md
CHANGED
|
@@ -1,6 +1,44 @@
|
|
|
1
|
-
# @goodandready/dsh-messenger-gateway
|
|
1
|
+
# 📦 @goodandready/dsh-messenger-gateway
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<div align="center">
|
|
4
|
+
|
|
5
|
+
<h3>Telegram Messenger Bridge with Interactive Buttons, Forum Topics & Voice Notes for DeepSeek Harness</h3>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://www.npmjs.com/package/@goodandready/dsh-messenger-gateway"><img src="https://img.shields.io/npm/v/@goodandready/dsh-messenger-gateway.svg?style=for-the-badge&color=6366f1&labelColor=1e1b4b" alt="npm version"></a>
|
|
9
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-10b981.svg?style=for-the-badge&color=10b981&labelColor=064e3b" alt="license"></a>
|
|
10
|
+
<a href="https://github.com/topics/dsh-plugin"><img src="https://img.shields.io/badge/DSH-Plugin-8b5cf6.svg?style=for-the-badge&labelColor=2e1065" alt="DSH Plugin"></a>
|
|
11
|
+
<a href="https://nodejs.org"><img src="https://img.shields.io/badge/Node-20%2B-f59e0b.svg?style=for-the-badge&labelColor=451a03" alt="Node version"></a>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
<p align="center">
|
|
15
|
+
<a href="https://goodandready.app/"><img src="https://img.shields.io/badge/All_Author_Projects-goodandready.app-ff4500.svg?style=for-the-badge&logo=rocket&logoColor=white&labelColor=1a1a2e" alt="All Author Projects"></a>
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
<p align="center">
|
|
19
|
+
<a href="README.md"><b>🇬🇧 English</b></a> •
|
|
20
|
+
<a href="README.ru.md"><b>🇷🇺 Русский</b></a> •
|
|
21
|
+
<a href="README.zh.md"><b>🇨🇳 中文说明</b></a>
|
|
22
|
+
</p>
|
|
23
|
+
|
|
24
|
+
<table align="center">
|
|
25
|
+
<tr>
|
|
26
|
+
<td align="center">
|
|
27
|
+
⭐ <strong>If you like this plugin, please star it on GitHub</strong> — it shows me that the plugin is useful to you and motivates me to keep developing it.
|
|
28
|
+
<br><br>
|
|
29
|
+
🐛 <strong>If you find a bug or would like to request a feature</strong>, open a GitHub issue in any language — I will review your proposal and implement useful suggestions in a future plugin version.
|
|
30
|
+
</td>
|
|
31
|
+
</tr>
|
|
32
|
+
</table>
|
|
33
|
+
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## ⚡ Overview
|
|
39
|
+
|
|
40
|
+
**`dsh-messenger-gateway`** provides an enterprise-grade, multi-transport messaging gateway for **DeepSeek Harness** agents.
|
|
41
|
+
Talk to your Harness agent directly from Telegram: interactive keyboard buttons, forum topic sessions, private user workspaces, pairing codes, and optional spoken voice replies.
|
|
4
42
|
|
|
5
43
|
Talk to your Harness agent from Telegram: text, voice, photos, documents, inline buttons, named homes, and optional spoken replies.
|
|
6
44
|
|
package/README.ru.md
CHANGED
|
@@ -21,6 +21,16 @@
|
|
|
21
21
|
<a href="README.zh.md"><b>🇨🇳 中文说明</b></a>
|
|
22
22
|
</p>
|
|
23
23
|
|
|
24
|
+
<table align="center">
|
|
25
|
+
<tr>
|
|
26
|
+
<td align="center">
|
|
27
|
+
⭐ <strong>Если вам нравится этот плагин, поставьте ему звезду на GitHub</strong> — это покажет мне, что плагин вам полезен, и будет мотивировать меня развивать его дальше.
|
|
28
|
+
<br><br>
|
|
29
|
+
🐛 <strong>Если вы нашли баг или хотите предложить новый функционал</strong>, создайте issue на GitHub на любом языке — я рассмотрю ваше предложение и реализую полезные идеи в одной из следующих версий плагина.
|
|
30
|
+
</td>
|
|
31
|
+
</tr>
|
|
32
|
+
</table>
|
|
33
|
+
|
|
24
34
|
</div>
|
|
25
35
|
|
|
26
36
|
---
|
package/README.zh.md
CHANGED
|
@@ -21,6 +21,16 @@
|
|
|
21
21
|
<a href="README.zh.md"><b>🇨🇳 中文说明</b></a>
|
|
22
22
|
</p>
|
|
23
23
|
|
|
24
|
+
<table align="center">
|
|
25
|
+
<tr>
|
|
26
|
+
<td align="center">
|
|
27
|
+
⭐ <strong>如果您喜欢这个插件,请在 GitHub 上为它点亮 Star</strong> — 这能让我知道插件对您有用,并鼓励我继续开发和维护它。
|
|
28
|
+
<br><br>
|
|
29
|
+
🐛 <strong>如果您发现 Bug 或希望增加功能</strong>,请使用任意语言在 GitHub 上提交 Issue — 我会评估您的建议,并在后续版本中实现有价值的改进。
|
|
30
|
+
</td>
|
|
31
|
+
</tr>
|
|
32
|
+
</table>
|
|
33
|
+
|
|
24
34
|
</div>
|
|
25
35
|
|
|
26
36
|
---
|
package/lib/adapters/discord.js
CHANGED
|
@@ -20,6 +20,30 @@ export function splitDiscordText(text, limit = DISCORD_MAX_LENGTH) {
|
|
|
20
20
|
return chunks
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
export function toDiscordComponents(buttons) {
|
|
24
|
+
if (!buttons || !Array.isArray(buttons) || !buttons.length) return undefined
|
|
25
|
+
const rows = []
|
|
26
|
+
const grid = Array.isArray(buttons[0]) ? buttons : [buttons]
|
|
27
|
+
for (const row of grid.slice(0, 5)) {
|
|
28
|
+
const components = []
|
|
29
|
+
for (const btn of (row || []).slice(0, 5)) {
|
|
30
|
+
if (!btn) continue
|
|
31
|
+
const custom_id = String(btn.id || btn.callback_data || btn.text || 'btn').slice(0, 100)
|
|
32
|
+
const label = String(btn.text || btn.label || 'Action').slice(0, 80)
|
|
33
|
+
components.push({
|
|
34
|
+
type: 2, // Button
|
|
35
|
+
style: 1, // Primary
|
|
36
|
+
label,
|
|
37
|
+
custom_id,
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
if (components.length) {
|
|
41
|
+
rows.push({ type: 1, components })
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return rows.length ? rows : undefined
|
|
45
|
+
}
|
|
46
|
+
|
|
23
47
|
export class DiscordAdapter {
|
|
24
48
|
constructor(opts = {}) {
|
|
25
49
|
this.name = 'discord'
|
|
@@ -45,18 +69,23 @@ export class DiscordAdapter {
|
|
|
45
69
|
const body = typeof payload === 'string' ? { text: payload } : (payload || {})
|
|
46
70
|
const text = String(body.text || '')
|
|
47
71
|
const files = Array.isArray(body.files) ? body.files : []
|
|
72
|
+
const components = toDiscordComponents(body.buttons || body.replyMarkup?.inline_keyboard)
|
|
48
73
|
|
|
49
74
|
const chunks = splitDiscordText(text)
|
|
50
|
-
if (!chunks.length && !files.length) return { ok: true }
|
|
75
|
+
if (!chunks.length && !files.length && !components) return { ok: true }
|
|
51
76
|
|
|
52
77
|
// Case 1: Webhook sending
|
|
53
78
|
const isWebhookTarget = !channelId || channelId === 'default' || channelId === 'webhook'
|
|
54
79
|
if (this.webhookUrl && isWebhookTarget) {
|
|
55
80
|
for (const chunk of (chunks.length ? chunks : [''])) {
|
|
81
|
+
const payloadJson = { content: chunk }
|
|
82
|
+
if (components) payloadJson.components = components
|
|
56
83
|
const res = await fetch(this.webhookUrl, {
|
|
57
84
|
method: 'POST',
|
|
58
85
|
headers: { 'Content-Type': 'application/json' },
|
|
59
|
-
body: JSON.stringify(
|
|
86
|
+
body: JSON.stringify(payloadJson),
|
|
87
|
+
keepalive: true,
|
|
88
|
+
signal: AbortSignal.timeout(15000),
|
|
60
89
|
})
|
|
61
90
|
if (!res.ok) {
|
|
62
91
|
const errText = await res.text().catch(() => '')
|
|
@@ -77,9 +106,9 @@ export class DiscordAdapter {
|
|
|
77
106
|
// Handle files if any on the first chunk
|
|
78
107
|
if (files.length > 0 && typeof FormData !== 'undefined') {
|
|
79
108
|
const form = new FormData()
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
109
|
+
const payloadJson = { content: chunks[0] || '' }
|
|
110
|
+
if (components) payloadJson.components = components
|
|
111
|
+
form.append('payload_json', JSON.stringify(payloadJson))
|
|
83
112
|
for (let i = 0; i < files.length; i++) {
|
|
84
113
|
const file = files[i]
|
|
85
114
|
const bytes = file.bytes || (file.path ? await readFile(file.path) : null)
|
|
@@ -95,37 +124,70 @@ export class DiscordAdapter {
|
|
|
95
124
|
Authorization: `Bot ${this.botToken}`,
|
|
96
125
|
},
|
|
97
126
|
body: form,
|
|
127
|
+
keepalive: true,
|
|
128
|
+
signal: AbortSignal.timeout(30000),
|
|
98
129
|
})
|
|
99
130
|
if (!res.ok) {
|
|
100
131
|
const errText = await res.text().catch(() => '')
|
|
101
132
|
throw new Error(`discord send error ${res.status}: ${errText}`)
|
|
102
133
|
}
|
|
134
|
+
const json = await res.json().catch(() => ({}))
|
|
103
135
|
// Send remaining text chunks if any
|
|
104
136
|
for (let i = 1; i < chunks.length; i++) {
|
|
105
137
|
await this._sendRestMessage(targetChannel, chunks[i])
|
|
106
138
|
}
|
|
107
|
-
return { ok: true }
|
|
139
|
+
return { ok: true, messageId: json.id }
|
|
108
140
|
}
|
|
109
141
|
|
|
110
|
-
// Pure text chunks
|
|
111
|
-
|
|
112
|
-
|
|
142
|
+
// Pure text chunks with components on first chunk
|
|
143
|
+
let firstResult = { ok: true }
|
|
144
|
+
for (let i = 0; i < (chunks.length ? chunks.length : 1); i++) {
|
|
145
|
+
const chunk = chunks[i] || ''
|
|
146
|
+
const res = await this._sendRestMessage(targetChannel, chunk, i === 0 ? components : undefined)
|
|
147
|
+
if (i === 0) firstResult = res
|
|
148
|
+
}
|
|
149
|
+
return firstResult
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async editMessage(channelId, messageId, content, components) {
|
|
153
|
+
if (!this.botToken) throw new Error('discord botToken required to edit message')
|
|
154
|
+
const body = { content: String(content || '').slice(0, DISCORD_MAX_LENGTH) }
|
|
155
|
+
if (components !== undefined) body.components = components
|
|
156
|
+
const res = await fetch(`${DISCORD_API}/channels/${channelId}/messages/${messageId}`, {
|
|
157
|
+
method: 'PATCH',
|
|
158
|
+
headers: {
|
|
159
|
+
Authorization: `Bot ${this.botToken}`,
|
|
160
|
+
'Content-Type': 'application/json',
|
|
161
|
+
},
|
|
162
|
+
body: JSON.stringify(body),
|
|
163
|
+
keepalive: true,
|
|
164
|
+
signal: AbortSignal.timeout(15000),
|
|
165
|
+
})
|
|
166
|
+
if (!res.ok) {
|
|
167
|
+
const errText = await res.text().catch(() => '')
|
|
168
|
+
throw new Error(`discord edit error ${res.status}: ${errText}`)
|
|
113
169
|
}
|
|
114
170
|
return { ok: true }
|
|
115
171
|
}
|
|
116
172
|
|
|
117
|
-
async _sendRestMessage(channelId, content) {
|
|
173
|
+
async _sendRestMessage(channelId, content, components) {
|
|
174
|
+
const body = { content }
|
|
175
|
+
if (components && components.length) body.components = components
|
|
118
176
|
const res = await fetch(`${DISCORD_API}/channels/${channelId}/messages`, {
|
|
119
177
|
method: 'POST',
|
|
120
178
|
headers: {
|
|
121
179
|
Authorization: `Bot ${this.botToken}`,
|
|
122
180
|
'Content-Type': 'application/json',
|
|
123
181
|
},
|
|
124
|
-
body: JSON.stringify(
|
|
182
|
+
body: JSON.stringify(body),
|
|
183
|
+
keepalive: true,
|
|
184
|
+
signal: AbortSignal.timeout(15000),
|
|
125
185
|
})
|
|
126
186
|
if (!res.ok) {
|
|
127
187
|
const errText = await res.text().catch(() => '')
|
|
128
188
|
throw new Error(`discord send error ${res.status}: ${errText}`)
|
|
129
189
|
}
|
|
190
|
+
const json = typeof res.json === 'function' ? await res.json().catch(() => ({})) : {}
|
|
191
|
+
return { ok: true, messageId: json?.id }
|
|
130
192
|
}
|
|
131
193
|
}
|
package/lib/adapters/slack.js
CHANGED
|
@@ -17,6 +17,35 @@ export function splitSlackText(text, limit = SLACK_MAX_LENGTH) {
|
|
|
17
17
|
return chunks
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
export function toSlackBlocks(text, buttons) {
|
|
21
|
+
const blocks = []
|
|
22
|
+
if (text) {
|
|
23
|
+
blocks.push({
|
|
24
|
+
type: 'section',
|
|
25
|
+
text: { type: 'mrkdwn', text: String(text).slice(0, 3000) },
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
if (buttons && Array.isArray(buttons) && buttons.length) {
|
|
29
|
+
const flat = (Array.isArray(buttons[0]) ? buttons.flat() : buttons).slice(0, 25)
|
|
30
|
+
const elements = []
|
|
31
|
+
for (const btn of flat) {
|
|
32
|
+
if (!btn) continue
|
|
33
|
+
const action_id = String(btn.id || btn.callback_data || btn.text || 'btn').slice(0, 100)
|
|
34
|
+
const label = String(btn.text || btn.label || 'Action').slice(0, 75)
|
|
35
|
+
elements.push({
|
|
36
|
+
type: 'button',
|
|
37
|
+
text: { type: 'plain_text', text: label },
|
|
38
|
+
action_id,
|
|
39
|
+
value: action_id,
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
if (elements.length) {
|
|
43
|
+
blocks.push({ type: 'actions', elements })
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return blocks.length ? blocks : undefined
|
|
47
|
+
}
|
|
48
|
+
|
|
20
49
|
export class SlackAdapter {
|
|
21
50
|
constructor(opts = {}) {
|
|
22
51
|
this.name = 'slack'
|
|
@@ -41,16 +70,21 @@ export class SlackAdapter {
|
|
|
41
70
|
if (this.stopped) throw new Error('slack adapter stopped')
|
|
42
71
|
const body = typeof payload === 'string' ? { text: payload } : (payload || {})
|
|
43
72
|
const text = String(body.text || '')
|
|
73
|
+
const blocks = toSlackBlocks(text, body.buttons || body.options || body.replyMarkup?.inline_keyboard)
|
|
44
74
|
const chunks = splitSlackText(text)
|
|
45
|
-
if (!chunks.length) return { ok: true }
|
|
75
|
+
if (!chunks.length && !blocks) return { ok: true }
|
|
46
76
|
|
|
47
77
|
const isWebhookTarget = !channelId || channelId === 'default' || channelId === 'webhook'
|
|
48
78
|
if (this.webhookUrl && isWebhookTarget) {
|
|
49
|
-
for (const chunk of chunks) {
|
|
79
|
+
for (const chunk of (chunks.length ? chunks : [''])) {
|
|
80
|
+
const payloadJson = { text: chunk }
|
|
81
|
+
if (blocks) payloadJson.blocks = blocks
|
|
50
82
|
const res = await fetch(this.webhookUrl, {
|
|
51
83
|
method: 'POST',
|
|
52
84
|
headers: { 'Content-Type': 'application/json' },
|
|
53
|
-
body: JSON.stringify(
|
|
85
|
+
body: JSON.stringify(payloadJson),
|
|
86
|
+
keepalive: true,
|
|
87
|
+
signal: AbortSignal.timeout(15000),
|
|
54
88
|
})
|
|
55
89
|
if (!res.ok) {
|
|
56
90
|
const errText = await res.text().catch(() => '')
|
|
@@ -68,23 +102,55 @@ export class SlackAdapter {
|
|
|
68
102
|
if (!targetChannel) throw new Error('slack channelId required')
|
|
69
103
|
|
|
70
104
|
const threadTs = opts.threadId || undefined
|
|
71
|
-
|
|
105
|
+
let firstResult = { ok: true }
|
|
106
|
+
for (let i = 0; i < (chunks.length ? chunks.length : 1); i++) {
|
|
107
|
+
const chunk = chunks[i] || ''
|
|
108
|
+
const reqBody = {
|
|
109
|
+
channel: targetChannel,
|
|
110
|
+
text: chunk,
|
|
111
|
+
thread_ts: threadTs,
|
|
112
|
+
}
|
|
113
|
+
if (i === 0 && blocks) reqBody.blocks = blocks
|
|
72
114
|
const res = await fetch(`${SLACK_API}/chat.postMessage`, {
|
|
73
115
|
method: 'POST',
|
|
74
116
|
headers: {
|
|
75
117
|
Authorization: `Bearer ${this.botToken}`,
|
|
76
118
|
'Content-Type': 'application/json; charset=utf-8',
|
|
77
119
|
},
|
|
78
|
-
body: JSON.stringify(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
thread_ts: threadTs,
|
|
82
|
-
}),
|
|
120
|
+
body: JSON.stringify(reqBody),
|
|
121
|
+
keepalive: true,
|
|
122
|
+
signal: AbortSignal.timeout(15000),
|
|
83
123
|
})
|
|
84
|
-
const json = await res.json().catch(() => ({}))
|
|
85
|
-
if (!res.ok || json
|
|
86
|
-
throw new Error(`slack chat.postMessage error: ${json
|
|
124
|
+
const json = typeof res.json === 'function' ? await res.json().catch(() => ({})) : {}
|
|
125
|
+
if (!res.ok || json?.ok === false) {
|
|
126
|
+
throw new Error(`slack chat.postMessage error: ${json?.error || res.status}`)
|
|
87
127
|
}
|
|
128
|
+
if (i === 0) firstResult = { ok: true, ts: json?.ts }
|
|
129
|
+
}
|
|
130
|
+
return firstResult
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async editMessage(channelId, ts, text, blocks) {
|
|
134
|
+
if (!this.botToken) throw new Error('slack botToken required to edit message')
|
|
135
|
+
const body = {
|
|
136
|
+
channel: channelId,
|
|
137
|
+
ts: String(ts),
|
|
138
|
+
text: String(text || '').slice(0, SLACK_MAX_LENGTH),
|
|
139
|
+
}
|
|
140
|
+
if (blocks !== undefined) body.blocks = blocks
|
|
141
|
+
const res = await fetch(`${SLACK_API}/chat.update`, {
|
|
142
|
+
method: 'POST',
|
|
143
|
+
headers: {
|
|
144
|
+
Authorization: `Bearer ${this.botToken}`,
|
|
145
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
146
|
+
},
|
|
147
|
+
body: JSON.stringify(body),
|
|
148
|
+
keepalive: true,
|
|
149
|
+
signal: AbortSignal.timeout(15000),
|
|
150
|
+
})
|
|
151
|
+
const json = typeof res.json === 'function' ? await res.json().catch(() => ({})) : {}
|
|
152
|
+
if (!res.ok || json?.ok === false) {
|
|
153
|
+
throw new Error(`slack chat.update error: ${json?.error || res.status}`)
|
|
88
154
|
}
|
|
89
155
|
return { ok: true }
|
|
90
156
|
}
|