@goodandready/dsh-messenger-gateway 0.3.19 → 0.3.21
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 +65 -11
- package/lib/adapters/slack.js +72 -12
- package/lib/adapters/telegram.js +708 -690
- package/lib/alerts.js +10 -10
- package/lib/artifacts.js +117 -117
- package/lib/ask.js +151 -151
- package/lib/client.js +104 -104
- package/lib/commands.js +73 -47
- package/lib/config.js +104 -98
- package/lib/content-guard.js +14 -14
- package/lib/documents.js +9 -9
- package/lib/file-manager.js +9 -9
- package/lib/gateway.js +1737 -1406
- package/lib/index.js +500 -488
- package/lib/locales/en.js +114 -0
- package/lib/locales/index.js +21 -0
- package/lib/locales/zh.js +114 -0
- package/lib/models.js +1 -1
- package/lib/personas.js +67 -14
- package/lib/photos.js +2 -2
- package/lib/scheduler.js +159 -142
- package/lib/stream.js +4 -2
- package/lib/telegram-errors.js +0 -0
- package/lib/text.js +39 -39
- 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,21 @@ 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),
|
|
60
87
|
})
|
|
61
88
|
if (!res.ok) {
|
|
62
89
|
const errText = await res.text().catch(() => '')
|
|
@@ -77,9 +104,9 @@ export class DiscordAdapter {
|
|
|
77
104
|
// Handle files if any on the first chunk
|
|
78
105
|
if (files.length > 0 && typeof FormData !== 'undefined') {
|
|
79
106
|
const form = new FormData()
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
107
|
+
const payloadJson = { content: chunks[0] || '' }
|
|
108
|
+
if (components) payloadJson.components = components
|
|
109
|
+
form.append('payload_json', JSON.stringify(payloadJson))
|
|
83
110
|
for (let i = 0; i < files.length; i++) {
|
|
84
111
|
const file = files[i]
|
|
85
112
|
const bytes = file.bytes || (file.path ? await readFile(file.path) : null)
|
|
@@ -100,32 +127,59 @@ export class DiscordAdapter {
|
|
|
100
127
|
const errText = await res.text().catch(() => '')
|
|
101
128
|
throw new Error(`discord send error ${res.status}: ${errText}`)
|
|
102
129
|
}
|
|
130
|
+
const json = await res.json().catch(() => ({}))
|
|
103
131
|
// Send remaining text chunks if any
|
|
104
132
|
for (let i = 1; i < chunks.length; i++) {
|
|
105
133
|
await this._sendRestMessage(targetChannel, chunks[i])
|
|
106
134
|
}
|
|
107
|
-
return { ok: true }
|
|
135
|
+
return { ok: true, messageId: json.id }
|
|
108
136
|
}
|
|
109
137
|
|
|
110
|
-
// Pure text chunks
|
|
111
|
-
|
|
112
|
-
|
|
138
|
+
// Pure text chunks with components on first chunk
|
|
139
|
+
let firstResult = { ok: true }
|
|
140
|
+
for (let i = 0; i < (chunks.length ? chunks.length : 1); i++) {
|
|
141
|
+
const chunk = chunks[i] || ''
|
|
142
|
+
const res = await this._sendRestMessage(targetChannel, chunk, i === 0 ? components : undefined)
|
|
143
|
+
if (i === 0) firstResult = res
|
|
144
|
+
}
|
|
145
|
+
return firstResult
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async editMessage(channelId, messageId, content, components) {
|
|
149
|
+
if (!this.botToken) throw new Error('discord botToken required to edit message')
|
|
150
|
+
const body = { content: String(content || '').slice(0, DISCORD_MAX_LENGTH) }
|
|
151
|
+
if (components !== undefined) body.components = components
|
|
152
|
+
const res = await fetch(`${DISCORD_API}/channels/${channelId}/messages/${messageId}`, {
|
|
153
|
+
method: 'PATCH',
|
|
154
|
+
headers: {
|
|
155
|
+
Authorization: `Bot ${this.botToken}`,
|
|
156
|
+
'Content-Type': 'application/json',
|
|
157
|
+
},
|
|
158
|
+
body: JSON.stringify(body),
|
|
159
|
+
})
|
|
160
|
+
if (!res.ok) {
|
|
161
|
+
const errText = await res.text().catch(() => '')
|
|
162
|
+
throw new Error(`discord edit error ${res.status}: ${errText}`)
|
|
113
163
|
}
|
|
114
164
|
return { ok: true }
|
|
115
165
|
}
|
|
116
166
|
|
|
117
|
-
async _sendRestMessage(channelId, content) {
|
|
167
|
+
async _sendRestMessage(channelId, content, components) {
|
|
168
|
+
const body = { content }
|
|
169
|
+
if (components && components.length) body.components = components
|
|
118
170
|
const res = await fetch(`${DISCORD_API}/channels/${channelId}/messages`, {
|
|
119
171
|
method: 'POST',
|
|
120
172
|
headers: {
|
|
121
173
|
Authorization: `Bot ${this.botToken}`,
|
|
122
174
|
'Content-Type': 'application/json',
|
|
123
175
|
},
|
|
124
|
-
body: JSON.stringify(
|
|
176
|
+
body: JSON.stringify(body),
|
|
125
177
|
})
|
|
126
178
|
if (!res.ok) {
|
|
127
179
|
const errText = await res.text().catch(() => '')
|
|
128
180
|
throw new Error(`discord send error ${res.status}: ${errText}`)
|
|
129
181
|
}
|
|
182
|
+
const json = typeof res.json === 'function' ? await res.json().catch(() => ({})) : {}
|
|
183
|
+
return { ok: true, messageId: json?.id }
|
|
130
184
|
}
|
|
131
185
|
}
|
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,19 @@ 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),
|
|
54
86
|
})
|
|
55
87
|
if (!res.ok) {
|
|
56
88
|
const errText = await res.text().catch(() => '')
|
|
@@ -68,23 +100,51 @@ export class SlackAdapter {
|
|
|
68
100
|
if (!targetChannel) throw new Error('slack channelId required')
|
|
69
101
|
|
|
70
102
|
const threadTs = opts.threadId || undefined
|
|
71
|
-
|
|
103
|
+
let firstResult = { ok: true }
|
|
104
|
+
for (let i = 0; i < (chunks.length ? chunks.length : 1); i++) {
|
|
105
|
+
const chunk = chunks[i] || ''
|
|
106
|
+
const reqBody = {
|
|
107
|
+
channel: targetChannel,
|
|
108
|
+
text: chunk,
|
|
109
|
+
thread_ts: threadTs,
|
|
110
|
+
}
|
|
111
|
+
if (i === 0 && blocks) reqBody.blocks = blocks
|
|
72
112
|
const res = await fetch(`${SLACK_API}/chat.postMessage`, {
|
|
73
113
|
method: 'POST',
|
|
74
114
|
headers: {
|
|
75
115
|
Authorization: `Bearer ${this.botToken}`,
|
|
76
116
|
'Content-Type': 'application/json; charset=utf-8',
|
|
77
117
|
},
|
|
78
|
-
body: JSON.stringify(
|
|
79
|
-
channel: targetChannel,
|
|
80
|
-
text: chunk,
|
|
81
|
-
thread_ts: threadTs,
|
|
82
|
-
}),
|
|
118
|
+
body: JSON.stringify(reqBody),
|
|
83
119
|
})
|
|
84
|
-
const json = await res.json().catch(() => ({}))
|
|
85
|
-
if (!res.ok || json
|
|
86
|
-
throw new Error(`slack chat.postMessage error: ${json
|
|
120
|
+
const json = typeof res.json === 'function' ? await res.json().catch(() => ({})) : {}
|
|
121
|
+
if (!res.ok || json?.ok === false) {
|
|
122
|
+
throw new Error(`slack chat.postMessage error: ${json?.error || res.status}`)
|
|
87
123
|
}
|
|
124
|
+
if (i === 0) firstResult = { ok: true, ts: json?.ts }
|
|
125
|
+
}
|
|
126
|
+
return firstResult
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async editMessage(channelId, ts, text, blocks) {
|
|
130
|
+
if (!this.botToken) throw new Error('slack botToken required to edit message')
|
|
131
|
+
const body = {
|
|
132
|
+
channel: channelId,
|
|
133
|
+
ts: String(ts),
|
|
134
|
+
text: String(text || '').slice(0, SLACK_MAX_LENGTH),
|
|
135
|
+
}
|
|
136
|
+
if (blocks !== undefined) body.blocks = blocks
|
|
137
|
+
const res = await fetch(`${SLACK_API}/chat.update`, {
|
|
138
|
+
method: 'POST',
|
|
139
|
+
headers: {
|
|
140
|
+
Authorization: `Bearer ${this.botToken}`,
|
|
141
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
142
|
+
},
|
|
143
|
+
body: JSON.stringify(body),
|
|
144
|
+
})
|
|
145
|
+
const json = typeof res.json === 'function' ? await res.json().catch(() => ({})) : {}
|
|
146
|
+
if (!res.ok || json?.ok === false) {
|
|
147
|
+
throw new Error(`slack chat.update error: ${json?.error || res.status}`)
|
|
88
148
|
}
|
|
89
149
|
return { ok: true }
|
|
90
150
|
}
|