@xbibzlibrary/telebibz 0.4.5 → 3.0.1

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.
Files changed (276) hide show
  1. package/CHANGELOG.md +35 -138
  2. package/LICENSE +1 -1
  3. package/NOTICE.md +9 -4
  4. package/README.md +173 -256
  5. package/examples/01-quickstart.js +13 -0
  6. package/examples/02-menu-tombol.js +22 -0
  7. package/examples/03-wizard.js +29 -0
  8. package/examples/04-broadcast.js +25 -0
  9. package/examples/05-kirim-file.js +19 -0
  10. package/examples/06-menu.js +35 -0
  11. package/examples/07-inline-query.js +18 -0
  12. package/index.d.ts +81 -0
  13. package/index.js +42 -0
  14. package/lib/api.js +154 -0
  15. package/lib/broadcast.js +35 -0
  16. package/lib/composer.js +174 -0
  17. package/lib/context.js +190 -0
  18. package/lib/errors.js +35 -0
  19. package/lib/file.js +41 -0
  20. package/lib/inline-query.js +28 -0
  21. package/lib/keyboard.js +83 -0
  22. package/lib/logger.js +38 -0
  23. package/lib/menus.js +90 -0
  24. package/lib/net.js +115 -0
  25. package/lib/ratelimit.js +61 -0
  26. package/lib/runner.js +45 -0
  27. package/lib/session.js +36 -0
  28. package/lib/telebibz.js +190 -0
  29. package/lib/wizard.js +81 -0
  30. package/package.json +35 -97
  31. package/test/all.test.js +334 -0
  32. package/CODE_OF_CONDUCT.md +0 -37
  33. package/CONTRIBUTING.md +0 -59
  34. package/CONTRIBUTION_RULES.md +0 -41
  35. package/GOVERNANCE.md +0 -47
  36. package/README.id.md +0 -306
  37. package/README.zh-CN.md +0 -306
  38. package/RELEASE_AUTOMATION.md +0 -78
  39. package/RELEASE_POLICY.md +0 -32
  40. package/SECURITY.md +0 -47
  41. package/SHOWCASE.md +0 -29
  42. package/SUPPORT.md +0 -30
  43. package/assets/readme-preview.html +0 -75
  44. package/assets/telebibz-logo.png +0 -0
  45. package/assets/telebibz-readme-preview.png +0 -0
  46. package/bin/telebibz.mjs +0 -3
  47. package/dist/generated/api.d.ts +0 -13
  48. package/dist/generated/api.d.ts.map +0 -1
  49. package/dist/generated/api.js +0 -192
  50. package/dist/generated/api.js.map +0 -1
  51. package/dist/src/api/client.d.ts +0 -62
  52. package/dist/src/api/client.d.ts.map +0 -1
  53. package/dist/src/api/client.js +0 -104
  54. package/dist/src/api/client.js.map +0 -1
  55. package/dist/src/api/errors.d.ts +0 -45
  56. package/dist/src/api/errors.d.ts.map +0 -1
  57. package/dist/src/api/errors.js +0 -65
  58. package/dist/src/api/errors.js.map +0 -1
  59. package/dist/src/api/index.d.ts +0 -6
  60. package/dist/src/api/index.d.ts.map +0 -1
  61. package/dist/src/api/index.js +0 -6
  62. package/dist/src/api/index.js.map +0 -1
  63. package/dist/src/api/telegram-types/LICENSE +0 -21
  64. package/dist/src/api/telegram-types/api.d.ts +0 -22
  65. package/dist/src/api/telegram-types/checklist.d.ts +0 -72
  66. package/dist/src/api/telegram-types/inline.d.ts +0 -692
  67. package/dist/src/api/telegram-types/langs.d.ts +0 -193
  68. package/dist/src/api/telegram-types/manage.d.ts +0 -1144
  69. package/dist/src/api/telegram-types/markup.d.ts +0 -268
  70. package/dist/src/api/telegram-types/message.d.ts +0 -1537
  71. package/dist/src/api/telegram-types/methods.d.ts +0 -2870
  72. package/dist/src/api/telegram-types/mod.d.ts +0 -14
  73. package/dist/src/api/telegram-types/passport.d.ts +0 -163
  74. package/dist/src/api/telegram-types/payment.d.ts +0 -570
  75. package/dist/src/api/telegram-types/rich.d.ts +0 -1010
  76. package/dist/src/api/telegram-types/settings.d.ts +0 -120
  77. package/dist/src/api/telegram-types/story.d.ts +0 -89
  78. package/dist/src/api/telegram-types/update.d.ts +0 -84
  79. package/dist/src/api/telegram.d.ts +0 -7
  80. package/dist/src/api/telegram.d.ts.map +0 -1
  81. package/dist/src/api/telegram.js +0 -2
  82. package/dist/src/api/telegram.js.map +0 -1
  83. package/dist/src/api/transport.d.ts +0 -68
  84. package/dist/src/api/transport.d.ts.map +0 -1
  85. package/dist/src/api/transport.js +0 -264
  86. package/dist/src/api/transport.js.map +0 -1
  87. package/dist/src/api/types.d.ts +0 -466
  88. package/dist/src/api/types.d.ts.map +0 -1
  89. package/dist/src/api/types.js +0 -2
  90. package/dist/src/api/types.js.map +0 -1
  91. package/dist/src/branding/terminal.d.ts +0 -77
  92. package/dist/src/branding/terminal.d.ts.map +0 -1
  93. package/dist/src/branding/terminal.js +0 -328
  94. package/dist/src/branding/terminal.js.map +0 -1
  95. package/dist/src/broadcast/broadcast.d.ts +0 -50
  96. package/dist/src/broadcast/broadcast.d.ts.map +0 -1
  97. package/dist/src/broadcast/broadcast.js +0 -56
  98. package/dist/src/broadcast/broadcast.js.map +0 -1
  99. package/dist/src/cache/cache.d.ts +0 -34
  100. package/dist/src/cache/cache.d.ts.map +0 -1
  101. package/dist/src/cache/cache.js +0 -41
  102. package/dist/src/cache/cache.js.map +0 -1
  103. package/dist/src/cli.d.ts +0 -2
  104. package/dist/src/cli.d.ts.map +0 -1
  105. package/dist/src/cli.js +0 -84
  106. package/dist/src/cli.js.map +0 -1
  107. package/dist/src/context/context.d.ts +0 -124
  108. package/dist/src/context/context.d.ts.map +0 -1
  109. package/dist/src/context/context.js +0 -302
  110. package/dist/src/context/context.js.map +0 -1
  111. package/dist/src/core/bot.d.ts +0 -204
  112. package/dist/src/core/bot.d.ts.map +0 -1
  113. package/dist/src/core/bot.js +0 -506
  114. package/dist/src/core/bot.js.map +0 -1
  115. package/dist/src/core/events.d.ts +0 -75
  116. package/dist/src/core/events.d.ts.map +0 -1
  117. package/dist/src/core/events.js +0 -35
  118. package/dist/src/core/events.js.map +0 -1
  119. package/dist/src/core/webhook-reply.d.ts +0 -34
  120. package/dist/src/core/webhook-reply.d.ts.map +0 -1
  121. package/dist/src/core/webhook-reply.js +0 -37
  122. package/dist/src/core/webhook-reply.js.map +0 -1
  123. package/dist/src/index.d.ts +0 -24
  124. package/dist/src/index.d.ts.map +0 -1
  125. package/dist/src/index.js +0 -24
  126. package/dist/src/index.js.map +0 -1
  127. package/dist/src/keyboard/index.d.ts +0 -46
  128. package/dist/src/keyboard/index.d.ts.map +0 -1
  129. package/dist/src/keyboard/index.js +0 -55
  130. package/dist/src/keyboard/index.js.map +0 -1
  131. package/dist/src/middleware/compose.d.ts +0 -5
  132. package/dist/src/middleware/compose.d.ts.map +0 -1
  133. package/dist/src/middleware/compose.js +0 -17
  134. package/dist/src/middleware/compose.js.map +0 -1
  135. package/dist/src/observability/logger.d.ts +0 -78
  136. package/dist/src/observability/logger.d.ts.map +0 -1
  137. package/dist/src/observability/logger.js +0 -285
  138. package/dist/src/observability/logger.js.map +0 -1
  139. package/dist/src/plugins/plugin.d.ts +0 -38
  140. package/dist/src/plugins/plugin.d.ts.map +0 -1
  141. package/dist/src/plugins/plugin.js +0 -59
  142. package/dist/src/plugins/plugin.js.map +0 -1
  143. package/dist/src/queue/queue.d.ts +0 -77
  144. package/dist/src/queue/queue.d.ts.map +0 -1
  145. package/dist/src/queue/queue.js +0 -213
  146. package/dist/src/queue/queue.js.map +0 -1
  147. package/dist/src/router/router.d.ts +0 -61
  148. package/dist/src/router/router.d.ts.map +0 -1
  149. package/dist/src/router/router.js +0 -183
  150. package/dist/src/router/router.js.map +0 -1
  151. package/dist/src/state/conversation.d.ts +0 -56
  152. package/dist/src/state/conversation.d.ts.map +0 -1
  153. package/dist/src/state/conversation.js +0 -133
  154. package/dist/src/state/conversation.js.map +0 -1
  155. package/dist/src/state/forms.d.ts +0 -34
  156. package/dist/src/state/forms.d.ts.map +0 -1
  157. package/dist/src/state/forms.js +0 -44
  158. package/dist/src/state/forms.js.map +0 -1
  159. package/dist/src/state/menu.d.ts +0 -78
  160. package/dist/src/state/menu.d.ts.map +0 -1
  161. package/dist/src/state/menu.js +0 -127
  162. package/dist/src/state/menu.js.map +0 -1
  163. package/dist/src/storage/storage.d.ts +0 -146
  164. package/dist/src/storage/storage.d.ts.map +0 -1
  165. package/dist/src/storage/storage.js +0 -195
  166. package/dist/src/storage/storage.js.map +0 -1
  167. package/dist/src/telegram-features.d.ts +0 -33
  168. package/dist/src/telegram-features.d.ts.map +0 -1
  169. package/dist/src/telegram-features.js +0 -71
  170. package/dist/src/telegram-features.js.map +0 -1
  171. package/dist/src/testing.d.ts +0 -24
  172. package/dist/src/testing.d.ts.map +0 -1
  173. package/dist/src/testing.js +0 -38
  174. package/dist/src/testing.js.map +0 -1
  175. package/dist/src/utils/concurrency.d.ts +0 -25
  176. package/dist/src/utils/concurrency.d.ts.map +0 -1
  177. package/dist/src/utils/concurrency.js +0 -52
  178. package/dist/src/utils/concurrency.js.map +0 -1
  179. package/dist/src/utils/files.d.ts +0 -45
  180. package/dist/src/utils/files.d.ts.map +0 -1
  181. package/dist/src/utils/files.js +0 -53
  182. package/dist/src/utils/files.js.map +0 -1
  183. package/dist/src/utils/text.d.ts +0 -39
  184. package/dist/src/utils/text.d.ts.map +0 -1
  185. package/dist/src/utils/text.js +0 -56
  186. package/dist/src/utils/text.js.map +0 -1
  187. package/dist/src/webhook/handler.d.ts +0 -19
  188. package/dist/src/webhook/handler.d.ts.map +0 -1
  189. package/dist/src/webhook/handler.js +0 -141
  190. package/dist/src/webhook/handler.js.map +0 -1
  191. package/dist-cjs/generated/api.js +0 -194
  192. package/dist-cjs/package.json +0 -3
  193. package/dist-cjs/src/api/client.js +0 -107
  194. package/dist-cjs/src/api/errors.js +0 -74
  195. package/dist-cjs/src/api/index.js +0 -21
  196. package/dist-cjs/src/api/telegram-types/LICENSE +0 -21
  197. package/dist-cjs/src/api/telegram-types/api.d.ts +0 -22
  198. package/dist-cjs/src/api/telegram-types/checklist.d.ts +0 -72
  199. package/dist-cjs/src/api/telegram-types/inline.d.ts +0 -692
  200. package/dist-cjs/src/api/telegram-types/langs.d.ts +0 -193
  201. package/dist-cjs/src/api/telegram-types/manage.d.ts +0 -1144
  202. package/dist-cjs/src/api/telegram-types/markup.d.ts +0 -268
  203. package/dist-cjs/src/api/telegram-types/message.d.ts +0 -1537
  204. package/dist-cjs/src/api/telegram-types/methods.d.ts +0 -2870
  205. package/dist-cjs/src/api/telegram-types/mod.d.ts +0 -14
  206. package/dist-cjs/src/api/telegram-types/passport.d.ts +0 -163
  207. package/dist-cjs/src/api/telegram-types/payment.d.ts +0 -570
  208. package/dist-cjs/src/api/telegram-types/rich.d.ts +0 -1010
  209. package/dist-cjs/src/api/telegram-types/settings.d.ts +0 -120
  210. package/dist-cjs/src/api/telegram-types/story.d.ts +0 -89
  211. package/dist-cjs/src/api/telegram-types/update.d.ts +0 -84
  212. package/dist-cjs/src/api/telegram.js +0 -2
  213. package/dist-cjs/src/api/transport.js +0 -267
  214. package/dist-cjs/src/api/types.js +0 -2
  215. package/dist-cjs/src/branding/terminal.js +0 -338
  216. package/dist-cjs/src/broadcast/broadcast.js +0 -58
  217. package/dist-cjs/src/cache/cache.js +0 -45
  218. package/dist-cjs/src/cli.js +0 -86
  219. package/dist-cjs/src/context/context.js +0 -305
  220. package/dist-cjs/src/core/bot.js +0 -510
  221. package/dist-cjs/src/core/events.js +0 -38
  222. package/dist-cjs/src/core/webhook-reply.js +0 -42
  223. package/dist-cjs/src/index.js +0 -47
  224. package/dist-cjs/src/keyboard/index.js +0 -61
  225. package/dist-cjs/src/middleware/compose.js +0 -20
  226. package/dist-cjs/src/observability/logger.js +0 -293
  227. package/dist-cjs/src/plugins/plugin.js +0 -63
  228. package/dist-cjs/src/queue/queue.js +0 -219
  229. package/dist-cjs/src/router/router.js +0 -186
  230. package/dist-cjs/src/state/conversation.js +0 -139
  231. package/dist-cjs/src/state/forms.js +0 -47
  232. package/dist-cjs/src/state/menu.js +0 -133
  233. package/dist-cjs/src/storage/storage.js +0 -202
  234. package/dist-cjs/src/telegram-features.js +0 -76
  235. package/dist-cjs/src/testing.js +0 -45
  236. package/dist-cjs/src/utils/concurrency.js +0 -57
  237. package/dist-cjs/src/utils/files.js +0 -58
  238. package/dist-cjs/src/utils/text.js +0 -63
  239. package/dist-cjs/src/webhook/handler.js +0 -144
  240. package/docs/API.id.md +0 -1935
  241. package/docs/API.md +0 -1969
  242. package/docs/API.zh-CN.md +0 -1929
  243. package/docs/COOKBOOK.id.md +0 -321
  244. package/docs/COOKBOOK.md +0 -321
  245. package/docs/COOKBOOK.zh-CN.md +0 -321
  246. package/docs/ERRORS.id.md +0 -194
  247. package/docs/ERRORS.md +0 -194
  248. package/docs/ERRORS.zh-CN.md +0 -194
  249. package/docs/FILES.id.md +0 -243
  250. package/docs/FILES.md +0 -243
  251. package/docs/FILES.zh-CN.md +0 -243
  252. package/docs/GETTING_STARTED.id.md +0 -89
  253. package/docs/GETTING_STARTED.md +0 -89
  254. package/docs/GETTING_STARTED.zh-CN.md +0 -89
  255. package/docs/GITHUB_PACKAGES.id.md +0 -82
  256. package/docs/GITHUB_PACKAGES.md +0 -82
  257. package/docs/GITHUB_PACKAGES.zh-CN.md +0 -82
  258. package/docs/MIGRATION_TELEGRAF.id.md +0 -147
  259. package/docs/MIGRATION_TELEGRAF.md +0 -154
  260. package/docs/MIGRATION_TELEGRAF.zh-CN.md +0 -147
  261. package/docs/README.md +0 -67
  262. package/docs/STORAGE.id.md +0 -105
  263. package/docs/STORAGE.md +0 -105
  264. package/docs/STORAGE.zh-CN.md +0 -105
  265. package/docs/TESTING.id.md +0 -203
  266. package/docs/TESTING.md +0 -203
  267. package/docs/TESTING.zh-CN.md +0 -203
  268. package/docs/WEBHOOK.id.md +0 -212
  269. package/docs/WEBHOOK.md +0 -215
  270. package/docs/WEBHOOK.zh-CN.md +0 -212
  271. package/examples/README.md +0 -37
  272. package/examples/files.ts +0 -35
  273. package/examples/minimal.ts +0 -12
  274. package/examples/tsconfig.json +0 -9
  275. package/examples/webhook.ts +0 -42
  276. package/examples/wizard-registration.ts +0 -42
package/lib/context.js ADDED
@@ -0,0 +1,190 @@
1
+ // lib/context.js — objek konteks: SEMUA accessor update + ~60 pintasan aksi.
2
+ 'use strict';
3
+
4
+ const { File } = require('./file');
5
+
6
+ /** Objek "pesan" dari berbagai field update (gaya ctx.msg). */
7
+ function msgOf(update) {
8
+ return update.message || update.edited_message || update.channel_post ||
9
+ update.edited_channel_post || update.business_message || update.edited_business_message || null;
10
+ }
11
+
12
+ const UPDATE_KEYS = [
13
+ 'message', 'edited_message', 'channel_post', 'edited_channel_post',
14
+ 'business_message', 'edited_business_message', 'callback_query', 'inline_query',
15
+ 'chosen_inline_result', 'chat_join_request', 'chat_member', 'my_chat_member',
16
+ 'message_reaction', 'message_reaction_count', 'business_connection',
17
+ 'poll', 'poll_answer', 'shipping_query', 'pre_checkout_query', 'purchased_paid_media',
18
+ ];
19
+
20
+ class Context {
21
+ constructor(update, api, me) {
22
+ this.update = update;
23
+ this.api = api;
24
+ this.me = me;
25
+ this.update_id = update.update_id;
26
+ for (const k of UPDATE_KEYS) if (update[k] !== undefined) this[k] = update[k];
27
+ this.msg = msgOf(update);
28
+ }
29
+
30
+ /* ---------- accessor dasar ---------- */
31
+ get chat() {
32
+ if (this.msg) return this.msg.chat;
33
+ if (this.callback_query) return this.callback_query.message && this.callback_query.message.chat;
34
+ if (this.inline_query || this.chosen_inline_result) return undefined;
35
+ if (this.business_connection) return this.business_connection.user;
36
+ if (this.chat_join_request) return this.chat_join_request.chat;
37
+ if (this.chat_member) return this.chat_member.chat;
38
+ if (this.my_chat_member) return this.my_chat_member.chat;
39
+ return undefined;
40
+ }
41
+ get from() {
42
+ const u = this.msg || this.callback_query || this.inline_query || this.chosen_inline_result ||
43
+ this.chat_join_request || this.chat_member || this.my_chat_member ||
44
+ this.business_connection || this.poll_answer || this.shipping_query || this.pre_checkout_query;
45
+ if (!u) return undefined;
46
+ return u.from || u.user || (u.voter_user !== undefined ? u.voter_user : undefined);
47
+ }
48
+ get chatId() { const c = this.chat; return c ? c.id : undefined; }
49
+ get msgId() { return this.msg ? this.msg.message_id : undefined; }
50
+ get businessConnectionId() {
51
+ return (this.business_connection && this.business_connection.id) ||
52
+ (this.msg && this.msg.business_connection_id) || undefined;
53
+ }
54
+ get senderChat() { return this.msg ? this.msg.sender_chat : undefined; }
55
+ get inlineMessageId() { return this.callback_query ? this.callback_query.inline_message_id : undefined; }
56
+
57
+ _cid(cid) {
58
+ if (cid !== undefined) return cid;
59
+ const c = this.chatId;
60
+ if (c === undefined) throw new Error('telebibz: ctx tanpa chat (update inline/business?)');
61
+ return c;
62
+ }
63
+ _biz(extra = {}) {
64
+ // Otomatis sertakan business_connection_id pada pesan business (gaya BizWrapper)
65
+ const bc = this.businessConnectionId;
66
+ return bc && this.msg && this.msg.business_connection_id ? { business_connection_id: bc, ...extra } : extra;
67
+ }
68
+
69
+ /* ---------- kirim balasan ---------- */
70
+ reply(text, extra = {}) { return this.api.sendMessage(this._cid(), text, this._biz(extra)); }
71
+ replyWithHTML(text, extra = {}) { return this.reply(text, { parse_mode: 'HTML', ...extra }); }
72
+ replyWithMarkdown(text, extra = {}) { return this.reply(text, { parse_mode: 'MarkdownV2', ...extra }); }
73
+ replyWithPhoto(p, e = {}) { return this.api.sendPhoto(this._cid(), p, this._biz(e)); }
74
+ replyWithVideo(v, e = {}) { return this.api.sendVideo(this._cid(), v, this._biz(e)); }
75
+ replyWithAudio(a, e = {}) { return this.api.sendAudio(this._cid(), a, this._biz(e)); }
76
+ replyWithDocument(d, e = {}) { return this.api.sendDocument(this._cid(), d, this._biz(e)); }
77
+ replyWithAnimation(a, e = {}) { return this.api.sendAnimation(this._cid(), a, this._biz(e)); }
78
+ replyWithVoice(v, e = {}) { return this.api.sendVoice(this._cid(), v, { caption: e.caption, ...e }); }
79
+ replyWithVideoNote(v, e = {}) { return this.api.sendVideoNote(this._cid(), v, e); }
80
+ replyWithSticker(s, e = {}) { return this.api.sendSticker(this._cid(), s, e); }
81
+ replyWithMediaGroup(media, e = {}) { return this.api.sendMediaGroup(this._cid(), media, e); }
82
+ replyWithLocation(lat, lon, e = {}) { return this.api.sendLocation(this._cid(), lat, lon, e); }
83
+ replyWithVenue(lat, lon, title, address, e = {}) { return this.api.sendVenue(this._cid(), lat, lon, title, address, e); }
84
+ replyWithContact(phone, first, e = {}) { return this.api.sendContact(this._cid(), phone, first, e); }
85
+ replyWithPoll(question, options, e = {}) { return this.api.sendPoll(this._cid(), question, options, e); }
86
+ replyWithDice(emoji, e = {}) { return this.api.sendDice(this._cid(), emoji, e); }
87
+ replyWithInvoice(title, description, payload, currency, prices, e = {}) {
88
+ return this.api.sendInvoice(this._cid(), title, description, payload, currency, prices, e);
89
+ }
90
+ replyWithChatAction(action = 'typing', e = {}) { return this.api.sendChatAction(this._cid(), action, e); }
91
+
92
+ /* ---------- teruskan & salin ---------- */
93
+ forwardMessage(to, from, messageId, e = {}) {
94
+ if (messageId === undefined) return this.api.forwardMessage(to, this._cid(from), this.msgId, e || {});
95
+ return this.api.forwardMessage(to, from, messageId, e);
96
+ }
97
+ copyMessage(to, from, messageId, e = {}) {
98
+ if (messageId === undefined) return this.api.copyMessage(to, this._cid(from), this.msgId, e || {});
99
+ return this.api.copyMessage(to, from, messageId, e);
100
+ }
101
+
102
+ /* ---------- edit & hapus ---------- */
103
+ editMessageText(text, e = {}) {
104
+ if (this.inlineMessageId) return this.api.callApi('editMessageText', { inline_message_id: this.inlineMessageId, text, ...e });
105
+ const m = this.callback_query && this.callback_query.message;
106
+ return this.api.editMessageText(this._cid(m ? m.chat.id : undefined), m ? m.message_id : this.msgId, text, e);
107
+ }
108
+ editMessageCaption(e = {}) {
109
+ const m = this.callback_query && this.callback_query.message;
110
+ if (this.inlineMessageId) return this.api.callApi('editMessageCaption', { inline_message_id: this.inlineMessageId, ...e });
111
+ return this.api.editMessageCaption(this._cid(m ? m.chat.id : undefined), m ? m.message_id : this.msgId, e);
112
+ }
113
+ editMessageMedia(media, e = {}) {
114
+ const m = this.callback_query && this.callback_query.message;
115
+ if (this.inlineMessageId) return this.api.callApi('editMessageMedia', { inline_message_id: this.inlineMessageId, media, ...e });
116
+ return this.api.editMessageMedia(this._cid(m ? m.chat.id : undefined), m ? m.message_id : this.msgId, media, e);
117
+ }
118
+ editMessageReplyMarkup(e = {}) {
119
+ const m = this.callback_query && this.callback_query.message;
120
+ if (this.inlineMessageId) return this.api.callApi('editMessageReplyMarkup', { inline_message_id: this.inlineMessageId, ...e });
121
+ return this.api.editMessageReplyMarkup(this._cid(m ? m.chat.id : undefined), m ? m.message_id : this.msgId, e);
122
+ }
123
+ deleteMessage(chatId, messageId) {
124
+ if (messageId !== undefined) return this.api.deleteMessage(chatId, messageId);
125
+ const m = this.callback_query && this.callback_query.message;
126
+ return this.api.deleteMessage(m ? m.chat.id : this._cid(chatId), m ? m.message_id : this.msgId);
127
+ }
128
+ deleteMessages(ids) { return this.api.deleteMessages(this._cid(), ids); }
129
+ react(emoji = '👍', e = {}) {
130
+ const reaction = [{ type: 'emoji', emoji }];
131
+ return this.api.setMessageReaction(this._cid(), this.msgId, reaction, e);
132
+ }
133
+
134
+ /* ---------- callback & inline ---------- */
135
+ answerCallbackQuery(e = {}) {
136
+ if (typeof e === 'string') e = { text: e };
137
+ if (!this.callback_query) return Promise.resolve(false);
138
+ return this.api.answerCallbackQuery(this.callback_query.id, e);
139
+ }
140
+ answerInlineQuery(results, e = {}) {
141
+ if (!this.inline_query) return Promise.resolve(false);
142
+ return this.api.answerInlineQuery(this.inline_query.id, results, e);
143
+ }
144
+
145
+ /* ---------- admin & grup ---------- */
146
+ banChatMember(cid, uid, e = {}) { return this.api.banChatMember(cid, uid, e); }
147
+ unbanChatMember(cid, uid, e = {}) { return this.api.unbanChatMember(cid, uid, e); }
148
+ restrictChatMember(cid, uid, permissions, e = {}) { return this.api.restrictChatMember(cid, uid, permissions, e); }
149
+ kickChatMember(cid, uid, e = {}) { return this.api.banChatMember(cid, uid, e); } // alias
150
+ banAuthor(e = {}) { return this.api.banChatMember(this._cid(), this.from.id, e); }
151
+ kickAuthor(e = {}) { return this.api.banChatMember(this._cid(), this.from.id, e); }
152
+ restrictAuthor(permissions, e = {}) { return this.api.restrictChatMember(this._cid(), this.from.id, permissions, e); }
153
+ getChat(cid) { return this.api.getChat(this._cid(cid)); }
154
+ getChatAdministrators(cid) { return this.api.getChatAdministrators(this._cid(cid)); }
155
+ getChatMemberCount(cid) { return this.api.getChatMemberCount(this._cid(cid)); }
156
+ getChatMember(uid, cid) { return this.api.getChatMember(this._cid(cid), uid); }
157
+ getAuthor(cid) { return this.api.getChatMember(this._cid(cid), this.from.id); }
158
+ leaveChat(cid) { return this.api.leaveChat(this._cid(cid)); }
159
+ setChatTitle(title, cid) { return this.api.setChatTitle(this._cid(cid), title); }
160
+ setChatDescription(d, cid) { return this.api.setChatDescription(this._cid(cid), d); }
161
+ pinChatMessage(cid, mid, e = {}) { return this.api.pinChatMessage(this._cid(cid || undefined), mid !== undefined ? mid : this.msgId, e); }
162
+ unpinChatMessage(e = {}) { return this.api.unpinChatMessage(this._cid(), e); }
163
+
164
+ /* ---------- download ---------- */
165
+ /** getFile() pesan terkait (photo terbesar / document / voice / video) lalu unduh. */
166
+ async getFile() {
167
+ const m = this.msg || {};
168
+ const f = m.document || m.audio || m.video || m.voice || m.video_note || m.sticker ||
169
+ (Array.isArray(m.photo) && m.photo.length ? m.photo[m.photo.length - 1] : null) ||
170
+ m.animation;
171
+ if (!f) throw new Error('telebibz: ctx.msg tanpa file');
172
+ return this.api.getFile(f.file_id);
173
+ }
174
+ async downloadFile(dest) {
175
+ const f = await this.getFile();
176
+ require('fs');
177
+ const axios = require('axios');
178
+ const fs = require('fs');
179
+ const url = `https://api.telegram.org/file/bot${this.api.token}/${f.file_path}`;
180
+ const res = await axios.get(url, { responseType: 'stream' });
181
+ const out = fs.createWriteStream(dest);
182
+ await new Promise((ok, no) => { res.data.pipe(out); out.on('finish', ok); out.on('error', no); });
183
+ return dest;
184
+ }
185
+
186
+ /* ---------- re-export util ---------- */
187
+ static File() { return File; }
188
+ }
189
+
190
+ module.exports = { Context, msgOf };
package/lib/errors.js ADDED
@@ -0,0 +1,35 @@
1
+ // lib/errors.js — terjemahan error Telegram jadi saran yang bisa ditindaklanjuti.
2
+ 'use strict';
3
+
4
+ const HINTS = [
5
+ [/401|unauthorized|not found.*token|404/i, 'Token salah/kadaluarsa — buat ulang lewat @BotFather (/revoke).'],
6
+ [/chat not found/i, 'Chat tak ditemukan — user belum pernah /start botmu, atau ID chat salah.'],
7
+ [/bot was blocked/i, 'Bot diblokir pengguna — jangan kirim ulang, hapus dari daftar broadcast.'],
8
+ [/user is deactivated/i, 'Akun pengguna sudah dihapus.'],
9
+ [/not enough rights|not enough permission/i, 'Bot bukan admin / kekurangan hak di grup itu.'],
10
+ [/can.t parse entities/i, 'Format teks rusak — cek tag HTML/Markdown yang belum ditutup.'],
11
+ [/message is not modified/i, 'Isi pesan sama — edit dibatalkan Telegram (aman diabaikan).'],
12
+ [/message to delete not found/i, 'Pesan sudah terhapus lebih dulu.'],
13
+ [/too many requests|retry after/i, 'Kena rate limit — kirim lebih lambat (lihat bot.broadcast()).'],
14
+ [/query is too old|query id is invalid/i, 'Callback basi (>±1 menit) — abaikan, user klik tombol lama.'],
15
+ [/button_data_invalid|BUTTON_DATA_INVALID/i, 'callback_data >64 byte / kosong.'],
16
+ [/file is too big/i, 'File >50 MB (limit upload bot).'],
17
+ [/wrong file identifier/i, 'file_id usang — kirim ulang dari sumbernya.'],
18
+ [/group chat was upgraded/i, 'Grup berubah jadi supergroup — pakai chat ID baru.'],
19
+ [/can't initiate conversation/i, 'Bot tak bisa PM user duluan — minta user /start botmu.'],
20
+ ];
21
+
22
+ /** Ubah error telegraf/grammy jadi { pesan, saran } bahasa manusia. */
23
+ function humanize(err) {
24
+ const desc =
25
+ (err && (err.description || err.message)) || String(err);
26
+ const found = HINTS.find(([re]) => re.test(desc));
27
+ return {
28
+ pesan: desc,
29
+ saran: found ? found[1] : null,
30
+ method: err && err.method,
31
+ code: err && (err.error_code || err.code),
32
+ };
33
+ }
34
+
35
+ module.exports = { humanize, HINTS };
package/lib/file.js ADDED
@@ -0,0 +1,41 @@
1
+ // lib/file.js — file kiriman (Buffer | path | stream) + builder InputMedia.
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ let mime = null;
7
+ try { mime = require('mime-types'); } catch { /* opsional */ }
8
+
9
+ class File {
10
+ constructor(src, filename, type) {
11
+ this.src = src;
12
+ this.filename = filename || (typeof src === 'string' ? path.basename(src) : 'file');
13
+ this.type = type || (mime ? (mime.lookup(this.filename) || undefined) : undefined);
14
+ this._attachId = Math.random().toString(36).slice(2);
15
+ }
16
+ attachName(field) { return `${field}_${this._attachId}`; }
17
+ async data() {
18
+ if (Buffer.isBuffer(this.src)) return this.src;
19
+ if (this.src instanceof Uint8Array) return Buffer.from(this.src);
20
+ if (typeof this.src === 'string') return fs.promises.readFile(this.src);
21
+ if (this.src && this.src.path && fs.existsSync(this.src.path)) return fs.promises.readFile(this.src.path); // stream fs
22
+ if (this.src && typeof this.src[Symbol.asyncIterator] === 'function') {
23
+ const chunks = [];
24
+ for await (const c of this.src) chunks.push(Buffer.isBuffer(c) ? c : Buffer.from(c));
25
+ return Buffer.concat(chunks);
26
+ }
27
+ throw new Error('telebibz File: sumber tidak dikenal (Buffer/path/stream)');
28
+ }
29
+ }
30
+ class InputFile extends File {}
31
+
32
+ /** Builder media grup (album): media.photo(src, {caption}), .video(...), ... */
33
+ const InputMediaBuilder = {
34
+ photo: (media, extra = {}) => ({ type: 'photo', media, ...extra }),
35
+ video: (media, extra = {}) => ({ type: 'video', media, ...extra }),
36
+ document: (media, extra = {}) => ({ type: 'document', media, ...extra }),
37
+ audio: (media, extra = {}) => ({ type: 'audio', media, ...extra }),
38
+ animation: (media, extra = {}) => ({ type: 'animation', media, ...extra }),
39
+ };
40
+
41
+ module.exports = { File, InputFile, InputMediaBuilder };
@@ -0,0 +1,28 @@
1
+ // lib/inline-query.js — helper mode inline: matcher query + builder hasil.
2
+ 'use strict';
3
+
4
+ /** bot.inlineQuery(/kucing|anjing/i, handler) atau bot.inlineQuery('*', handler) */
5
+ function matchInlineQuery(trigger) {
6
+ return (ctx) => {
7
+ const q = ctx.inline_query;
8
+ if (!q) return false;
9
+ if (trigger === undefined || trigger === '*') return true;
10
+ const list = [].concat(trigger);
11
+ const text = q.query || '';
12
+ return list.some((t) => t instanceof RegExp ? t.test(text) : text.toLowerCase().includes(String(t).toLowerCase()));
13
+ };
14
+ }
15
+
16
+ /** Builder hasil inline (persis spek Bot API). */
17
+ const iq = {
18
+ article: (id, title, extra = {}) => ({ type: 'article', id: String(id), title, input_message_content: { message_text: extra.message_text || title, ...(extra.input_message_content || {}) }, ...omit(extra, ['message_text', 'input_message_content']) }),
19
+ photo: (id, url, thumb, extra = {}) => ({ type: 'photo', id: String(id), photo_url: url, thumbnail_url: thumb || url, ...extra }),
20
+ gif: (id, url, thumb, extra = {}) => ({ type: 'gif', id: String(id), gif_url: url, thumbnail_url: thumb || url, ...extra }),
21
+ video: (id, url, thumb, title, extra = {}) => ({ type: 'video', id: String(id), video_url: url, thumbnail_url: thumb, title, mime_type: 'video/mp4', input_message_content: { message_text: url }, ...extra }),
22
+ audio: (id, url, title, extra = {}) => ({ type: 'audio', id: String(id), audio_url: url, title, ...extra }),
23
+ location: (id, lat, lon, title, extra = {}) => ({ type: 'location', id: String(id), latitude: lat, longitude: lon, title, ...extra }),
24
+ sticker: (id, fileId, extra = {}) => ({ type: 'sticker', id: String(id), sticker_file_id: fileId, ...extra }),
25
+ };
26
+ function omit(o, keys) { const c = { ...o }; for (const k of keys) delete c[k]; return c; }
27
+
28
+ module.exports = { matchInlineQuery, iq };
@@ -0,0 +1,83 @@
1
+ // lib/keyboard.js — pembangun tombol inline, tanpa perlu tahu struktur mentah API.
2
+ 'use strict';
3
+
4
+ /**
5
+ * Buat tombol callback.
6
+ * btn('Menu', 'menu')
7
+ * btn('Hapus', 'del', 'danger') // warna merah
8
+ * btn('Gas', 'go', 'success', '5408846744727334338') // + ikon animated
9
+ * Warna (Bot API 9.4): 'danger' merah · 'success' hijau · 'primary' biru/ungu.
10
+ * iconId = custom_emoji_id animated (butuh Premium owner / username Fragment).
11
+ */
12
+ function btn(text, callbackData, style, iconId) {
13
+ const b = { text, callback_data: String(callbackData) };
14
+ if (style) b.style = style;
15
+ if (iconId) b.icon_custom_emoji_id = String(iconId);
16
+ return b;
17
+ }
18
+
19
+ /** Tombol tautan (membuka URL). */
20
+ function url(text, link, style, iconId) {
21
+ const b = { text, url: String(link) };
22
+ if (style) b.style = style;
23
+ if (iconId) b.icon_custom_emoji_id = String(iconId);
24
+ return b;
25
+ }
26
+
27
+ /** Tombol WebApp (khusus chat pribadi). */
28
+ function webApp(text, link, iconId) {
29
+ const b = { text, web_app: { url: String(link) } };
30
+ if (iconId) b.icon_custom_emoji_id = String(iconId);
31
+ return b;
32
+ }
33
+
34
+ /** Tombol salin teks ke clipboard. */
35
+ function copy(text, value, iconId) {
36
+ const b = { text, copy_text: { text: String(value) } };
37
+ if (iconId) b.icon_custom_emoji_id = String(iconId);
38
+ return b;
39
+ }
40
+
41
+ /**
42
+ * Susun keyboard dari array 2 dimensi baris-kolom, lalu kirim apa adanya lewat
43
+ * ctx.reply(..., { keyboard: kb([...]) }) atau simpan sebagai markup.
44
+ */
45
+ function kb(rows) {
46
+ return { reply_markup: { inline_keyboard: rows } };
47
+ }
48
+
49
+ /** Versi markup polos (untuk editMessageReplyMarkup dll). */
50
+ kb.markup = (rows) => ({ inline_keyboard: rows });
51
+
52
+ /** Tiga gaya warna dalam satu baris (pola konfirmasi umum). */
53
+ kb.confirm = (yesData, noData, labelYes = '✅ Ya', labelNo = '❌ Batal') =>
54
+ kb([[btn(labelYes, yesData, 'success'), btn(labelNo, noData, 'danger')]]);
55
+
56
+ module.exports = { btn, url, webApp, copy, kb };
57
+
58
+ /* ===== kelas builder fluent (kompat gaya grammY) ===== */
59
+ class InlineKeyboard {
60
+ constructor(initRows = []) { this.rows = initRows; }
61
+ _add(b) { (this.rows[this.rows.length - 1] || (this.rows.push([]), this.rows[this.rows.length - 1])).push(b); return this; }
62
+ text(t, data, style, iconId) { return this._add(btn(t, data, style, iconId)); }
63
+ url(t, link, style, iconId) { return this._add(url(t, link, style, iconId)); }
64
+ webApp(t, link, iconId) { return this._add(webApp(t, link, iconId)); }
65
+ copy(t, v, iconId) { return this._add(copy(t, v, iconId)); }
66
+ row() { if (this.rows[this.rows.length - 1].length) this.rows.push([]); return this; }
67
+ build() { return { reply_markup: { inline_keyboard: this.rows.filter((r) => r.length) } }; }
68
+ }
69
+ class Keyboard {
70
+ constructor() { this.rows = [[]]; this.opts = { resize_keyboard: true }; }
71
+ _add(b) { this.rows[this.rows.length - 1].push(b); return this; }
72
+ text(t) { return this._add({ text: t }); }
73
+ requestContact(t) { return this._add({ text: t, request_contact: true }); }
74
+ requestLocation(t) { return this._add({ text: t, request_location: true }); }
75
+ row() { if (this.rows[this.rows.length - 1].length) this.rows.push([]); return this; }
76
+ resized(v = true) { this.opts.resize_keyboard = v; return this; }
77
+ oneTime(v = true) { this.opts.one_time_keyboard = v; return this; }
78
+ build() {
79
+ return { reply_markup: { keyboard: this.rows.filter((r) => r.length), ...this.opts } };
80
+ }
81
+ }
82
+
83
+ module.exports = { btn, url, webApp, copy, kb, InlineKeyboard, Keyboard };
package/lib/logger.js ADDED
@@ -0,0 +1,38 @@
1
+ // lib/logger.js — log terminal cantik tanpa dependency.
2
+ 'use strict';
3
+
4
+ const C = {
5
+ reset: '', dim: '', bold: '',
6
+ red: '', green: '', yellow: '', blue: '',
7
+ magenta: '', cyan: '', gray: '',
8
+ };
9
+
10
+ function paint(code, s) {
11
+ return process.stdout && process.stdout.isTTY ? `${C[code]}${s}${C.reset}` : String(s);
12
+ }
13
+
14
+ function stamp() {
15
+ return paint('gray', new Date().toLocaleTimeString('id-ID', { hour12: false }));
16
+ }
17
+
18
+ const log = {
19
+ info: (...a) => console.log(`${stamp()} ${paint('cyan', 'ℹ')} `, ...a),
20
+ ok: (...a) => console.log(`${stamp()} ${paint('green', '✔')} `, ...a),
21
+ warn: (...a) => console.warn(`${stamp()} ${paint('yellow', '⚠')} `, ...a),
22
+ error:(...a) => console.error(`${stamp()} ${paint('red', '✖')} `, ...a),
23
+ paint,
24
+ };
25
+
26
+ /** Banner ASCII saat bot nyala. */
27
+ log.banner = (title, rows) => {
28
+ const width = Math.max(title.length, ...rows.map((r) => strip(r).length)) + 2;
29
+ const line = '─'.repeat(width + 2);
30
+ console.log(`┌${line}┐`);
31
+ console.log(`│ ${paint('bold', title)}${' '.repeat(width - title.length)}│`);
32
+ for (const r of rows) console.log(`│ ${r}${' '.repeat(Math.max(0, width - strip(r).length))}│`);
33
+ console.log(`└${line}┘`);
34
+ };
35
+
36
+ function strip(s) { return String(s).replace(/\[\d+m/g, ''); }
37
+
38
+ module.exports = log;
package/lib/menus.js ADDED
@@ -0,0 +1,90 @@
1
+ // lib/menus.js — Menu inline ala @grammyjs/menu (mandiri, tanpa dep tambahan).
2
+ 'use strict';
3
+
4
+ const { Composer } = require('./composer');
5
+
6
+ const re = (s) => String(s).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
7
+
8
+ /**
9
+ * const menu = new Menu('cfg');
10
+ * menu.text('🔔 Notif', async (ctx) => ctx.answerCallbackQuery('ditekan!'))
11
+ * .row()
12
+ * .url('Web', 'https://x')
13
+ * .submenu('Lanjut ▶', 'cfg-lanjut');
14
+ *
15
+ * bot.use(menu); // handler tombol terdaftar otomatis
16
+ * bot.cmd('cfg', async (ctx) =>
17
+ * ctx.reply('Menu:', { reply_markup: await menu.render(ctx) }));
18
+ */
19
+ class Menu extends Composer {
20
+ constructor(id) {
21
+ super();
22
+ this.id = id;
23
+ this._ops = [];
24
+ this.callbackQuery(new RegExp(`^${re(id)}\\|`), async (ctx) => {
25
+ const i = Number(ctx.callback_query.data.slice(id.length + 1));
26
+ const op = this._ops[i];
27
+ if (!op) return ctx.answerCallbackQuery({ text: '⌛ Tombol usang — kirim ulang menunya', show_alert: true }).catch(() => {});
28
+ if (op.type === 'text' && op.handler) return op.handler(ctx);
29
+ if (op.type === 'submenu') {
30
+ const target = this._menus && this._menus.get(op.target);
31
+ if (target) {
32
+ try { await ctx.editMessageReplyMarkup({ reply_markup: await target.render(ctx) }); }
33
+ catch { /* pesan mungkin termodifikasi */ }
34
+ return ctx.answerCallbackQuery().catch(() => {});
35
+ }
36
+ }
37
+ return ctx.answerCallbackQuery().catch(() => {});
38
+ });
39
+ }
40
+
41
+ _add(o) { this._ops.push(o); return this; }
42
+
43
+ /** Tombol teks + handler saat ditekan. */
44
+ text(label, handler) { return this._add({ type: 'text', label, handler }); }
45
+ /** Tombol tautan. */
46
+ url(label, link, opts = {}) { return this._add({ type: 'url', label, link, opts }); }
47
+ /** Tombol WebApp. */
48
+ webApp(label, link) { return this._add({ type: 'webApp', label, link }); }
49
+ /** Tombol pindah ke menu lain dengan id target (Menu diregister lewat container). */
50
+ submenu(label, targetId) { return this._add({ type: 'submenu', label, target: targetId }); }
51
+ /** Baris baru. */
52
+ row() { return this._add({ type: 'row' }); }
53
+ /** Shortcut: kembali ke menu parent. */
54
+ back(label = '◀️ Kembali', parentId) { return this.submenu(label, parentId); }
55
+
56
+ /** Render keyboard (inline_keyboard). */
57
+ async render(ctx) {
58
+ const rows = [[]];
59
+ const push = (b) => rows[rows.length - 1].push(b);
60
+ for (let i = 0; i < this._ops.length; i++) {
61
+ const o = this._ops[i];
62
+ if (o.type === 'row') { rows.push([]); continue; }
63
+ if (o.type === 'url') { push({ text: o.label, url: o.link, ...(o.opts.style ? { style: o.opts.style } : {}), ...(o.opts.iconId ? { icon_custom_emoji_id: o.opts.iconId } : {}) }); continue; }
64
+ if (o.type === 'webApp') { push({ text: o.label, web_app: { url: o.link } }); continue; }
65
+ push({ text: o.label, callback_data: `${this.id}|${i}` });
66
+ }
67
+ return { inline_keyboard: rows.filter((r) => r.length) };
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Wadah beberapa menu terkait (dengan submenu silang):
73
+ * const mc = new MenuContainer();
74
+ * const utama = mc.create('utama'), lanjut = mc.create('lanjut');
75
+ * utama.submenu('Lanjut ▶', 'lanjut'); lanjut.back('◀️', 'utama');
76
+ * bot.use(mc);
77
+ */
78
+ class MenuContainer extends Composer {
79
+ constructor() { super(); this._menus = new Map(); }
80
+ create(id) {
81
+ const m = new Menu(id);
82
+ m._menus = this._menus;
83
+ this._menus.set(id, m);
84
+ this.use(m);
85
+ return m;
86
+ }
87
+ get(id) { return this._menus.get(id); }
88
+ }
89
+
90
+ module.exports = { Menu, MenuContainer };
package/lib/net.js ADDED
@@ -0,0 +1,115 @@
1
+ // lib/net.js — transport Bot API production-grade (axios keep-alive, proxy, timeout,
2
+ // multipart attach://, 429 retry_after handler, debug logging).
3
+ 'use strict';
4
+
5
+ const axios = require('axios');
6
+ const http = require('http');
7
+ const https = require('https');
8
+ const dbg = require('debug')('telebibz:net');
9
+ let HttpsProxyAgent = null;
10
+ try { ({ HttpsProxyAgent } = require('https-proxy-agent')); } catch { /* proxy opsional */ }
11
+
12
+ const { File } = require('./file');
13
+ const mime = require('mime-types');
14
+
15
+ /** Error API Telegram dengan konteks debugging. */
16
+ class ApiError extends Error {
17
+ constructor(description, errorCode, method, payload, parameters) {
18
+ super(description);
19
+ this.name = 'ApiError';
20
+ this.description = description;
21
+ this.error_code = errorCode;
22
+ this.parameters = parameters || {};
23
+ this.method = method;
24
+ this.payload = payload;
25
+ }
26
+ }
27
+
28
+ const keepAliveHttp = new http.Agent({ keepAlive: true, maxSockets: 64 });
29
+ const keepAliveHttps = new https.Agent({ keepAlive: true, maxSockets: 64 });
30
+
31
+ /**
32
+ * Bangun fungsi transport (method, payload) => result.
33
+ * opts: { apiRoot, proxy, timeoutMs, headers }
34
+ */
35
+ function createTransport(token, opts = {}) {
36
+ const apiRoot = (opts.apiRoot || 'https://api.telegram.org').replace(/\/$/, '');
37
+ const agent = opts.proxy && HttpsProxyAgent ? new HttpsProxyAgent(opts.proxy) : keepAliveHttps;
38
+ const client = axios.create({
39
+ baseURL: `${apiRoot}/bot${token}/`,
40
+ httpAgent: keepAliveHttp,
41
+ httpsAgent: agent,
42
+ timeout: opts.timeoutMs || 35000, // sedikit > long-poll 25s
43
+ headers: opts.headers || {},
44
+ maxContentLength: Infinity,
45
+ maxBodyLength: Infinity,
46
+ proxy: false, // kendalikan lewat httpsAgent sendiri
47
+ });
48
+
49
+ return async function transport(method, payload = {}) {
50
+ dbg('--> %s %s', method, JSON.stringify(payload, safeReplacer).slice(0, 400));
51
+ const files = [];
52
+ // Deep-walk: File di MANA PUN (media[i].media, photo top-level, thumbnail, dll)
53
+ const clean = collectAttach(payload, files);
54
+
55
+ let data, config = {};
56
+ if (files.length) {
57
+ const fd = new FormData();
58
+ for (const [k, v] of Object.entries(clean)) fd.append(k, typeof v === 'object' ? JSON.stringify(v) : String(v));
59
+ for (const [an, f] of files) {
60
+ const buf = await f.data();
61
+ fd.append(an, new Blob([buf], { type: f.type || mime.lookup(f.filename) || 'application/octet-stream' }), f.filename || an);
62
+ }
63
+ data = fd;
64
+ } else {
65
+ data = JSON.stringify(clean);
66
+ config.headers = { 'Content-Type': 'application/json' };
67
+ }
68
+
69
+ let res;
70
+ try {
71
+ res = await client.post(method, data, config);
72
+ } catch (err) {
73
+ if (err.response && err.response.data) {
74
+ const d = err.response.data;
75
+ dbg('<-- ERR %s: %s', method, d.description);
76
+ throw new ApiError(d.description || `HTTP ${err.response.status}`, d.error_code || err.response.status, method, payload, d.parameters);
77
+ }
78
+ // error jaringan murni: tag agar Runner menganggapnya sementara
79
+ err.network = !err.response;
80
+ dbg('<-- NETWORK ERR %s: %s', method, err.message);
81
+ throw err;
82
+ }
83
+ const env = res.data;
84
+ if (!env.ok) {
85
+ dbg('<-- ERR %s: %s', method, env.description);
86
+ throw new ApiError(env.description || 'unknown error', env.error_code, method, payload, env.parameters);
87
+ }
88
+ dbg('<-- OK %s', method);
89
+ return env.result;
90
+ };
91
+ }
92
+
93
+ /** Rekursif: salin payload sambil mengganti File jadi "attach://fileN". */
94
+ function collectAttach(value, files) {
95
+ if (value instanceof File) {
96
+ const an = `file${files.length}`;
97
+ files.push([an, value]);
98
+ return `attach://${an}`;
99
+ }
100
+ if (Array.isArray(value)) return value.map((v) => collectAttach(v, files));
101
+ if (value && typeof value === 'object') {
102
+ const o = {};
103
+ for (const [k, v] of Object.entries(value)) {
104
+ if (v === undefined || v === null) continue;
105
+ o[k] = collectAttach(v, files);
106
+ }
107
+ return o;
108
+ }
109
+ return value;
110
+ }
111
+
112
+ /** JSON.stringify yang aman terhadap File (untuk debug log). */
113
+ function safeReplacer(k, v) { return v instanceof File ? `<File:${v.filename}>` : v; }
114
+
115
+ module.exports = { createTransport, ApiError };