@s0nderlabs/anima-plugin-telegram 0.19.9 → 0.19.11
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/package.json +2 -2
- package/src/listener.ts +56 -34
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@s0nderlabs/anima-plugin-telegram",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.11",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Telegram gateway plugin for anima — long-poll bot, debounced dispatch, reactions, allowlist",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"test": "bun test"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@s0nderlabs/anima-core": "0.19.
|
|
31
|
+
"@s0nderlabs/anima-core": "0.19.11",
|
|
32
32
|
"grammy": "^1.42.0",
|
|
33
33
|
"zod": "^3.23.8"
|
|
34
34
|
}
|
package/src/listener.ts
CHANGED
|
@@ -49,6 +49,9 @@ export class TelegramListener {
|
|
|
49
49
|
private running = false
|
|
50
50
|
private tokenLock: TokenLock | null = null
|
|
51
51
|
private refreshTimer: ReturnType<typeof setInterval> | null = null
|
|
52
|
+
private approvalResolver:
|
|
53
|
+
| ((approvalId: string, choice: ApprovalChoice, fromUserId: number) => void)
|
|
54
|
+
| null = null
|
|
52
55
|
|
|
53
56
|
constructor(opts: TelegramListenerOpts) {
|
|
54
57
|
this.opts = opts
|
|
@@ -58,12 +61,54 @@ export class TelegramListener {
|
|
|
58
61
|
quietPeriodMs: opts.debounceMs,
|
|
59
62
|
})
|
|
60
63
|
this.bot.on('message', ctx => this.onMessage(ctx))
|
|
64
|
+
// Register callback_query handler at construction time. grammY rejects
|
|
65
|
+
// late `bot.on()` registration once polling starts, so any approval
|
|
66
|
+
// resolver wiring must happen via the `approvalResolver` slot, not by
|
|
67
|
+
// calling `bot.on()` again. See approvalBridge.installCallbackHandler.
|
|
68
|
+
this.bot.on('callback_query:data', ctx => this.handleCallbackQuery(ctx))
|
|
61
69
|
this.bot.catch(err => {
|
|
62
70
|
const msg = err instanceof Error ? err.message : String(err)
|
|
63
71
|
this.log(`grammy.catch: ${msg.slice(0, 200)}`)
|
|
64
72
|
})
|
|
65
73
|
}
|
|
66
74
|
|
|
75
|
+
private async handleCallbackQuery(ctx: Context): Promise<void> {
|
|
76
|
+
const q = ctx.callbackQuery
|
|
77
|
+
if (!q) return
|
|
78
|
+
const parsed = parseCallbackData(q.data)
|
|
79
|
+
if (!parsed) {
|
|
80
|
+
try {
|
|
81
|
+
await ctx.answerCallbackQuery({ text: 'malformed approval callback' })
|
|
82
|
+
} catch {
|
|
83
|
+
/* ignore */
|
|
84
|
+
}
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
if (this.opts.allowedUserIds.length > 0 && !this.opts.allowedUserIds.includes(q.from.id)) {
|
|
88
|
+
try {
|
|
89
|
+
await ctx.answerCallbackQuery({ text: '⛔ You are not authorized to approve commands.' })
|
|
90
|
+
} catch {
|
|
91
|
+
/* ignore */
|
|
92
|
+
}
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
const resolver = this.approvalResolver
|
|
96
|
+
if (!resolver) {
|
|
97
|
+
try {
|
|
98
|
+
await ctx.answerCallbackQuery({ text: 'no approval pending' })
|
|
99
|
+
} catch {
|
|
100
|
+
/* ignore */
|
|
101
|
+
}
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
resolver(parsed.approvalId, parsed.choice, q.from.id)
|
|
105
|
+
try {
|
|
106
|
+
await ctx.answerCallbackQuery({ text: `✓ ${parsed.choice}` })
|
|
107
|
+
} catch {
|
|
108
|
+
/* ignore */
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
67
112
|
async start(): Promise<void> {
|
|
68
113
|
if (this.running) return
|
|
69
114
|
|
|
@@ -169,44 +214,20 @@ export class TelegramListener {
|
|
|
169
214
|
}
|
|
170
215
|
|
|
171
216
|
/**
|
|
172
|
-
* Register
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
217
|
+
* Register the caller's approval resolver. The actual `bot.on('callback_query:data', ...)`
|
|
218
|
+
* middleware is installed once in the constructor (grammY rejects late
|
|
219
|
+
* registration after polling starts, so we cannot wire the handler lazily
|
|
220
|
+
* inside a dispatch turn). This method just swaps the resolver slot the
|
|
221
|
+
* pre-installed handler reads from. Returns a no-op uninstaller for
|
|
222
|
+
* back-compat with the previous API; teardown happens via `bot.stop()`.
|
|
177
223
|
*/
|
|
178
224
|
private installCallbackHandler(
|
|
179
225
|
onResolve: (approvalId: string, choice: ApprovalChoice, fromUserId: number) => void,
|
|
180
226
|
): () => void {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const parsed = parseCallbackData(q.data)
|
|
185
|
-
if (!parsed) {
|
|
186
|
-
try {
|
|
187
|
-
await ctx.answerCallbackQuery({ text: 'malformed approval callback' })
|
|
188
|
-
} catch {
|
|
189
|
-
/* ignore */
|
|
190
|
-
}
|
|
191
|
-
return
|
|
192
|
-
}
|
|
193
|
-
if (this.opts.allowedUserIds.length > 0 && !this.opts.allowedUserIds.includes(q.from.id)) {
|
|
194
|
-
try {
|
|
195
|
-
await ctx.answerCallbackQuery({ text: '⛔ You are not authorized to approve commands.' })
|
|
196
|
-
} catch {
|
|
197
|
-
/* ignore */
|
|
198
|
-
}
|
|
199
|
-
return
|
|
200
|
-
}
|
|
201
|
-
onResolve(parsed.approvalId, parsed.choice, q.from.id)
|
|
202
|
-
try {
|
|
203
|
-
await ctx.answerCallbackQuery({ text: `✓ ${parsed.choice}` })
|
|
204
|
-
} catch {
|
|
205
|
-
/* ignore */
|
|
206
|
-
}
|
|
227
|
+
this.approvalResolver = onResolve
|
|
228
|
+
return () => {
|
|
229
|
+
this.approvalResolver = null
|
|
207
230
|
}
|
|
208
|
-
this.bot.on('callback_query:data', handler)
|
|
209
|
-
return () => {}
|
|
210
231
|
}
|
|
211
232
|
|
|
212
233
|
/**
|
|
@@ -316,7 +337,8 @@ export class TelegramListener {
|
|
|
316
337
|
} catch (err) {
|
|
317
338
|
ok = false
|
|
318
339
|
const msg = err instanceof Error ? err.message : String(err)
|
|
319
|
-
|
|
340
|
+
const stack = err instanceof Error && err.stack ? `\n${err.stack}` : ''
|
|
341
|
+
console.error(`[telegram] dispatch failed: ${msg.slice(0, 500)}${stack}`)
|
|
320
342
|
void reactError(this.bot, chatId, messageId)
|
|
321
343
|
try {
|
|
322
344
|
await this.bot.api.sendMessage(
|