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.
@@ -65,7 +65,7 @@ class TelegramBot extends EventEmitter {
65
65
  method: "POST",
66
66
  headers: {
67
67
  ...headers,
68
- "User-Agent": "TehBot/1.0.0 (Modern; High-Performance)",
68
+ "User-Agent": "TehBot/1.0.5 (Modern; High-Performance)",
69
69
  },
70
70
  timeout: this.options.requestTimeout,
71
71
  }
@@ -86,16 +86,46 @@ class TelegramBot extends EventEmitter {
86
86
  })
87
87
 
88
88
  req.on("error", reject)
89
- if (body instanceof Stream) body.pipe(req)
90
- else {
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 {
91
97
  req.write(body)
92
98
  req.end()
93
99
  }
94
100
  })
95
101
  }
96
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
+
97
128
  async sendMessage(chatId, content, options = {}) {
98
- // content can be string (text) or object { image: ..., caption: ... }
99
129
  if (typeof content === "string") {
100
130
  return this.request("sendMessage", { chat_id: chatId, text: content, ...options })
101
131
  }
@@ -601,7 +631,6 @@ class TelegramBot extends EventEmitter {
601
631
  _createContext(update) {
602
632
  const message = update.message || update.edited_message || update.channel_post || update.callback_query?.message
603
633
 
604
- // Fallback logic for chat extraction from various update types
605
634
  const chat =
606
635
  update.message?.chat ||
607
636
  update.callback_query?.message?.chat ||
@@ -636,7 +665,6 @@ class TelegramBot extends EventEmitter {
636
665
  chatMember: update.chat_member,
637
666
  }
638
667
 
639
- // Baileys-style simplified response
640
668
  ctx.send = (content, opts) => {
641
669
  const chatId =
642
670
  ctx.chat?.id ||
@@ -646,7 +674,7 @@ class TelegramBot extends EventEmitter {
646
674
  from?.id
647
675
 
648
676
  if (!chatId) {
649
- console.error("[v0] Context update without valid chatId destination:", JSON.stringify(update))
677
+ console.error("[Teh] Context update without valid chatId destination:", JSON.stringify(update))
650
678
  throw new Error("[Teh] Cannot send message: chat_id could not be resolved from this update context")
651
679
  }
652
680
  return this.sendMessage(chatId, content, opts)
@@ -663,13 +691,11 @@ class TelegramBot extends EventEmitter {
663
691
  })
664
692
  }
665
693
 
666
- // Context helpers for media
667
694
  ctx.replyWithPhoto = (photo, opts) => ctx.send({ image: photo, ...opts })
668
695
  ctx.replyWithVideo = (video, opts) => ctx.send({ video, ...opts })
669
696
  ctx.replyWithAudio = (audio, opts) => ctx.send({ audio, ...opts })
670
697
  ctx.replyWithDocument = (doc, opts) => ctx.send({ document: doc, ...opts })
671
698
 
672
- // Helper methods for callback queries
673
699
  ctx.answerCallbackQuery = (options = {}) => {
674
700
  if (!ctx.callbackQuery?.id) return Promise.resolve(false)
675
701
  return this.answerCallbackQuery(ctx.callbackQuery.id, options)
@@ -677,7 +703,6 @@ class TelegramBot extends EventEmitter {
677
703
 
678
704
  ctx.editMessageText = (text, options = {}) => {
679
705
  if (!ctx.callbackQuery?.message) {
680
- // If it's an inline message (no message object), we need the inline_message_id
681
706
  if (ctx.callbackQuery?.inline_message_id) {
682
707
  return this.editMessageText(text, {
683
708
  inline_message_id: ctx.callbackQuery.inline_message_id,
@@ -716,116 +741,103 @@ class TelegramBot extends EventEmitter {
716
741
  }
717
742
 
718
743
  _buildMultipartStream(formData, boundary) {
719
- const stream = new Readable({ read() {} })
744
+ const stream = new Readable({
745
+ read() {},
746
+ })
720
747
  const nl = "\r\n"
721
748
  ;(async () => {
722
- for (const [key, value] of Object.entries(formData)) {
723
- if (value === undefined || value === null) continue
724
-
725
- stream.push(`--${boundary}${nl}`)
726
- if (value && typeof value === "object" && value.data) {
727
- const filename = value.filename || `file_${Date.now()}`
728
- stream.push(`Content-Disposition: form-data; name="${key}"; filename="${filename}"${nl}`)
729
- stream.push(`Content-Type: ${value.contentType || "application/octet-stream"}${nl}${nl}`)
730
-
731
- if (value.data instanceof Stream) {
732
- for await (const chunk of value.data) stream.push(chunk)
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
+ }
733
771
  } else {
734
- stream.push(value.data)
772
+ stream.push(`Content-Disposition: form-data; name="${key}"${nl}${nl}`)
773
+ stream.push(String(value))
735
774
  }
736
- } else {
737
- stream.push(`Content-Disposition: form-data; name="${key}"${nl}${nl}`)
738
- stream.push(typeof value === "object" ? JSON.stringify(value) : String(value))
775
+ stream.push(`${nl}`)
739
776
  }
740
- stream.push(nl)
777
+ stream.push(`--${boundary}--${nl}`)
778
+ stream.push(null)
779
+ } catch (err) {
780
+ stream.destroy(err)
741
781
  }
742
- stream.push(`--${boundary}--${nl}`)
743
- stream.push(null)
744
782
  })()
745
-
746
783
  return stream
747
784
  }
748
785
 
749
- async _prepareFile(source, type) {
750
- if (source instanceof Stream) return { data: source, contentType: this._getMime(type) }
751
- if (Buffer.isBuffer(source)) return { data: source, contentType: this._getMime(type) }
752
-
753
- if (typeof source === "string") {
754
- if (source.startsWith("http")) {
755
- // Fetch remote URL and return as stream
756
- return new Promise((resolve, reject) => {
757
- const client = source.startsWith("https") ? https : http
758
- client
759
- .get(source, (res) => {
760
- if (res.statusCode !== 200) {
761
- return reject(new Error(`Failed to fetch remote file: ${res.statusCode}`))
762
- }
763
- resolve({
764
- data: res,
765
- filename: basename(new URL(source).pathname) || `file_${Date.now()}`,
766
- contentType: res.headers["content-type"] || this._getMime(type),
767
- })
768
- })
769
- .on("error", reject)
770
- })
771
- }
772
-
773
- // Local file
774
- return {
775
- data: createReadStream(source),
776
- filename: basename(source),
777
- contentType: this._getMime(extname(source)),
778
- }
779
- }
780
- return source
781
- }
782
-
783
786
  _getMime(ext) {
784
- const mimes = {
785
- photo: "image/jpeg",
786
- video: "video/mp4",
787
- audio: "audio/mpeg",
787
+ const mimeTypes = {
788
788
  ".jpg": "image/jpeg",
789
+ ".jpeg": "image/jpeg",
789
790
  ".png": "image/png",
791
+ ".gif": "image/gif",
790
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",
791
801
  }
792
- return mimes[ext] || "application/octet-stream"
802
+ return mimeTypes[ext.toLowerCase()] || "application/octet-stream"
793
803
  }
794
804
 
795
- _flattenOptions(options) {
796
- const flat = {}
797
- for (const [key, value] of Object.entries(options)) {
798
- if (typeof value === "object" && value !== null) {
799
- flat[key] = JSON.stringify(value)
800
- } else {
801
- flat[key] = value
805
+ async _prepareFile(source, type) {
806
+ if (typeof source === "string") {
807
+ if (source.startsWith("http://") || source.startsWith("https://")) {
808
+ return source
802
809
  }
803
- }
804
- return flat
805
- }
806
810
 
807
- _formatError(resp) {
808
- const err = new Error(resp.description || "Unknown Telegram Error")
809
- err.code = resp.error_code
810
- err.parameters = resp.parameters
811
- err.response = resp
812
- err.name = "TelegramAPIError"
813
- return err
814
- }
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
+ }
815
824
 
816
- async deleteWebhook(options = {}) {
817
- return this.request("deleteWebhook", options)
818
- }
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
+ }
819
832
 
820
- async setWebhook(url, options = {}) {
821
- return this.request("setWebhook", {
822
- url,
823
- ...options,
824
- })
833
+ return source
825
834
  }
826
835
 
827
- async getWebhookInfo() {
828
- return this.request("getWebhookInfo")
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
829
841
  }
830
842
 
831
843
  static InlineKeyboard() {
@@ -836,25 +848,22 @@ class TelegramBot extends EventEmitter {
836
848
  return new ReplyKeyboardBuilder()
837
849
  }
838
850
 
839
- static RemoveKeyboard(selective = false) {
840
- return { remove_keyboard: true, selective }
851
+ static RemoveKeyboard() {
852
+ return { remove_keyboard: true }
841
853
  }
842
854
 
843
- static ForceReply(selective = false, placeholder = "") {
844
- const obj = { force_reply: true, selective }
845
- if (placeholder) obj.input_field_placeholder = placeholder
846
- return obj
855
+ static ForceReply() {
856
+ return { force_reply: true }
847
857
  }
848
858
  }
849
859
 
850
860
  class InlineKeyboardBuilder {
851
861
  constructor() {
852
- this.keyboard = []
853
- this.currentRow = []
862
+ this.keyboard = [[]]
854
863
  }
855
864
 
856
865
  text(text, callbackData) {
857
- this.currentRow.push({
866
+ this.keyboard[this.keyboard.length - 1].push({
858
867
  text,
859
868
  callback_data: callbackData,
860
869
  })
@@ -862,7 +871,7 @@ class InlineKeyboardBuilder {
862
871
  }
863
872
 
864
873
  url(text, url) {
865
- this.currentRow.push({
874
+ this.keyboard[this.keyboard.length - 1].push({
866
875
  text,
867
876
  url,
868
877
  })
@@ -870,7 +879,7 @@ class InlineKeyboardBuilder {
870
879
  }
871
880
 
872
881
  login(text, loginUrl) {
873
- this.currentRow.push({
882
+ this.keyboard[this.keyboard.length - 1].push({
874
883
  text,
875
884
  login_url: loginUrl,
876
885
  })
@@ -878,7 +887,7 @@ class InlineKeyboardBuilder {
878
887
  }
879
888
 
880
889
  switchInline(text, query = "") {
881
- this.currentRow.push({
890
+ this.keyboard[this.keyboard.length - 1].push({
882
891
  text,
883
892
  switch_inline_query: query,
884
893
  })
@@ -886,7 +895,7 @@ class InlineKeyboardBuilder {
886
895
  }
887
896
 
888
897
  switchInlineCurrent(text, query = "") {
889
- this.currentRow.push({
898
+ this.keyboard[this.keyboard.length - 1].push({
890
899
  text,
891
900
  switch_inline_query_current_chat: query,
892
901
  })
@@ -894,7 +903,7 @@ class InlineKeyboardBuilder {
894
903
  }
895
904
 
896
905
  game(text) {
897
- this.currentRow.push({
906
+ this.keyboard[this.keyboard.length - 1].push({
898
907
  text,
899
908
  callback_game: {},
900
909
  })
@@ -902,7 +911,7 @@ class InlineKeyboardBuilder {
902
911
  }
903
912
 
904
913
  pay(text) {
905
- this.currentRow.push({
914
+ this.keyboard[this.keyboard.length - 1].push({
906
915
  text,
907
916
  pay: true,
908
917
  })
@@ -910,35 +919,28 @@ class InlineKeyboardBuilder {
910
919
  }
911
920
 
912
921
  row() {
913
- if (this.currentRow.length > 0) {
914
- this.keyboard.push([...this.currentRow])
915
- this.currentRow = []
916
- }
922
+ this.keyboard.push([])
917
923
  return this
918
924
  }
919
925
 
920
926
  build() {
921
- this.row()
922
- return {
923
- inline_keyboard: this.keyboard,
924
- }
927
+ return { inline_keyboard: this.keyboard }
925
928
  }
926
929
  }
927
930
 
928
931
  class ReplyKeyboardBuilder {
929
932
  constructor() {
930
- this.keyboard = []
931
- this.currentRow = []
933
+ this.keyboard = [[]]
932
934
  this.options = {}
933
935
  }
934
936
 
935
937
  text(text) {
936
- this.currentRow.push({ text })
938
+ this.keyboard[this.keyboard.length - 1].push({ text })
937
939
  return this
938
940
  }
939
941
 
940
942
  requestContact(text) {
941
- this.currentRow.push({
943
+ this.keyboard[this.keyboard.length - 1].push({
942
944
  text,
943
945
  request_contact: true,
944
946
  })
@@ -946,15 +948,15 @@ class ReplyKeyboardBuilder {
946
948
  }
947
949
 
948
950
  requestLocation(text) {
949
- this.currentRow.push({
951
+ this.keyboard[this.keyboard.length - 1].push({
950
952
  text,
951
953
  request_location: true,
952
954
  })
953
955
  return this
954
956
  }
955
957
 
956
- requestPoll(text, type) {
957
- this.currentRow.push({
958
+ requestPoll(text, type = "quiz") {
959
+ this.keyboard[this.keyboard.length - 1].push({
958
960
  text,
959
961
  request_poll: { type },
960
962
  })
@@ -962,10 +964,7 @@ class ReplyKeyboardBuilder {
962
964
  }
963
965
 
964
966
  row() {
965
- if (this.currentRow.length > 0) {
966
- this.keyboard.push([...this.currentRow])
967
- this.currentRow = []
968
- }
967
+ this.keyboard.push([])
969
968
  return this
970
969
  }
971
970
 
@@ -990,7 +989,6 @@ class ReplyKeyboardBuilder {
990
989
  }
991
990
 
992
991
  build() {
993
- this.row()
994
992
  return {
995
993
  keyboard: this.keyboard,
996
994
  ...this.options,