@xbibzlibrary/telebibz 0.4.5 → 3.1.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.
- package/CHANGELOG.md +53 -138
- package/LICENSE +1 -1
- package/NOTICE.md +9 -4
- package/README.id.md +542 -206
- package/README.md +539 -213
- package/examples/01-quickstart.js +13 -0
- package/examples/02-menu-tombol.js +22 -0
- package/examples/03-wizard.js +58 -0
- package/examples/04-broadcast.js +25 -0
- package/examples/05-kirim-file.js +19 -0
- package/examples/06-menu.js +35 -0
- package/examples/07-inline-query.js +18 -0
- package/index.d.ts +106 -0
- package/index.js +44 -0
- package/lib/api.js +154 -0
- package/lib/broadcast.js +35 -0
- package/lib/composer.js +174 -0
- package/lib/context.js +190 -0
- package/lib/errors.js +35 -0
- package/lib/file.js +41 -0
- package/lib/inline-query.js +28 -0
- package/lib/keyboard.js +83 -0
- package/lib/logger.js +38 -0
- package/lib/menus.js +90 -0
- package/lib/net.js +115 -0
- package/lib/ratelimit.js +61 -0
- package/lib/runner.js +45 -0
- package/lib/session.js +36 -0
- package/lib/telebibz.js +193 -0
- package/lib/wizard.js +247 -0
- package/package.json +35 -97
- package/test/all.test.js +439 -0
- package/CODE_OF_CONDUCT.md +0 -37
- package/CONTRIBUTING.md +0 -59
- package/CONTRIBUTION_RULES.md +0 -41
- package/GOVERNANCE.md +0 -47
- package/README.zh-CN.md +0 -306
- package/RELEASE_AUTOMATION.md +0 -78
- package/RELEASE_POLICY.md +0 -32
- package/SECURITY.md +0 -47
- package/SHOWCASE.md +0 -29
- package/SUPPORT.md +0 -30
- package/assets/readme-preview.html +0 -75
- package/assets/telebibz-logo.png +0 -0
- package/assets/telebibz-readme-preview.png +0 -0
- package/bin/telebibz.mjs +0 -3
- package/dist/generated/api.d.ts +0 -13
- package/dist/generated/api.d.ts.map +0 -1
- package/dist/generated/api.js +0 -192
- package/dist/generated/api.js.map +0 -1
- package/dist/src/api/client.d.ts +0 -62
- package/dist/src/api/client.d.ts.map +0 -1
- package/dist/src/api/client.js +0 -104
- package/dist/src/api/client.js.map +0 -1
- package/dist/src/api/errors.d.ts +0 -45
- package/dist/src/api/errors.d.ts.map +0 -1
- package/dist/src/api/errors.js +0 -65
- package/dist/src/api/errors.js.map +0 -1
- package/dist/src/api/index.d.ts +0 -6
- package/dist/src/api/index.d.ts.map +0 -1
- package/dist/src/api/index.js +0 -6
- package/dist/src/api/index.js.map +0 -1
- package/dist/src/api/telegram-types/LICENSE +0 -21
- package/dist/src/api/telegram-types/api.d.ts +0 -22
- package/dist/src/api/telegram-types/checklist.d.ts +0 -72
- package/dist/src/api/telegram-types/inline.d.ts +0 -692
- package/dist/src/api/telegram-types/langs.d.ts +0 -193
- package/dist/src/api/telegram-types/manage.d.ts +0 -1144
- package/dist/src/api/telegram-types/markup.d.ts +0 -268
- package/dist/src/api/telegram-types/message.d.ts +0 -1537
- package/dist/src/api/telegram-types/methods.d.ts +0 -2870
- package/dist/src/api/telegram-types/mod.d.ts +0 -14
- package/dist/src/api/telegram-types/passport.d.ts +0 -163
- package/dist/src/api/telegram-types/payment.d.ts +0 -570
- package/dist/src/api/telegram-types/rich.d.ts +0 -1010
- package/dist/src/api/telegram-types/settings.d.ts +0 -120
- package/dist/src/api/telegram-types/story.d.ts +0 -89
- package/dist/src/api/telegram-types/update.d.ts +0 -84
- package/dist/src/api/telegram.d.ts +0 -7
- package/dist/src/api/telegram.d.ts.map +0 -1
- package/dist/src/api/telegram.js +0 -2
- package/dist/src/api/telegram.js.map +0 -1
- package/dist/src/api/transport.d.ts +0 -68
- package/dist/src/api/transport.d.ts.map +0 -1
- package/dist/src/api/transport.js +0 -264
- package/dist/src/api/transport.js.map +0 -1
- package/dist/src/api/types.d.ts +0 -466
- package/dist/src/api/types.d.ts.map +0 -1
- package/dist/src/api/types.js +0 -2
- package/dist/src/api/types.js.map +0 -1
- package/dist/src/branding/terminal.d.ts +0 -77
- package/dist/src/branding/terminal.d.ts.map +0 -1
- package/dist/src/branding/terminal.js +0 -328
- package/dist/src/branding/terminal.js.map +0 -1
- package/dist/src/broadcast/broadcast.d.ts +0 -50
- package/dist/src/broadcast/broadcast.d.ts.map +0 -1
- package/dist/src/broadcast/broadcast.js +0 -56
- package/dist/src/broadcast/broadcast.js.map +0 -1
- package/dist/src/cache/cache.d.ts +0 -34
- package/dist/src/cache/cache.d.ts.map +0 -1
- package/dist/src/cache/cache.js +0 -41
- package/dist/src/cache/cache.js.map +0 -1
- package/dist/src/cli.d.ts +0 -2
- package/dist/src/cli.d.ts.map +0 -1
- package/dist/src/cli.js +0 -84
- package/dist/src/cli.js.map +0 -1
- package/dist/src/context/context.d.ts +0 -124
- package/dist/src/context/context.d.ts.map +0 -1
- package/dist/src/context/context.js +0 -302
- package/dist/src/context/context.js.map +0 -1
- package/dist/src/core/bot.d.ts +0 -204
- package/dist/src/core/bot.d.ts.map +0 -1
- package/dist/src/core/bot.js +0 -506
- package/dist/src/core/bot.js.map +0 -1
- package/dist/src/core/events.d.ts +0 -75
- package/dist/src/core/events.d.ts.map +0 -1
- package/dist/src/core/events.js +0 -35
- package/dist/src/core/events.js.map +0 -1
- package/dist/src/core/webhook-reply.d.ts +0 -34
- package/dist/src/core/webhook-reply.d.ts.map +0 -1
- package/dist/src/core/webhook-reply.js +0 -37
- package/dist/src/core/webhook-reply.js.map +0 -1
- package/dist/src/index.d.ts +0 -24
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/index.js +0 -24
- package/dist/src/index.js.map +0 -1
- package/dist/src/keyboard/index.d.ts +0 -46
- package/dist/src/keyboard/index.d.ts.map +0 -1
- package/dist/src/keyboard/index.js +0 -55
- package/dist/src/keyboard/index.js.map +0 -1
- package/dist/src/middleware/compose.d.ts +0 -5
- package/dist/src/middleware/compose.d.ts.map +0 -1
- package/dist/src/middleware/compose.js +0 -17
- package/dist/src/middleware/compose.js.map +0 -1
- package/dist/src/observability/logger.d.ts +0 -78
- package/dist/src/observability/logger.d.ts.map +0 -1
- package/dist/src/observability/logger.js +0 -285
- package/dist/src/observability/logger.js.map +0 -1
- package/dist/src/plugins/plugin.d.ts +0 -38
- package/dist/src/plugins/plugin.d.ts.map +0 -1
- package/dist/src/plugins/plugin.js +0 -59
- package/dist/src/plugins/plugin.js.map +0 -1
- package/dist/src/queue/queue.d.ts +0 -77
- package/dist/src/queue/queue.d.ts.map +0 -1
- package/dist/src/queue/queue.js +0 -213
- package/dist/src/queue/queue.js.map +0 -1
- package/dist/src/router/router.d.ts +0 -61
- package/dist/src/router/router.d.ts.map +0 -1
- package/dist/src/router/router.js +0 -183
- package/dist/src/router/router.js.map +0 -1
- package/dist/src/state/conversation.d.ts +0 -56
- package/dist/src/state/conversation.d.ts.map +0 -1
- package/dist/src/state/conversation.js +0 -133
- package/dist/src/state/conversation.js.map +0 -1
- package/dist/src/state/forms.d.ts +0 -34
- package/dist/src/state/forms.d.ts.map +0 -1
- package/dist/src/state/forms.js +0 -44
- package/dist/src/state/forms.js.map +0 -1
- package/dist/src/state/menu.d.ts +0 -78
- package/dist/src/state/menu.d.ts.map +0 -1
- package/dist/src/state/menu.js +0 -127
- package/dist/src/state/menu.js.map +0 -1
- package/dist/src/storage/storage.d.ts +0 -146
- package/dist/src/storage/storage.d.ts.map +0 -1
- package/dist/src/storage/storage.js +0 -195
- package/dist/src/storage/storage.js.map +0 -1
- package/dist/src/telegram-features.d.ts +0 -33
- package/dist/src/telegram-features.d.ts.map +0 -1
- package/dist/src/telegram-features.js +0 -71
- package/dist/src/telegram-features.js.map +0 -1
- package/dist/src/testing.d.ts +0 -24
- package/dist/src/testing.d.ts.map +0 -1
- package/dist/src/testing.js +0 -38
- package/dist/src/testing.js.map +0 -1
- package/dist/src/utils/concurrency.d.ts +0 -25
- package/dist/src/utils/concurrency.d.ts.map +0 -1
- package/dist/src/utils/concurrency.js +0 -52
- package/dist/src/utils/concurrency.js.map +0 -1
- package/dist/src/utils/files.d.ts +0 -45
- package/dist/src/utils/files.d.ts.map +0 -1
- package/dist/src/utils/files.js +0 -53
- package/dist/src/utils/files.js.map +0 -1
- package/dist/src/utils/text.d.ts +0 -39
- package/dist/src/utils/text.d.ts.map +0 -1
- package/dist/src/utils/text.js +0 -56
- package/dist/src/utils/text.js.map +0 -1
- package/dist/src/webhook/handler.d.ts +0 -19
- package/dist/src/webhook/handler.d.ts.map +0 -1
- package/dist/src/webhook/handler.js +0 -141
- package/dist/src/webhook/handler.js.map +0 -1
- package/dist-cjs/generated/api.js +0 -194
- package/dist-cjs/package.json +0 -3
- package/dist-cjs/src/api/client.js +0 -107
- package/dist-cjs/src/api/errors.js +0 -74
- package/dist-cjs/src/api/index.js +0 -21
- package/dist-cjs/src/api/telegram-types/LICENSE +0 -21
- package/dist-cjs/src/api/telegram-types/api.d.ts +0 -22
- package/dist-cjs/src/api/telegram-types/checklist.d.ts +0 -72
- package/dist-cjs/src/api/telegram-types/inline.d.ts +0 -692
- package/dist-cjs/src/api/telegram-types/langs.d.ts +0 -193
- package/dist-cjs/src/api/telegram-types/manage.d.ts +0 -1144
- package/dist-cjs/src/api/telegram-types/markup.d.ts +0 -268
- package/dist-cjs/src/api/telegram-types/message.d.ts +0 -1537
- package/dist-cjs/src/api/telegram-types/methods.d.ts +0 -2870
- package/dist-cjs/src/api/telegram-types/mod.d.ts +0 -14
- package/dist-cjs/src/api/telegram-types/passport.d.ts +0 -163
- package/dist-cjs/src/api/telegram-types/payment.d.ts +0 -570
- package/dist-cjs/src/api/telegram-types/rich.d.ts +0 -1010
- package/dist-cjs/src/api/telegram-types/settings.d.ts +0 -120
- package/dist-cjs/src/api/telegram-types/story.d.ts +0 -89
- package/dist-cjs/src/api/telegram-types/update.d.ts +0 -84
- package/dist-cjs/src/api/telegram.js +0 -2
- package/dist-cjs/src/api/transport.js +0 -267
- package/dist-cjs/src/api/types.js +0 -2
- package/dist-cjs/src/branding/terminal.js +0 -338
- package/dist-cjs/src/broadcast/broadcast.js +0 -58
- package/dist-cjs/src/cache/cache.js +0 -45
- package/dist-cjs/src/cli.js +0 -86
- package/dist-cjs/src/context/context.js +0 -305
- package/dist-cjs/src/core/bot.js +0 -510
- package/dist-cjs/src/core/events.js +0 -38
- package/dist-cjs/src/core/webhook-reply.js +0 -42
- package/dist-cjs/src/index.js +0 -47
- package/dist-cjs/src/keyboard/index.js +0 -61
- package/dist-cjs/src/middleware/compose.js +0 -20
- package/dist-cjs/src/observability/logger.js +0 -293
- package/dist-cjs/src/plugins/plugin.js +0 -63
- package/dist-cjs/src/queue/queue.js +0 -219
- package/dist-cjs/src/router/router.js +0 -186
- package/dist-cjs/src/state/conversation.js +0 -139
- package/dist-cjs/src/state/forms.js +0 -47
- package/dist-cjs/src/state/menu.js +0 -133
- package/dist-cjs/src/storage/storage.js +0 -202
- package/dist-cjs/src/telegram-features.js +0 -76
- package/dist-cjs/src/testing.js +0 -45
- package/dist-cjs/src/utils/concurrency.js +0 -57
- package/dist-cjs/src/utils/files.js +0 -58
- package/dist-cjs/src/utils/text.js +0 -63
- package/dist-cjs/src/webhook/handler.js +0 -144
- package/docs/API.id.md +0 -1935
- package/docs/API.md +0 -1969
- package/docs/API.zh-CN.md +0 -1929
- package/docs/COOKBOOK.id.md +0 -321
- package/docs/COOKBOOK.md +0 -321
- package/docs/COOKBOOK.zh-CN.md +0 -321
- package/docs/ERRORS.id.md +0 -194
- package/docs/ERRORS.md +0 -194
- package/docs/ERRORS.zh-CN.md +0 -194
- package/docs/FILES.id.md +0 -243
- package/docs/FILES.md +0 -243
- package/docs/FILES.zh-CN.md +0 -243
- package/docs/GETTING_STARTED.id.md +0 -89
- package/docs/GETTING_STARTED.md +0 -89
- package/docs/GETTING_STARTED.zh-CN.md +0 -89
- package/docs/GITHUB_PACKAGES.id.md +0 -82
- package/docs/GITHUB_PACKAGES.md +0 -82
- package/docs/GITHUB_PACKAGES.zh-CN.md +0 -82
- package/docs/MIGRATION_TELEGRAF.id.md +0 -147
- package/docs/MIGRATION_TELEGRAF.md +0 -154
- package/docs/MIGRATION_TELEGRAF.zh-CN.md +0 -147
- package/docs/README.md +0 -67
- package/docs/STORAGE.id.md +0 -105
- package/docs/STORAGE.md +0 -105
- package/docs/STORAGE.zh-CN.md +0 -105
- package/docs/TESTING.id.md +0 -203
- package/docs/TESTING.md +0 -203
- package/docs/TESTING.zh-CN.md +0 -203
- package/docs/WEBHOOK.id.md +0 -212
- package/docs/WEBHOOK.md +0 -215
- package/docs/WEBHOOK.zh-CN.md +0 -212
- package/examples/README.md +0 -37
- package/examples/files.ts +0 -35
- package/examples/minimal.ts +0 -12
- package/examples/tsconfig.json +0 -9
- package/examples/webhook.ts +0 -42
- package/examples/wizard-registration.ts +0 -42
package/lib/composer.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// lib/composer.js — mesin middleware & routing filter (recoded, konsep gaya grammY).
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { msgOf } = require('./context');
|
|
5
|
+
|
|
6
|
+
/* ---------- matcher filter ---------- */
|
|
7
|
+
const UPDATE_FIELDS = {
|
|
8
|
+
message: (u) => u.message, edited_message: (u) => u.edited_message,
|
|
9
|
+
channel_post: (u) => u.channel_post, edited_channel_post: (u) => u.edited_channel_post,
|
|
10
|
+
business_message: (u) => u.business_message, edited_business_message: (u) => u.edited_business_message,
|
|
11
|
+
callback_query: (u) => u.callback_query, inline_query: (u) => u.inline_query,
|
|
12
|
+
chat_join_request: (u) => u.chat_join_request, chat_member: (u) => u.chat_member,
|
|
13
|
+
my_chat_member: (u) => u.my_chat_member, message_reaction: (u) => u.message_reaction,
|
|
14
|
+
message_reaction_count: (u) => u.message_reaction_count,
|
|
15
|
+
business_connection: (u) => u.business_connection, poll: (u) => u.poll, poll_answer: (u) => u.poll_answer,
|
|
16
|
+
chosen_inline_result: (u) => u.chosen_inline_result, shipping_query: (u) => u.shipping_query,
|
|
17
|
+
pre_checkout_query: (u) => u.pre_checkout_query, purchased_paid_media: (u) => u.purchased_paid_media,
|
|
18
|
+
};
|
|
19
|
+
const MSG_PROPS = {
|
|
20
|
+
text: (m) => typeof m.text === 'string', caption: (m) => typeof m.caption === 'string',
|
|
21
|
+
photo: (m) => Array.isArray(m.photo), video: (m) => !!m.video, audio: (m) => !!m.audio,
|
|
22
|
+
voice: (m) => !!m.voice, video_note: (m) => !!m.video_note, document: (m) => !!m.document,
|
|
23
|
+
sticker: (m) => !!m.sticker, animation: (m) => !!m.animation, location: (m) => !!m.location,
|
|
24
|
+
contact: (m) => !!m.contact, poll: (m) => !!m.poll, venue: (m) => !!m.venue,
|
|
25
|
+
dice: (m) => !!m.dice, game: (m) => !!m.game, invoice: (m) => !!m.invoice,
|
|
26
|
+
new_chat_members: (m) => Array.isArray(m.new_chat_members), left_chat_member: (m) => !!m.left_chat_member,
|
|
27
|
+
successful_payment: (m) => !!m.successful_payment, web_app_data: (m) => !!m.web_app_data,
|
|
28
|
+
via_bot: (m) => !!m.via_bot, reply_to_message: (m) => !!m.reply_to_message, entities: (m) => Array.isArray(m.entities),
|
|
29
|
+
media: (m) => Array.isArray(m.photo) || !!m.video || !!m.document || !!m.audio,
|
|
30
|
+
data: () => false, // khusus callback_query ditangani di bawah
|
|
31
|
+
successful_payment: (m) => !!m.successful_payment,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** filter 'message:photo', ':text', 'callback_query': → fungsi (ctx) => boolean */
|
|
35
|
+
function compileFilter(str) {
|
|
36
|
+
// ':prop' → pesan (atau edited/channel/business) dengan properti tsb — gaya grammY
|
|
37
|
+
if (String(str).startsWith(':')) {
|
|
38
|
+
const prop = String(str).slice(1);
|
|
39
|
+
const test = MSG_PROPS[prop];
|
|
40
|
+
if (test) return (ctx) => { const m = msgOf(ctx.update); return !!m && test(m); };
|
|
41
|
+
return () => false;
|
|
42
|
+
}
|
|
43
|
+
const [head, prop] = String(str).split(':');
|
|
44
|
+
if (head === 'chat_type') return (ctx) => { const c = ctx.chat; return !!c && c.type === prop; };
|
|
45
|
+
if (head === 'callback_query' && prop === 'data') return (ctx) => !!(ctx.callback_query && typeof ctx.callback_query.data === 'string');
|
|
46
|
+
if (head && prop) {
|
|
47
|
+
const get = UPDATE_FIELDS[head];
|
|
48
|
+
const test = MSG_PROPS[prop];
|
|
49
|
+
if (get && test) return (ctx) => { const m = get(ctx.update); return !!m && test(m); };
|
|
50
|
+
if (get) return (ctx) => !!get(ctx.update);
|
|
51
|
+
if (test) return (ctx) => { const m = msgOf(ctx.update); return !!m && test(m); };
|
|
52
|
+
}
|
|
53
|
+
if (UPDATE_FIELDS[head]) return (ctx) => !!UPDATE_FIELDS[head](ctx.update);
|
|
54
|
+
if (MSG_PROPS[head]) return (ctx) => { const m = msgOf(ctx.update); return !!m && MSG_PROPS[head](m); };
|
|
55
|
+
return () => false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* ---------- composer ---------- */
|
|
59
|
+
class Composer {
|
|
60
|
+
constructor(...mw) { this.stack = []; for (const m of mw) this.use(m); }
|
|
61
|
+
|
|
62
|
+
use(...mw) {
|
|
63
|
+
for (const m of mw) {
|
|
64
|
+
const fn = m instanceof Composer ? m.middleware() : m;
|
|
65
|
+
this.stack.push({ match: () => true, fns: [fn] });
|
|
66
|
+
}
|
|
67
|
+
return this;
|
|
68
|
+
}
|
|
69
|
+
on(filter, ...mw) { [].concat(filter).forEach((f) => this.stack.push({ match: compileFilter(f), fns: mw })); return this; }
|
|
70
|
+
hears(re, ...mw) {
|
|
71
|
+
[].concat(re).forEach((r) => this.stack.push({
|
|
72
|
+
match: (ctx) => { const m = msgOf(ctx.update); const t = m && (m.text || m.caption || ''); return !!t && r.test(t); },
|
|
73
|
+
fns: mw,
|
|
74
|
+
}));
|
|
75
|
+
return this;
|
|
76
|
+
}
|
|
77
|
+
command(names, ...mw) {
|
|
78
|
+
const set = [].concat(names).map((n) => String(n).toLowerCase());
|
|
79
|
+
this.stack.push({
|
|
80
|
+
match: (ctx) => {
|
|
81
|
+
const m = msgOf(ctx.update);
|
|
82
|
+
if (!m || typeof m.text !== 'string') return false;
|
|
83
|
+
const mobj = /^\/([A-Za-z0-9_]+)(?:@([A-Za-z0-9_]+))?(\s|$)/.exec(m.text);
|
|
84
|
+
if (!mobj) return false;
|
|
85
|
+
if (!set.includes(mobj[1].toLowerCase())) return false;
|
|
86
|
+
if (mobj[2] && ctx.me && ctx.me.username && mobj[2].toLowerCase() !== ctx.me.username.toLowerCase()) return false;
|
|
87
|
+
ctx.match = m.text.slice(mobj[0].length); // argumen setelah perintah
|
|
88
|
+
return true;
|
|
89
|
+
},
|
|
90
|
+
fns: mw,
|
|
91
|
+
});
|
|
92
|
+
return this;
|
|
93
|
+
}
|
|
94
|
+
callbackQuery(trigs, ...mw) {
|
|
95
|
+
const list = [].concat(trigs);
|
|
96
|
+
this.stack.push({
|
|
97
|
+
match: (ctx) => {
|
|
98
|
+
const q = ctx.callback_query || ctx.update.callback_query;
|
|
99
|
+
if (!q || typeof q.data !== 'string') return false;
|
|
100
|
+
return list.some((t) => t instanceof RegExp ? t.test(q.data) : String(t) === q.data);
|
|
101
|
+
},
|
|
102
|
+
fns: mw,
|
|
103
|
+
});
|
|
104
|
+
return this;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Jalankan mw hanya jika pred true. */
|
|
108
|
+
filter(pred, ...mw) { const c = new Composer(...mw); this.use(async (ctx, next) => (await pred(ctx) ? c.run(ctx, next) : next())); return c; }
|
|
109
|
+
/** Lewati mw jika pred true. */
|
|
110
|
+
drop(pred, ...mw) { const c = new Composer(...mw); this.use(async (ctx, next) => (await pred(ctx) ? undefined : c.run(ctx, next))); return c; }
|
|
111
|
+
/** Cabang dua: pred ? A : B. */
|
|
112
|
+
branch(pred, a, b) { const ca = new Composer(a), cb = new Composer(b); this.use(async (ctx, next) => (await pred(ctx) ? ca.run(ctx, next) : cb.run(ctx, next))); return this; }
|
|
113
|
+
/** Petakan ctx→key ke composer berbeda: route('type',{private:c, group:c2}) */
|
|
114
|
+
route(router, by) {
|
|
115
|
+
const map = new Map(Object.entries(by).map(([k, v]) => [k, v instanceof Composer ? v : new Composer(v)]));
|
|
116
|
+
this.use(async (ctx, next) => { const k = typeof router === 'function' ? await router(ctx) : ctx[router]; const c = map.get(k); return c ? c.run(ctx, next) : next(); });
|
|
117
|
+
return this;
|
|
118
|
+
}
|
|
119
|
+
/** Bangun mw malas per-update: lazy((ctx)=>ctx.from?.is_bot?botmw:usermw) */
|
|
120
|
+
lazy(factory) { this.use(async (ctx, next) => { const mw = await factory(ctx); const c = new Composer(...(Array.isArray(mw) ? mw : [mw]).filter(Boolean)); return c.run(ctx, next); }); return this; }
|
|
121
|
+
/** Jalankan mw DI LATAR BELAKANG (tidak menahan next) — cocok untuk tugas lambat. */
|
|
122
|
+
fork(...mw) { const c = new Composer(...mw); this.use((ctx, next) => { c.run(ctx).then(()=>{},(e)=>ctx.api && console.error('[telebibz:fork]', e.message)); return next(); }); return this; }
|
|
123
|
+
|
|
124
|
+
/** Lindungi subpohon middleware dengan handler error — pola batas rambu grammY. */
|
|
125
|
+
errorBoundary(handler, ...mw) {
|
|
126
|
+
const sub = new Composer(...mw);
|
|
127
|
+
this.use(async (ctx, next) => {
|
|
128
|
+
let called = false;
|
|
129
|
+
const cont = () => ((called = true), Promise.resolve());
|
|
130
|
+
try { await sub.run(ctx, cont); }
|
|
131
|
+
catch (err) { called = false; await handler(new BotError(err, ctx), cont); }
|
|
132
|
+
if (called) return next();
|
|
133
|
+
});
|
|
134
|
+
return sub;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Jalankan rantai middleware terhadap satu ctx. */
|
|
138
|
+
async run(ctx, next) {
|
|
139
|
+
const entries = this.stack;
|
|
140
|
+
let idx = -1;
|
|
141
|
+
const dispatch = async (i) => {
|
|
142
|
+
if (i <= idx) throw new Error('next() dipanggil dua kali');
|
|
143
|
+
idx = i;
|
|
144
|
+
if (i >= entries.length) return next ? next() : undefined;
|
|
145
|
+
const e = entries[i];
|
|
146
|
+
if (!e.match(ctx)) return dispatch(i + 1);
|
|
147
|
+
let continued = false;
|
|
148
|
+
for (let j = 0; j < e.fns.length; j++) {
|
|
149
|
+
const isLast = j === e.fns.length - 1;
|
|
150
|
+
let stepNext = false;
|
|
151
|
+
const r = await e.fns[j](ctx, async () => {
|
|
152
|
+
stepNext = true;
|
|
153
|
+
if (isLast) { continued = true; return dispatch(i + 1); }
|
|
154
|
+
});
|
|
155
|
+
if (!stepNext) return r; // handler berhenti di sini (tak lanjut middleware)
|
|
156
|
+
}
|
|
157
|
+
if (!continued) return undefined;
|
|
158
|
+
};
|
|
159
|
+
return dispatch(0);
|
|
160
|
+
}
|
|
161
|
+
middleware() { return (ctx, next) => this.run(ctx, next); }
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Error yang dibungkus konteksnya, gaya BotError grammY. */
|
|
165
|
+
class BotError extends Error {
|
|
166
|
+
constructor(err, ctx) {
|
|
167
|
+
super(err && err.message ? err.message : String(err));
|
|
168
|
+
this.name = 'BotError';
|
|
169
|
+
this.error = err;
|
|
170
|
+
this.ctx = ctx;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
module.exports = { Composer, BotError, compileFilter };
|
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 };
|
package/lib/keyboard.js
ADDED
|
@@ -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: '[0m', dim: '[2m', bold: '[1m',
|
|
6
|
+
red: '[31m', green: '[32m', yellow: '[33m', blue: '[34m',
|
|
7
|
+
magenta: '[35m', cyan: '[36m', gray: '[90m',
|
|
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;
|