ultra-telegram-framework 1.0.3

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 (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +174 -0
  3. package/dist/adapters/gas.d.ts +10 -0
  4. package/dist/adapters/gas.js +90 -0
  5. package/dist/adapters/node.d.ts +52 -0
  6. package/dist/adapters/node.js +150 -0
  7. package/dist/adapters/web.d.ts +4 -0
  8. package/dist/adapters/web.js +90 -0
  9. package/dist/core/base-api.d.ts +40 -0
  10. package/dist/core/base-api.js +26 -0
  11. package/dist/core/bot.d.ts +1828 -0
  12. package/dist/core/bot.js +2753 -0
  13. package/dist/core/composer.d.ts +87 -0
  14. package/dist/core/composer.js +164 -0
  15. package/dist/core/context/base-context.d.ts +24 -0
  16. package/dist/core/context/base-context.js +56 -0
  17. package/dist/core/context/reply-context.d.ts +234 -0
  18. package/dist/core/context/reply-context.js +528 -0
  19. package/dist/core/context.d.ts +8 -0
  20. package/dist/core/context.js +34 -0
  21. package/dist/core/keyboard.d.ts +76 -0
  22. package/dist/core/keyboard.js +182 -0
  23. package/dist/core/menu.d.ts +58 -0
  24. package/dist/core/menu.js +87 -0
  25. package/dist/index.d.ts +18 -0
  26. package/dist/index.js +24 -0
  27. package/dist/scenes/scene-manager.d.ts +39 -0
  28. package/dist/scenes/scene-manager.js +65 -0
  29. package/dist/scenes/stage.d.ts +8 -0
  30. package/dist/scenes/stage.js +34 -0
  31. package/dist/scenes/wizard.d.ts +18 -0
  32. package/dist/scenes/wizard.js +32 -0
  33. package/dist/session/gas-cache-storage.d.ts +34 -0
  34. package/dist/session/gas-cache-storage.js +49 -0
  35. package/dist/session/gas-hybrid-storage.d.ts +27 -0
  36. package/dist/session/gas-hybrid-storage.js +49 -0
  37. package/dist/session/gas-storage.d.ts +24 -0
  38. package/dist/session/gas-storage.js +47 -0
  39. package/dist/session/index.d.ts +28 -0
  40. package/dist/session/index.js +43 -0
  41. package/dist/session/memory-storage.d.ts +28 -0
  42. package/dist/session/memory-storage.js +35 -0
  43. package/dist/session/storage.d.ts +18 -0
  44. package/dist/session/storage.js +2 -0
  45. package/dist/types/telegram.d.ts +7560 -0
  46. package/dist/types/telegram.js +4 -0
  47. package/package.json +42 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Volodymyr Dzola
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,174 @@
1
+ # 🚀 Ultra Telegram Framework (UTF)
2
+
3
+ [![Bot API 10.0](https://img.shields.io/badge/Bot%20API-10.0-blue.svg?logo=telegram)](https://core.telegram.org/bots/api)
4
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.9-blue.svg?logo=typescript)](https://www.typescriptlang.org/)
5
+ [![Google Apps Script](https://img.shields.io/badge/Google%20Apps%20Script-powered-orange.svg?logo=google-apps-script)](https://developers.google.com/apps-script/)
6
+ [![Documentation](https://img.shields.io/badge/docs-TypeDoc-blue.svg?logo=readthedocs)](https://volodymyrdzola.github.io/ultra-tg-framework/)
7
+ [![MIT License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
8
+
9
+ **Ultra Telegram Framework (UTF)** is a modern, lightweight, and strongly typed Telegram Bot API framework built for **TypeScript**.
10
+
11
+ Designed with flexibility in mind, UTF features a unique **adapter-based architecture** that allows your bot to run seamlessly across various environments — from classic Node.js servers to Serverless Edge functions and Google Apps Script — without rewriting your core logic.
12
+
13
+ ---
14
+
15
+ ## ✨ Why Choose UTF?
16
+
17
+ ### 🌍 Write Once, Run Anywhere
18
+ UTF provides dedicated API clients optimized for specific runtimes:
19
+ - **`NodeApiClient`** — Node.js 18+ with native `fetch` and Webhook Reply optimizations.
20
+ - **`WebApiClient`** — Universal client for Edge runtimes (Cloudflare Workers, Vercel Edge, Deno, Bun).
21
+ - **`GasApiClient`** — Exclusive adapter for **Google Apps Script** using `UrlFetchApp`.
22
+
23
+ ### 🔘 Declarative Inline Menus
24
+ Build interactive, multi-page menus with the page-based `InlineMenu` system. Define pages with `.page()` and button actions with `.action()`, with automatic state management and navigation.
25
+
26
+ ### 🤖 AI-Ready & Streaming Support
27
+ The `ctx.replyWithDraft()` method lets you stream "thinking" indicators for LLM responses with built-in rate-limit protection and auto-debouncing.
28
+
29
+ ### 🛡️ 100% Type-Safe
30
+ Exhaustive TypeScript typings for **all** Telegram Bot API 10.0 objects, methods, and parameters. Rich IDE autocompletion and compile-time error checking.
31
+
32
+ ### 🎭 Built-in Scenes & Sessions
33
+ Manage multi-step flows with `WizardScene`, `Stage`, and `SceneManager`. Multiple session storage adapters included: `MemoryStorage`, `PropertiesStorage`, `CacheStorage`, and the exclusive `GasHybridStorage`.
34
+
35
+ ### 🔋 Batteries Included
36
+ No external plugins needed. Scenes, sessions, inline menus, keyboards, and error handling are all part of the core framework.
37
+
38
+ ---
39
+
40
+ ## 📦 Installation
41
+
42
+ ```bash
43
+ npm install ultra-tg-framework
44
+ ```
45
+
46
+ ---
47
+
48
+ ## 🏁 Quick Start
49
+
50
+ ### Example 1: Node.js (Polling)
51
+
52
+ The fastest way to get started. No server setup required.
53
+
54
+ ```typescript
55
+ import { TelegramBot, NodeApiClient } from 'ultra-tg-framework';
56
+
57
+ const bot = new TelegramBot(new NodeApiClient('YOUR_BOT_TOKEN'));
58
+
59
+ bot.command('start', async (ctx) => {
60
+ await ctx.reply('Hello! I am an Ultra Telegram Framework bot 🚀');
61
+ });
62
+
63
+ bot.on('text', async (ctx) => {
64
+ await ctx.reply(`You said: ${ctx.text}`);
65
+ });
66
+
67
+ bot.launch()
68
+ .then(() => console.log('Bot is running...'))
69
+ .catch(console.error);
70
+ ```
71
+
72
+ ### Example 2: Google Apps Script (Webhook)
73
+
74
+ Deploy your bot for free on Google's infrastructure:
75
+
76
+ ```typescript
77
+ import { TelegramBot, GasApiClient } from 'ultra-tg-framework';
78
+
79
+ const bot = new TelegramBot(new GasApiClient('YOUR_BOT_TOKEN'));
80
+
81
+ bot.command('start', async (ctx) => {
82
+ await ctx.reply('Hello from Google Apps Script! ☁️');
83
+ });
84
+
85
+ globalThis.doPost = (e: GoogleAppsScript.Events.DoPost) => {
86
+ if (e?.postData?.contents) {
87
+ const update = JSON.parse(e.postData.contents);
88
+ bot.handleUpdate(update);
89
+ }
90
+ };
91
+ ```
92
+
93
+ ### Example 3: Cloudflare Workers (Edge)
94
+
95
+ Global low-latency deployment:
96
+
97
+ ```typescript
98
+ import { TelegramBot, WebApiClient } from 'ultra-tg-framework';
99
+
100
+ export default {
101
+ async fetch(request: Request, env: any, ctx: ExecutionContext) {
102
+ if (request.method !== 'POST') {
103
+ return new Response('Bot is running ⚡');
104
+ }
105
+
106
+ const update = await request.json();
107
+ const bot = new TelegramBot(new WebApiClient(env.BOT_TOKEN));
108
+
109
+ bot.command('start', async (ctx) => {
110
+ await ctx.reply('Hello from the Edge! 🌍⚡');
111
+ });
112
+
113
+ ctx.waitUntil(bot.handleUpdate(update));
114
+ return new Response('OK');
115
+ }
116
+ };
117
+ ```
118
+
119
+ ---
120
+
121
+ ## 🧰 Key Features at a Glance
122
+
123
+ | Feature | Description |
124
+ | :--- | :--- |
125
+ | **Context Helpers** | `ctx.reply()`, `ctx.editMessage()`, `ctx.answerCbQuery()`, `ctx.replyWithPhoto()`, and 30+ more. |
126
+ | **Smart `ctx.text`** | Returns message text OR media caption — works universally. |
127
+ | **`ctx.payload`** | Command arguments (e.g., `/start ref_123` → `"ref_123"`). |
128
+ | **InlineKeyboard** | Fluent builder: `.text()`, `.url()`, `.webApp()`, `.menu()`, `.pay()`, `.game()`. |
129
+ | **ReplyKeyboard** | `.text()`, `.requestContact()`, `.requestLocation()`, `.oneTime()`, `.resized()`. |
130
+ | **InlineMenu** | Page-based menus with automatic navigation, back buttons, and state management. |
131
+ | **WizardScene** | Linear step-by-step dialogues with `ctx.scene.next()`, `leave()`, `selectStep()`. |
132
+ | **Session Storage** | `MemoryStorage`, `PropertiesStorage`, `CacheStorage`, `GasHybridStorage`. |
133
+ | **Error Handling** | Built-in `bot.catch()` and middleware-based error boundaries. |
134
+ | **AI Drafts** | `ctx.replyWithDraft()` for streaming LLM responses with rate-limit protection. |
135
+ | **Paid Media** | `ctx.replyWithPaidMedia()` for Telegram Stars ⭐️ monetization. |
136
+ | **Games** | `ctx.replyWithGame()`, `ctx.setGameScore()`, `ctx.getGameHighScores()`. |
137
+ | **Payments** | `ctx.replyWithInvoice()`, `ctx.answerShippingQuery()`, `ctx.answerPreCheckoutQuery()`. |
138
+
139
+ ---
140
+
141
+ ## 📚 Documentation & Guides
142
+
143
+ Explore the detailed guides in our [Wiki](https://github.com/VolodymyrDzola/ultra-tg-framework/wiki):
144
+
145
+ ### 🚀 Getting Started
146
+ - **[Migrating to UTF](https://github.com/VolodymyrDzola/ultra-tg-framework/wiki/Migrating-to-UTF)** — Step-by-step guide for upgrading from grammY, Telegraf, or legacy scripts.
147
+ - **[Architecture](https://github.com/VolodymyrDzola/ultra-tg-framework/wiki/Architecture)** — Understand the adapter-based design and the four architectural layers.
148
+ - **[Local Dev (Polling vs Webhooks)](https://github.com/VolodymyrDzola/ultra-tg-framework/wiki/Local-Dev)** — Best practices for running and testing your bot locally.
149
+
150
+ ### ☁️ Deployment
151
+ - **[Deploy to GAS](https://github.com/VolodymyrDzola/ultra-tg-framework/wiki/Deploy-to-GAS)** — Comprehensive tutorial for Google Apps Script deployment with `esbuild` and `clasp`.
152
+ - **[Deploy to Edge](https://github.com/VolodymyrDzola/ultra-tg-framework/wiki/Edge-Computing)** — Cloudflare Workers, Deno Deploy, and Bun examples.
153
+
154
+ ### 🧠 Framework Deep Dive
155
+ - **[Core (TelegramBot)](https://github.com/VolodymyrDzola/ultra-tg-framework/wiki/Core)** — The bot entry point, lifecycle, and initialization.
156
+ - **[Context](https://github.com/VolodymyrDzola/ultra-tg-framework/wiki/Context)** — Complete API reference for the `ctx` object with all 40+ helper methods.
157
+ - **[Composer & Routing](https://github.com/VolodymyrDzola/ultra-tg-framework/wiki/Composer)** — `.command()`, `.on()`, `.action()`, type narrowing, and modular sub-routers.
158
+ - **[Middleware](https://github.com/VolodymyrDzola/ultra-tg-framework/wiki/Middleware)** — The "onion" model, context hydration, and chain control.
159
+ - **[Sessions & Storage](https://github.com/VolodymyrDzola/ultra-tg-framework/wiki/Session)** — Memory, GAS Cache, Hybrid, and custom storage adapters.
160
+ - **[Error Handling](https://github.com/VolodymyrDzola/ultra-tg-framework/wiki/Error-Handling)** — `bot.catch()`, middleware boundaries, and Telegram-specific error patterns.
161
+
162
+ ### 🧰 UI & Tools
163
+ - **[Inline Menus](https://github.com/VolodymyrDzola/ultra-tg-framework/wiki/Inline-Menus)** — Page-based declarative menus with navigation and back buttons.
164
+ - **[Wizard Scenes](https://github.com/VolodymyrDzola/ultra-tg-framework/wiki/Wizard-Scenes)** — Multi-step forms and conversational flows.
165
+ - **[Handling Files](https://github.com/VolodymyrDzola/ultra-tg-framework/wiki/Handling-Files)** — Working with Telegram files, downloads, and Google Drive integration.
166
+
167
+ ### 🔗 Resources
168
+ - **[TypeDoc API Reference](https://volodymyrdzola.github.io/ultra-tg-framework/)** — Auto-generated TypeScript API documentation for all classes, methods, and types.
169
+
170
+ ---
171
+
172
+ ## 📄 License
173
+
174
+ This project is licensed under the MIT License — see the [LICENSE](https://github.com/VolodymyrDzola/ultra-tg-framework?tab=MIT-1-ov-file) file for details.
@@ -0,0 +1,10 @@
1
+ import { BaseTelegramClient } from '../core/base-api';
2
+ export declare class GasApiClient extends BaseTelegramClient {
3
+ /**
4
+ * Sends a request to the Telegram API.
5
+ * @param method - method name
6
+ * @param payload - parameters object
7
+ * @returns `Promise<T>`
8
+ */
9
+ callApi<T>(method: string, payload?: Record<string, unknown>): Promise<T>;
10
+ }
@@ -0,0 +1,90 @@
1
+ // src/adapters/gas.ts
2
+ import { BaseTelegramClient } from '../core/base-api';
3
+ export class GasApiClient extends BaseTelegramClient {
4
+ /**
5
+ * Sends a request to the Telegram API.
6
+ * @param method - method name
7
+ * @param payload - parameters object
8
+ * @returns `Promise<T>`
9
+ */
10
+ async callApi(method, payload = {}) {
11
+ const url = `${this.baseUrl}/${method}`;
12
+ let fileIndex = 0;
13
+ const files = {};
14
+ /**
15
+ * Recursive function to find files in nested objects.
16
+ * @param value - value to process
17
+ * @returns `unknown`
18
+ */
19
+ const extractFiles = (value) => {
20
+ if (value == null)
21
+ return value;
22
+ if (typeof value === 'object' && typeof value.getBytes === 'function') {
23
+ const attachName = `file_attach_${fileIndex++}`;
24
+ files[attachName] = value;
25
+ return `attach://${attachName}`;
26
+ }
27
+ if (Array.isArray(value)) {
28
+ return value.map(extractFiles);
29
+ }
30
+ if (typeof value === 'object') {
31
+ const processedObj = {};
32
+ for (const [k, v] of Object.entries(value)) {
33
+ processedObj[k] = extractFiles(v);
34
+ }
35
+ return processedObj;
36
+ }
37
+ return value;
38
+ };
39
+ const processedPayload = {};
40
+ for (const [key, value] of Object.entries(payload)) {
41
+ processedPayload[key] = extractFiles(value);
42
+ }
43
+ const hasFiles = Object.keys(files).length > 0;
44
+ let options;
45
+ if (hasFiles) {
46
+ const multipartPayload = { ...files };
47
+ for (const [key, value] of Object.entries(processedPayload)) {
48
+ if (value == null)
49
+ continue;
50
+ if (typeof value === 'object') {
51
+ multipartPayload[key] = JSON.stringify(value);
52
+ }
53
+ else {
54
+ multipartPayload[key] = String(value);
55
+ }
56
+ }
57
+ options = {
58
+ method: 'post',
59
+ payload: multipartPayload,
60
+ muteHttpExceptions: true,
61
+ };
62
+ }
63
+ else {
64
+ options = {
65
+ method: 'post',
66
+ contentType: 'application/json',
67
+ payload: JSON.stringify(processedPayload),
68
+ muteHttpExceptions: true,
69
+ };
70
+ }
71
+ const response = UrlFetchApp.fetch(url, options);
72
+ const statusCode = response.getResponseCode();
73
+ const contentText = response.getContentText();
74
+ if (statusCode !== 200) {
75
+ let errorData;
76
+ try {
77
+ errorData = JSON.parse(contentText);
78
+ }
79
+ catch {
80
+ throw new Error(`HTTP Error ${statusCode}: ${contentText}`);
81
+ }
82
+ throw new Error(`Telegram API Error ${statusCode}: ${errorData.description}`);
83
+ }
84
+ const data = JSON.parse(contentText);
85
+ if (!data.ok) {
86
+ throw new Error(`Telegram Logic Error: ${data.description}`);
87
+ }
88
+ return data.result;
89
+ }
90
+ }
@@ -0,0 +1,52 @@
1
+ import { BaseTelegramClient } from '../core/base-api';
2
+ /**
3
+ * Shape of a captured webhook reply payload.
4
+ */
5
+ export interface WebhookReplyPayload {
6
+ method: string;
7
+ [key: string]: unknown;
8
+ }
9
+ export declare class NodeApiClient extends BaseTelegramClient {
10
+ /**
11
+ * Stores the first API call as a JSON payload for webhook reply optimization.
12
+ * When set, this payload should be returned as the HTTP response body
13
+ * instead of making a separate fetch request to Telegram.
14
+ */
15
+ private _webhookReply;
16
+ private _useWebhookReply;
17
+ /**
18
+ * Enable webhook reply mode.
19
+ * Call this BEFORE `handleUpdate()` to capture the first text-only API call
20
+ * as a JSON payload instead of sending it via fetch.
21
+ *
22
+ * **⚠️ Important trade-off:** When a call is captured, the corresponding
23
+ * `ctx.reply()` / `ctx.api.*` method returns an **empty object** `{}` instead
24
+ * of the real Telegram response. This means you **cannot** rely on the return
25
+ * value (e.g., `msg.message_id` will be `undefined`).
26
+ *
27
+ * Use this only when you don't need the return value of the first API call.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * app.post('/webhook', async (req, res) => {
32
+ * client.enableWebhookReply();
33
+ * await bot.handleUpdate(req.body);
34
+ *
35
+ * const reply = client.consumeWebhookReply();
36
+ * if (reply) {
37
+ * res.json(reply);
38
+ * } else {
39
+ * res.json({ ok: true });
40
+ * }
41
+ * });
42
+ * ```
43
+ */
44
+ enableWebhookReply(): void;
45
+ /**
46
+ * Retrieve and clear the captured webhook reply payload.
47
+ * Returns `null` if no payload was captured (e.g., the first call had files,
48
+ * or no API calls were made during this update).
49
+ */
50
+ consumeWebhookReply(): WebhookReplyPayload | null;
51
+ callApi<T>(method: string, payload?: Record<string, unknown>): Promise<T>;
52
+ }
@@ -0,0 +1,150 @@
1
+ // src/adapters/node.ts
2
+ import { BaseTelegramClient } from '../core/base-api';
3
+ export class NodeApiClient extends BaseTelegramClient {
4
+ constructor() {
5
+ super(...arguments);
6
+ /**
7
+ * Stores the first API call as a JSON payload for webhook reply optimization.
8
+ * When set, this payload should be returned as the HTTP response body
9
+ * instead of making a separate fetch request to Telegram.
10
+ */
11
+ this._webhookReply = null;
12
+ this._useWebhookReply = false;
13
+ }
14
+ /**
15
+ * Enable webhook reply mode.
16
+ * Call this BEFORE `handleUpdate()` to capture the first text-only API call
17
+ * as a JSON payload instead of sending it via fetch.
18
+ *
19
+ * **⚠️ Important trade-off:** When a call is captured, the corresponding
20
+ * `ctx.reply()` / `ctx.api.*` method returns an **empty object** `{}` instead
21
+ * of the real Telegram response. This means you **cannot** rely on the return
22
+ * value (e.g., `msg.message_id` will be `undefined`).
23
+ *
24
+ * Use this only when you don't need the return value of the first API call.
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * app.post('/webhook', async (req, res) => {
29
+ * client.enableWebhookReply();
30
+ * await bot.handleUpdate(req.body);
31
+ *
32
+ * const reply = client.consumeWebhookReply();
33
+ * if (reply) {
34
+ * res.json(reply);
35
+ * } else {
36
+ * res.json({ ok: true });
37
+ * }
38
+ * });
39
+ * ```
40
+ */
41
+ enableWebhookReply() {
42
+ this._useWebhookReply = true;
43
+ this._webhookReply = null;
44
+ }
45
+ /**
46
+ * Retrieve and clear the captured webhook reply payload.
47
+ * Returns `null` if no payload was captured (e.g., the first call had files,
48
+ * or no API calls were made during this update).
49
+ */
50
+ consumeWebhookReply() {
51
+ const reply = this._webhookReply;
52
+ this._webhookReply = null;
53
+ this._useWebhookReply = false;
54
+ return reply;
55
+ }
56
+ async callApi(method, payload = {}) {
57
+ const url = `${this.baseUrl}/${method}`;
58
+ let fileIndex = 0;
59
+ const files = {};
60
+ const extractFiles = (value) => {
61
+ if (value == null)
62
+ return value;
63
+ let isFile = false;
64
+ let blobValue = null;
65
+ if (value instanceof Blob) {
66
+ isFile = true;
67
+ blobValue = value;
68
+ }
69
+ else if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) {
70
+ isFile = true;
71
+ blobValue = new Blob([value]);
72
+ }
73
+ if (isFile && blobValue) {
74
+ const attachName = `file_attach_${fileIndex++}`;
75
+ files[attachName] = blobValue;
76
+ return `attach://${attachName}`;
77
+ }
78
+ if (Array.isArray(value)) {
79
+ return value.map(extractFiles);
80
+ }
81
+ if (typeof value === 'object') {
82
+ const processedObj = {};
83
+ for (const [k, v] of Object.entries(value)) {
84
+ processedObj[k] = extractFiles(v);
85
+ }
86
+ return processedObj;
87
+ }
88
+ return value;
89
+ };
90
+ const processedPayload = {};
91
+ for (const [key, value] of Object.entries(payload)) {
92
+ processedPayload[key] = extractFiles(value);
93
+ }
94
+ const hasFiles = Object.keys(files).length > 0;
95
+ // Webhook Reply optimization: capture the first text-only call
96
+ if (this._useWebhookReply && !hasFiles) {
97
+ this._useWebhookReply = false; // Only the first call
98
+ this._webhookReply = { method, ...processedPayload };
99
+ console.warn(`[UTF] ⚡ Webhook Reply captured: ${method}. ` +
100
+ `The return value of this API call is an empty object. ` +
101
+ `Do not use the returned value (e.g., msg.message_id will be undefined).`);
102
+ return {};
103
+ }
104
+ let requestOptions;
105
+ if (hasFiles) {
106
+ const form = new FormData();
107
+ for (const [key, file] of Object.entries(files)) {
108
+ form.append(key, file, `${key}.file`);
109
+ }
110
+ for (const [key, value] of Object.entries(processedPayload)) {
111
+ if (value == null)
112
+ continue;
113
+ if (typeof value === 'object') {
114
+ form.append(key, JSON.stringify(value));
115
+ }
116
+ else {
117
+ form.append(key, String(value));
118
+ }
119
+ }
120
+ requestOptions = {
121
+ method: 'POST',
122
+ body: form,
123
+ };
124
+ }
125
+ else {
126
+ requestOptions = {
127
+ method: 'POST',
128
+ headers: { 'Content-Type': 'application/json' },
129
+ body: JSON.stringify(processedPayload),
130
+ };
131
+ }
132
+ const response = await fetch(url, requestOptions);
133
+ if (!response.ok) {
134
+ const textData = await response.text();
135
+ let jsonData;
136
+ try {
137
+ jsonData = JSON.parse(textData);
138
+ }
139
+ catch {
140
+ throw new Error(`Telegram HTTP Error ${response.status}: ${textData}`);
141
+ }
142
+ throw new Error(`Telegram API Error [${method}] ${response.status}: ${jsonData.description}`);
143
+ }
144
+ const data = (await response.json());
145
+ if (!data.ok) {
146
+ throw new Error(`Telegram Logic Error [${method}]: ${data.description}`);
147
+ }
148
+ return data.result;
149
+ }
150
+ }
@@ -0,0 +1,4 @@
1
+ import { BaseTelegramClient } from '../core/base-api';
2
+ export declare class WebApiClient extends BaseTelegramClient {
3
+ callApi<T>(method: string, payload?: Record<string, unknown>): Promise<T>;
4
+ }
@@ -0,0 +1,90 @@
1
+ // src/adapters/web.ts
2
+ import { BaseTelegramClient } from '../core/base-api';
3
+ export class WebApiClient extends BaseTelegramClient {
4
+ async callApi(method, payload = {}) {
5
+ const url = `${this.baseUrl}/${method}`;
6
+ let fileIndex = 0;
7
+ const files = {};
8
+ const extractFiles = (value) => {
9
+ if (value == null)
10
+ return value;
11
+ let isFile = false;
12
+ let blobValue = null;
13
+ if (value instanceof Blob) {
14
+ isFile = true;
15
+ blobValue = value;
16
+ }
17
+ else if (typeof globalThis !== 'undefined' &&
18
+ globalThis.Buffer &&
19
+ globalThis.Buffer.isBuffer(value)) {
20
+ isFile = true;
21
+ blobValue = new Blob([value]);
22
+ }
23
+ if (isFile && blobValue) {
24
+ const attachName = `file_attach_${fileIndex++}`;
25
+ files[attachName] = blobValue;
26
+ return `attach://${attachName}`;
27
+ }
28
+ if (Array.isArray(value)) {
29
+ return value.map(extractFiles);
30
+ }
31
+ if (typeof value === 'object') {
32
+ const processedObj = {};
33
+ for (const [k, v] of Object.entries(value)) {
34
+ processedObj[k] = extractFiles(v);
35
+ }
36
+ return processedObj;
37
+ }
38
+ return value;
39
+ };
40
+ const processedPayload = {};
41
+ for (const [key, value] of Object.entries(payload)) {
42
+ processedPayload[key] = extractFiles(value);
43
+ }
44
+ const hasFiles = Object.keys(files).length > 0;
45
+ let requestOptions;
46
+ if (hasFiles) {
47
+ const form = new FormData();
48
+ for (const [key, file] of Object.entries(files)) {
49
+ form.append(key, file, `${key}.file`);
50
+ }
51
+ for (const [key, value] of Object.entries(processedPayload)) {
52
+ if (value == null)
53
+ continue;
54
+ if (typeof value === 'object') {
55
+ form.append(key, JSON.stringify(value));
56
+ }
57
+ else {
58
+ form.append(key, String(value));
59
+ }
60
+ }
61
+ requestOptions = {
62
+ method: 'POST',
63
+ body: form,
64
+ };
65
+ }
66
+ else {
67
+ requestOptions = {
68
+ method: 'POST',
69
+ headers: { 'Content-Type': 'application/json' },
70
+ body: JSON.stringify(processedPayload),
71
+ };
72
+ }
73
+ const response = await fetch(url, requestOptions);
74
+ if (!response.ok) {
75
+ const textData = await response.text();
76
+ try {
77
+ const jsonData = JSON.parse(textData);
78
+ throw new Error(`Telegram API Error [${method}] ${response.status}: ${jsonData.description}`);
79
+ }
80
+ catch {
81
+ throw new Error(`Telegram HTTP Error ${response.status}: ${textData}`);
82
+ }
83
+ }
84
+ const data = (await response.json());
85
+ if (!data.ok) {
86
+ throw new Error(`Telegram Logic Error [${method}]: ${data.description}`);
87
+ }
88
+ return data.result;
89
+ }
90
+ }
@@ -0,0 +1,40 @@
1
+ import { TelegramBotApi } from '../types/telegram';
2
+ /**
3
+ * Shape of a Telegram API error response (non-2xx HTTP status).
4
+ */
5
+ export interface TelegramErrorResponse {
6
+ ok: false;
7
+ description: string;
8
+ error_code: number;
9
+ }
10
+ /**
11
+ * Shape of a successful Telegram API response envelope.
12
+ */
13
+ export interface TelegramApiResponse<T = unknown> {
14
+ ok: boolean;
15
+ description?: string;
16
+ result: T;
17
+ }
18
+ /**
19
+ * Abstract core describing the transport layer.
20
+ */
21
+ export declare abstract class BaseTelegramClient {
22
+ protected readonly baseUrl: string;
23
+ /**
24
+ * Creates a new client instance.
25
+ * @param token Your bot's token
26
+ */
27
+ constructor(token: string);
28
+ /**
29
+ * Request method implemented by each platform.
30
+ * @param method Method name
31
+ * @param payload Parameters object
32
+ * @returns `Promise<T>`
33
+ */
34
+ abstract callApi<T>(method: string, payload?: Record<string, unknown>): Promise<T>;
35
+ /**
36
+ * Proxy that "pretends" to be a full TelegramBotApi implementation.
37
+ * @returns `TelegramBotApi`
38
+ */
39
+ get raw(): TelegramBotApi;
40
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Abstract core describing the transport layer.
3
+ */
4
+ export class BaseTelegramClient {
5
+ /**
6
+ * Creates a new client instance.
7
+ * @param token Your bot's token
8
+ */
9
+ constructor(token) {
10
+ this.baseUrl = `https://api.telegram.org/bot${token}`;
11
+ }
12
+ /**
13
+ * Proxy that "pretends" to be a full TelegramBotApi implementation.
14
+ * @returns `TelegramBotApi`
15
+ */
16
+ get raw() {
17
+ return new Proxy({}, {
18
+ get: (target, method) => {
19
+ if (typeof method !== 'string' || method === 'then') {
20
+ return Reflect.get(target, method);
21
+ }
22
+ return (params = {}) => this.callApi(method, params);
23
+ }
24
+ });
25
+ }
26
+ }