teh-bot 1.0.4 → 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/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 }