@zero-bot.net/tg-bot-api 1.2.0 → 1.5.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.
package/README.md CHANGED
@@ -2,130 +2,149 @@
2
2
 
3
3
  <div align="center">
4
4
 
5
- Node.js module to interact with the official [Telegram Bot API](https://core.telegram.org/bots/api).
5
+ A lightweight, dependency-light Node.js library for the [Telegram Bot API](https://core.telegram.org/bots/api).
6
6
 
7
-
8
- [![Bot API](https://img.shields.io/badge/Bot%20API-v.3-00aced.svg?style=flat-square&logo=telegram)](https://core.telegram.org/bots/api)
7
+ [![Bot API](https://img.shields.io/badge/Bot%20API-v.10.3-00aced.svg?style=flat-square&logo=telegram)](https://core.telegram.org/bots/api)
9
8
  [![npm package](https://img.shields.io/npm/v/@zero-bot.net/tg-bot-api?logo=npm&style=flat-square)](https://www.npmjs.org/package/@zero-bot.net/tg-bot-api)
10
- [![Build Status](https://img.shields.io/travis/ZeroBot-net/tg-bot-api/master?style=flat-square&logo=travis)](https://travis-ci.org/ZeroBot-net/tg-bot-api)
11
- [![Coverage Status](https://img.shields.io/codecov/c/github/ZeroBot-net/tg-bot-api?style=flat-square&logo=codecov)](https://codecov.io/gh/ZeroBot-net/tg-bot-api)
12
-
13
- [![https://telegram.me/node_telegram_bot_api](https://img.shields.io/badge/💬%20Telegram-Channel-blue.svg?style=flat-square)](https://telegram.me/node_telegram_bot_api)
14
- [![https://t.me/+nc3A9Hs1S81mYzdk](https://img.shields.io/badge/💬%20Telegram-Group-blue.svg?style=flat-square)](https://t.me/+nc3A9Hs1S81mYzdk)
15
- [![https://telegram.me/Yago_Perez](https://img.shields.io/badge/💬%20Telegram-Yago_Perez-blue.svg?style=flat-square)](https://telegram.me/Yago_Perez)
16
9
 
17
10
  </div>
18
11
 
12
+ ## ✨ Features
13
+
14
+ - **202 methods** — full Telegram Bot API 10.3 coverage
15
+ - **No build step** — ships native CommonJS, runs on Node.js 18+
16
+ - **Small dependency tree** — file-type detection and MIME lookup are built in
17
+ - **Built-in TypeScript definitions**
18
+ - **Both update modes** — long polling *and* webhooks (with a built-in HTTP(S) server)
19
+ - Rich Messages, Ephemeral Messages, Guest Mode, Business Accounts
20
+ - Gifts, Stars & Payments, Checklists, Media Polls, Live Photos, Stories
21
+ - Communities, Managed Bots, Suggested Posts, and more
22
+
19
23
  ## 📦 Install
20
24
 
21
25
  ```sh
22
26
  npm i @zero-bot.net/tg-bot-api
23
27
  ```
24
28
 
25
- <br/>
26
-
27
- > ✍️ **Note:** If you use Typescript you can install this package that contains type definitions for this library
28
- >```sh
29
- >npm install --save-dev @types/@zero-bot.net/tg-bot-api
30
- >```
31
-
32
29
  ## 🚀 Usage
33
30
 
34
31
  ```js
35
32
  const TelegramBot = require('@zero-bot.net/tg-bot-api');
36
33
 
37
- // replace the value below with the Telegram token you receive from @BotFather
38
34
  const token = 'YOUR_TELEGRAM_BOT_TOKEN';
39
35
 
40
- // Create a bot that uses 'polling' to fetch new updates
41
- const bot = new TelegramBot(token, {polling: true});
36
+ // Polling mode
37
+ const bot = new TelegramBot(token, { polling: true });
42
38
 
43
- // Matches "/echo [whatever]"
44
39
  bot.onText(/\/echo (.+)/, (msg, match) => {
45
- // 'msg' is the received Message from Telegram
46
- // 'match' is the result of executing the regexp above on the text content
47
- // of the message
48
-
49
- const chatId = msg.chat.id;
50
- const resp = match[1]; // the captured "whatever"
51
-
52
- // send back the matched "whatever" to the chat
53
- bot.sendMessage(chatId, resp);
40
+ bot.sendMessage(msg.chat.id, match[1]);
54
41
  });
55
42
 
56
- // Listen for any kind of message. There are different kinds of
57
- // messages.
58
43
  bot.on('message', (msg) => {
59
- const chatId = msg.chat.id;
44
+ console.log(msg.chat.id, msg.text);
45
+ });
46
+ ```
47
+
48
+ ### Sending files
49
+
50
+ `sendPhoto`, `sendDocument`, `sendAudio`, ... accept a **local path**, a
51
+ **stream**, a **Buffer**, a **URL**, or a **`file_id`**:
52
+
53
+ ```js
54
+ await bot.sendPhoto(chatId, './cat.png'); // path
55
+ await bot.sendPhoto(chatId, buffer); // Buffer (type auto-detected)
56
+ await bot.sendPhoto(chatId, 'https://x/y.png'); // URL
57
+ await bot.sendPhoto(chatId, fileId); // previously uploaded file
58
+ ```
60
59
 
61
- // send a message to the chat acknowledging receipt of their message
62
- bot.sendMessage(chatId, 'Received your message');
60
+ ### Webhooks
61
+
62
+ ```js
63
+ const bot = new TelegramBot(token, {
64
+ webHook: { port: 8443, host: '0.0.0.0', healthEndpoint: '/healthz' },
63
65
  });
66
+
67
+ bot.on('message', (msg) => bot.sendMessage(msg.chat.id, 'hi'));
68
+ await bot.setWebHook('https://example.com:8443');
64
69
  ```
65
70
 
66
- ## 📚 Documentation
71
+ ### Error handling
67
72
 
68
- * [Usage][usage]
69
- * [Examples][examples]
70
- * [Tutorials][tutorials]
71
- * [Help Information][help]
72
- * API Reference: ([api-release](../master/doc/api.md) / [development][api-dev] / [experimental][api-experimental])
73
- * [Contributing to the Project][contributing]
74
- * [Experimental Features][experimental]
73
+ All API errors are `errors.TelegramError` (code `ETELEGRAM`) and carry the raw
74
+ server `response`. Transport failures are `errors.FatalError` (code `EFATAL`)
75
+ with the original error preserved as `.cause`.
75
76
 
76
- _**Note**: Development is done against the **development** branch.
77
- Code for the latest release resides on the **master** branch.
78
- Experimental features reside on the **experimental** branch._
77
+ ```js
78
+ const { TelegramError } = require('@zero-bot.net/tg-bot-api');
79
79
 
80
+ bot.on('polling_error', (err) => {
81
+ if (err instanceof TelegramError) console.error(err.response.body);
82
+ else console.error(err.message, err.cause);
83
+ });
84
+ ```
80
85
 
81
- ## 💭 Community
86
+ ## Performance & latency
87
+
88
+ Warm (keep-alive) requests take **20–80ms**. Cold connections pay DNS + TCP +
89
+ TLS **plus Node's 250ms IPv6 fallback window** — that is what pushes a request
90
+ to 200–600ms. Ranked fixes:
91
+
92
+ 1. **Host near Telegram** (EU/US) — RTT drops from ~200ms to ~10–30ms. No code.
93
+ 2. **Self-hosted Bot API server** — biggest win for media; files never leave
94
+ your machine:
95
+ ```js
96
+ const bot = new TelegramBot(token, { baseApiUrl: 'http://127.0.0.1:8081' });
97
+ ```
98
+ 3. **Remove the IPv6 fallback penalty** (up to −250ms per connection):
99
+ ```js
100
+ TelegramBot.applyNetworkTuning(); // ipv4-first + 100ms fallback window
101
+ // or: new TelegramBot(token, { ipv4First: true });
102
+ ```
103
+ 4. **Pre-warm the connection** so the first request is not a cold start:
104
+ ```js
105
+ const bot = new TelegramBot(token, { prewarm: true });
106
+ // or: await bot.preheat();
107
+ ```
108
+ 5. **Tune polling** for instant updates:
109
+ `{ polling: { params: { timeout: 30 }, interval: 50 } }`.
110
+ 6. **Fewer round-trips** — batch with `sendMediaGroup` / `forwardMessages` /
111
+ `copyMessages`, and reuse `file_id`s instead of re-uploading.
112
+
113
+ Keep-alive is enabled by default (`forever: true`); tune the pool if needed:
82
114
 
83
- We thank all the developers in the Open-Source community who continuously
84
- take their time and effort in advancing this project.
85
- See our [list of contributors][contributors].
115
+ ```js
116
+ new TelegramBot(token, {
117
+ request: {
118
+ agentOptions: { keepAlive: true, keepAliveMsecs: 10000, maxSockets: 64 },
119
+ },
120
+ });
121
+ ```
86
122
 
87
- We have a [Telegram channel][tg-channel] where we post updates on
88
- the Project. Head over and subscribe!
123
+ ## 📚 Documentation
89
124
 
90
- We also have a [Telegram group][tg-group] to discuss issues related to this library.
125
+ - [`doc/usage.md`](doc/usage.md) full usage guide
126
+ - [`doc/api.md`](doc/api.md) — API reference (generated from `src/telegram.js`)
127
+ - [`doc/help.md`](doc/help.md) — FAQs and common pitfalls
128
+ - [`examples/`](examples/) — runnable examples
91
129
 
92
- Some things built using this library that might interest you:
130
+ ## 🧪 Development
93
131
 
94
- * [tgfancy](https://github.com/GochoMugo/tgfancy): A fancy, higher-level wrapper for Telegram Bot API
95
- * [@zero-bot.net/tg-bot-api-middleware](https://github.com/idchlife/@zero-bot.net/tg-bot-api-middleware): Middleware for @zero-bot.net/tg-bot-api
96
- * [teleirc](https://github.com/FruitieX/teleirc): A simple Telegram ↔ IRC gateway
97
- * [bot-brother](https://github.com/SerjoPepper/bot-brother): Node.js library to help you easily create telegram bots
98
- * [redbot](https://github.com/guidone/node-red-contrib-chatbot): A Node-RED plugin to create telegram bots visually
99
- * [node-telegram-keyboard-wrapper](https://github.com/alexandercerutti/node-telegram-keyboard-wrapper): A wrapper to improve keyboards structures creation through a more easy-to-see way (supports Inline Keyboards, Reply Keyboard, Remove Keyboard and Force Reply)
100
- * [beetube-bot](https://github.com/kodjunkie/beetube-bot): A telegram bot for music, videos, movies, EDM tracks, torrent downloads, files and more.
101
- * [telegram-inline-calendar](https://github.com/VDS13/telegram-inline-calendar): Date and time picker and inline calendar for Node.js telegram bots.
102
- * [telegram-captcha](https://github.com/VDS13/telegram-captcha): Telegram bot to protect Telegram groups from automatic bots.
132
+ ```sh
133
+ npm test # offline mocha test suite
134
+ npm run lint # eslint
135
+ npm run doc # regenerate doc/api.md
136
+ ```
103
137
 
138
+ Requires Node.js >= 18.
104
139
 
105
140
  ## 👥 Contributors
106
141
 
107
- <p align="center">
108
- <a href="https://github.com/ZeroBot-net/tg-bot-api/graphs/contributors">
109
- <img src="https://contrib.rocks/image?repo=ZeroBot-net/tg-bot-api" />
110
- </a>
111
- </p>
142
+ <a href="https://github.com/ZeroBot-net/tg-bot-api/graphs/contributors">
143
+ <img src="https://contrib.rocks/image?repo=ZeroBot-net/tg-bot-api" />
144
+ </a>
112
145
 
113
146
  ## License
114
147
 
115
148
  **The MIT License (MIT)**
116
149
 
117
- Copyright © 2019 Yago
118
150
  Copyright © 2026 Grandpa EJ
119
-
120
- [usage]:https://github.com/ZeroBot-net/tg-bot-api/tree/master/doc/usage.md
121
- [examples]:https://github.com/ZeroBot-net/tg-bot-api/tree/master/examples
122
- [help]:https://github.com/ZeroBot-net/tg-bot-api/tree/master/doc/help.md
123
- [tutorials]:https://github.com/ZeroBot-net/tg-bot-api/tree/master/doc/tutorials.md
124
- [api-dev]:https://github.com/ZeroBot-net/tg-bot-api/tree/master/doc/api.md
125
- [api-release]:https://github.com/ZeroBot-net/tg-bot-api/tree/release/doc/api.md
126
- [api-experimental]:https://github.com/ZeroBot-net/tg-bot-api/tree/experimental/doc/api.md
127
- [contributing]:https://github.com/ZeroBot-net/tg-bot-api/tree/master/CONTRIBUTING.md
128
- [contributors]:https://github.com/ZeroBot-net/tg-bot-api/graphs/contributors
129
- [experimental]:https://github.com/ZeroBot-net/tg-bot-api/tree/master/doc/experimental.md
130
- [tg-channel]:https://telegram.me/node_telegram_bot_api
131
- [tg-group]:https://t.me/+nc3A9Hs1S81mYzdk
package/index.js CHANGED
@@ -1,13 +1,7 @@
1
1
  /**
2
- * If running on Nodejs 5.x and below, we load the transpiled code.
3
- * Otherwise, we use the ES6 code.
4
- * We are deprecating support for Node.js v5.x and below.
2
+ * @zero-bot.net/tg-bot-api
3
+ *
4
+ * CommonJS entry point. The library ships native ES2020 source (no build or
5
+ * transpile step), so this simply re-exports it.
5
6
  */
6
- const majorVersion = parseInt(process.versions.node.split('.')[0], 10);
7
- if (majorVersion <= 5) {
8
- const deprecate = require('./src/utils').deprecate;
9
- deprecate('Node.js v5.x and below will no longer be supported in the future');
10
- module.exports = require('./lib/telegram');
11
- } else {
12
- module.exports = require('./src/telegram');
13
- }
7
+ module.exports = require('./src/telegram');
package/package.json CHANGED
@@ -1,9 +1,17 @@
1
1
  {
2
2
  "name": "@zero-bot.net/tg-bot-api",
3
- "version": "1.2.0",
4
- "description": "Telegram Bot API",
3
+ "version": "1.5.0",
4
+ "description": "Lightweight, dependency-light Telegram Bot API library for Node.js",
5
5
  "main": "./index.js",
6
- "types": "./lib/telegram.d.ts",
6
+ "types": "./src/telegram.d.ts",
7
+ "files": [
8
+ "index.js",
9
+ "src/"
10
+ ],
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "sideEffects": false,
7
15
  "directories": {
8
16
  "example": "examples",
9
17
  "test": "test"
@@ -17,60 +25,34 @@
17
25
  "zero-bot.net"
18
26
  ],
19
27
  "scripts": {
20
- "gen-doc": "echo 'WARNING: `npm run gen-doc` is deprecated. Use `npm run doc` instead.' && npm run doc",
21
28
  "doc": "jsdoc2md --files src/telegram.js --template doc/api.hbs > doc/api.md",
22
- "build": "babel -d ./lib src",
23
- "prepublishOnly": "npm run build && npm run gen-doc",
24
- "eslint": "eslint ./src ./test ./examples",
25
- "mocha": "mocha",
26
- "pretest": "npm run build",
27
- "test": "npm run eslint && istanbul cover ./node_modules/mocha/bin/_mocha"
29
+ "lint": "eslint .",
30
+ "test": "mocha",
31
+ "test:watch": "mocha --watch",
32
+ "prepublishOnly": "npm run lint && npm test && npm run doc"
28
33
  },
29
34
  "author": "ZeroBot",
30
35
  "license": "MIT",
31
36
  "engines": {
32
- "node": ">=0.12"
37
+ "node": ">=18"
33
38
  },
34
39
  "dependencies": {
35
40
  "@zero-bot.net/request": "^1.2.1",
36
- "array.prototype.findindex": "^2.2.4",
37
- "bl": "^7.0.12",
38
41
  "debug": "^4.4.3",
39
42
  "eventemitter3": "^5.0.4",
40
- "file-type": "^22.1.0",
41
- "mime": "^4.1.0",
42
43
  "pump": "^3.0.4"
43
44
  },
44
45
  "devDependencies": {
45
- "babel-cli": "^6.26.0",
46
- "babel-eslint": "^10.1.0",
47
- "babel-plugin-transform-class-properties": "^6.24.1",
48
- "babel-plugin-transform-es2015-destructuring": "^6.23.0",
49
- "babel-plugin-transform-es2015-parameters": "^6.24.1",
50
- "babel-plugin-transform-es2015-shorthand-properties": "^6.24.1",
51
- "babel-plugin-transform-es2015-spread": "^6.22.0",
52
- "babel-plugin-transform-object-rest-spread": "^6.26.0",
53
- "babel-plugin-transform-strict-mode": "^6.24.1",
54
- "babel-preset-es2015": "^6.24.1",
55
- "babel-register": "^6.26.0",
56
- "concat-stream": "^2.0.0",
57
46
  "eslint": "^10.10.0",
58
- "eslint-config-airbnb": "^19.0.4",
59
- "eslint-plugin-mocha": "^12.0.2",
60
- "is": "^3.3.2",
61
- "is-ci": "^4.1.0",
62
- "istanbul": "^1.1.0-alpha.1",
63
47
  "jsdoc-to-markdown": "^9.1.3",
64
- "mocha": "^12.0.1",
65
- "mocha-lcov-reporter": "^1.3.0",
66
- "node-static": "^0.7.11"
48
+ "mocha": "^12.0.1"
67
49
  },
68
50
  "repository": {
69
51
  "type": "git",
70
- "url": "https://github.com/ZeroBot-net/tg-bot-api.git"
52
+ "url": "git+https://github.com/ZeroBot-net/tg-bot-api.git"
71
53
  },
72
54
  "bugs": {
73
55
  "url": "https://github.com/ZeroBot-net/tg-bot-api/issues"
74
56
  },
75
57
  "homepage": "https://github.com/ZeroBot-net/tg-bot-api"
76
- }
58
+ }
package/src/errors.js CHANGED
@@ -19,6 +19,33 @@ exports.BaseError = class BaseError extends Error {
19
19
  };
20
20
 
21
21
 
22
+ /**
23
+ * Build a readable message from an arbitrary thrown value. Handles the common
24
+ * case where a non-`Error` or an `AggregateError` has an empty `message`, which
25
+ * previously produced a blank `EFATAL:` error.
26
+ * @private
27
+ * @param {*} data
28
+ * @return {String}
29
+ */
30
+ function describeError(data) {
31
+ if (typeof data === 'string') return data;
32
+ if (data === null || data === undefined) return String(data);
33
+ if (data instanceof Error) {
34
+ if (data.errors && data.errors.length) {
35
+ const nested = data.errors.map(describeError).join('; ');
36
+ return data.message ? `${data.message} (${nested})` : nested;
37
+ }
38
+ const label = data.code || data.name || 'Error';
39
+ return data.message ? `${label}: ${data.message}` : String(label);
40
+ }
41
+ if (typeof data.message === 'string' && data.message) return data.message;
42
+ try {
43
+ return JSON.stringify(data);
44
+ } catch {
45
+ return String(data);
46
+ }
47
+ }
48
+
22
49
  exports.FatalError = class FatalError extends exports.BaseError {
23
50
  /**
24
51
  * Fatal Error. Error code is `"EFATAL"`.
@@ -28,9 +55,12 @@ exports.FatalError = class FatalError extends exports.BaseError {
28
55
  */
29
56
  constructor(data) {
30
57
  const error = (typeof data === 'string') ? null : data;
31
- const message = error ? error.message : data;
32
- super('EFATAL', message);
33
- if (error) this.stack = error.stack;
58
+ super('EFATAL', describeError(data));
59
+ if (error) {
60
+ this.stack = error.stack || this.stack;
61
+ // Preserve the original error for programmatic inspection & logging.
62
+ this.cause = error;
63
+ }
34
64
  }
35
65
  };
36
66
 
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Zero-dependency file type detection and MIME lookup.
3
+ *
4
+ * Replaces the `file-type` and `mime` packages so the library installs with a
5
+ * minimal, fully CommonJS dependency tree and no ESM/CJS interop surprises.
6
+ *
7
+ * @module fileTypes
8
+ * @private
9
+ */
10
+
11
+ /**
12
+ * Magic-byte signatures for the file formats that can be uploaded through the
13
+ * Telegram Bot API. Each entry is `{ ext, mime, bytes? , ascii?, offset? }`.
14
+ */
15
+ const SIGNATURES = [
16
+ { bytes: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a], ext: 'png', mime: 'image/png' },
17
+ { bytes: [0xff, 0xd8, 0xff], ext: 'jpg', mime: 'image/jpeg' },
18
+ { ascii: 'GIF87a', ext: 'gif', mime: 'image/gif' },
19
+ { ascii: 'GIF89a', ext: 'gif', mime: 'image/gif' },
20
+ { ascii: 'RIFF', ext: 'webp', mime: 'image/webp', tail: { offset: 8, ascii: 'WEBP' } },
21
+ { ascii: 'RIFF', ext: 'wav', mime: 'audio/wav', tail: { offset: 8, ascii: 'WAVE' } },
22
+ { ascii: 'RIFF', ext: 'avi', mime: 'video/x-msvideo', tail: { offset: 8, ascii: 'AVI ' } },
23
+ { bytes: [0x42, 0x4d], ext: 'bmp', mime: 'image/bmp' },
24
+ { ascii: 'II*\u0000', ext: 'tiff', mime: 'image/tiff' },
25
+ { ascii: 'MM\u0000*', ext: 'tiff', mime: 'image/tiff' },
26
+ { ascii: '%PDF', ext: 'pdf', mime: 'application/pdf' },
27
+ { bytes: [0x50, 0x4b, 0x03, 0x04], ext: 'zip', mime: 'application/zip' },
28
+ { bytes: [0x1f, 0x8b], ext: 'gz', mime: 'application/gzip' },
29
+ { bytes: [0x52, 0x61, 0x72, 0x21, 0x1a, 0x07], ext: 'rar', mime: 'application/x-rar-compressed' },
30
+ { bytes: [0x37, 0x7a, 0xbc, 0xaf, 0x27, 0x1c], ext: '7z', mime: 'application/x-7z-compressed' },
31
+ { ascii: 'ID3', ext: 'mp3', mime: 'audio/mpeg' },
32
+ { bytes: [0xff, 0xfb], ext: 'mp3', mime: 'audio/mpeg' },
33
+ { bytes: [0xff, 0xf3], ext: 'mp3', mime: 'audio/mpeg' },
34
+ { bytes: [0xff, 0xf2], ext: 'mp3', mime: 'audio/mpeg' },
35
+ { ascii: 'OggS', ext: 'ogg', mime: 'audio/ogg' },
36
+ { ascii: 'fLaC', ext: 'flac', mime: 'audio/flac' },
37
+ { bytes: [0x1a, 0x45, 0xdf, 0xa3], ext: 'webm', mime: 'video/webm' },
38
+ { ascii: 'MThd', ext: 'mid', mime: 'audio/midi' },
39
+ { ascii: '{\\rtf', ext: 'rtf', mime: 'application/rtf' },
40
+ ];
41
+
42
+ /** ISO-BMFF brands (`ftyp` box) mapped to a container extension + MIME type. */
43
+ const FTYP_BRANDS = {
44
+ 'M4A ': { ext: 'm4a', mime: 'audio/mp4' },
45
+ 'M4V ': { ext: 'm4v', mime: 'video/x-m4v' },
46
+ 'qt ': { ext: 'mov', mime: 'video/quicktime' },
47
+ '3gp4': { ext: '3gp', mime: 'video/3gpp' },
48
+ '3gp5': { ext: '3gp', mime: 'video/3gpp' },
49
+ };
50
+
51
+ /**
52
+ * Common filename extensions mapped to their MIME type. Used when an explicit
53
+ * content type is not provided for a path/stream upload.
54
+ */
55
+ const EXTENSION_MIME = {
56
+ png: 'image/png',
57
+ jpg: 'image/jpeg',
58
+ jpeg: 'image/jpeg',
59
+ gif: 'image/gif',
60
+ webp: 'image/webp',
61
+ bmp: 'image/bmp',
62
+ tiff: 'image/tiff',
63
+ tif: 'image/tiff',
64
+ svg: 'image/svg+xml',
65
+ heic: 'image/heic',
66
+ ico: 'image/x-icon',
67
+ mp3: 'audio/mpeg',
68
+ m4a: 'audio/mp4',
69
+ aac: 'audio/aac',
70
+ ogg: 'audio/ogg',
71
+ oga: 'audio/ogg',
72
+ opus: 'audio/opus',
73
+ wav: 'audio/wav',
74
+ flac: 'audio/flac',
75
+ mp4: 'video/mp4',
76
+ m4v: 'video/x-m4v',
77
+ mov: 'video/quicktime',
78
+ avi: 'video/x-msvideo',
79
+ webm: 'video/webm',
80
+ mkv: 'video/x-matroska',
81
+ '3gp': 'video/3gpp',
82
+ pdf: 'application/pdf',
83
+ zip: 'application/zip',
84
+ gz: 'application/gzip',
85
+ tgz: 'application/gzip',
86
+ tar: 'application/x-tar',
87
+ rar: 'application/x-rar-compressed',
88
+ '7z': 'application/x-7z-compressed',
89
+ txt: 'text/plain',
90
+ csv: 'text/csv',
91
+ html: 'text/html',
92
+ htm: 'text/html',
93
+ css: 'text/css',
94
+ js: 'application/javascript',
95
+ json: 'application/json',
96
+ xml: 'application/xml',
97
+ md: 'text/markdown',
98
+ rtf: 'application/rtf',
99
+ doc: 'application/msword',
100
+ docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
101
+ xls: 'application/vnd.ms-excel',
102
+ xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
103
+ ppt: 'application/vnd.ms-powerpoint',
104
+ pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
105
+ epub: 'application/epub+zip',
106
+ apk: 'application/vnd.android.package-archive',
107
+ tgs: 'application/x-tgsticker',
108
+ ttf: 'font/ttf',
109
+ otf: 'font/otf',
110
+ woff: 'font/woff',
111
+ woff2: 'font/woff2',
112
+ };
113
+
114
+ /**
115
+ * Compare a byte sequence against the buffer at the given offset.
116
+ * @private
117
+ */
118
+ function matchesBytes(buffer, bytes, offset = 0) {
119
+ if (buffer.length < offset + bytes.length) return false;
120
+ for (let i = 0; i < bytes.length; i += 1) {
121
+ if (buffer[offset + i] !== bytes[i]) return false;
122
+ }
123
+ return true;
124
+ }
125
+
126
+ /**
127
+ * Compare an ASCII sequence against the buffer at the given offset.
128
+ * @private
129
+ */
130
+ function matchesAscii(buffer, ascii, offset = 0) {
131
+ if (buffer.length < offset + ascii.length) return false;
132
+ for (let i = 0; i < ascii.length; i += 1) {
133
+ if (buffer[offset + i] !== ascii.charCodeAt(i)) return false;
134
+ }
135
+ return true;
136
+ }
137
+
138
+ /**
139
+ * Detect the file type of a Buffer from its magic bytes.
140
+ *
141
+ * @param {Buffer} buffer Data to inspect
142
+ * @return {Object|null} `{ ext, mime }` when recognized, otherwise `null`
143
+ */
144
+ function detectFileType(buffer) {
145
+ if (!Buffer.isBuffer(buffer) || buffer.length < 3) {
146
+ return null;
147
+ }
148
+
149
+ for (const sig of SIGNATURES) {
150
+ const headMatches = sig.bytes
151
+ ? matchesBytes(buffer, sig.bytes)
152
+ : matchesAscii(buffer, sig.ascii);
153
+
154
+ if (!headMatches) continue;
155
+
156
+ if (sig.tail && !matchesAscii(buffer, sig.tail.ascii, sig.tail.offset)) {
157
+ continue;
158
+ }
159
+
160
+ return { ext: sig.ext, mime: sig.mime };
161
+ }
162
+
163
+ // ISO Base Media File Format (mp4/m4a/mov/3gp) — 'ftyp' box at offset 4.
164
+ if (buffer.length >= 12 && matchesAscii(buffer, 'ftyp', 4)) {
165
+ const brand = buffer.toString('ascii', 8, 12);
166
+ return FTYP_BRANDS[brand] || { ext: 'mp4', mime: 'video/mp4' };
167
+ }
168
+
169
+ return null;
170
+ }
171
+
172
+ /**
173
+ * Look up a MIME type by filename or extension. Falls back to
174
+ * `application/octet-stream` for unknown extensions.
175
+ *
176
+ * @param {String} filename File name or extension (with or without a dot)
177
+ * @return {String} MIME type
178
+ */
179
+ function lookupMime(filename) {
180
+ if (!filename || typeof filename !== 'string') {
181
+ return 'application/octet-stream';
182
+ }
183
+ const match = /\.([a-z0-9]+)$/i.exec(filename);
184
+ const ext = (match ? match[1] : filename).toLowerCase();
185
+ return EXTENSION_MIME[ext] || 'application/octet-stream';
186
+ }
187
+
188
+ module.exports = { detectFileType, lookupMime };
package/src/network.js ADDED
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Optional, opt-in network tuning that removes the most common latency
3
+ * penalties when talking to the Telegram Bot API over a fresh connection.
4
+ *
5
+ * The biggest offender is Node's "happy eyeballs" behaviour: on networks with
6
+ * broken or absent IPv6 it waits `autoSelectFamilyAttemptTimeout` (250ms by
7
+ * default) before falling back to IPv4 — on *every* new connection. Preferring
8
+ * IPv4 and shortening that window can cut 100–250ms off cold requests.
9
+ *
10
+ * These helpers mutate process-global Node settings, so they are never applied
11
+ * automatically — call `applyNetworkTuning()` once at startup, or pass
12
+ * `{ ipv4First: true }` to the `TelegramBot` constructor.
13
+ *
14
+ * @module network
15
+ */
16
+
17
+ /**
18
+ * Apply latency-oriented network defaults.
19
+ *
20
+ * @param {Object} [options]
21
+ * @param {Boolean} [options.ipv4First=true] Prefer IPv4 when resolving hosts
22
+ * (`dns.setDefaultResultOrder('ipv4first')`).
23
+ * @param {Number} [options.autoSelectFamilyAttemptTimeout=100] Milliseconds to
24
+ * wait for an IPv6 connection before falling back to IPv4 (Node default: 250).
25
+ * @return {Object} The applied settings.
26
+ */
27
+ function applyNetworkTuning(options = {}) {
28
+ const ipv4First = options.ipv4First !== false;
29
+ const autoSelectFamilyAttemptTimeout = typeof options.autoSelectFamilyAttemptTimeout === 'number'
30
+ ? options.autoSelectFamilyAttemptTimeout
31
+ : 100;
32
+
33
+ const dns = require('dns');
34
+ const net = require('net');
35
+
36
+ if (ipv4First && typeof dns.setDefaultResultOrder === 'function') {
37
+ dns.setDefaultResultOrder('ipv4first');
38
+ }
39
+
40
+ if (typeof net.setDefaultAutoSelectFamilyAttemptTimeout === 'function') {
41
+ net.setDefaultAutoSelectFamilyAttemptTimeout(autoSelectFamilyAttemptTimeout);
42
+ }
43
+
44
+ return { ipv4First, autoSelectFamilyAttemptTimeout };
45
+ }
46
+
47
+ module.exports = { applyNetworkTuning };