teh-bot 1.0.3 → 1.0.5
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 +571 -349
- package/{index.js → dist/index.cjs} +130 -132
- package/{index.d.ts → dist/index.d.ts} +518 -23
- package/dist/index.mjs +1002 -0
- package/package.json +27 -11
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1002 @@
|
|
|
1
|
+
import https from "https"
|
|
2
|
+
import http from "http"
|
|
3
|
+
import { EventEmitter } from "events"
|
|
4
|
+
import { createReadStream, statSync, createWriteStream, promises: fsPromises } from "fs"
|
|
5
|
+
import { basename, extname } from "path"
|
|
6
|
+
import { URL } from "url"
|
|
7
|
+
import { Stream, Readable } from "stream"
|
|
8
|
+
|
|
9
|
+
const DEFAULT_OPTIONS = {
|
|
10
|
+
polling: false,
|
|
11
|
+
pollingInterval: 1000,
|
|
12
|
+
pollingTimeout: 30,
|
|
13
|
+
webhook: false,
|
|
14
|
+
webhookPort: 3000,
|
|
15
|
+
webhookPath: "/webhook",
|
|
16
|
+
requestTimeout: 30000,
|
|
17
|
+
maxConnections: 40,
|
|
18
|
+
allowedUpdates: [],
|
|
19
|
+
baseApiUrl: "https://api.telegram.org",
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
class TelegramBot extends EventEmitter {
|
|
23
|
+
constructor(token, options = {}) {
|
|
24
|
+
super()
|
|
25
|
+
|
|
26
|
+
if (!token) throw new Error("[Teh] Bot token is required")
|
|
27
|
+
|
|
28
|
+
this.token = token
|
|
29
|
+
this.options = { ...DEFAULT_OPTIONS, ...options }
|
|
30
|
+
this.apiUrl = `${this.options.baseApiUrl}/bot${token}`
|
|
31
|
+
|
|
32
|
+
this.offset = 0
|
|
33
|
+
this.middleware = []
|
|
34
|
+
this.commands = new Map()
|
|
35
|
+
this.pollingActive = false
|
|
36
|
+
this.webhookServer = null
|
|
37
|
+
|
|
38
|
+
this._queue = []
|
|
39
|
+
this._isProcessing = false
|
|
40
|
+
this._rateLimitDelay = 0
|
|
41
|
+
|
|
42
|
+
if (this.options.polling) this.startPolling()
|
|
43
|
+
if (this.options.webhook) this.startWebhook()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async request(method, params = {}, formData = null) {
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
const url = new URL(`${this.apiUrl}/${method}`)
|
|
49
|
+
const headers = {}
|
|
50
|
+
let body
|
|
51
|
+
|
|
52
|
+
if (formData) {
|
|
53
|
+
const boundary = `----TehBoundary${Math.random().toString(36).substring(2)}`
|
|
54
|
+
headers["Content-Type"] = `multipart/form-data; boundary=${boundary}`
|
|
55
|
+
body = this._buildMultipartStream(formData, boundary)
|
|
56
|
+
} else {
|
|
57
|
+
headers["Content-Type"] = "application/json"
|
|
58
|
+
body = JSON.stringify(params)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const options = {
|
|
62
|
+
hostname: url.hostname,
|
|
63
|
+
port: url.port || 443,
|
|
64
|
+
path: url.pathname + url.search,
|
|
65
|
+
method: "POST",
|
|
66
|
+
headers: {
|
|
67
|
+
...headers,
|
|
68
|
+
"User-Agent": "TehBot/1.0.5 (Modern; High-Performance)",
|
|
69
|
+
},
|
|
70
|
+
timeout: this.options.requestTimeout,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const req = https.request(options, (res) => {
|
|
74
|
+
const chunks = []
|
|
75
|
+
res.on("data", (chunk) => chunks.push(chunk))
|
|
76
|
+
res.on("end", () => {
|
|
77
|
+
const raw = Buffer.concat(chunks).toString()
|
|
78
|
+
try {
|
|
79
|
+
const response = JSON.parse(raw)
|
|
80
|
+
if (response.ok) resolve(response.result)
|
|
81
|
+
else reject(this._formatError(response))
|
|
82
|
+
} catch (e) {
|
|
83
|
+
reject(new Error(`[Teh] Parse Error: ${raw.substring(0, 100)}`))
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
req.on("error", reject)
|
|
89
|
+
if (body instanceof Stream) {
|
|
90
|
+
body.pipe(req)
|
|
91
|
+
body.on("end", () => req.end())
|
|
92
|
+
body.on("error", (err) => {
|
|
93
|
+
req.destroy()
|
|
94
|
+
reject(err)
|
|
95
|
+
})
|
|
96
|
+
} else {
|
|
97
|
+
req.write(body)
|
|
98
|
+
req.end()
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async getMe() {
|
|
104
|
+
return this.request("getMe")
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async getUpdates(params = {}) {
|
|
108
|
+
return this.request("getUpdates", {
|
|
109
|
+
offset: this.offset,
|
|
110
|
+
timeout: this.options.pollingTimeout,
|
|
111
|
+
allowed_updates: this.options.allowedUpdates,
|
|
112
|
+
...params,
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async setWebhook(url, params = {}) {
|
|
117
|
+
return this.request("setWebhook", { url, ...params })
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async deleteWebhook(params = {}) {
|
|
121
|
+
return this.request("deleteWebhook", params)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async getWebhookInfo() {
|
|
125
|
+
return this.request("getWebhookInfo")
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async sendMessage(chatId, content, options = {}) {
|
|
129
|
+
if (typeof content === "string") {
|
|
130
|
+
return this.request("sendMessage", { chat_id: chatId, text: content, ...options })
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const { image, video, audio, document, sticker, caption, ...rest } = content
|
|
134
|
+
const mediaType = image
|
|
135
|
+
? "photo"
|
|
136
|
+
: video
|
|
137
|
+
? "video"
|
|
138
|
+
: audio
|
|
139
|
+
? "audio"
|
|
140
|
+
: document
|
|
141
|
+
? "document"
|
|
142
|
+
: sticker
|
|
143
|
+
? "sticker"
|
|
144
|
+
: null
|
|
145
|
+
const mediaSource = image || video || audio || document || sticker
|
|
146
|
+
|
|
147
|
+
if (!mediaType) return this.request("sendMessage", { chat_id: chatId, text: content.text, ...options })
|
|
148
|
+
|
|
149
|
+
const method = `send${mediaType.charAt(0).toUpperCase() + mediaType.slice(1)}`
|
|
150
|
+
const formData = { chat_id: chatId, caption, [mediaType]: await this._prepareFile(mediaSource, mediaType), ...rest }
|
|
151
|
+
|
|
152
|
+
return this.request(method, {}, formData)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async sendPhoto(chatId, photo, options = {}) {
|
|
156
|
+
return this._sendFile("sendPhoto", chatId, photo, "photo", options)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async sendAudio(chatId, audio, options = {}) {
|
|
160
|
+
return this._sendFile("sendAudio", chatId, audio, "audio", options)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async sendDocument(chatId, document, options = {}) {
|
|
164
|
+
return this._sendFile("sendDocument", chatId, document, "document", options)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async sendVideo(chatId, video, options = {}) {
|
|
168
|
+
return this._sendFile("sendVideo", chatId, video, "video", options)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async sendAnimation(chatId, animation, options = {}) {
|
|
172
|
+
return this._sendFile("sendAnimation", chatId, animation, "animation", options)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async sendVoice(chatId, voice, options = {}) {
|
|
176
|
+
return this._sendFile("sendVoice", chatId, voice, "voice", options)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async sendVideoNote(chatId, videoNote, options = {}) {
|
|
180
|
+
return this._sendFile("sendVideoNote", chatId, videoNote, "video_note", options)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async sendSticker(chatId, sticker, options = {}) {
|
|
184
|
+
return this._sendFile("sendSticker", chatId, sticker, "sticker", options)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async sendLocation(chatId, latitude, longitude, options = {}) {
|
|
188
|
+
return this.request("sendLocation", {
|
|
189
|
+
chat_id: chatId,
|
|
190
|
+
latitude,
|
|
191
|
+
longitude,
|
|
192
|
+
...options,
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async sendVenue(chatId, latitude, longitude, title, address, options = {}) {
|
|
197
|
+
return this.request("sendVenue", {
|
|
198
|
+
chat_id: chatId,
|
|
199
|
+
latitude,
|
|
200
|
+
longitude,
|
|
201
|
+
title,
|
|
202
|
+
address,
|
|
203
|
+
...options,
|
|
204
|
+
})
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async sendContact(chatId, phoneNumber, firstName, options = {}) {
|
|
208
|
+
return this.request("sendContact", {
|
|
209
|
+
chat_id: chatId,
|
|
210
|
+
phone_number: phoneNumber,
|
|
211
|
+
first_name: firstName,
|
|
212
|
+
...options,
|
|
213
|
+
})
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async sendPoll(chatId, question, optionsArray, params = {}) {
|
|
217
|
+
return this.request("sendPoll", {
|
|
218
|
+
chat_id: chatId,
|
|
219
|
+
question,
|
|
220
|
+
options: optionsArray,
|
|
221
|
+
...params,
|
|
222
|
+
})
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async sendDice(chatId, options = {}) {
|
|
226
|
+
return this.request("sendDice", {
|
|
227
|
+
chat_id: chatId,
|
|
228
|
+
...options,
|
|
229
|
+
})
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async sendChatAction(chatId, action) {
|
|
233
|
+
return this.request("sendChatAction", {
|
|
234
|
+
chat_id: chatId,
|
|
235
|
+
action,
|
|
236
|
+
})
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async forwardMessage(chatId, fromChatId, messageId, options = {}) {
|
|
240
|
+
return this.request("forwardMessage", {
|
|
241
|
+
chat_id: chatId,
|
|
242
|
+
from_chat_id: fromChatId,
|
|
243
|
+
message_id: messageId,
|
|
244
|
+
...options,
|
|
245
|
+
})
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async copyMessage(chatId, fromChatId, messageId, options = {}) {
|
|
249
|
+
return this.request("copyMessage", {
|
|
250
|
+
chat_id: chatId,
|
|
251
|
+
from_chat_id: fromChatId,
|
|
252
|
+
message_id: messageId,
|
|
253
|
+
...options,
|
|
254
|
+
})
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async editMessageText(text, options = {}) {
|
|
258
|
+
return this.request("editMessageText", {
|
|
259
|
+
text,
|
|
260
|
+
...options,
|
|
261
|
+
})
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async editMessageCaption(options = {}) {
|
|
265
|
+
return this.request("editMessageCaption", options)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async editMessageReplyMarkup(options = {}) {
|
|
269
|
+
return this.request("editMessageReplyMarkup", options)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async deleteMessage(chatId, messageId) {
|
|
273
|
+
return this.request("deleteMessage", {
|
|
274
|
+
chat_id: chatId,
|
|
275
|
+
message_id: messageId,
|
|
276
|
+
})
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async answerCallbackQuery(callbackQueryId, options = {}) {
|
|
280
|
+
return this.request("answerCallbackQuery", {
|
|
281
|
+
callback_query_id: callbackQueryId,
|
|
282
|
+
...options,
|
|
283
|
+
})
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async answerInlineQuery(inlineQueryId, results, options = {}) {
|
|
287
|
+
return this.request("answerInlineQuery", {
|
|
288
|
+
inline_query_id: inlineQueryId,
|
|
289
|
+
results,
|
|
290
|
+
...options,
|
|
291
|
+
})
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async getChat(chatId) {
|
|
295
|
+
return this.request("getChat", { chat_id: chatId })
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async getChatAdministrators(chatId) {
|
|
299
|
+
return this.request("getChatAdministrators", { chat_id: chatId })
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async getChatMemberCount(chatId) {
|
|
303
|
+
return this.request("getChatMemberCount", { chat_id: chatId })
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async getChatMember(chatId, userId) {
|
|
307
|
+
return this.request("getChatMember", {
|
|
308
|
+
chat_id: chatId,
|
|
309
|
+
user_id: userId,
|
|
310
|
+
})
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async setChatTitle(chatId, title) {
|
|
314
|
+
return this.request("setChatTitle", {
|
|
315
|
+
chat_id: chatId,
|
|
316
|
+
title,
|
|
317
|
+
})
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async setChatDescription(chatId, description) {
|
|
321
|
+
return this.request("setChatDescription", {
|
|
322
|
+
chat_id: chatId,
|
|
323
|
+
description,
|
|
324
|
+
})
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
async pinChatMessage(chatId, messageId, options = {}) {
|
|
328
|
+
return this.request("pinChatMessage", {
|
|
329
|
+
chat_id: chatId,
|
|
330
|
+
message_id: messageId,
|
|
331
|
+
...options,
|
|
332
|
+
})
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async unpinChatMessage(chatId, options = {}) {
|
|
336
|
+
return this.request("unpinChatMessage", {
|
|
337
|
+
chat_id: chatId,
|
|
338
|
+
...options,
|
|
339
|
+
})
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
async unpinAllChatMessages(chatId) {
|
|
343
|
+
return this.request("unpinAllChatMessages", { chat_id: chatId })
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
async leaveChat(chatId) {
|
|
347
|
+
return this.request("leaveChat", { chat_id: chatId })
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async banChatMember(chatId, userId, options = {}) {
|
|
351
|
+
return this.request("banChatMember", {
|
|
352
|
+
chat_id: chatId,
|
|
353
|
+
user_id: userId,
|
|
354
|
+
...options,
|
|
355
|
+
})
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
async unbanChatMember(chatId, userId, options = {}) {
|
|
359
|
+
return this.request("unbanChatMember", {
|
|
360
|
+
chat_id: chatId,
|
|
361
|
+
user_id: userId,
|
|
362
|
+
...options,
|
|
363
|
+
})
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async restrictChatMember(chatId, userId, permissions, options = {}) {
|
|
367
|
+
return this.request("restrictChatMember", {
|
|
368
|
+
chat_id: chatId,
|
|
369
|
+
user_id: userId,
|
|
370
|
+
permissions,
|
|
371
|
+
...options,
|
|
372
|
+
})
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
async promoteChatMember(chatId, userId, options = {}) {
|
|
376
|
+
return this.request("promoteChatMember", {
|
|
377
|
+
chat_id: chatId,
|
|
378
|
+
user_id: userId,
|
|
379
|
+
...options,
|
|
380
|
+
})
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
async getFile(fileId) {
|
|
384
|
+
return this.request("getFile", { file_id: fileId })
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
async downloadFile(fileId, destination) {
|
|
388
|
+
const file = await this.getFile(fileId)
|
|
389
|
+
const fileUrl = `https://api.telegram.org/file/bot${this.token}/${file.file_path}`
|
|
390
|
+
|
|
391
|
+
return new Promise((resolve, reject) => {
|
|
392
|
+
https
|
|
393
|
+
.get(fileUrl, (response) => {
|
|
394
|
+
const writeStream = createWriteStream(destination)
|
|
395
|
+
response.pipe(writeStream)
|
|
396
|
+
|
|
397
|
+
writeStream.on("finish", () => {
|
|
398
|
+
writeStream.close()
|
|
399
|
+
resolve(destination)
|
|
400
|
+
})
|
|
401
|
+
|
|
402
|
+
writeStream.on("error", reject)
|
|
403
|
+
})
|
|
404
|
+
.on("error", reject)
|
|
405
|
+
})
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
use(middleware) {
|
|
409
|
+
if (typeof middleware !== "function") {
|
|
410
|
+
throw new Error("Middleware must be a function")
|
|
411
|
+
}
|
|
412
|
+
this.middleware.push(middleware)
|
|
413
|
+
return this
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
command(cmd, handler) {
|
|
417
|
+
if (handler === undefined) {
|
|
418
|
+
const commandName = typeof cmd === "string" ? (cmd.startsWith("/") ? cmd : `/${cmd}`) : ""
|
|
419
|
+
return this.commands.get(commandName)
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (typeof handler !== "function") {
|
|
423
|
+
throw new Error("Command handler must be a function")
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const commands = Array.isArray(cmd) ? cmd : [cmd]
|
|
427
|
+
commands.forEach((c) => {
|
|
428
|
+
this.commands.set(c.startsWith("/") ? c : `/${c}`, handler)
|
|
429
|
+
})
|
|
430
|
+
|
|
431
|
+
return this
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
on(event, listener) {
|
|
435
|
+
super.on(event, listener)
|
|
436
|
+
return this
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
async startPolling() {
|
|
440
|
+
if (this.pollingActive) {
|
|
441
|
+
return
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
this.pollingActive = true
|
|
445
|
+
|
|
446
|
+
try {
|
|
447
|
+
await this.deleteWebhook()
|
|
448
|
+
} catch (error) {
|
|
449
|
+
console.error("Failed to delete webhook:", error.message)
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
this.emit("polling_start")
|
|
453
|
+
this._poll()
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
async _poll() {
|
|
457
|
+
if (!this.pollingActive) return
|
|
458
|
+
|
|
459
|
+
try {
|
|
460
|
+
const updates = await this.request("getUpdates", {
|
|
461
|
+
offset: this.offset,
|
|
462
|
+
timeout: this.options.pollingTimeout,
|
|
463
|
+
allowed_updates: this.options.allowedUpdates,
|
|
464
|
+
})
|
|
465
|
+
|
|
466
|
+
for (const update of updates) {
|
|
467
|
+
this.offset = update.update_id + 1
|
|
468
|
+
this._processUpdate(update)
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
setTimeout(() => this._poll(), this.options.pollingInterval)
|
|
472
|
+
} catch (error) {
|
|
473
|
+
this.emit("polling_error", error)
|
|
474
|
+
setTimeout(() => this._poll(), this.options.pollingInterval * 2)
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
stopPolling() {
|
|
479
|
+
this.pollingActive = false
|
|
480
|
+
this.emit("polling_stop")
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
async startWebhook() {
|
|
484
|
+
if (this.webhookServer) {
|
|
485
|
+
return
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const server = http.createServer((req, res) => {
|
|
489
|
+
if (req.url === this.options.webhookPath && req.method === "POST") {
|
|
490
|
+
let body = ""
|
|
491
|
+
|
|
492
|
+
req.on("data", (chunk) => {
|
|
493
|
+
body += chunk.toString()
|
|
494
|
+
})
|
|
495
|
+
|
|
496
|
+
req.on("end", () => {
|
|
497
|
+
try {
|
|
498
|
+
const update = JSON.parse(body)
|
|
499
|
+
this._processUpdate(update)
|
|
500
|
+
res.writeHead(200)
|
|
501
|
+
res.end("OK")
|
|
502
|
+
} catch (error) {
|
|
503
|
+
this.emit("webhook_error", error)
|
|
504
|
+
res.writeHead(500)
|
|
505
|
+
res.end("Error")
|
|
506
|
+
}
|
|
507
|
+
})
|
|
508
|
+
} else {
|
|
509
|
+
res.writeHead(404)
|
|
510
|
+
res.end("Not Found")
|
|
511
|
+
}
|
|
512
|
+
})
|
|
513
|
+
|
|
514
|
+
server.listen(this.options.webhookPort, () => {
|
|
515
|
+
this.emit("webhook_start", this.options.webhookPort)
|
|
516
|
+
})
|
|
517
|
+
|
|
518
|
+
this.webhookServer = server
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
stopWebhook() {
|
|
522
|
+
if (this.webhookServer) {
|
|
523
|
+
this.webhookServer.close(() => {
|
|
524
|
+
this.emit("webhook_stop")
|
|
525
|
+
})
|
|
526
|
+
this.webhookServer = null
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
async _processUpdate(update) {
|
|
531
|
+
try {
|
|
532
|
+
const ctx = this._createContext(update)
|
|
533
|
+
|
|
534
|
+
await this._runMiddleware(ctx)
|
|
535
|
+
|
|
536
|
+
if (ctx.message?.text?.startsWith("/")) {
|
|
537
|
+
const command = ctx.message.text.split(" ")[0]
|
|
538
|
+
const handler = this.commands.get(command)
|
|
539
|
+
|
|
540
|
+
if (handler) {
|
|
541
|
+
await handler(ctx)
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
this.emit("update", update)
|
|
546
|
+
|
|
547
|
+
if (update.message) {
|
|
548
|
+
this.emit("message", update.message, ctx)
|
|
549
|
+
|
|
550
|
+
if (update.message.text) {
|
|
551
|
+
this.emit("text", update.message, ctx)
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
if (update.message.photo) {
|
|
555
|
+
this.emit("photo", update.message, ctx)
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
if (update.message.document) {
|
|
559
|
+
this.emit("document", update.message, ctx)
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
if (update.message.video) {
|
|
563
|
+
this.emit("video", update.message, ctx)
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (update.message.audio) {
|
|
567
|
+
this.emit("audio", update.message, ctx)
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
if (update.message.voice) {
|
|
571
|
+
this.emit("voice", update.message, ctx)
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if (update.message.sticker) {
|
|
575
|
+
this.emit("sticker", update.message, ctx)
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
if (update.message.location) {
|
|
579
|
+
this.emit("location", update.message, ctx)
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
if (update.message.contact) {
|
|
583
|
+
this.emit("contact", update.message, ctx)
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if (update.edited_message) {
|
|
588
|
+
this.emit("edited_message", update.edited_message, ctx)
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
if (update.channel_post) {
|
|
592
|
+
this.emit("channel_post", update.channel_post, ctx)
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
if (update.edited_channel_post) {
|
|
596
|
+
this.emit("edited_channel_post", update.edited_channel_post, ctx)
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
if (update.callback_query) {
|
|
600
|
+
this.emit("callback_query", update.callback_query, ctx)
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if (update.inline_query) {
|
|
604
|
+
this.emit("inline_query", update.inline_query, ctx)
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
if (update.chosen_inline_result) {
|
|
608
|
+
this.emit("chosen_inline_result", update.chosen_inline_result, ctx)
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
if (update.poll) {
|
|
612
|
+
this.emit("poll", update.poll, ctx)
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
if (update.poll_answer) {
|
|
616
|
+
this.emit("poll_answer", update.poll_answer, ctx)
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
if (update.my_chat_member) {
|
|
620
|
+
this.emit("my_chat_member", update.my_chat_member, ctx)
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
if (update.chat_member) {
|
|
624
|
+
this.emit("chat_member", update.chat_member, ctx)
|
|
625
|
+
}
|
|
626
|
+
} catch (error) {
|
|
627
|
+
this.emit("error", error)
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
_createContext(update) {
|
|
632
|
+
const message = update.message || update.edited_message || update.channel_post || update.callback_query?.message
|
|
633
|
+
|
|
634
|
+
const chat =
|
|
635
|
+
update.message?.chat ||
|
|
636
|
+
update.callback_query?.message?.chat ||
|
|
637
|
+
update.my_chat_member?.chat ||
|
|
638
|
+
update.chat_member?.chat ||
|
|
639
|
+
update.chat_join_request?.chat ||
|
|
640
|
+
update.edited_message?.chat ||
|
|
641
|
+
update.channel_post?.chat
|
|
642
|
+
|
|
643
|
+
const from =
|
|
644
|
+
update.message?.from ||
|
|
645
|
+
update.callback_query?.from ||
|
|
646
|
+
update.inline_query?.from ||
|
|
647
|
+
update.chosen_inline_result?.from ||
|
|
648
|
+
update.shipping_query?.from ||
|
|
649
|
+
update.pre_checkout_query?.from ||
|
|
650
|
+
update.poll_answer?.user ||
|
|
651
|
+
update.my_chat_member?.from
|
|
652
|
+
|
|
653
|
+
const ctx = {
|
|
654
|
+
update,
|
|
655
|
+
bot: this,
|
|
656
|
+
message,
|
|
657
|
+
chat,
|
|
658
|
+
from,
|
|
659
|
+
callbackQuery: update.callback_query,
|
|
660
|
+
inlineQuery: update.inline_query,
|
|
661
|
+
chosenInlineResult: update.chosen_inline_result,
|
|
662
|
+
poll: update.poll,
|
|
663
|
+
pollAnswer: update.poll_answer,
|
|
664
|
+
myChatMember: update.my_chat_member,
|
|
665
|
+
chatMember: update.chat_member,
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
ctx.send = (content, opts) => {
|
|
669
|
+
const chatId =
|
|
670
|
+
ctx.chat?.id ||
|
|
671
|
+
update.callback_query?.from?.id ||
|
|
672
|
+
update.inline_query?.from?.id ||
|
|
673
|
+
update.message?.from?.id ||
|
|
674
|
+
from?.id
|
|
675
|
+
|
|
676
|
+
if (!chatId) {
|
|
677
|
+
console.error("[Teh] Context update without valid chatId destination:", JSON.stringify(update))
|
|
678
|
+
throw new Error("[Teh] Cannot send message: chat_id could not be resolved from this update context")
|
|
679
|
+
}
|
|
680
|
+
return this.sendMessage(chatId, content, opts)
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
ctx.reply = (text, opts) => {
|
|
684
|
+
const chatId = ctx.chat?.id || update.callback_query?.from?.id || from?.id
|
|
685
|
+
if (!chatId) {
|
|
686
|
+
throw new Error("[Teh] Cannot reply: chat_id could not be resolved from this update context")
|
|
687
|
+
}
|
|
688
|
+
return this.sendMessage(chatId, text, {
|
|
689
|
+
reply_to_message_id: ctx.message?.message_id,
|
|
690
|
+
...opts,
|
|
691
|
+
})
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
ctx.replyWithPhoto = (photo, opts) => ctx.send({ image: photo, ...opts })
|
|
695
|
+
ctx.replyWithVideo = (video, opts) => ctx.send({ video, ...opts })
|
|
696
|
+
ctx.replyWithAudio = (audio, opts) => ctx.send({ audio, ...opts })
|
|
697
|
+
ctx.replyWithDocument = (doc, opts) => ctx.send({ document: doc, ...opts })
|
|
698
|
+
|
|
699
|
+
ctx.answerCallbackQuery = (options = {}) => {
|
|
700
|
+
if (!ctx.callbackQuery?.id) return Promise.resolve(false)
|
|
701
|
+
return this.answerCallbackQuery(ctx.callbackQuery.id, options)
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
ctx.editMessageText = (text, options = {}) => {
|
|
705
|
+
if (!ctx.callbackQuery?.message) {
|
|
706
|
+
if (ctx.callbackQuery?.inline_message_id) {
|
|
707
|
+
return this.editMessageText(text, {
|
|
708
|
+
inline_message_id: ctx.callbackQuery.inline_message_id,
|
|
709
|
+
...options,
|
|
710
|
+
})
|
|
711
|
+
}
|
|
712
|
+
return Promise.resolve(false)
|
|
713
|
+
}
|
|
714
|
+
return this.editMessageText(text, {
|
|
715
|
+
chat_id: ctx.chat?.id,
|
|
716
|
+
message_id: ctx.callbackQuery.message.message_id,
|
|
717
|
+
...options,
|
|
718
|
+
})
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
return ctx
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
async _runMiddleware(ctx, index = 0) {
|
|
725
|
+
if (index >= this.middleware.length) {
|
|
726
|
+
return
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
const middleware = this.middleware[index]
|
|
730
|
+
let nextCalled = false
|
|
731
|
+
|
|
732
|
+
const next = async () => {
|
|
733
|
+
if (nextCalled) {
|
|
734
|
+
throw new Error("next() called multiple times")
|
|
735
|
+
}
|
|
736
|
+
nextCalled = true
|
|
737
|
+
await this._runMiddleware(ctx, index + 1)
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
await middleware(ctx, next)
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
_buildMultipartStream(formData, boundary) {
|
|
744
|
+
const stream = new Readable({
|
|
745
|
+
read() {},
|
|
746
|
+
})
|
|
747
|
+
const nl = "\r\n"
|
|
748
|
+
;(async () => {
|
|
749
|
+
try {
|
|
750
|
+
for (const [key, value] of Object.entries(formData)) {
|
|
751
|
+
if (value === undefined || value === null) continue
|
|
752
|
+
|
|
753
|
+
stream.push(`--${boundary}${nl}`)
|
|
754
|
+
if (value && typeof value === "object" && (value.data || value instanceof Stream || Buffer.isBuffer(value))) {
|
|
755
|
+
const fileData = value.data || value
|
|
756
|
+
const filename = value.filename || `file_${Date.now()}.jpg`
|
|
757
|
+
const contentType = value.contentType || this._getMime(extname(filename)) || "image/jpeg"
|
|
758
|
+
|
|
759
|
+
stream.push(`Content-Disposition: form-data; name="${key}"; filename="${filename}"${nl}`)
|
|
760
|
+
stream.push(`Content-Type: ${contentType}${nl}${nl}`)
|
|
761
|
+
|
|
762
|
+
if (fileData instanceof Stream) {
|
|
763
|
+
fileData.pipe(stream, { end: false })
|
|
764
|
+
await new Promise((resolve, reject) => {
|
|
765
|
+
fileData.on("end", resolve)
|
|
766
|
+
fileData.on("error", reject)
|
|
767
|
+
})
|
|
768
|
+
} else {
|
|
769
|
+
stream.push(fileData)
|
|
770
|
+
}
|
|
771
|
+
} else {
|
|
772
|
+
stream.push(`Content-Disposition: form-data; name="${key}"${nl}${nl}`)
|
|
773
|
+
stream.push(String(value))
|
|
774
|
+
}
|
|
775
|
+
stream.push(`${nl}`)
|
|
776
|
+
}
|
|
777
|
+
stream.push(`--${boundary}--${nl}`)
|
|
778
|
+
stream.push(null)
|
|
779
|
+
} catch (err) {
|
|
780
|
+
stream.destroy(err)
|
|
781
|
+
}
|
|
782
|
+
})()
|
|
783
|
+
return stream
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
_getMime(ext) {
|
|
787
|
+
const mimeTypes = {
|
|
788
|
+
".jpg": "image/jpeg",
|
|
789
|
+
".jpeg": "image/jpeg",
|
|
790
|
+
".png": "image/png",
|
|
791
|
+
".gif": "image/gif",
|
|
792
|
+
".mp4": "video/mp4",
|
|
793
|
+
".avi": "video/x-msvideo",
|
|
794
|
+
".webm": "video/webm",
|
|
795
|
+
".mp3": "audio/mpeg",
|
|
796
|
+
".ogg": "audio/ogg",
|
|
797
|
+
".wav": "audio/wav",
|
|
798
|
+
".pdf": "application/pdf",
|
|
799
|
+
".doc": "application/msword",
|
|
800
|
+
".txt": "text/plain",
|
|
801
|
+
}
|
|
802
|
+
return mimeTypes[ext.toLowerCase()] || "application/octet-stream"
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
async _prepareFile(source, type) {
|
|
806
|
+
if (typeof source === "string") {
|
|
807
|
+
if (source.startsWith("http://") || source.startsWith("https://")) {
|
|
808
|
+
return source
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
try {
|
|
812
|
+
const stat = statSync(source)
|
|
813
|
+
if (stat.isFile()) {
|
|
814
|
+
return {
|
|
815
|
+
data: createReadStream(source),
|
|
816
|
+
filename: basename(source),
|
|
817
|
+
contentType: this._getMime(extname(source)),
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
} catch (e) {
|
|
821
|
+
return source
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
if (Buffer.isBuffer(source) || source instanceof Stream) {
|
|
826
|
+
return {
|
|
827
|
+
data: source,
|
|
828
|
+
filename: `file_${Date.now()}.jpg`,
|
|
829
|
+
contentType: "image/jpeg",
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
return source
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
_formatError(response) {
|
|
837
|
+
const error = new Error(response.description || "Telegram API Error")
|
|
838
|
+
error.response = response
|
|
839
|
+
error.errorCode = response.error_code
|
|
840
|
+
return error
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
static InlineKeyboard() {
|
|
844
|
+
return new InlineKeyboardBuilder()
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
static ReplyKeyboard() {
|
|
848
|
+
return new ReplyKeyboardBuilder()
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
static RemoveKeyboard() {
|
|
852
|
+
return { remove_keyboard: true }
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
static ForceReply() {
|
|
856
|
+
return { force_reply: true }
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
class InlineKeyboardBuilder {
|
|
861
|
+
constructor() {
|
|
862
|
+
this.keyboard = [[]]
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
text(text, callbackData) {
|
|
866
|
+
this.keyboard[this.keyboard.length - 1].push({
|
|
867
|
+
text,
|
|
868
|
+
callback_data: callbackData,
|
|
869
|
+
})
|
|
870
|
+
return this
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
url(text, url) {
|
|
874
|
+
this.keyboard[this.keyboard.length - 1].push({
|
|
875
|
+
text,
|
|
876
|
+
url,
|
|
877
|
+
})
|
|
878
|
+
return this
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
login(text, loginUrl) {
|
|
882
|
+
this.keyboard[this.keyboard.length - 1].push({
|
|
883
|
+
text,
|
|
884
|
+
login_url: loginUrl,
|
|
885
|
+
})
|
|
886
|
+
return this
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
switchInline(text, query = "") {
|
|
890
|
+
this.keyboard[this.keyboard.length - 1].push({
|
|
891
|
+
text,
|
|
892
|
+
switch_inline_query: query,
|
|
893
|
+
})
|
|
894
|
+
return this
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
switchInlineCurrent(text, query = "") {
|
|
898
|
+
this.keyboard[this.keyboard.length - 1].push({
|
|
899
|
+
text,
|
|
900
|
+
switch_inline_query_current_chat: query,
|
|
901
|
+
})
|
|
902
|
+
return this
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
game(text) {
|
|
906
|
+
this.keyboard[this.keyboard.length - 1].push({
|
|
907
|
+
text,
|
|
908
|
+
callback_game: {},
|
|
909
|
+
})
|
|
910
|
+
return this
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
pay(text) {
|
|
914
|
+
this.keyboard[this.keyboard.length - 1].push({
|
|
915
|
+
text,
|
|
916
|
+
pay: true,
|
|
917
|
+
})
|
|
918
|
+
return this
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
row() {
|
|
922
|
+
this.keyboard.push([])
|
|
923
|
+
return this
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
build() {
|
|
927
|
+
return { inline_keyboard: this.keyboard }
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
class ReplyKeyboardBuilder {
|
|
932
|
+
constructor() {
|
|
933
|
+
this.keyboard = [[]]
|
|
934
|
+
this.options = {}
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
text(text) {
|
|
938
|
+
this.keyboard[this.keyboard.length - 1].push({ text })
|
|
939
|
+
return this
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
requestContact(text) {
|
|
943
|
+
this.keyboard[this.keyboard.length - 1].push({
|
|
944
|
+
text,
|
|
945
|
+
request_contact: true,
|
|
946
|
+
})
|
|
947
|
+
return this
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
requestLocation(text) {
|
|
951
|
+
this.keyboard[this.keyboard.length - 1].push({
|
|
952
|
+
text,
|
|
953
|
+
request_location: true,
|
|
954
|
+
})
|
|
955
|
+
return this
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
requestPoll(text, type = "quiz") {
|
|
959
|
+
this.keyboard[this.keyboard.length - 1].push({
|
|
960
|
+
text,
|
|
961
|
+
request_poll: { type },
|
|
962
|
+
})
|
|
963
|
+
return this
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
row() {
|
|
967
|
+
this.keyboard.push([])
|
|
968
|
+
return this
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
resize(resize = true) {
|
|
972
|
+
this.options.resize_keyboard = resize
|
|
973
|
+
return this
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
oneTime(oneTime = true) {
|
|
977
|
+
this.options.one_time_keyboard = oneTime
|
|
978
|
+
return this
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
selective(selective = true) {
|
|
982
|
+
this.options.selective = selective
|
|
983
|
+
return this
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
placeholder(text) {
|
|
987
|
+
this.options.input_field_placeholder = text
|
|
988
|
+
return this
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
build() {
|
|
992
|
+
return {
|
|
993
|
+
keyboard: this.keyboard,
|
|
994
|
+
...this.options,
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
export default TelegramBot
|
|
1000
|
+
export { TelegramBot as TelegramBot }
|
|
1001
|
+
export { InlineKeyboardBuilder as InlineKeyboardBuilder }
|
|
1002
|
+
export { ReplyKeyboardBuilder as ReplyKeyboardBuilder }
|