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