@xbibzlibrary/telebibz 0.4.4 → 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.
- package/CHANGELOG.md +35 -138
- package/LICENSE +1 -1
- package/NOTICE.md +9 -4
- package/README.md +173 -242
- package/examples/01-quickstart.js +13 -0
- package/examples/02-menu-tombol.js +22 -0
- package/examples/03-wizard.js +29 -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 +81 -0
- package/index.js +42 -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 +190 -0
- package/lib/wizard.js +81 -0
- package/package.json +35 -97
- package/test/all.test.js +334 -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.id.md +0 -292
- package/README.zh-CN.md +0 -292
- 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/GETTING_STARTED.id.md +0 -85
- package/docs/GETTING_STARTED.md +0 -85
- package/docs/GETTING_STARTED.zh-CN.md +0 -85
- 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/README.md +0 -48
- package/docs/STORAGE.id.md +0 -105
- package/docs/STORAGE.md +0 -105
- package/docs/STORAGE.zh-CN.md +0 -105
- 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/test/all.test.js
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
// test/all.test.js — 14 kasus, 100% tanpa jaringan (transport disuntik).
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const assert = require('assert');
|
|
5
|
+
const { TeleBibz, btn, url, kb, webApp, copy, humanize, File, InlineKeyboard,
|
|
6
|
+
Menu, MenuContainer, iq, autoRetry, throttler, limiter, InputMediaBuilder } = require('..');
|
|
7
|
+
|
|
8
|
+
let N = 0, OK = 0;
|
|
9
|
+
const TOTAL = 24;
|
|
10
|
+
const t = (nama, fn) => {
|
|
11
|
+
const done = () => { N++; if (N === TOTAL) { console.log(`\n${OK}/${N} lulus`); process.exit(OK === N ? 0 : 1); } };
|
|
12
|
+
Promise.resolve()
|
|
13
|
+
.then(fn)
|
|
14
|
+
.then(() => { OK++; console.log(` ✓ ${nama}`); done(); })
|
|
15
|
+
.catch((e) => { console.error(` ✗ ${nama}\n ${e.stack || e}`); done(); });
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/* ===== harness: bot dengan transport palsu (merekam panggilan API) ===== */
|
|
19
|
+
function buatBot(overrides = {}) {
|
|
20
|
+
const calls = [];
|
|
21
|
+
const transport = async (method, payload = {}) => {
|
|
22
|
+
calls.push({ method, payload });
|
|
23
|
+
if (method === 'getMe') return { id: 99, is_bot: true, first_name: 'Tes', username: 'tesbot' };
|
|
24
|
+
if (method === 'sendMessage') return { message_id: calls.length, ...payload };
|
|
25
|
+
if (overrides[method]) return overrides[method](payload);
|
|
26
|
+
return true;
|
|
27
|
+
};
|
|
28
|
+
const bot = new TeleBibz('123456:TESTTOKEN-TESTTOKEN-TESTTOKEN-TESTOKEN', { silent: true, transport });
|
|
29
|
+
return { bot, calls, me: null };
|
|
30
|
+
}
|
|
31
|
+
async function boot(b) { await b.init(); }
|
|
32
|
+
const ME = { id: 99, is_bot: true, first_name: 'Tes', username: 'tesbot' };
|
|
33
|
+
const USER = { id: 555, is_bot: false, first_name: 'Budi' };
|
|
34
|
+
const CHAT = { id: 555, type: 'private', first_name: 'Budi' };
|
|
35
|
+
let uid = 0, mid = 0;
|
|
36
|
+
function uMsg(text, chat = CHAT) {
|
|
37
|
+
return {
|
|
38
|
+
update_id: ++uid,
|
|
39
|
+
message: {
|
|
40
|
+
message_id: ++mid, from: USER, chat, date: Math.floor(Date.now() / 1000),
|
|
41
|
+
text,
|
|
42
|
+
...(text.startsWith('/') ? { entities: [{ type: 'bot_command', offset: 0, length: text.split(' ')[0].length }] } : {}),
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const texts = (calls) => calls.filter((c) => c.method === 'sendMessage').map((c) => c.payload.text || '');
|
|
47
|
+
|
|
48
|
+
/* ===== 1. keyboard builder ===== */
|
|
49
|
+
t('btn()/url()/kb(): bentuk objek + warna + ikon', () => {
|
|
50
|
+
assert.deepStrictEqual(btn('A', 'a'), { text: 'A', callback_data: 'a' });
|
|
51
|
+
assert.deepStrictEqual(btn('B', 'b', 'danger', '123'), { text: 'B', callback_data: 'b', style: 'danger', icon_custom_emoji_id: '123' });
|
|
52
|
+
assert.strictEqual(url('W', 'https://x', 'success').style, 'success');
|
|
53
|
+
assert.strictEqual(webApp('App', 'https://w').web_app.url, 'https://w');
|
|
54
|
+
assert.strictEqual(copy('Salin', 'kode').copy_text.text, 'kode');
|
|
55
|
+
assert.ok(kb([[btn('X', 'x')]]).reply_markup.inline_keyboard[0][0].text === 'X');
|
|
56
|
+
assert.ok(kb.confirm('y', 'n').reply_markup.inline_keyboard[0][0].style === 'success');
|
|
57
|
+
const ik = new InlineKeyboard().text('A', 'a').url('B', 'https://b').row().text('C', 'c', 'danger').build();
|
|
58
|
+
assert.strictEqual(ik.reply_markup.inline_keyboard.length, 2);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
/* ===== 2. token guard ===== */
|
|
62
|
+
t('constructor menolak token ngaco cepat', () => {
|
|
63
|
+
assert.throws(() => new TeleBibz('bukan-token'), /token tidak valid/i);
|
|
64
|
+
assert.throws(() => new TeleBibz(), /token tidak valid/i);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
/* ===== 3. cmd + hears ===== */
|
|
68
|
+
t('cmd & hears merespons via sendMessage', async () => {
|
|
69
|
+
const { bot, calls } = buatBot(); await boot(bot);
|
|
70
|
+
let startHit = 0;
|
|
71
|
+
bot.cmd('start', (ctx) => { startHit++; return ctx.reply(`Halo ${ctx.from.first_name}`); });
|
|
72
|
+
bot.hears(/halo/i, (ctx) => ctx.reply('halo juga'));
|
|
73
|
+
await bot.handleUpdate(uMsg('/start'));
|
|
74
|
+
await bot.handleUpdate(uMsg('Halo kawan'));
|
|
75
|
+
assert.strictEqual(startHit, 1);
|
|
76
|
+
const tx = texts(calls);
|
|
77
|
+
assert.ok(tx.some((x) => /Halo Budi/.test(x)) && tx.some((x) => /halo juga/.test(x)));
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
/* ===== 4. action ===== */
|
|
81
|
+
t('action() menangkap callback_data', async () => {
|
|
82
|
+
const { bot, calls } = buatBot(); await boot(bot);
|
|
83
|
+
bot.action('menu:premium', (ctx) => ctx.editMessageText('premium dibuka'));
|
|
84
|
+
await bot.handleUpdate({
|
|
85
|
+
update_id: ++uid,
|
|
86
|
+
callback_query: {
|
|
87
|
+
id: 'q1', from: USER, chat_instance: 'ci', data: 'menu:premium',
|
|
88
|
+
message: { message_id: 7, from: { id: 99, is_bot: true, first_name: 'T' }, chat: CHAT, date: 1 },
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
assert.ok(calls.some((c) => c.method === 'editMessageText' && /premium dibuka/.test(c.payload.text)));
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
/* ===== 5. hears string persis ===== */
|
|
95
|
+
t('hears(string) hanya cocok teks persis (ci)', async () => {
|
|
96
|
+
const { bot, calls } = buatBot(); await boot(bot);
|
|
97
|
+
bot.hears('ping', (ctx) => ctx.reply('pong'));
|
|
98
|
+
await bot.handleUpdate(uMsg('PING'));
|
|
99
|
+
await bot.handleUpdate(uMsg('ping pong'));
|
|
100
|
+
const po = texts(calls).filter((x) => x === 'pong');
|
|
101
|
+
assert.strictEqual(po.length, 1, 'hears string harus cocok persis sekali saja');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
/* ===== 6–8. wizard ===== */
|
|
105
|
+
t('wizard mengalir penuh: tanya → validasi → done', async () => {
|
|
106
|
+
const { bot, calls } = buatBot(); await boot(bot);
|
|
107
|
+
const jawab = [];
|
|
108
|
+
bot.wizard('daftar', {
|
|
109
|
+
steps: [
|
|
110
|
+
{ key: 'nama', ask: 'Nama?' },
|
|
111
|
+
{ key: 'umur', ask: 'Umur?', parse: (t) => parseInt(t, 10), validate: (n) => (n > 0 ? null : 'Ulangi angka:') },
|
|
112
|
+
],
|
|
113
|
+
done: async (ans, ctx) => { jawab.push(ans); await ctx.reply('selesai'); },
|
|
114
|
+
});
|
|
115
|
+
const send = (t) => bot.handleUpdate(uMsg(t));
|
|
116
|
+
await send('/daftar'); await send('Budi'); await send('abc'); await send('25');
|
|
117
|
+
const tx = texts(calls);
|
|
118
|
+
assert.deepStrictEqual(jawab, [{ nama: 'Budi', umur: 25 }]);
|
|
119
|
+
assert.ok(tx.some((x) => /Nama\?/.test(x)) && tx.some((x) => /Ulangi angka/.test(x)) && tx.some((x) => /selesai/.test(x)));
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
t('wizard batal via kata batal', async () => {
|
|
123
|
+
const { bot, calls } = buatBot(); await boot(bot);
|
|
124
|
+
bot.wizard('isi', { steps: [{ key: 'a', ask: 'A?' }], done: () => {} });
|
|
125
|
+
await bot.handleUpdate(uMsg('/isi'));
|
|
126
|
+
await bot.handleUpdate(uMsg('batal'));
|
|
127
|
+
assert.ok(texts(calls).some((x) => /dibatalkan/i.test(x)));
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
t('pesan non-teks di tengah wizard tidak error & wizard lanjut', async () => {
|
|
131
|
+
const { bot, calls } = buatBot(); await boot(bot);
|
|
132
|
+
let doneHit = 0;
|
|
133
|
+
bot.wizard('form', { steps: [{ key: 'a', ask: 'A?' }], done: () => doneHit++ });
|
|
134
|
+
await bot.handleUpdate(uMsg('/form'));
|
|
135
|
+
await bot.handleUpdate({ update_id: ++uid, message: { message_id: ++mid, from: USER, chat: CHAT, date: 1, photo: [{ file_id: 'f', width: 1, height: 1 }] } });
|
|
136
|
+
await bot.handleUpdate(uMsg('oke'));
|
|
137
|
+
assert.strictEqual(doneHit, 1);
|
|
138
|
+
assert.ok(!texts(calls).some((x) => /error/i.test(x)));
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
/* ===== 9. broadcast ===== */
|
|
142
|
+
t('broadcast mengirim ke semua + merangkum kegagalan', async () => {
|
|
143
|
+
const { bot } = buatBot(); await boot(bot);
|
|
144
|
+
bot.api.config.use(async (prev, method, payload) => {
|
|
145
|
+
if (method === 'sendMessage' && payload.chat_id === 99) {
|
|
146
|
+
const e = new Error('Forbidden: bot was blocked by the user');
|
|
147
|
+
e.description = 'bot was blocked by the user'; throw e;
|
|
148
|
+
}
|
|
149
|
+
return prev(method, payload);
|
|
150
|
+
});
|
|
151
|
+
const hasil = await bot.broadcast([1, 99, 2], 'tes', { delay: 0 });
|
|
152
|
+
assert.strictEqual(hasil.terkirim + hasil.gagal, 3);
|
|
153
|
+
assert.strictEqual(hasil.gagal, 1);
|
|
154
|
+
assert.strictEqual(hasil.errors[0].chatId, 99);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
/* ===== 10. humanize ===== */
|
|
158
|
+
t('humanize menerjemahkan error API populer', () => {
|
|
159
|
+
assert.ok(/diblokir/i.test(humanize(new Error('bot was blocked by the user')).saran));
|
|
160
|
+
const asing = humanize(new Error('sesuatu yang tidak dikenal'));
|
|
161
|
+
assert.strictEqual(asing.saran, null);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
/* ===== 11. session default ===== */
|
|
165
|
+
t('ctx.session selalu {} tanpa setup apa pun', async () => {
|
|
166
|
+
const { bot } = buatBot(); await boot(bot);
|
|
167
|
+
let seen = null;
|
|
168
|
+
bot.on('message:text', (ctx) => { seen = ctx.session; });
|
|
169
|
+
await bot.handleUpdate(uMsg('cek sesi'));
|
|
170
|
+
assert.ok(seen && typeof seen === 'object');
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
/* ===== 12. onError kustom ===== */
|
|
174
|
+
t('onError kustom menerima err+ctx, handler error TIDAK crash', async () => {
|
|
175
|
+
const { bot } = buatBot(); await boot(bot);
|
|
176
|
+
const got = [];
|
|
177
|
+
bot.opts.onError = (err, ctx) => got.push([String(err), ctx && ctx.chat.id]);
|
|
178
|
+
bot.cmd('boom', () => { throw new Error('meledak'); });
|
|
179
|
+
await bot.handleUpdate(uMsg('/boom'));
|
|
180
|
+
assert.strictEqual(got.length, 1);
|
|
181
|
+
assert.ok(/meledak/.test(got[0][0]) && got[0][1] === 555);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
/* ===== 13. polling 409 → auto-retry; stop bersih ===== */
|
|
185
|
+
t('launch: getUpdates 409 → retry halus; stop() resolve runPromise', async () => {
|
|
186
|
+
let polls = 0;
|
|
187
|
+
const transport = async (method) => {
|
|
188
|
+
if (method === 'getMe') return { id: 1, is_bot: true, first_name: 'X', username: 'x' };
|
|
189
|
+
if (method === 'getUpdates') {
|
|
190
|
+
polls++;
|
|
191
|
+
if (polls === 1) { const e = new Error('terminated by other getUpdates request'); e.error_code = 409; e.description = e.message; throw e; }
|
|
192
|
+
await new Promise((r) => setTimeout(r, 40));
|
|
193
|
+
return [];
|
|
194
|
+
}
|
|
195
|
+
return true;
|
|
196
|
+
};
|
|
197
|
+
const bot = new TeleBibz('123456:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', { silent: true, transport });
|
|
198
|
+
await bot.launch({ noSignalHandlers: true, conflictDelay: 25 });
|
|
199
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
200
|
+
bot.stop();
|
|
201
|
+
await bot.runPromise;
|
|
202
|
+
assert.ok(polls >= 2, `harus retry minimal sekali, polls=${polls}`);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
/* ===== 15. Proxy API generik: metode apapun ===== */
|
|
206
|
+
t('Proxy: api.metodeBebas(payload) → callApi mentah', async () => {
|
|
207
|
+
const { bot, calls } = buatBot(); await boot(bot);
|
|
208
|
+
await bot.api.sendDiceCustom({ chat_id: 1, emoji: '🎲' });
|
|
209
|
+
const c = calls.find((x) => x.method === 'sendDiceCustom');
|
|
210
|
+
assert.ok(c && c.payload.emoji === '🎲');
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
/* ===== 16. transformer pipeline ===== */
|
|
214
|
+
t('api.config.use: transformer melihat semua panggilan', async () => {
|
|
215
|
+
const { bot } = buatBot(); await boot(bot);
|
|
216
|
+
const seen = [];
|
|
217
|
+
bot.api.config.use(async (prev, m, p) => { seen.push(m); return prev(m, p); });
|
|
218
|
+
await bot.api.sendMessage(1, 'a'); await bot.api.getMyCommands();
|
|
219
|
+
assert.deepStrictEqual(seen, ['sendMessage', 'getMyCommands']);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
/* ===== 17. autoRetry menahan 429 sekali ===== */
|
|
223
|
+
t('autoRetry: 429 + retry_after dihormati lalu sukses', async () => {
|
|
224
|
+
let hits = 0;
|
|
225
|
+
const tr = async (m) => { hits++; if (hits === 1) { const e = new Error('Too Many Requests'); e.error_code = 429; e.parameters = { retry_after: 0 }; throw e; } return true; };
|
|
226
|
+
const out = await autoRetry({ maxRetry: 2, baseDelayMs: 1 })(tr, 'sendMessage', {});
|
|
227
|
+
assert.ok(out === true && hits === 2);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
/* ===== 18. throttler memanggil berurutan ===== */
|
|
231
|
+
t('throttler: semua panggilan lolos berurutan', async () => {
|
|
232
|
+
const order = []; const tr = async (m) => { order.push(m); return m; };
|
|
233
|
+
const th = throttler({ perSecond: 1000 });
|
|
234
|
+
await Promise.all([th(tr, 'a', {}), th(tr, 'b', {}), th(tr, 'c', {})]);
|
|
235
|
+
assert.deepStrictEqual(order, ['a', 'b', 'c']);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
/* ===== 19. limiter menahan spam ===== */
|
|
239
|
+
t('limiter: update ke-4 dalam window ditahan', async () => {
|
|
240
|
+
const { bot } = buatBot(); await boot(bot);
|
|
241
|
+
let hits = 0;
|
|
242
|
+
bot.use(limiter({ windowMs: 60000, limit: 3 }));
|
|
243
|
+
bot.on(':text', () => { hits++; });
|
|
244
|
+
for (let i = 0; i < 5; i++) await bot.handleUpdate(uMsg('x' + i));
|
|
245
|
+
assert.strictEqual(hits, 3);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
/* ===== 20. Menu render + tekan ===== */
|
|
249
|
+
t('Menu: render keyboard & handler tombol jalan', async () => {
|
|
250
|
+
const { bot, calls } = buatBot(); await boot(bot);
|
|
251
|
+
const mc = new MenuContainer();
|
|
252
|
+
const m = mc.create('cfg');
|
|
253
|
+
let pressed = 0;
|
|
254
|
+
m.text('Tombol', async (ctx) => { pressed++; await ctx.answerCallbackQuery('sip'); });
|
|
255
|
+
bot.use(mc);
|
|
256
|
+
const rm = await m.render({});
|
|
257
|
+
const cbData = rm.inline_keyboard[0][0].callback_data;
|
|
258
|
+
assert.strictEqual(cbData, 'cfg|0');
|
|
259
|
+
await bot.handleUpdate({ update_id: ++uid, callback_query: { id: 'c1', from: USER, chat_instance: 'x', data: cbData, message: { message_id: 9, from: { id: 99, is_bot: true, first_name: 'B' }, chat: CHAT, date: 1 } } });
|
|
260
|
+
assert.strictEqual(pressed, 1);
|
|
261
|
+
assert.ok(calls.some((c) => c.method === 'answerCallbackQuery' && c.payload.text === 'sip'));
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
/* ===== 21. inlineQuery ===== */
|
|
265
|
+
t('inlineQuery: regex cocok, answerInlineQuery terkirim', async () => {
|
|
266
|
+
const { bot, calls } = buatBot(); await boot(bot);
|
|
267
|
+
bot.inlineQuery(/kucing/i, async (ctx) => {
|
|
268
|
+
await ctx.answerInlineQuery([iq.article('1', 'Kucing garong', { message_text: 'meong!' })], { cache_time: 0 });
|
|
269
|
+
});
|
|
270
|
+
await bot.handleUpdate({ update_id: ++uid, inline_query: { id: 'i1', from: USER, query: 'kucing lucu', offset: '' } });
|
|
271
|
+
const c = calls.find((x) => x.method === 'answerInlineQuery');
|
|
272
|
+
assert.ok(c && c.payload.results[0].type === 'article');
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
/* ===== 22. replyWithMediaGroup ===== */
|
|
276
|
+
t('replyWithMediaGroup membentuk payload media', async () => {
|
|
277
|
+
const { bot, calls } = buatBot(); await boot(bot);
|
|
278
|
+
bot.cmd('album', (ctx) => ctx.replyWithMediaGroup([
|
|
279
|
+
InputMediaBuilder.photo('https://a/1.jpg'), InputMediaBuilder.photo('https://a/2.jpg', { caption: 'dua' }),
|
|
280
|
+
]));
|
|
281
|
+
await bot.handleUpdate(uMsg('/album'));
|
|
282
|
+
const c = calls.find((x) => x.method === 'sendMediaGroup');
|
|
283
|
+
assert.ok(c && c.payload.media.length === 2 && c.payload.media[1].caption === 'dua');
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
/* ===== 23. composer lanjutan: branch/drop/filter ===== */
|
|
287
|
+
t('branch/drop/filter berperilaku benar (semantik grammY)', async () => {
|
|
288
|
+
// bot A: branch memilih sub-pohon
|
|
289
|
+
const A = buatBot(); await boot(A.bot);
|
|
290
|
+
const tagA = [];
|
|
291
|
+
A.bot.branch((ctx) => ctx.msg.text === 'yes', async () => { tagA.push('A'); }, async () => { tagA.push('B'); });
|
|
292
|
+
await A.bot.handleUpdate(uMsg('yes'));
|
|
293
|
+
await A.bot.handleUpdate(uMsg('no'));
|
|
294
|
+
assert.deepStrictEqual(tagA, ['A', 'B']);
|
|
295
|
+
|
|
296
|
+
// bot B: filter menjalankan hanya saat cocok; drop menahan saat cocok
|
|
297
|
+
const B = buatBot(); await boot(B.bot);
|
|
298
|
+
const tagB = [];
|
|
299
|
+
B.bot.filter((ctx) => ctx.msg.text.startsWith('fi'), async () => { tagB.push('F'); });
|
|
300
|
+
B.bot.drop((ctx) => ctx.msg.text === 'toxic', async () => { tagB.push('TERTAHAN'); });
|
|
301
|
+
await B.bot.handleUpdate(uMsg('file update'));
|
|
302
|
+
await B.bot.handleUpdate(uMsg('toxic')); // drop(pred=true) → mw ditahan ✓
|
|
303
|
+
await B.bot.handleUpdate(uMsg('bukan cocok')); // drop(pred=false) → mw jalan ✓
|
|
304
|
+
assert.deepStrictEqual(tagB, ['F', 'TERTAHAN']);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
/* ===== 24. ctx.getFile pilih photo terbesar ===== */
|
|
308
|
+
t('ctx.getFile() memilih ukuran photo terbesar', async () => {
|
|
309
|
+
const { bot, calls } = buatBot(); await boot(bot);
|
|
310
|
+
bot.on('message:photo', async (ctx) => { const f = await ctx.getFile(); await ctx.reply('got:' + f.file_path || 'x'); });
|
|
311
|
+
await bot.handleUpdate({
|
|
312
|
+
update_id: ++uid,
|
|
313
|
+
message: { message_id: ++mid, from: USER, chat: CHAT, date: 1, photo: [
|
|
314
|
+
{ file_id: 'kecil', width: 90, height: 90 }, { file_id: 'besar', width: 800, height: 600 },
|
|
315
|
+
] },
|
|
316
|
+
});
|
|
317
|
+
const g = calls.find((c) => c.method === 'getFile');
|
|
318
|
+
assert.ok(g && g.payload.file_id === 'besar');
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
/* ===== 14. webhook handler Node murni ===== */
|
|
322
|
+
t('webhook(): body JSON diproses handleUpdate', async () => {
|
|
323
|
+
const { Readable } = require('stream');
|
|
324
|
+
const { bot, calls } = buatBot(); await boot(bot);
|
|
325
|
+
bot.cmd('start', (ctx) => ctx.reply('hai webhook'));
|
|
326
|
+
const handler = bot.webhook();
|
|
327
|
+
const upd = uMsg('/start');
|
|
328
|
+
const req = Readable.from([JSON.stringify(upd)]);
|
|
329
|
+
let status = 0, bodyOut = '';
|
|
330
|
+
const res = { set statusCode(v) { status = v; }, get statusCode() { return status; }, setHeader() {}, end(b) { bodyOut = b; } };
|
|
331
|
+
await handler(req, res);
|
|
332
|
+
assert.strictEqual(status, 200);
|
|
333
|
+
assert.ok(texts(calls).some((x) => /hai webhook/.test(x)));
|
|
334
|
+
});
|
package/CODE_OF_CONDUCT.md
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
# Code of Conduct
|
|
2
|
-
|
|
3
|
-
## Our pledge
|
|
4
|
-
|
|
5
|
-
We are committed to making participation in the telebibz community welcoming, respectful, technically rigorous, and safe for everyone. Contributors, maintainers, reviewers, users, and other participants are expected to act in good faith and to prioritize constructive collaboration.
|
|
6
|
-
|
|
7
|
-
## Expected behavior
|
|
8
|
-
|
|
9
|
-
Participants should communicate clearly and respectfully, assume good intent while discussing technical disagreements, provide actionable feedback, document relevant context, respect privacy and confidentiality, and accept that maintainers may make decisions based on project scope, security, compatibility, and release risk.
|
|
10
|
-
|
|
11
|
-
When reporting a defect or security issue, include reproducible technical information without publishing secrets, personal data, or exploit details that could put users at risk. When reviewing code, focus on the implementation and its consequences rather than the person who wrote it.
|
|
12
|
-
|
|
13
|
-
## Unacceptable behavior
|
|
14
|
-
|
|
15
|
-
Harassment, discrimination, threats, intimidation, personal attacks, doxxing, deliberate disruption, sexualized conduct in project spaces, impersonation, publishing private information without consent, spam, and knowingly submitting malicious or deceptive changes are not acceptable. Attempts to bypass project security controls, expose credentials, poison dependencies, or manipulate release automation are treated as serious violations.
|
|
16
|
-
|
|
17
|
-
## Scope
|
|
18
|
-
|
|
19
|
-
This Code of Conduct applies to repository issues, pull requests, discussions, reviews, commits, releases, documentation, official project channels, and any interaction where a participant is representing telebibz or its maintainers.
|
|
20
|
-
|
|
21
|
-
## Enforcement
|
|
22
|
-
|
|
23
|
-
Maintainers may remove or edit content, close discussions, restrict participation, reject contributions, revoke project permissions, or take other proportionate action. Serious or repeated violations may result in a permanent ban from project spaces. Enforcement decisions consider the severity, intent, impact, recurrence, and available evidence.
|
|
24
|
-
|
|
25
|
-
## Reporting
|
|
26
|
-
|
|
27
|
-
Report conduct concerns privately to the maintainers through the repository's private security/contact channel. Do not open a public issue for sensitive reports. Include links, dates, relevant usernames, and a concise description of what occurred. Do not include credentials or unnecessary personal information.
|
|
28
|
-
|
|
29
|
-
Security vulnerabilities must be reported according to [SECURITY.md](SECURITY.md), not through a public issue.
|
|
30
|
-
|
|
31
|
-
## Appeals
|
|
32
|
-
|
|
33
|
-
A participant may request a review of an enforcement decision by providing relevant context privately. Appeals are reviewed by a maintainer who was not the sole decision-maker for the original action where practical. Retaliation against a good-faith report or appeal is prohibited.
|
|
34
|
-
|
|
35
|
-
## Attribution
|
|
36
|
-
|
|
37
|
-
This policy is informed by the [Contributor Covenant](https://www.contributor-covenant.org/version/2/1/code_of_conduct/), adapted for the technical and security needs of this project.
|
package/CONTRIBUTING.md
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
# Contributing to telebibz
|
|
2
|
-
|
|
3
|
-
Thank you for helping improve telebibz. Contributions are welcome when they are focused, reproducible, tested, documented, and compatible with the project's security and release policies.
|
|
4
|
-
|
|
5
|
-
## Before you start
|
|
6
|
-
|
|
7
|
-
Read [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md), [CONTRIBUTION_RULES.md](CONTRIBUTION_RULES.md), [SECURITY.md](SECURITY.md), and [RELEASE_POLICY.md](RELEASE_POLICY.md). Do not open a public issue for a security vulnerability or include tokens, private chat identifiers, production payloads, or other sensitive data in an issue, pull request, test, log, screenshot, or commit.
|
|
8
|
-
|
|
9
|
-
Check existing issues and pull requests before opening a new one. For a defect, use the bug template and include a minimal reproduction. For a new capability, use the feature template and explain the public API, behavior, compatibility, and testing implications.
|
|
10
|
-
|
|
11
|
-
## Local setup
|
|
12
|
-
|
|
13
|
-
The project requires Node.js `>=22` and uses npm for release-compatible commands. Install dependencies and run the baseline checks:
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
npm ci
|
|
17
|
-
npm run typecheck
|
|
18
|
-
npm run test:types
|
|
19
|
-
npm run test:examples
|
|
20
|
-
npm run lint
|
|
21
|
-
npm test
|
|
22
|
-
npm run build
|
|
23
|
-
npm run security
|
|
24
|
-
npm run release:check
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
The credential-gated Telegram E2E suite is skipped unless the required test environment variables are provided. Never use production credentials in tests. Use `MockTransport`, `createTestBot()`, `createMockUpdate()`, and the integration fixtures for deterministic tests.
|
|
28
|
-
|
|
29
|
-
## Branches and commits
|
|
30
|
-
|
|
31
|
-
Create a focused branch from `main`. Keep unrelated refactors out of a feature or bug-fix pull request. Use an imperative Conventional Commit-style message, for example `fix: isolate polling handler failures` or `docs: expand storage adapter reference`. Include `[skip release]` only when a change must not trigger the GitHub-to-npm release workflow; maintainers may remove that marker when a release is appropriate.
|
|
32
|
-
|
|
33
|
-
## Implementation expectations
|
|
34
|
-
|
|
35
|
-
Public behavior must be implemented in TypeScript with strict typing. Do not add fake responses, silent stubs, undocumented breaking behavior, or untested branches. Preserve ESM and CommonJS builds, zero runtime vendor dependencies in the core package, Node.js `>=22` compatibility, and the existing package export map.
|
|
36
|
-
|
|
37
|
-
Changes to routing, context, lifecycle, transport, storage, queue, scheduler, approval, generated API declarations, or release automation require regression tests. Changes to a public function, class, method, option, error, event, or generated method require a corresponding API documentation update. Changes to examples require `npm run test:examples` and must use placeholders instead of credentials. Changes that affect package contents must pass `release:check` and `npm pack --dry-run`.
|
|
38
|
-
|
|
39
|
-
## Pull request process
|
|
40
|
-
|
|
41
|
-
Open a pull request against `main` and complete the pull request template. Explain the problem, solution, compatibility impact, test evidence, documentation changes, and security implications. Keep the diff reviewable. Maintainers may request changes, split a pull request, or ask for a follow-up issue when scope is too broad.
|
|
42
|
-
|
|
43
|
-
Every pull request must pass the CI workflow. A maintainer reviews API compatibility, error handling, tests, documentation, package contents, and release impact before approval. Do not merge while required checks are failing or unresolved security concerns remain.
|
|
44
|
-
|
|
45
|
-
## Documentation and translations
|
|
46
|
-
|
|
47
|
-
English is the default README language. Keep `README.id.md`, `README.zh-CN.md`, `docs/API.id.md`, and `docs/API.zh-CN.md` synchronized when public behavior changes. Code signatures, method names, package names, environment variables, and command names must remain exact in every translation.
|
|
48
|
-
|
|
49
|
-
## Release process
|
|
50
|
-
|
|
51
|
-
Normal pushes to `main` can trigger the protected auto-publish workflow. The workflow runs quality gates, computes an unused patch version, publishes the package, commits the version, creates a tag, and creates a GitHub Release. See [RELEASE_AUTOMATION.md](RELEASE_AUTOMATION.md) for the required `NPM_TOKEN` secret and private-source provenance constraint. Contributors must not publish directly to npm unless explicitly authorized by the maintainers.
|
|
52
|
-
|
|
53
|
-
## Showcase submissions
|
|
54
|
-
|
|
55
|
-
Public projects using Telebibz may be submitted through a pull request to [SHOWCASE.md](SHOWCASE.md). Include only public links and a short description. Never include bot tokens, private endpoints, personal data, or confidential source code.
|
|
56
|
-
|
|
57
|
-
## Questions
|
|
58
|
-
|
|
59
|
-
Use the question/support template for usage questions and consult the [English API reference](docs/API.md) first. Keep support requests free of credentials and private user data.
|
package/CONTRIBUTION_RULES.md
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# Contribution Rules
|
|
2
|
-
|
|
3
|
-
These rules are the operational requirements for contributions to telebibz. They complement [CONTRIBUTING.md](CONTRIBUTING.md) and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md).
|
|
4
|
-
|
|
5
|
-
## Scope and ownership
|
|
6
|
-
|
|
7
|
-
A contribution must have a clear owner, a stated problem, and a bounded outcome. Large changes should be split into reviewable pull requests. Maintainers may reject work that is technically correct but outside the package scope, duplicates an existing design, introduces unnecessary dependencies, or creates disproportionate maintenance cost.
|
|
8
|
-
|
|
9
|
-
## API and compatibility rules
|
|
10
|
-
|
|
11
|
-
Public exports, constructor options, method signatures, event names, error classes, generated method names, package entrypoints, and persisted data formats are compatibility-sensitive. A breaking change requires an explicit migration note, a versioning decision, updated type-level tests, and maintainer approval.
|
|
12
|
-
|
|
13
|
-
New public APIs must have strict TypeScript types, runtime validation where input can be unsafe, deterministic error behavior, examples, and API reference documentation. Avoid `any` in public signatures. When a Telegram type is not specialized in the core method map, use the vendored `TelegramTypes` declarations or an honest generic boundary rather than inventing an inaccurate shape.
|
|
14
|
-
|
|
15
|
-
## Runtime and error-handling rules
|
|
16
|
-
|
|
17
|
-
Network operations must honor timeout and cancellation behavior. Retry logic must be bounded and must not retry authentication or validation failures blindly. Update handlers must not terminate polling because one update failed. Background tasks must surface errors through documented hooks or events and must not create unhandled promise rejections.
|
|
18
|
-
|
|
19
|
-
Callback-query handling must work for both message-backed and inline callbacks. Router behavior must state whether first-match or all-match is used. Nested middleware must not execute terminal handlers twice. Persistent state must use the `Storage` abstraction and must document consistency, TTL, and driver assumptions.
|
|
20
|
-
|
|
21
|
-
## Testing rules
|
|
22
|
-
|
|
23
|
-
A pull request must add or update tests for every changed behavior. Use unit tests for pure logic, integration tests for module boundaries, type-level tests for public signatures, and credential-gated E2E tests only for real Telegram behavior that cannot be represented safely by mocks. Tests must be deterministic and must clean up timers, temporary files, listeners, and network resources.
|
|
24
|
-
|
|
25
|
-
A bug fix is incomplete without a regression test that fails against the old behavior. A feature is incomplete without tests for success, invalid input, cancellation, retry/failure, and boundary behavior where applicable.
|
|
26
|
-
|
|
27
|
-
## Documentation rules
|
|
28
|
-
|
|
29
|
-
Documentation must describe the implementation that exists in source, not a planned feature. Every documented limitation must remain accurate. English is canonical; Indonesian and Simplified Chinese translations must preserve signatures, code blocks, URLs, environment variables, and command names exactly.
|
|
30
|
-
|
|
31
|
-
## Dependency and security rules
|
|
32
|
-
|
|
33
|
-
Do not add a runtime dependency without a written justification, license review, bundle/package impact analysis, and maintainer approval. Never commit credentials, private keys, Telegram tokens, npm tokens, test secrets, or real user data. Security-sensitive changes require review of [SECURITY.md](SECURITY.md) and must not be disclosed publicly before a fix or coordinated disclosure decision.
|
|
34
|
-
|
|
35
|
-
## Release and repository rules
|
|
36
|
-
|
|
37
|
-
Do not edit or overwrite an already published npm version. Do not manually create a release tag that conflicts with the protected workflow. Do not bypass required checks, disable security audit, weaken release checks, or modify workflow permissions without maintainer approval. Use `[skip release]` only for changes that must not publish a package.
|
|
38
|
-
|
|
39
|
-
## Review standards
|
|
40
|
-
|
|
41
|
-
Reviewers should verify correctness, compatibility, tests, documentation, security, package contents, and operational behavior. Approval is not a guarantee that no defect exists; it confirms that the change meets the current project acceptance criteria and is safe to merge based on the available evidence.
|
package/GOVERNANCE.md
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
# Governance
|
|
2
|
-
|
|
3
|
-
## Project purpose
|
|
4
|
-
|
|
5
|
-
telebibz is a TypeScript Telegram Bot Framework. Governance exists to preserve a stable public API, honest feature scope, secure release automation, and a respectful contributor community.
|
|
6
|
-
|
|
7
|
-
## Roles
|
|
8
|
-
|
|
9
|
-
| Role | Responsibility |
|
|
10
|
-
|---|---|
|
|
11
|
-
| Users | Report reproducible defects, explain use cases, and provide feedback without publishing secrets. |
|
|
12
|
-
| Contributors | Submit focused code, tests, documentation, and issue reports that follow the project rules. |
|
|
13
|
-
| Reviewers | Examine correctness, compatibility, security, tests, documentation, and operational risk. |
|
|
14
|
-
| Maintainers | Set project direction, review and merge changes, manage releases, triage issues, and protect the repository. |
|
|
15
|
-
| Release maintainers | Control npm credentials, GitHub Actions environments, version tags, npm publication, and release verification. |
|
|
16
|
-
|
|
17
|
-
One person may hold multiple roles, but security-sensitive actions should use separation of duties whenever practical.
|
|
18
|
-
|
|
19
|
-
## Decision-making
|
|
20
|
-
|
|
21
|
-
Routine decisions are made by maintainers through pull request review and documented issue discussion. Decisions should be based on user benefit, implementation quality, compatibility, security, maintenance cost, and evidence from tests or production reports.
|
|
22
|
-
|
|
23
|
-
For controversial or breaking changes, maintainers should document alternatives, migration impact, and the reason for the selected approach. A maintainer may request a design note before implementation. Silence is not approval for a breaking change.
|
|
24
|
-
|
|
25
|
-
## Triage
|
|
26
|
-
|
|
27
|
-
New issues are initially classified as bug, feature, documentation, security, support, or duplicate. Maintainers may request a reproduction, reduce sensitive details, split scope, mark a report as blocked, or close it when the requested behavior conflicts with the documented project contract.
|
|
28
|
-
|
|
29
|
-
Security reports are handled privately according to [SECURITY.md](SECURITY.md). Public issues must not contain exploit instructions, credentials, or personal data.
|
|
30
|
-
|
|
31
|
-
## Pull requests
|
|
32
|
-
|
|
33
|
-
A pull request requires a passing CI workflow and at least one maintainer review. Changes affecting authentication, transport, update dispatch, persistence, release automation, package exports, or security policy may require additional review. Maintainers can require a regression test, API documentation, migration note, or threat-model explanation before merge.
|
|
34
|
-
|
|
35
|
-
## Releases
|
|
36
|
-
|
|
37
|
-
The protected GitHub Actions workflow is the canonical release path. It runs quality gates, computes a new immutable npm version, publishes the package, creates a version commit and tag, and creates a GitHub Release. Direct npm publication is restricted to authorized release maintainers. Release credentials must remain in GitHub or npm secret storage and must be rotated after exposure.
|
|
38
|
-
|
|
39
|
-
Because the source repository is private, the workflow uses npm publish without provenance. If the repository becomes public and provenance is enabled, the policy and workflow must be reviewed together before the next release.
|
|
40
|
-
|
|
41
|
-
## Repository protection
|
|
42
|
-
|
|
43
|
-
Required CI checks, branch protection, environment approvals, workflow permissions, package scope, and immutable release rules must not be weakened casually. Any change to these controls requires a pull request, a written rationale, and maintainer approval.
|
|
44
|
-
|
|
45
|
-
## Amendments
|
|
46
|
-
|
|
47
|
-
Governance changes are proposed through a pull request that explains the current problem, proposed rule, affected roles, and migration plan. The updated governance document becomes effective when the pull request merges.
|