@wenbin_wb/dsh-bridge 2.8.6 → 2.9.0

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.
@@ -1,213 +1,216 @@
1
- // dsh-bridge Telegram platform adapter
2
- // 编排 TelegramGateway(长轮询 + 代理支持)+ TelegramConversationNode(Telegram⇄DSH 会话桥)。
3
- // 作为 Platform 子类,注册进 PlatformManager 统一管理。
4
-
5
- import QRCode from 'qrcode'
6
- import { Platform } from '../platform/base.js'
7
- import { TelegramGateway } from './gateway.js'
8
- import { TelegramConversationNode } from './node.js'
9
-
10
- export class TelegramService extends Platform {
11
- /**
12
- * @param {object} opts
13
- * @param {object} opts.ctx Cordis 上下文
14
- * @param {object} opts.logger 日志器
15
- * @param {object} [opts.config] 已持久化的 telegram 配置(凭证 + allowFrom + 间隔)
16
- * @param {(patch: object) => (void|Promise<void>)} opts.onPersist 主插件保存回调
17
- */
18
- constructor({ ctx, logger, config = {}, onPersist }) {
19
- super({ ctx, logger, config, onPersist })
20
- this.id = 'telegram'
21
- this.name = 'Telegram'
22
- this._botQrCache = { username: '', qr: '' }
23
-
24
- this.gateway = new TelegramGateway(ctx, {
25
- botToken: config.botToken ?? '',
26
- proxy: config.proxy ?? '',
27
- })
28
-
29
- // 挂到 ctx 供会话节点读取
30
- try { ctx.telegram = this.gateway } catch {}
31
-
32
- this.node = new TelegramConversationNode(ctx, {
33
- allowFrom: Array.isArray(config.allowFrom) ? config.allowFrom : [],
34
- digestIntervalSec: config.digestIntervalSec,
35
- approvalTimeoutSec: config.approvalTimeoutSec,
36
- maxMessageChars: config.maxMessageChars || 4096,
37
- sendChunkDelayMs: config.sendChunkDelayMs,
38
- activeSessionId: config.activeSessionId,
39
- }, logger, {
40
- onFirstSender: () => this.persist({ allowFrom: [...(this.node?.config?.allowFrom ?? [])] }),
41
- onActiveSessionChange: (sessionId) => this.persist({ activeSessionId: sessionId }),
42
- })
43
- this.bridge = this.node
44
-
45
- if (this.gateway.configured) {
46
- void this.start().catch((err) => {
47
- this.logger.error?.('[dsh-bridge telegram] start failed:', err?.message ?? err)
48
- })
49
- }
50
- }
51
-
52
- // ---- Platform 接口 ----
53
-
54
- get configured() { return this.gateway.configured }
55
- get accountId() { return this.gateway.accountId }
56
-
57
- get capabilities() {
58
- return {
59
- group: true,
60
- media: true,
61
- approvals: true,
62
- maxMessageChars: this.node.config.maxMessageChars || 4096,
63
- }
64
- }
65
-
66
- async sendText(peerId, text, opts = {}) {
67
- return this.gateway.sendText(peerId, text, opts)
68
- }
69
-
70
- async sendTyping(peerId, opts = {}) {
71
- return this.gateway.sendTyping?.(peerId, opts)
72
- }
73
-
74
- // ---- 生命周期控制 ----
75
-
76
- async start() {
77
- if (!this.gateway.configured) {
78
- this.setStatus('idle')
79
- return { success: false, error: 'Telegram Bot Token 未配置' }
80
- }
81
- this.setStatus('starting')
82
- const ok = await this.gateway.start()
83
- if (ok) {
84
- this.setStatus('connected')
85
- return { success: true }
86
- } else {
87
- this.setStatus('error', '连接 Telegram API 失败,请检查 Bot Token 或网络代理')
88
- return { success: false, error: '连接失败' }
89
- }
90
- }
91
-
92
- async stop() {
93
- await this.gateway.stop()
94
- this.setStatus('offline')
95
- return { success: true }
96
- }
97
-
98
- /**
99
- * 配置或登录
100
- * @param {object} creds - { botToken, proxy }
101
- */
102
- async login(creds = {}) {
103
- const patch = {}
104
- if (creds.botToken !== undefined) patch.botToken = String(creds.botToken).trim()
105
- if (creds.proxy !== undefined) patch.proxy = String(creds.proxy).trim()
106
-
107
- this.gateway.setCredentials(patch)
108
- this.persist(patch)
109
-
110
- if (!this.gateway.configured) {
111
- await this.stop()
112
- return { success: false, error: '请填写完整的 Telegram Bot Token' }
113
- }
114
-
115
- return this.start()
116
- }
117
-
118
- async unbind() {
119
- await this.stop()
120
- this.gateway.setCredentials({ botToken: '', proxy: '' })
121
- this.persist({ botToken: '', proxy: '', allowFrom: [] })
122
- if (this.node) this.node.config.allowFrom = []
123
- return { success: true }
124
- }
125
-
126
- getStatus() {
127
- const allowFrom = [...(this.node?.config?.allowFrom ?? [])]
128
- const username = this.gateway.botInfo?.username || ''
129
- const botLink = username ? `https://t.me/${encodeURIComponent(username)}` : null
130
- if (botLink && this._botQrCache.username !== username) {
131
- this._botQrCache.username = username
132
- void QRCode.toDataURL(botLink, {
133
- width: 260,
134
- margin: 2,
135
- color: { dark: '#1F2421', light: '#FFFFFF' },
136
- }).then((qr) => {
137
- this._botQrCache.qr = qr
138
- }).catch(() => {})
139
- }
140
-
141
- return {
142
- id: this.id,
143
- name: this.name,
144
- status: this.status === 'connected' ? 'connected' : this.gateway.status,
145
- configured: this.gateway.configured,
146
- accountId: this.gateway.accountId || (this.gateway.configured ? '已配置 Token' : ''),
147
- allowFrom,
148
- peerId: this.node?.peerId,
149
- sessionId: this.node?.activeSessionId,
150
- login: {
151
- phase: this.status === 'connected' ? 'done' : this.status === 'error' ? 'error' : 'idle',
152
- },
153
- capabilities: { ...this.capabilities },
154
- config: {
155
- digestIntervalSec: this.node?.config?.digestIntervalSec,
156
- approvalTimeoutSec: this.node?.config?.approvalTimeoutSec,
157
- maxMessageChars: this.node?.config?.maxMessageChars,
158
- sendChunkDelayMs: this.node?.config?.sendChunkDelayMs,
159
- botToken: this.gateway.config.botToken ? '******' : '',
160
- proxy: this.gateway.config.proxy || '',
161
- },
162
- botInfo: this.gateway.botInfo,
163
- botLink,
164
- botQr: this._botQrCache.qr,
165
- }
166
- }
167
-
168
- setAllowFrom(allowFrom) {
169
- const list = Array.isArray(allowFrom) ? allowFrom.map((s) => String(s).trim()).filter(Boolean) : []
170
- if (this.node) this.node.config.allowFrom = list
171
- this.persist({ allowFrom: list })
172
- return { success: true, allowFrom: list }
173
- }
174
-
175
- async setConfig({ digestIntervalSec, approvalTimeoutSec, maxMessageChars, sendChunkDelayMs, botToken, proxy } = {}) {
176
- if (digestIntervalSec != null) this.node.config.digestIntervalSec = Number(digestIntervalSec)
177
- if (approvalTimeoutSec != null) this.node.config.approvalTimeoutSec = Number(approvalTimeoutSec)
178
- if (maxMessageChars != null) {
179
- const val = Number(maxMessageChars)
180
- this.node.config.maxMessageChars = (val >= 200) ? val : 4096
181
- }
182
- if (sendChunkDelayMs != null) this.node.config.sendChunkDelayMs = Number(sendChunkDelayMs)
183
-
184
- if (botToken !== undefined || proxy !== undefined) {
185
- const patch = {}
186
- if (botToken !== undefined) patch.botToken = botToken.trim()
187
- if (proxy !== undefined) patch.proxy = proxy.trim()
188
- this.gateway.setCredentials(patch)
189
- }
190
-
191
- const patch = {
192
- digestIntervalSec: this.node.config.digestIntervalSec,
193
- approvalTimeoutSec: this.node.config.approvalTimeoutSec,
194
- maxMessageChars: this.node.config.maxMessageChars,
195
- sendChunkDelayMs: this.node.config.sendChunkDelayMs,
196
- }
197
- if (botToken !== undefined) patch.botToken = this.gateway.config.botToken
198
- if (proxy !== undefined) patch.proxy = this.gateway.config.proxy
199
- await this.persist(patch)
200
-
201
- if (this.gateway.configured && (botToken !== undefined || proxy !== undefined)) {
202
- await this.start().catch((err) => {
203
- this.logger?.warn?.('[dsh-bridge telegram] auto-start failed: %s', err?.message ?? err)
204
- })
205
- }
206
- return { success: true }
207
- }
208
-
209
- dispose() {
210
- super.dispose()
211
- this.gateway?.dispose?.()
212
- }
1
+ // dsh-bridge Telegram platform adapter
2
+ // 编排 TelegramGateway(长轮询 + 代理支持)+ TelegramConversationNode(Telegram⇄DSH 会话桥)。
3
+ // 作为 Platform 子类,注册进 PlatformManager 统一管理。
4
+
5
+ import QRCode from 'qrcode'
6
+ import { Platform } from '../platform/base.js'
7
+ import { TelegramGateway } from './gateway.js'
8
+ import { TelegramConversationNode } from './node.js'
9
+
10
+ export class TelegramService extends Platform {
11
+ /**
12
+ * @param {object} opts
13
+ * @param {object} opts.ctx Cordis 上下文
14
+ * @param {object} opts.logger 日志器
15
+ * @param {object} [opts.config] 已持久化的 telegram 配置(凭证 + allowFrom + 间隔)
16
+ * @param {(patch: object) => (void|Promise<void>)} opts.onPersist 主插件保存回调
17
+ */
18
+ constructor({ ctx, logger, config = {}, onPersist }) {
19
+ super({ ctx, logger, config, onPersist })
20
+ this.id = 'telegram'
21
+ this.name = 'Telegram'
22
+ this._botQrCache = { username: '', qr: '' }
23
+
24
+ this.gateway = new TelegramGateway(ctx, {
25
+ botToken: config.botToken ?? '',
26
+ proxy: config.proxy ?? '',
27
+ })
28
+
29
+ // 挂到 ctx 供会话节点读取
30
+ try { ctx.telegram = this.gateway } catch {}
31
+
32
+ this.node = new TelegramConversationNode(ctx, {
33
+ allowFrom: Array.isArray(config.allowFrom) ? config.allowFrom : [],
34
+ groupAutoApprove: config.groupAutoApprove,
35
+ digestIntervalSec: config.digestIntervalSec,
36
+ approvalTimeoutSec: config.approvalTimeoutSec,
37
+ maxMessageChars: config.maxMessageChars || 4096,
38
+ sendChunkDelayMs: config.sendChunkDelayMs,
39
+ activeSessionId: config.activeSessionId,
40
+ }, logger, {
41
+ onFirstSender: () => this.persist({ allowFrom: [...(this.node?.config?.allowFrom ?? [])] }),
42
+ onActiveSessionChange: (sessionId) => this.persist({ activeSessionId: sessionId }),
43
+ })
44
+ this.bridge = this.node
45
+
46
+ if (this.gateway.configured) {
47
+ void this.start().catch((err) => {
48
+ this.logger.error?.('[dsh-bridge telegram] start failed:', err?.message ?? err)
49
+ })
50
+ }
51
+ }
52
+
53
+ // ---- Platform 接口 ----
54
+
55
+ get configured() { return this.gateway.configured }
56
+ get accountId() { return this.gateway.accountId }
57
+
58
+ get capabilities() {
59
+ return {
60
+ group: true,
61
+ media: true,
62
+ approvals: true,
63
+ maxMessageChars: this.node.config.maxMessageChars || 4096,
64
+ }
65
+ }
66
+
67
+ async sendText(peerId, text, opts = {}) {
68
+ return this.gateway.sendText(peerId, text, opts)
69
+ }
70
+
71
+ async sendTyping(peerId, opts = {}) {
72
+ return this.gateway.sendTyping?.(peerId, opts)
73
+ }
74
+
75
+ // ---- 生命周期控制 ----
76
+
77
+ async start() {
78
+ if (!this.gateway.configured) {
79
+ this.setStatus('idle')
80
+ return { success: false, error: 'Telegram Bot Token 未配置' }
81
+ }
82
+ this.setStatus('starting')
83
+ const ok = await this.gateway.start()
84
+ if (ok) {
85
+ this.setStatus('connected')
86
+ return { success: true }
87
+ } else {
88
+ this.setStatus('error', '连接 Telegram API 失败,请检查 Bot Token 或网络代理')
89
+ return { success: false, error: '连接失败' }
90
+ }
91
+ }
92
+
93
+ async stop() {
94
+ await this.gateway.stop()
95
+ this.setStatus('offline')
96
+ return { success: true }
97
+ }
98
+
99
+ /**
100
+ * 配置或登录
101
+ * @param {object} creds - { botToken, proxy }
102
+ */
103
+ async login(creds = {}) {
104
+ const patch = {}
105
+ if (creds.botToken !== undefined) patch.botToken = String(creds.botToken).trim()
106
+ if (creds.proxy !== undefined) patch.proxy = String(creds.proxy).trim()
107
+
108
+ this.gateway.setCredentials(patch)
109
+ this.persist(patch)
110
+
111
+ if (!this.gateway.configured) {
112
+ await this.stop()
113
+ return { success: false, error: '请填写完整的 Telegram Bot Token' }
114
+ }
115
+
116
+ return this.start()
117
+ }
118
+
119
+ async unbind() {
120
+ await this.stop()
121
+ this.gateway.setCredentials({ botToken: '', proxy: '' })
122
+ this.persist({ botToken: '', proxy: '', allowFrom: [] })
123
+ if (this.node) this.node.config.allowFrom = []
124
+ return { success: true }
125
+ }
126
+
127
+ getStatus() {
128
+ const allowFrom = [...(this.node?.config?.allowFrom ?? [])]
129
+ const username = this.gateway.botInfo?.username || ''
130
+ const botLink = username ? `https://t.me/${encodeURIComponent(username)}` : null
131
+ if (botLink && this._botQrCache.username !== username) {
132
+ this._botQrCache.username = username
133
+ void QRCode.toDataURL(botLink, {
134
+ width: 260,
135
+ margin: 2,
136
+ color: { dark: '#1F2421', light: '#FFFFFF' },
137
+ }).then((qr) => {
138
+ this._botQrCache.qr = qr
139
+ }).catch(() => {})
140
+ }
141
+
142
+ return {
143
+ id: this.id,
144
+ name: this.name,
145
+ status: this.status === 'connected' ? 'connected' : this.gateway.status,
146
+ configured: this.gateway.configured,
147
+ accountId: this.gateway.accountId || (this.gateway.configured ? '已配置 Token' : ''),
148
+ allowFrom,
149
+ peerId: this.node?.peerId,
150
+ sessionId: this.node?.activeSessionId,
151
+ login: {
152
+ phase: this.status === 'connected' ? 'done' : this.status === 'error' ? 'error' : 'idle',
153
+ },
154
+ capabilities: { ...this.capabilities },
155
+ config: {
156
+ digestIntervalSec: this.node?.config?.digestIntervalSec,
157
+ approvalTimeoutSec: this.node?.config?.approvalTimeoutSec,
158
+ maxMessageChars: this.node?.config?.maxMessageChars,
159
+ sendChunkDelayMs: this.node?.config?.sendChunkDelayMs,
160
+ botToken: this.gateway.config.botToken ? '******' : '',
161
+ proxy: this.gateway.config.proxy || '',
162
+ },
163
+ botInfo: this.gateway.botInfo,
164
+ botLink,
165
+ botQr: this._botQrCache.qr,
166
+ }
167
+ }
168
+
169
+ setAllowFrom(allowFrom) {
170
+ const list = Array.isArray(allowFrom) ? allowFrom.map((s) => String(s).trim()).filter(Boolean) : []
171
+ if (this.node) this.node.config.allowFrom = list
172
+ this.persist({ allowFrom: list })
173
+ return { success: true, allowFrom: list }
174
+ }
175
+
176
+ async setConfig({ digestIntervalSec, approvalTimeoutSec, maxMessageChars, sendChunkDelayMs, groupAutoApprove, botToken, proxy } = {}) {
177
+ if (digestIntervalSec != null) this.node.config.digestIntervalSec = Number(digestIntervalSec)
178
+ if (approvalTimeoutSec != null) this.node.config.approvalTimeoutSec = Number(approvalTimeoutSec)
179
+ if (maxMessageChars != null) {
180
+ const val = Number(maxMessageChars)
181
+ this.node.config.maxMessageChars = (val >= 200) ? val : 4096
182
+ }
183
+ if (sendChunkDelayMs != null) this.node.config.sendChunkDelayMs = Number(sendChunkDelayMs)
184
+ if (groupAutoApprove != null) this.node.config.groupAutoApprove = groupAutoApprove === true
185
+
186
+ if (botToken !== undefined || proxy !== undefined) {
187
+ const patch = {}
188
+ if (botToken !== undefined) patch.botToken = botToken.trim()
189
+ if (proxy !== undefined) patch.proxy = proxy.trim()
190
+ this.gateway.setCredentials(patch)
191
+ }
192
+
193
+ const patch = {
194
+ digestIntervalSec: this.node.config.digestIntervalSec,
195
+ approvalTimeoutSec: this.node.config.approvalTimeoutSec,
196
+ maxMessageChars: this.node.config.maxMessageChars,
197
+ sendChunkDelayMs: this.node.config.sendChunkDelayMs,
198
+ groupAutoApprove: this.node.config.groupAutoApprove === true,
199
+ }
200
+ if (botToken !== undefined) patch.botToken = this.gateway.config.botToken
201
+ if (proxy !== undefined) patch.proxy = this.gateway.config.proxy
202
+ await this.persist(patch)
203
+
204
+ if (this.gateway.configured && (botToken !== undefined || proxy !== undefined)) {
205
+ await this.start().catch((err) => {
206
+ this.logger?.warn?.('[dsh-bridge telegram] auto-start failed: %s', err?.message ?? err)
207
+ })
208
+ }
209
+ return { success: true }
210
+ }
211
+
212
+ dispose() {
213
+ super.dispose()
214
+ this.gateway?.dispose?.()
215
+ }
213
216
  }