@xbibzlibrary/telebibz 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/APPROVAL_FEATURE.md +73 -0
  2. package/CHANGELOG.md +21 -0
  3. package/LICENSE +21 -0
  4. package/README.md +171 -0
  5. package/SECURITY.md +15 -0
  6. package/bin/telebibz.mjs +3 -0
  7. package/dist/generated/api.d.ts +13 -0
  8. package/dist/generated/api.d.ts.map +1 -0
  9. package/dist/generated/api.js +192 -0
  10. package/dist/generated/api.js.map +1 -0
  11. package/dist/src/api/client.d.ts +32 -0
  12. package/dist/src/api/client.d.ts.map +1 -0
  13. package/dist/src/api/client.js +50 -0
  14. package/dist/src/api/client.js.map +1 -0
  15. package/dist/src/api/errors.d.ts +45 -0
  16. package/dist/src/api/errors.d.ts.map +1 -0
  17. package/dist/src/api/errors.js +65 -0
  18. package/dist/src/api/errors.js.map +1 -0
  19. package/dist/src/api/index.d.ts +5 -0
  20. package/dist/src/api/index.d.ts.map +1 -0
  21. package/dist/src/api/index.js +5 -0
  22. package/dist/src/api/index.js.map +1 -0
  23. package/dist/src/api/transport.d.ts +37 -0
  24. package/dist/src/api/transport.d.ts.map +1 -0
  25. package/dist/src/api/transport.js +134 -0
  26. package/dist/src/api/transport.js.map +1 -0
  27. package/dist/src/api/types.d.ts +441 -0
  28. package/dist/src/api/types.d.ts.map +1 -0
  29. package/dist/src/api/types.js +2 -0
  30. package/dist/src/api/types.js.map +1 -0
  31. package/dist/src/approval/approval.d.ts +63 -0
  32. package/dist/src/approval/approval.d.ts.map +1 -0
  33. package/dist/src/approval/approval.js +90 -0
  34. package/dist/src/approval/approval.js.map +1 -0
  35. package/dist/src/cache/cache.d.ts +33 -0
  36. package/dist/src/cache/cache.d.ts.map +1 -0
  37. package/dist/src/cache/cache.js +37 -0
  38. package/dist/src/cache/cache.js.map +1 -0
  39. package/dist/src/cli.d.ts +2 -0
  40. package/dist/src/cli.d.ts.map +1 -0
  41. package/dist/src/cli.js +55 -0
  42. package/dist/src/cli.js.map +1 -0
  43. package/dist/src/context/context.d.ts +46 -0
  44. package/dist/src/context/context.d.ts.map +1 -0
  45. package/dist/src/context/context.js +63 -0
  46. package/dist/src/context/context.js.map +1 -0
  47. package/dist/src/core/bot.d.ts +76 -0
  48. package/dist/src/core/bot.d.ts.map +1 -0
  49. package/dist/src/core/bot.js +160 -0
  50. package/dist/src/core/bot.js.map +1 -0
  51. package/dist/src/core/events.d.ts +71 -0
  52. package/dist/src/core/events.d.ts.map +1 -0
  53. package/dist/src/core/events.js +24 -0
  54. package/dist/src/core/events.js.map +1 -0
  55. package/dist/src/index.d.ts +18 -0
  56. package/dist/src/index.d.ts.map +1 -0
  57. package/dist/src/index.js +18 -0
  58. package/dist/src/index.js.map +1 -0
  59. package/dist/src/keyboard/index.d.ts +34 -0
  60. package/dist/src/keyboard/index.d.ts.map +1 -0
  61. package/dist/src/keyboard/index.js +43 -0
  62. package/dist/src/keyboard/index.js.map +1 -0
  63. package/dist/src/middleware/compose.d.ts +5 -0
  64. package/dist/src/middleware/compose.d.ts.map +1 -0
  65. package/dist/src/middleware/compose.js +17 -0
  66. package/dist/src/middleware/compose.js.map +1 -0
  67. package/dist/src/plugins/plugin.d.ts +36 -0
  68. package/dist/src/plugins/plugin.d.ts.map +1 -0
  69. package/dist/src/plugins/plugin.js +31 -0
  70. package/dist/src/plugins/plugin.js.map +1 -0
  71. package/dist/src/queue/queue.d.ts +52 -0
  72. package/dist/src/queue/queue.d.ts.map +1 -0
  73. package/dist/src/queue/queue.js +82 -0
  74. package/dist/src/queue/queue.js.map +1 -0
  75. package/dist/src/router/router.d.ts +35 -0
  76. package/dist/src/router/router.d.ts.map +1 -0
  77. package/dist/src/router/router.js +23 -0
  78. package/dist/src/router/router.js.map +1 -0
  79. package/dist/src/state/conversation.d.ts +40 -0
  80. package/dist/src/state/conversation.d.ts.map +1 -0
  81. package/dist/src/state/conversation.js +38 -0
  82. package/dist/src/state/conversation.js.map +1 -0
  83. package/dist/src/state/forms.d.ts +35 -0
  84. package/dist/src/state/forms.d.ts.map +1 -0
  85. package/dist/src/state/forms.js +41 -0
  86. package/dist/src/state/forms.js.map +1 -0
  87. package/dist/src/state/menu.d.ts +30 -0
  88. package/dist/src/state/menu.d.ts.map +1 -0
  89. package/dist/src/state/menu.js +28 -0
  90. package/dist/src/state/menu.js.map +1 -0
  91. package/dist/src/storage/storage.d.ts +33 -0
  92. package/dist/src/storage/storage.d.ts.map +1 -0
  93. package/dist/src/storage/storage.js +51 -0
  94. package/dist/src/storage/storage.js.map +1 -0
  95. package/dist/src/testing.d.ts +17 -0
  96. package/dist/src/testing.d.ts.map +1 -0
  97. package/dist/src/testing.js +12 -0
  98. package/dist/src/testing.js.map +1 -0
  99. package/dist/src/utils/text.d.ts +17 -0
  100. package/dist/src/utils/text.d.ts.map +1 -0
  101. package/dist/src/utils/text.js +0 -0
  102. package/dist/src/utils/text.js.map +1 -0
  103. package/dist/src/webhook/handler.d.ts +8 -0
  104. package/dist/src/webhook/handler.d.ts.map +1 -0
  105. package/dist/src/webhook/handler.js +27 -0
  106. package/dist/src/webhook/handler.js.map +1 -0
  107. package/dist-cjs/generated/api.js +194 -0
  108. package/dist-cjs/package.json +3 -0
  109. package/dist-cjs/src/api/client.js +53 -0
  110. package/dist-cjs/src/api/errors.js +74 -0
  111. package/dist-cjs/src/api/index.js +20 -0
  112. package/dist-cjs/src/api/transport.js +137 -0
  113. package/dist-cjs/src/api/types.js +2 -0
  114. package/dist-cjs/src/approval/approval.js +94 -0
  115. package/dist-cjs/src/cache/cache.js +41 -0
  116. package/dist-cjs/src/cli.js +57 -0
  117. package/dist-cjs/src/context/context.js +66 -0
  118. package/dist-cjs/src/core/bot.js +163 -0
  119. package/dist-cjs/src/core/events.js +27 -0
  120. package/dist-cjs/src/index.js +33 -0
  121. package/dist-cjs/src/keyboard/index.js +49 -0
  122. package/dist-cjs/src/middleware/compose.js +20 -0
  123. package/dist-cjs/src/plugins/plugin.js +35 -0
  124. package/dist-cjs/src/queue/queue.js +86 -0
  125. package/dist-cjs/src/router/router.js +26 -0
  126. package/dist-cjs/src/state/conversation.js +43 -0
  127. package/dist-cjs/src/state/forms.js +44 -0
  128. package/dist-cjs/src/state/menu.js +33 -0
  129. package/dist-cjs/src/storage/storage.js +54 -0
  130. package/dist-cjs/src/testing.js +18 -0
  131. package/dist-cjs/src/utils/text.js +0 -0
  132. package/dist-cjs/src/webhook/handler.js +29 -0
  133. package/docs/README.md +21 -0
  134. package/package.json +78 -0
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EventBus = void 0;
4
+ class EventBus {
5
+ listeners = new Map();
6
+ on(event, listener) {
7
+ const bucket = this.listeners.get(event) ?? new Set();
8
+ bucket.add(listener);
9
+ this.listeners.set(event, bucket);
10
+ return () => this.off(event, listener);
11
+ }
12
+ once(event, listener) {
13
+ const dispose = this.on(event, async (payload) => { dispose(); await listener(payload); });
14
+ return dispose;
15
+ }
16
+ off(event, listener) {
17
+ this.listeners.get(event)?.delete(listener);
18
+ }
19
+ async emit(event, payload) {
20
+ const listeners = [...(this.listeners.get(event) ?? [])];
21
+ for (const listener of listeners)
22
+ await listener(payload);
23
+ }
24
+ removeAllListeners() { this.listeners.clear(); }
25
+ listenerCount(event) { return this.listeners.get(event)?.size ?? 0; }
26
+ }
27
+ exports.EventBus = EventBus;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./core/bot.js"), exports);
18
+ __exportStar(require("./core/events.js"), exports);
19
+ __exportStar(require("./api/index.js"), exports);
20
+ __exportStar(require("./context/context.js"), exports);
21
+ __exportStar(require("./middleware/compose.js"), exports);
22
+ __exportStar(require("./router/router.js"), exports);
23
+ __exportStar(require("./keyboard/index.js"), exports);
24
+ __exportStar(require("./storage/storage.js"), exports);
25
+ __exportStar(require("./cache/cache.js"), exports);
26
+ __exportStar(require("./queue/queue.js"), exports);
27
+ __exportStar(require("./plugins/plugin.js"), exports);
28
+ __exportStar(require("./webhook/handler.js"), exports);
29
+ __exportStar(require("./utils/text.js"), exports);
30
+ __exportStar(require("./state/conversation.js"), exports);
31
+ __exportStar(require("./state/forms.js"), exports);
32
+ __exportStar(require("./state/menu.js"), exports);
33
+ __exportStar(require("./approval/approval.js"), exports);
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ReplyKeyboard = exports.InlineKeyboard = void 0;
4
+ exports.removeKeyboard = removeKeyboard;
5
+ exports.forceReply = forceReply;
6
+ class InlineKeyboard {
7
+ rows = [];
8
+ static from(rows) { const keyboard = new InlineKeyboard(); keyboard.rows.push(...rows.map((row) => [...row])); return keyboard; }
9
+ text(text, callbackData) { return this.button({ text, callback_data: callbackData }); }
10
+ url(text, url) { return this.button({ text, url }); }
11
+ webApp(text, url) { return this.button({ text, web_app: { url } }); }
12
+ pay(text = "Pay") { return this.button({ text, pay: true }); }
13
+ copy(text, copiedText) { return this.button({ text, copy_text: { text: copiedText } }); }
14
+ button(button) { this.ensureRow().push(validateInlineButton(button)); return this; }
15
+ row(...buttons) { this.rows.push(buttons.map(validateInlineButton)); return this; }
16
+ conditional(condition, factory) { return condition ? factory(this) : this; }
17
+ grid(buttons, columns) { if (!Number.isInteger(columns) || columns < 1)
18
+ throw new RangeError("columns must be a positive integer"); for (let index = 0; index < buttons.length; index += columns)
19
+ this.rows.push(buttons.slice(index, index + columns).map(validateInlineButton)); return this; }
20
+ build() { return { inline_keyboard: this.rows.map((row) => [...row]) }; }
21
+ asReplyMarkup() { return this.build(); }
22
+ ensureRow() { const row = this.rows.at(-1); if (row)
23
+ return row; const next = []; this.rows.push(next); return next; }
24
+ }
25
+ exports.InlineKeyboard = InlineKeyboard;
26
+ class ReplyKeyboard {
27
+ rows = [];
28
+ text(text) { return this.button({ text }); }
29
+ contact(text) { return this.button({ text, request_contact: true }); }
30
+ location(text) { return this.button({ text, request_location: true }); }
31
+ poll(text, type) { return this.button({ text, request_poll: type ? { type } : {} }); }
32
+ webApp(text, url) { return this.button({ text, web_app: { url } }); }
33
+ button(button) { this.ensureRow().push(button); return this; }
34
+ row(...buttons) { this.rows.push([...buttons]); return this; }
35
+ grid(buttons, columns) { if (!Number.isInteger(columns) || columns < 1)
36
+ throw new RangeError("columns must be a positive integer"); for (let index = 0; index < buttons.length; index += columns)
37
+ this.rows.push(buttons.slice(index, index + columns)); return this; }
38
+ build(options = {}) { return { keyboard: this.rows.map((row) => [...row]), ...options }; }
39
+ asReplyMarkup() { return this.build(); }
40
+ ensureRow() { const row = this.rows.at(-1); if (row)
41
+ return row; const next = []; this.rows.push(next); return next; }
42
+ }
43
+ exports.ReplyKeyboard = ReplyKeyboard;
44
+ function removeKeyboard(selective = false) { return { remove_keyboard: true, ...(selective ? { selective: true } : {}) }; }
45
+ function forceReply(placeholder, selective = false) { return { force_reply: true, ...(placeholder ? { input_field_placeholder: placeholder } : {}), ...(selective ? { selective: true } : {}) }; }
46
+ function validateInlineButton(button) { if (!button.text)
47
+ throw new Error("Inline keyboard button text is required."); const actions = [button.url, button.callback_data, button.web_app, button.login_url, button.switch_inline_query, button.switch_inline_query_current_chat, button.callback_game, button.pay, button.copy_text].filter((value) => value !== undefined); if (actions.length !== 1)
48
+ throw new Error(`Inline button ${button.text} must contain exactly one action.`); if (button.callback_data && new TextEncoder().encode(button.callback_data).length > 64)
49
+ throw new RangeError("callback_data must be at most 64 bytes."); return { ...button }; }
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.compose = compose;
4
+ exports.middleware = middleware;
5
+ function compose(middleware) {
6
+ return async function run(ctx) {
7
+ let index = -1;
8
+ const dispatch = async (position) => {
9
+ if (position <= index)
10
+ throw new Error("next() called multiple times");
11
+ index = position;
12
+ const handler = middleware[position];
13
+ if (!handler)
14
+ return;
15
+ await handler(ctx, () => dispatch(position + 1));
16
+ };
17
+ await dispatch(0);
18
+ };
19
+ }
20
+ function middleware(handler) { return handler; }
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PluginManager = exports.ServiceContainer = void 0;
4
+ class ServiceContainer {
5
+ values = new Map();
6
+ register(name, value) { this.values.set(name, value); return this; }
7
+ get(name) { if (!this.values.has(name))
8
+ throw new Error(`Service not registered: ${String(name)}`); return this.values.get(name); }
9
+ has(name) { return this.values.has(name); }
10
+ delete(name) { return this.values.delete(name); }
11
+ }
12
+ exports.ServiceContainer = ServiceContainer;
13
+ class PluginManager {
14
+ plugins = [];
15
+ api;
16
+ constructor(bot) {
17
+ this.api = { bot, services: new ServiceContainer(), registerMiddleware: () => undefined, registerRoute: () => undefined };
18
+ }
19
+ use(plugin) { if (this.plugins.some((existing) => existing.name === plugin.name))
20
+ throw new Error(`Plugin already registered: ${plugin.name}`); this.plugins.push(plugin); return this; }
21
+ async setup() { for (const plugin of this.plugins) {
22
+ await plugin.install?.(this.api);
23
+ await plugin.setup?.(this.api);
24
+ } }
25
+ async start() { for (const plugin of this.plugins)
26
+ await plugin.onStart?.(this.api); }
27
+ async update(context) { for (const plugin of this.plugins)
28
+ await plugin.onUpdate?.(context); }
29
+ async stop() { for (const plugin of this.plugins)
30
+ await plugin.onStop?.(this.api); }
31
+ async dispose() { for (const plugin of [...this.plugins].reverse())
32
+ await plugin.dispose?.(this.api); }
33
+ list() { return this.plugins; }
34
+ }
35
+ exports.PluginManager = PluginManager;
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Scheduler = exports.TaskQueue = void 0;
4
+ class TaskQueue {
5
+ worker;
6
+ options;
7
+ pending = [];
8
+ jobs = new Map();
9
+ controllers = new Map();
10
+ active = 0;
11
+ sequence = 0;
12
+ draining = false;
13
+ constructor(worker, options = {}) {
14
+ this.worker = worker;
15
+ this.options = options;
16
+ }
17
+ add(data, options = {}) { const id = options.id ?? `job_${Date.now()}_${this.sequence++}`; if (this.jobs.has(id))
18
+ throw new Error(`Job already exists: ${id}`); const job = { id, data, attempts: 0, priority: options.priority ?? 0, runAt: Date.now() + (options.delayMs ?? 0), status: "queued" }; this.jobs.set(id, job); this.pending.push(job); this.pending.sort((a, b) => b.priority - a.priority || a.runAt - b.runAt); void this.drain(); return { ...job }; }
19
+ get(id) { const job = this.jobs.get(id); return job ? { ...job } : undefined; }
20
+ cancel(id) { const job = this.jobs.get(id); if (!job || ["completed", "failed", "cancelled"].includes(job.status))
21
+ return false; job.status = "cancelled"; this.controllers.get(id)?.abort(); return true; }
22
+ async onIdle() { while (this.pending.length || this.active)
23
+ await new Promise((resolve) => setTimeout(resolve, 10)); }
24
+ async close() { this.draining = false; for (const id of this.controllers.keys())
25
+ this.cancel(id); }
26
+ async drain() { if (this.draining)
27
+ return; this.draining = true; try {
28
+ while (this.pending.length && this.active < (this.options.concurrency ?? 1)) {
29
+ const index = this.pending.findIndex((job) => job.runAt <= Date.now() && job.status === "queued");
30
+ if (index < 0) {
31
+ const wait = Math.max(1, Math.min(...this.pending.map((job) => job.runAt - Date.now())));
32
+ await new Promise((resolve) => setTimeout(resolve, wait));
33
+ continue;
34
+ }
35
+ const [job] = this.pending.splice(index, 1);
36
+ if (!job)
37
+ continue;
38
+ this.active += 1;
39
+ void this.run(job).finally(() => { this.active -= 1; void this.drain(); });
40
+ }
41
+ }
42
+ finally {
43
+ this.draining = false;
44
+ } }
45
+ async run(job) { const maxAttempts = this.options.retries ?? 0; const controller = new AbortController(); this.controllers.set(job.id, controller); try {
46
+ while (job.attempts <= maxAttempts && job.status !== "cancelled") {
47
+ job.status = "running";
48
+ job.attempts += 1;
49
+ try {
50
+ await this.worker(job, controller.signal);
51
+ job.status = "completed";
52
+ return;
53
+ }
54
+ catch (error) {
55
+ job.error = error;
56
+ if (job.attempts > maxAttempts) {
57
+ job.status = "failed";
58
+ return;
59
+ }
60
+ const delay = Math.min(this.options.maxBackoffMs ?? 30_000, (this.options.backoffMs ?? 250) * 2 ** (job.attempts - 1));
61
+ await new Promise((resolve) => setTimeout(resolve, delay));
62
+ }
63
+ }
64
+ }
65
+ finally {
66
+ this.controllers.delete(job.id);
67
+ } }
68
+ }
69
+ exports.TaskQueue = TaskQueue;
70
+ class Scheduler {
71
+ timers = new Map();
72
+ every(id, intervalMs, task) { if (intervalMs <= 0)
73
+ throw new RangeError("intervalMs must be positive"); this.cancel(id); const timer = setInterval(() => { void task(); }, intervalMs); this.timers.set(id, timer); return { id, cancel: () => this.cancel(id) }; }
74
+ after(id, delayMs, task) { this.cancel(id); const timer = setTimeout(() => { this.timers.delete(id); void task(); }, delayMs); this.timers.set(id, timer); return { id, cancel: () => this.cancel(id) }; }
75
+ cron(id, expression, task) { const interval = parseSimpleCronInterval(expression); return this.every(id, interval, task); }
76
+ cancel(id) { const timer = this.timers.get(id); if (!timer)
77
+ return false; clearTimeout(timer); clearInterval(timer); this.timers.delete(id); return true; }
78
+ clear() { for (const id of [...this.timers.keys()])
79
+ this.cancel(id); }
80
+ }
81
+ exports.Scheduler = Scheduler;
82
+ function parseSimpleCronInterval(expression) { const minute = expression.trim().split(/\s+/)[0]; if (minute?.startsWith("*/")) {
83
+ const value = Number(minute.slice(2));
84
+ if (Number.isInteger(value) && value > 0)
85
+ return value * 60_000;
86
+ } throw new Error("Only interval cron expressions such as */5 are supported by the built-in scheduler; use an adapter for full cron syntax."); }
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Router = void 0;
4
+ const compose_js_1 = require("../middleware/compose.js");
5
+ class Router {
6
+ routes = [];
7
+ sequence = 0;
8
+ use(...middleware) { this.routes.push({ priority: -1_000_000 + this.sequence++, matcher: () => true, middleware }); return this; }
9
+ route(matcher, ...middleware) { this.routes.push({ priority: this.sequence++, matcher, middleware }); return this; }
10
+ command(name, ...middleware) { return this.route(async (ctx) => { const text = ctx.message?.text; if (!text?.startsWith("/"))
11
+ return false; const command = text.slice(1).split(/[\s@]/, 1)[0] ?? ""; return typeof name === "string" ? command === name.replace(/^\//, "") : name.test(command); }, ...middleware); }
12
+ text(value, ...middleware) { return this.route((ctx) => ctx.message?.text === value, ...middleware); }
13
+ regex(expression, ...middleware) { return this.route((ctx) => expression.test(ctx.message?.text ?? ""), ...middleware); }
14
+ callback(pattern, ...middleware) { return this.route((ctx) => { const data = ctx.callbackQuery?.data ?? ""; return typeof pattern === "string" ? pattern.endsWith("*") ? data.startsWith(pattern.slice(0, -1)) : data === pattern : pattern.test(data); }, ...middleware); }
15
+ chat(chatId, ...middleware) { return this.route((ctx) => { const id = ctx.message?.chat?.id; return id === chatId || String(id) === String(chatId); }, ...middleware); }
16
+ predicate(matcher, ...middleware) { return this.route(matcher, ...middleware); }
17
+ nest(child) { return this.route(() => true, async (ctx, next) => { await child.handle(ctx, next); }); }
18
+ async handle(ctx, terminal) { const ordered = [...this.routes].sort((left, right) => left.priority - right.priority); let matched = false; for (const route of ordered) {
19
+ if (!(await route.matcher(ctx)))
20
+ continue;
21
+ matched = true;
22
+ await (0, compose_js_1.compose)(route.middleware)(ctx);
23
+ } if (!matched)
24
+ await terminal?.(); }
25
+ }
26
+ exports.Router = Router;
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Wizard = exports.ConversationManager = exports.ConversationFlow = void 0;
4
+ class ConversationFlow {
5
+ ctx;
6
+ state;
7
+ constructor(ctx, state) {
8
+ this.ctx = ctx;
9
+ this.state = state;
10
+ }
11
+ get values() { return this.state.values; }
12
+ set(key, value) { this.state.values[key] = value; this.state.updatedAt = Date.now(); return this; }
13
+ get(key) { return this.state.values[key]; }
14
+ next() { this.state.step += 1; this.state.updatedAt = Date.now(); return this; }
15
+ previous() { this.state.step = Math.max(0, this.state.step - 1); this.state.updatedAt = Date.now(); return this; }
16
+ complete() { this.state.status = "completed"; this.state.updatedAt = Date.now(); }
17
+ cancel() { this.state.status = "cancelled"; this.state.updatedAt = Date.now(); }
18
+ }
19
+ exports.ConversationFlow = ConversationFlow;
20
+ class ConversationManager {
21
+ active = new Map();
22
+ start(key, name, values = {}) { const state = { name, step: 0, values, status: "active", updatedAt: Date.now() }; this.active.set(key, state); return state; }
23
+ get(key) { return this.active.get(key); }
24
+ cancel(key) { const state = this.active.get(key); if (!state)
25
+ return false; state.status = "cancelled"; state.updatedAt = Date.now(); return true; }
26
+ clearExpired(maxAgeMs) { const threshold = Date.now() - maxAgeMs; let removed = 0; for (const [key, state] of this.active)
27
+ if (state.updatedAt < threshold) {
28
+ this.active.delete(key);
29
+ removed += 1;
30
+ } return removed; }
31
+ async run(ctx, key, name, steps) { const state = this.active.get(key) ?? this.start(key, name); const flow = new ConversationFlow(ctx, state); const step = steps[state.step]; if (!step) {
32
+ state.status = "completed";
33
+ return state;
34
+ } await step(flow); return state; }
35
+ }
36
+ exports.ConversationManager = ConversationManager;
37
+ class Wizard {
38
+ stepsList = [];
39
+ step(step) { this.stepsList.push(step); return this; }
40
+ async run(ctx, key, manager = new ConversationManager()) { return manager.run(ctx, key, "wizard", this.stepsList.map((step) => step.run)); }
41
+ get steps() { return this.stepsList; }
42
+ }
43
+ exports.Wizard = Wizard;
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validators = exports.Form = void 0;
4
+ class Form {
5
+ fields = new Map();
6
+ values = {};
7
+ field(definition) { this.fields.set(definition.name, definition); return this; }
8
+ async parse(input) { const issues = []; for (const [name, field] of this.fields) {
9
+ const raw = input[name];
10
+ if ((raw === undefined || raw === null || raw === "") && field.required) {
11
+ issues.push({ path: name, message: "Field is required", code: "required" });
12
+ continue;
13
+ }
14
+ if (raw === undefined || raw === null || raw === "")
15
+ continue;
16
+ try {
17
+ let value = field.parse(raw);
18
+ if (field.transform)
19
+ value = await field.transform(value);
20
+ const error = await field.validate?.(value);
21
+ if (error)
22
+ issues.push({ path: name, message: error, code: "invalid" });
23
+ else
24
+ this.values[name] = value;
25
+ }
26
+ catch (error) {
27
+ issues.push({ path: name, message: error instanceof Error ? error.message : "Invalid value", code: "parse" });
28
+ }
29
+ } return issues.length ? { success: false, issues } : { success: true, data: this.values }; }
30
+ reset() { for (const key of Object.keys(this.values))
31
+ delete this.values[key]; }
32
+ }
33
+ exports.Form = Form;
34
+ exports.validators = { string: (value) => { if (typeof value !== "string")
35
+ throw new TypeError("Expected string"); return value; }, number: (value) => { const number = typeof value === "number" ? value : Number(value); if (!Number.isFinite(number))
36
+ throw new TypeError("Expected number"); return number; }, integer: (value) => { const number = exports.validators.number(value); if (!Number.isInteger(number))
37
+ throw new TypeError("Expected integer"); return number; }, email: (value) => { const email = exports.validators.string(value); if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email))
38
+ throw new TypeError("Expected email"); return email; }, url: (value) => { const url = exports.validators.string(value); try {
39
+ new URL(url);
40
+ return url;
41
+ }
42
+ catch {
43
+ throw new TypeError("Expected URL");
44
+ } } };
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Menu = void 0;
4
+ exports.paginate = paginate;
5
+ exports.paginationButtons = paginationButtons;
6
+ const index_js_1 = require("../keyboard/index.js");
7
+ function paginate(items, page, pageSize) { if (!Number.isInteger(page) || page < 0)
8
+ throw new RangeError("page must be a non-negative integer"); if (!Number.isInteger(pageSize) || pageSize < 1)
9
+ throw new RangeError("pageSize must be a positive integer"); const pageCount = Math.max(1, Math.ceil(items.length / pageSize)); const safePage = Math.min(page, pageCount - 1); const start = safePage * pageSize; return { items: items.slice(start, start + pageSize), page: safePage, pageCount, hasPrevious: safePage > 0, hasNext: safePage < pageCount - 1 }; }
10
+ function paginationButtons(page, prefix) { const buttons = []; if (page.hasPrevious)
11
+ buttons.push({ text: "Previous", callback_data: `${prefix}:page:${page.page - 1}` }); buttons.push({ text: `${page.page + 1}/${page.pageCount}`, callback_data: `${prefix}:noop` }); if (page.hasNext)
12
+ buttons.push({ text: "Next", callback_data: `${prefix}:page:${page.page + 1}` }); return buttons; }
13
+ class Menu {
14
+ id;
15
+ itemsList = [];
16
+ parents = [];
17
+ constructor(id) {
18
+ this.id = id;
19
+ }
20
+ item(item) { this.itemsList.push(item); return this; }
21
+ breadcrumb(label) { this.parents.push(label); return this; }
22
+ async build() { const keyboard = new index_js_1.InlineKeyboard(); for (const item of this.itemsList) {
23
+ const visible = typeof item.visible === "function" ? await item.visible() : item.visible ?? true;
24
+ if (!visible)
25
+ continue;
26
+ if (item.url)
27
+ keyboard.url(item.label, item.url);
28
+ else
29
+ keyboard.text(item.label, item.callbackData ?? `${this.id}:${item.id}`);
30
+ } return keyboard; }
31
+ get breadcrumbs() { return this.parents; }
32
+ }
33
+ exports.Menu = Menu;
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MemoryStorage = void 0;
4
+ class MemoryStorage {
5
+ valuesMap = new Map();
6
+ locks = new Map();
7
+ async get(key) {
8
+ const entry = this.valuesMap.get(key);
9
+ if (!entry)
10
+ return undefined;
11
+ if (entry.expiresAt !== undefined && entry.expiresAt <= Date.now()) {
12
+ this.valuesMap.delete(key);
13
+ return undefined;
14
+ }
15
+ return entry.value;
16
+ }
17
+ async set(key, value, options = {}) {
18
+ const entry = { value };
19
+ if (options.ttlMs !== undefined)
20
+ entry.expiresAt = Date.now() + options.ttlMs;
21
+ this.valuesMap.set(key, entry);
22
+ }
23
+ async delete(key) { return this.valuesMap.delete(key); }
24
+ async has(key) { return (await this.get(key)) !== undefined; }
25
+ async clear() { this.valuesMap.clear(); }
26
+ async *keys() { for (const key of this.valuesMap.keys())
27
+ if (await this.has(key))
28
+ yield key; }
29
+ async *values() { for await (const [, value] of this.entries())
30
+ yield value; }
31
+ async *entries() { for (const key of this.valuesMap.keys()) {
32
+ const value = await this.get(key);
33
+ if (value !== undefined)
34
+ yield [key, value];
35
+ } }
36
+ async update(key, updater, options = {}) {
37
+ const previous = this.locks.get(key) ?? Promise.resolve();
38
+ let release;
39
+ const currentLock = new Promise((resolve) => { release = resolve; });
40
+ this.locks.set(key, currentLock);
41
+ await previous;
42
+ try {
43
+ const value = await updater(await this.get(key));
44
+ await this.set(key, value, options);
45
+ return value;
46
+ }
47
+ finally {
48
+ release();
49
+ if (this.locks.get(key) === currentLock)
50
+ this.locks.delete(key);
51
+ }
52
+ }
53
+ }
54
+ exports.MemoryStorage = MemoryStorage;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MockTransport = void 0;
4
+ exports.createMockUpdate = createMockUpdate;
5
+ exports.createTestBot = createTestBot;
6
+ exports.createMockContext = createMockContext;
7
+ const bot_js_1 = require("./core/bot.js");
8
+ const context_js_1 = require("./context/context.js");
9
+ class MockTransport {
10
+ calls = [];
11
+ responses = new Map();
12
+ respond(method, response) { this.responses.set(method, response); return this; }
13
+ async request(request) { this.calls.push(request); const configured = this.responses.get(request.method); const data = typeof configured === "function" ? configured(request.payload) : configured ?? { ok: true, result: true }; return { status: data.ok ? 200 : (data.error_code ?? 500), headers: new Headers(), data: data }; }
14
+ }
15
+ exports.MockTransport = MockTransport;
16
+ function createMockUpdate(overrides = {}) { return { update_id: 1, message: { message_id: 1, date: Date.now(), chat: { id: 1, type: "private" }, from: { id: 2, is_bot: false, first_name: "Test" }, text: "/start" }, ...overrides }; }
17
+ function createTestBot() { const transport = new MockTransport(); transport.respond("getMe", { ok: true, result: { id: 99, is_bot: true, first_name: "TestBot", username: "test_bot" } }); return { bot: new bot_js_1.Bot({ token: "123456:TEST_TOKEN", transport }), transport }; }
18
+ function createMockContext(bot, update = createMockUpdate()) { return new context_js_1.Context({ update, api: bot.api, session: {}, services: {} }); }
Binary file
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createWebhookHandler = createWebhookHandler;
4
+ function createWebhookHandler(bot, options = {}) {
5
+ const maxBodyBytes = options.maxBodyBytes ?? 1_048_576;
6
+ return async (request) => {
7
+ if (request.method !== "POST")
8
+ return new Response("Method Not Allowed", { status: 405, headers: { allow: "POST" } });
9
+ if (options.secretToken && request.headers.get("x-telegram-bot-api-secret-token") !== options.secretToken)
10
+ return new Response("Unauthorized", { status: 401 });
11
+ const contentLength = Number(request.headers.get("content-length") ?? 0);
12
+ if (contentLength > maxBodyBytes)
13
+ return new Response("Payload Too Large", { status: 413 });
14
+ try {
15
+ const raw = await request.arrayBuffer();
16
+ if (raw.byteLength > maxBodyBytes)
17
+ return new Response("Payload Too Large", { status: 413 });
18
+ const update = JSON.parse(new TextDecoder().decode(raw));
19
+ if (!Number.isInteger(update.update_id))
20
+ return new Response("Bad Request", { status: 400 });
21
+ await bot.handleUpdate(update);
22
+ return new Response("OK", { status: 200 });
23
+ }
24
+ catch (error) {
25
+ await options.onError?.(error);
26
+ return new Response("Internal Server Error", { status: 500 });
27
+ }
28
+ };
29
+ }
package/docs/README.md ADDED
@@ -0,0 +1,21 @@
1
+ # Dokumentasi telebibz
2
+
3
+ Dokumentasi ini disusun menurut lifecycle aplikasi: mulai dari bot paling sederhana, API client dan transport, update routing, middleware dan context, state/session, interaction UI, background work, deployment, lalu testing dan migration.
4
+
5
+ | Area | Status |
6
+ |---|---|
7
+ | Getting started | Tersedia di `README.md` |
8
+ | Bot lifecycle, polling, webhook | Core implementation dan tests tersedia |
9
+ | API client dan generated method list | Tersedia; object type generation masih partial |
10
+ | Router, middleware, context | Tersedia dan diuji |
11
+ | Keyboard, callback, menus, pagination | Keyboard/callback core tersedia; menu/pagination lanjutan partial |
12
+ | Sessions, conversations, scenes, wizard, forms | Memory session dan primitives forms/conversation tersedia; integration lanjutan partial |
13
+ | Storage, cache, queue, scheduler | Memory primitives tersedia; distributed adapters belum tersedia |
14
+ | Plugins, services, observability | Lifecycle/plugin/service hooks tersedia |
15
+ | Mini Apps, payments, business features | Raw API access tersedia; high-level subsystem belum lengkap |
16
+ | Testing dan security | Unit, integration, type-level, gated E2E, CI, security policy tersedia |
17
+ | Deployment dan migration | Roadmap dan package boundaries perlu diperluas sebelum stable release |
18
+
19
+ ## Prinsip dokumentasi
20
+
21
+ Dokumentasi tidak akan menyebut fitur `production-ready` jika implementasi, test, atau adapter-nya belum benar-benar tersedia. Batas native Telegram dan kemampuan Mini App/Web App harus dijelaskan terpisah agar API tidak menjanjikan kemampuan yang tidak dimiliki Telegram.
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "@xbibzlibrary/telebibz",
3
+ "version": "0.1.0",
4
+ "description": "Production-grade, strongly typed Telegram Bot API SDK and framework for Node.js and TypeScript.",
5
+ "type": "module",
6
+ "private": false,
7
+ "publishConfig": {
8
+ "access": "public",
9
+ "provenance": true
10
+ },
11
+ "license": "MIT",
12
+ "engines": {
13
+ "node": ">=20"
14
+ },
15
+ "scripts": {
16
+ "generate": "node scripts/generate-api.mjs",
17
+ "update:telegram": "node scripts/fetch-telegram-api.mjs && npm run generate",
18
+ "build": "tsc -p tsconfig.json && tsc -p tsconfig.cjs.json && node scripts/create-cjs-package.mjs",
19
+ "typecheck": "tsc -p tsconfig.json --noEmit",
20
+ "lint": "tsc -p tsconfig.json --noEmit && eslint scripts/*.mjs bin/*.mjs --no-error-on-unmatched-pattern",
21
+ "test": "vitest run",
22
+ "test:watch": "vitest",
23
+ "test:integration": "vitest run tests/integration/**/*.test.ts",
24
+ "test:e2e": "vitest run tests/e2e",
25
+ "test:types": "tsc -p tests/tsconfig.json --noEmit",
26
+ "benchmark": "tsx tests/benchmark.ts",
27
+ "package:check": "npm run build && npm pack --dry-run",
28
+ "security": "npm audit --audit-level=high --package-lock-only",
29
+ "release:check": "node scripts/release-check.mjs",
30
+ "doctor": "tsx src/cli.ts doctor",
31
+ "dev": "tsx examples/minimal.ts",
32
+ "prepack": "npm run build",
33
+ "prepublishOnly": "npm run typecheck && npm run test:types && npm run lint && npm test && npm run build && npm run security && npm run release:check"
34
+ },
35
+ "bin": {
36
+ "telebibz": "./bin/telebibz.mjs"
37
+ },
38
+ "exports": {
39
+ ".": {
40
+ "types": "./dist/src/index.d.ts",
41
+ "import": "./dist/src/index.js",
42
+ "require": "./dist-cjs/src/index.js"
43
+ },
44
+ "./api": {
45
+ "types": "./dist/src/api/index.d.ts",
46
+ "import": "./dist/src/api/index.js",
47
+ "require": "./dist-cjs/src/api/index.js"
48
+ },
49
+ "./keyboard": {
50
+ "types": "./dist/src/keyboard/index.d.ts",
51
+ "import": "./dist/src/keyboard/index.js",
52
+ "require": "./dist-cjs/src/keyboard/index.js"
53
+ },
54
+ "./testing": {
55
+ "types": "./dist/src/testing.d.ts",
56
+ "import": "./dist/src/testing.js",
57
+ "require": "./dist-cjs/src/testing.js"
58
+ }
59
+ },
60
+ "files": [
61
+ "dist",
62
+ "dist-cjs",
63
+ "bin",
64
+ "README.md",
65
+ "LICENSE",
66
+ "CHANGELOG.md",
67
+ "SECURITY.md",
68
+ "APPROVAL_FEATURE.md",
69
+ "docs"
70
+ ],
71
+ "devDependencies": {
72
+ "@types/node": "^22.10.0",
73
+ "eslint": "^9.17.0",
74
+ "tsx": "^4.19.2",
75
+ "typescript": "^5.7.2",
76
+ "vitest": "^4.1.11"
77
+ }
78
+ }