teleproto 1.225.1 → 1.225.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.
@@ -1,10 +1,7 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 sanyok12345. All rights reserved.
4
-
5
- This project, teleproto, is an independent work originally derived from GramJS.
6
- GramJS is an open source project licensed under the MIT License.
7
- Portions of teleproto are adapted from GramJS and remain subject to the MIT terms.
3
+ Copyright (c) 2019 GramJS
4
+ Copyright (c) 2025-present sanyok12345
8
5
 
9
6
  Permission is hereby granted, free of charge, to any person obtaining a copy
10
7
  of this software and associated documentation files (the "Software"), to deal
@@ -22,4 +19,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
19
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
20
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
21
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
- SOFTWARE.
22
+ SOFTWARE.
package/README.md CHANGED
@@ -1,150 +1,115 @@
1
- # teleproto
1
+ [![npm](https://img.shields.io/npm/v/teleproto)](https://www.npmjs.com/package/teleproto)
2
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue)](./LICENSE)
2
3
 
3
- <p align="center">
4
- <img src="https://img.shields.io/npm/v/teleproto" alt="npm version">
5
- <img src="https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen" alt="node version">
6
- <img src="https://img.shields.io/badge/language-TypeScript-3178c6" alt="typescript">
7
- <img src="https://img.shields.io/badge/license-MIT-blue" alt="license">
8
- <a href="https://t.me/teleproto"><img src="https://img.shields.io/badge/Telegram-Chat-26A5E4?logo=telegram" alt="telegram chat"></a>
9
- </p>
4
+ This project was forked from the open source GramJS project in 2025 and is now developed independently.
10
5
 
11
- Modern Telegram MTProto client for Node.js, written in TypeScript.
12
- `teleproto` is a high-performance fork of GramJS focused on clean API ergonomics, runtime reliability, and up-to-date Telegram layers.
6
+ This README is just a fast *quick start*. Ongoing discussion happens in the [Telegram chat](https://t.me/teleproto).
13
7
 
14
- ## Features
8
+ # What is teleproto?
15
9
 
16
- - **MTProto-first**: Full Telegram API access through high-level client methods and raw `Api` calls.
17
- - **TypeScript-friendly**: Strong typings across client methods, events, sessions, and TL objects.
18
- - **Session options**: Use `StringSession` for portability or `StoreSession` for local persistence.
19
- - **Event system**: Handle updates with builders like `NewMessage`, `EditedMessage`, `CallbackQuery`, and more.
20
- - **Examples included**: Ready-to-run scripts in `teleproto_examples`.
10
+ teleproto is a TypeScript client for Telegram's MTProto API the same protocol Telegram's own apps speak. Through it, your code gets the full account surface: userbots, multi-account automation, file transfer, raw TL invocation when you need it. If you only need to push notifications from a bot, the official Bot API is simpler; teleproto exists for everything *beyond* that.
21
11
 
22
- ## Installation
12
+ # Installing teleproto
23
13
 
24
- ```bash
25
- npm i teleproto
26
- ```
14
+ % npm install teleproto
15
+
16
+ Pure JavaScript, no native build step — installs cleanly on Alpine, ARM, and serverless runtimes.
27
17
 
28
- ## Quick Start
18
+ # Connecting to Telegram
29
19
 
30
- 1. Open https://my.telegram.org
31
- 2. Create an app in **API development tools**
32
- 3. Copy your `api_id` and `api_hash`
20
+ You need an `api_id` and `api_hash` from <https://my.telegram.org>. Then:
33
21
 
34
22
  ```ts
35
23
  import { TelegramClient } from "teleproto";
36
24
  import { StringSession } from "teleproto/sessions";
37
- import readline from "readline";
25
+ import { createInterface } from "node:readline/promises";
26
+
27
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
38
28
 
39
- const apiId = 123456;
40
- const apiHash = "0123456789abcdef0123456789abcdef";
29
+ const apiId = 0; // from https://my.telegram.org
30
+ const apiHash = ""; // from https://my.telegram.org
41
31
  const session = new StringSession("");
42
32
 
43
- const rl = readline.createInterface({
44
- input: process.stdin,
45
- output: process.stdout,
33
+ const client = new TelegramClient(session, apiId, apiHash, {
34
+ connectionRetries: 5,
46
35
  });
47
36
 
48
- const ask = (q: string) =>
49
- new Promise<string>((resolve) => rl.question(q, resolve));
50
-
51
- async function main() {
52
- const client = new TelegramClient(session, apiId, apiHash, {
53
- connectionRetries: 5,
54
- });
55
-
56
- await client.start({
57
- phoneNumber: async () => await ask("Phone number: "),
58
- password: async () => await ask("2FA password (if enabled): "),
59
- phoneCode: async () => await ask("Code from Telegram: "),
60
- onError: (err) => console.error(err),
61
- });
62
-
63
- console.log("Connected as:", (await client.getMe())?.username || "unknown");
64
- console.log("String session:\n", client.session.save());
37
+ await client.start({
38
+ phoneNumber: () => rl.question("Phone: "),
39
+ password: () => rl.question("2FA password: "),
40
+ phoneCode: () => rl.question("Code: "),
41
+ onError: console.error,
42
+ });
65
43
 
66
- await client.sendMessage("me", { message: "Hello from teleproto!" });
67
- await client.disconnect();
68
- rl.close();
69
- }
44
+ console.log(await client.getMe());
45
+ console.log("Session string:", client.session.save());
70
46
 
71
- main().catch(console.error);
47
+ rl.close();
72
48
  ```
73
49
 
74
- ## Sessions
75
-
76
- Use `StringSession` when you want to store auth as a single string:
50
+ The session string is your saved login. Drop it back into `new StringSession(saved)` next time and skip the auth flow entirely.
77
51
 
78
- ```ts
79
- import { StringSession } from "teleproto/sessions";
80
- const session = new StringSession("");
81
- ```
52
+ # Sending and receiving
82
53
 
83
- Use `StoreSession` when you want local folder-based persistence:
84
-
85
- ```ts
86
- import { StoreSession } from "teleproto/sessions";
87
- const session = new StoreSession("teleproto_session");
88
- ```
89
-
90
- ## Events
54
+ Send a message, listen for incoming ones:
91
55
 
92
56
  ```ts
93
57
  import { NewMessage } from "teleproto/events";
94
58
 
59
+ await client.sendMessage("me", { message: "hello from teleproto" });
60
+
95
61
  client.addEventHandler(
96
- async (event) => {
97
- const text = event.message.message || "";
98
- if (/^hello$/i.test(text.trim())) {
99
- await event.message.reply({ message: "Hi there!" });
100
- }
101
- },
102
- new NewMessage({})
62
+ (event) => console.log(event.message.message),
63
+ new NewMessage({}),
103
64
  );
104
65
  ```
105
66
 
106
- ## Raw API
67
+ # Raw MTProto API
68
+
69
+ Every method in Telegram's TL schema is callable directly through `Api.*`. teleproto follows the schema layer-for-layer, so what Telegram adds is usually available here within days.
107
70
 
108
71
  ```ts
109
72
  import { Api } from "teleproto";
110
73
 
111
- const result = await client.invoke(
112
- new Api.help.GetConfig()
113
- );
114
- console.log(result);
74
+ const config = await client.invoke(new Api.help.GetConfig());
115
75
  ```
116
76
 
117
- ## Examples
77
+ # Versioning
118
78
 
119
- Practical scripts are available in `teleproto_examples`:
79
+ teleproto uses a three-part version `MAJOR.LAYER.PATCH`:
120
80
 
121
- - `print_updates.ts`
122
- - `print_messages.ts`
123
- - `replier.ts`
124
- - `interactive_terminal.ts`
81
+ - **MAJOR** — bumped on breaking API changes in teleproto itself.
82
+ - **LAYER** — the Telegram TL schema layer the release ships against
83
+ (e.g. `1.225.x` ships layer 225).
84
+ - **PATCH** — fixes and non-breaking improvements within the same layer.
125
85
 
126
- Run any example from the project root:
86
+ This stays compatible with npm's range syntax:
127
87
 
128
- ```bash
129
- npx ts-node --transpile-only teleproto_examples/print_updates.ts
130
- ```
88
+ - `^1.225.0` accepts new layers and patches — recommended default.
89
+ - `~1.225.0` sticks to layer 225 only; useful if you depend on
90
+ schema specifics that newer layers might change.
91
+ - `1.225.1` is an exact pin.
92
+
93
+ # Examples
94
+
95
+ Runnable scripts live in [teleproto_examples/](teleproto_examples/):
96
+
97
+ - `print_updates.ts` — log every update the client receives
98
+ - `print_messages.ts` — listen for new messages only
99
+ - `replier.ts` — auto-reply pattern for bots and userbots
100
+ - `interactive_terminal.ts` — REPL against a live client
131
101
 
132
- ## Community
102
+ Each is self-contained. Set your credentials at the top and run:
133
103
 
134
- - [Telegram Chat](https://t.me/teleproto) — questions, discussions, updates
135
- - [GitHub Issues](https://github.com/sanyok12345/teleproto/issues) — bug reports and feature requests
104
+ % npx ts-node --transpile-only teleproto_examples/print_updates.ts
136
105
 
137
- ## Support the project ☕
106
+ # Code contributions
138
107
 
139
- If teleproto saves you time or powers your product — consider buying me a coffee.
140
- Every donation keeps the lights on and the commits flowing.
108
+ Please see [CONTRIBUTING.md][1].
141
109
 
142
- | Network | Address |
143
- |---------|---------|
144
- | **TON** | `sanyok12345.ton` |
145
- | **TRX** | `TXCtN1UrxST9ovq5tDN3Zb96eNF4164nK5` |
146
- | **SOL** | `B3XcKmAR9aG2nBiWSMDe76GGa55hPNgLQ92QR2SjRR6F` |
110
+ # License
147
111
 
148
- ## License
112
+ teleproto is distributed under the [MIT License][2].
149
113
 
150
- MIT
114
+ [1]: ./CONTRIBUTING.md
115
+ [2]: ./LICENSE
package/Version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const version = "1.225.1";
1
+ export declare const version = "1.225.3";
package/Version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.version = void 0;
4
- exports.version = "1.225.1";
4
+ exports.version = "1.225.3";
@@ -85,6 +85,22 @@ export declare class TelegramClient extends TelegramBaseClient {
85
85
  * @return boolean (true of authorized else false)
86
86
  */
87
87
  checkAuthorization(): Promise<boolean>;
88
+ /**
89
+ * Logs out the currently authenticated user/bot. Invalidates the session
90
+ * on Telegram's side (via `auth.LogOut`), disconnects the client and wipes
91
+ * local session data so a subsequent `session.save()` returns nothing.
92
+ *
93
+ * After this call the client is disconnected and event handlers stop
94
+ * firing; reconnecting requires going through `start`/`signInUser` again.
95
+ *
96
+ * @returns `true` if the server-side log out succeeded, `false` if the
97
+ * `auth.LogOut` call failed. Local session data is wiped in both cases.
98
+ * @example
99
+ * ```ts
100
+ * await client.logOut();
101
+ * ```
102
+ */
103
+ logOut(): Promise<boolean>;
88
104
  /**
89
105
  * Logs in as a user. Should only be used when not already logged in.<br/>
90
106
  * This method will send a code when needed.<br/>
@@ -127,6 +127,24 @@ class TelegramClient extends telegramBaseClient_1.TelegramBaseClient {
127
127
  checkAuthorization() {
128
128
  return authMethods.checkAuthorization(this);
129
129
  }
130
+ /**
131
+ * Logs out the currently authenticated user/bot. Invalidates the session
132
+ * on Telegram's side (via `auth.LogOut`), disconnects the client and wipes
133
+ * local session data so a subsequent `session.save()` returns nothing.
134
+ *
135
+ * After this call the client is disconnected and event handlers stop
136
+ * firing; reconnecting requires going through `start`/`signInUser` again.
137
+ *
138
+ * @returns `true` if the server-side log out succeeded, `false` if the
139
+ * `auth.LogOut` call failed. Local session data is wiped in both cases.
140
+ * @example
141
+ * ```ts
142
+ * await client.logOut();
143
+ * ```
144
+ */
145
+ logOut() {
146
+ return authMethods.logOut(this);
147
+ }
130
148
  /**
131
149
  * Logs in as a user. Should only be used when not already logged in.<br/>
132
150
  * This method will send a code when needed.<br/>
package/client/auth.d.ts CHANGED
@@ -115,6 +115,8 @@ export declare function start(client: TelegramClient, authParams: UserAuthParams
115
115
  /** @hidden */
116
116
  export declare function checkAuthorization(client: TelegramClient): Promise<boolean>;
117
117
  /** @hidden */
118
+ export declare function logOut(client: TelegramClient): Promise<boolean>;
119
+ /** @hidden */
118
120
  export declare function signInUser(client: TelegramClient, apiCredentials: ApiCredentials, authParams: UserAuthParams): Promise<Api.TypeUser>;
119
121
  /** @hidden */
120
122
  export declare function signInUserWithQrCode(client: TelegramClient, apiCredentials: ApiCredentials, authParams: QrCodeAuthParams): Promise<Api.TypeUser>;
package/client/auth.js CHANGED
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.start = start;
37
37
  exports.checkAuthorization = checkAuthorization;
38
+ exports.logOut = logOut;
38
39
  exports.signInUser = signInUser;
39
40
  exports.signInUserWithQrCode = signInUserWithQrCode;
40
41
  exports.sendCode = sendCode;
@@ -75,6 +76,20 @@ async function checkAuthorization(client) {
75
76
  }
76
77
  }
77
78
  /** @hidden */
79
+ async function logOut(client) {
80
+ let success = true;
81
+ try {
82
+ await client.invoke(new tl_1.Api.auth.LogOut());
83
+ }
84
+ catch (e) {
85
+ client._log.warn("auth.LogOut failed: " + e.message);
86
+ success = false;
87
+ }
88
+ await client.disconnect();
89
+ await client.session.delete();
90
+ return success;
91
+ }
92
+ /** @hidden */
78
93
  async function signInUser(client, apiCredentials, authParams) {
79
94
  let phoneNumber = "";
80
95
  let phoneCodeHash = "";
package/client/users.js CHANGED
@@ -435,7 +435,7 @@ async function getInputEntity(client, peer) {
435
435
  }
436
436
  throw new Error(`Could not find the input entity for ${JSON.stringify(peer)}.
437
437
  Please read https://` +
438
- "docs.telethon.dev/en/stable/concepts/entities.html to" +
438
+ "docs.teleproto.dev/concepts/entities to" +
439
439
  " find out more details.");
440
440
  }
441
441
  /** @hidden */
@@ -14,26 +14,22 @@ export declare class MTProtoState {
14
14
  private securityChecks;
15
15
  /**
16
16
  *
17
- `telethon.network.mtprotosender.MTProtoSender` needs to hold a state
18
- in order to be able to encrypt and decrypt incoming/outgoing messages,
19
- as well as generating the message IDs. Instances of this class hold
20
- together all the required information.
17
+ `MTProtoSender` needs to hold a state in order to encrypt and decrypt
18
+ incoming/outgoing messages, as well as generate message IDs. Instances
19
+ of this class hold together all the required information.
21
20
 
22
- It doesn't make sense to use `telethon.sessions.abstract.Session` for
23
- the sender because the sender should *not* be concerned about storing
24
- this information to disk, as one may create as many senders as they
25
- desire to any other data center, or some CDN. Using the same session
26
- for all these is not a good idea as each need their own authkey, and
27
- the concept of "copying" sessions with the unnecessary entities or
28
- updates state for these connections doesn't make sense.
21
+ A `Session` is intentionally not used here: the sender should *not* be
22
+ concerned with persisting this state to disk. Multiple senders may
23
+ exist for different data centers or CDNs, each requiring its own
24
+ authkey "copying" a session along with unrelated entities or update
25
+ state would make no sense.
29
26
 
30
- While it would be possible to have a `MTProtoPlainState` that does no
31
- encryption so that it was usable through the `MTProtoLayer` and thus
32
- avoid the need for a `MTProtoPlainSender`, the `MTProtoLayer` is more
33
- focused to efficiency and this state is also more advanced (since it
34
- supports gzipping and invoking after other message IDs). There are too
35
- many methods that would be needed to make it convenient to use for the
36
- authentication process, at which point the `MTProtoPlainSender` is better
27
+ A `MTProtoPlainState` doing no encryption could in principle be used
28
+ through `MTProtoLayer` and remove the need for `MTProtoPlainSender`,
29
+ but `MTProtoLayer` targets efficient throughput and this state class
30
+ is also more advanced (gzipping, invoking after other message IDs).
31
+ Too many helper methods would be needed to make it convenient during
32
+ authentication, where `MTProtoPlainSender` is the better fit.
37
33
  * @param authKey
38
34
  * @param loggers
39
35
  * @param securityChecks
@@ -16,26 +16,22 @@ const ReceivedIdsManager_1 = require("./ReceivedIdsManager");
16
16
  class MTProtoState {
17
17
  /**
18
18
  *
19
- `telethon.network.mtprotosender.MTProtoSender` needs to hold a state
20
- in order to be able to encrypt and decrypt incoming/outgoing messages,
21
- as well as generating the message IDs. Instances of this class hold
22
- together all the required information.
19
+ `MTProtoSender` needs to hold a state in order to encrypt and decrypt
20
+ incoming/outgoing messages, as well as generate message IDs. Instances
21
+ of this class hold together all the required information.
23
22
 
24
- It doesn't make sense to use `telethon.sessions.abstract.Session` for
25
- the sender because the sender should *not* be concerned about storing
26
- this information to disk, as one may create as many senders as they
27
- desire to any other data center, or some CDN. Using the same session
28
- for all these is not a good idea as each need their own authkey, and
29
- the concept of "copying" sessions with the unnecessary entities or
30
- updates state for these connections doesn't make sense.
23
+ A `Session` is intentionally not used here: the sender should *not* be
24
+ concerned with persisting this state to disk. Multiple senders may
25
+ exist for different data centers or CDNs, each requiring its own
26
+ authkey "copying" a session along with unrelated entities or update
27
+ state would make no sense.
31
28
 
32
- While it would be possible to have a `MTProtoPlainState` that does no
33
- encryption so that it was usable through the `MTProtoLayer` and thus
34
- avoid the need for a `MTProtoPlainSender`, the `MTProtoLayer` is more
35
- focused to efficiency and this state is also more advanced (since it
36
- supports gzipping and invoking after other message IDs). There are too
37
- many methods that would be needed to make it convenient to use for the
38
- authentication process, at which point the `MTProtoPlainSender` is better
29
+ A `MTProtoPlainState` doing no encryption could in principle be used
30
+ through `MTProtoLayer` and remove the need for `MTProtoPlainSender`,
31
+ but `MTProtoLayer` targets efficient throughput and this state class
32
+ is also more advanced (gzipping, invoking after other message IDs).
33
+ Too many helper methods would be needed to make it convenient during
34
+ authentication, where `MTProtoPlainSender` is the better fit.
39
35
  * @param authKey
40
36
  * @param loggers
41
37
  * @param securityChecks
@@ -1,9 +1,6 @@
1
1
  import { ObfuscatedConnection } from "./Connection";
2
2
  import { AbridgedPacketCodec } from "./TCPAbridged";
3
3
  import { Logger, PromisedNetSockets } from "../../extensions";
4
- /**
5
- * Base proxy configuration shared by all proxy types.
6
- */
7
4
  interface BasicProxyInterface {
8
5
  /** Proxy server IP address. */
9
6
  ip: string;
@@ -38,9 +35,13 @@ interface BasicProxyInterface {
38
35
  */
39
36
  export type MTProxyType = BasicProxyInterface & {
40
37
  /**
41
- * The proxy secret (hex or base64 encoded).
42
- * Must decode to exactly 16 bytes. Secrets prefixed with `dd` (fake-TLS)
43
- * are also supported the leading byte is stripped automatically.
38
+ * The proxy secret (hex or base64 encoded). Three formats are accepted:
39
+ * - 16-byte plain secret.
40
+ * - 17 bytes with a leading `dd` byte random-padding mode.
41
+ * - A leading `ee` byte + 16 bytes of secret + UTF-8 SNI domain bytes —
42
+ * fake-TLS mode. The connection is wrapped in a spoofed TLS 1.2
43
+ * handshake and all MTProxy bytes are tunneled inside TLS
44
+ * application_data records.
44
45
  */
45
46
  secret: string;
46
47
  /** Must be `true` to identify this as an MTProxy configuration. */
@@ -50,12 +51,11 @@ export type MTProxyType = BasicProxyInterface & {
50
51
  * Configuration for SOCKS4/SOCKS5 proxy connections.
51
52
  *
52
53
  * SOCKS proxies are handled at the socket level via the `socks` package and
53
- * work transparently with any connection type (Full, Abridged, Obfuscated).
54
- * SOCKS5 supports username/password authentication.
54
+ * work transparently with any connection type. SOCKS5 supports username/
55
+ * password authentication.
55
56
  *
56
57
  * @example
57
58
  * ```ts
58
- * // SOCKS5 with authentication
59
59
  * const client = new TelegramClient(session, apiId, apiHash, {
60
60
  * proxy: {
61
61
  * socksType: 5,
@@ -65,15 +65,6 @@ export type MTProxyType = BasicProxyInterface & {
65
65
  * password: "pass",
66
66
  * },
67
67
  * });
68
- *
69
- * // SOCKS4 (no authentication)
70
- * const client = new TelegramClient(session, apiId, apiHash, {
71
- * proxy: {
72
- * socksType: 4,
73
- * ip: "127.0.0.1",
74
- * port: 1080,
75
- * },
76
- * });
77
68
  * ```
78
69
  */
79
70
  export type SocksProxyType = BasicProxyInterface & {
@@ -81,32 +72,10 @@ export type SocksProxyType = BasicProxyInterface & {
81
72
  socksType: 4 | 5;
82
73
  };
83
74
  /**
84
- * Union type for all supported proxy configurations.
85
- *
86
- * Pass to `TelegramClientParams.proxy` to route all connections through a proxy.
87
- *
88
- * - {@link MTProxyType} — Telegram MTProxy (obfuscated, secret-based)
89
- * - {@link SocksProxyType} — SOCKS4/SOCKS5 (general-purpose, optional auth)
75
+ * Union type for all supported proxy configurations. Pass to
76
+ * `TelegramClientParams.proxy` to route all connections through a proxy.
90
77
  */
91
78
  export type ProxyInterface = MTProxyType | SocksProxyType;
92
- /**
93
- * Handles the MTProxy obfuscation layer: generates the initial handshake header
94
- * and wraps all reads/writes in AES-CTR encryption keyed by the proxy secret.
95
- * @internal
96
- */
97
- declare class MTProxyIO {
98
- header?: Buffer;
99
- private connection;
100
- private _encrypt?;
101
- private _decrypt?;
102
- private _packetClass;
103
- private _secret;
104
- private _dcId;
105
- constructor(connection: TCPMTProxy);
106
- initHeader(): Promise<void>;
107
- read(n: number): Promise<Buffer<any>>;
108
- write(data: Buffer): void;
109
- }
110
79
  interface TCPMTProxyInterfaceParams {
111
80
  ip: string;
112
81
  port: number;
@@ -116,25 +85,47 @@ interface TCPMTProxyInterfaceParams {
116
85
  socket: typeof PromisedNetSockets;
117
86
  }
118
87
  /**
119
- * Connection that routes traffic through a Telegram MTProxy server.
88
+ * Generates the MTProxy obfuscation header and wraps every subsequent
89
+ * read/write in AES-CTR. MTProxy derives mirrored CTR streams from the same
90
+ * 64-byte header — bytes 8..56 supply the client→server key/iv, and the
91
+ * reversed copy supplies the server→client side.
120
92
  *
121
- * Connects to the proxy address (not Telegram directly), performs the
122
- * MTProxy obfuscated handshake using the provided secret, and then tunnels
123
- * all MTProto traffic through the proxy with AES-CTR encryption.
93
+ * @internal
94
+ */
95
+ declare class MTProxyIO {
96
+ header?: Buffer;
97
+ private readonly stream;
98
+ private readonly packetCodec;
99
+ private readonly secret;
100
+ private readonly dcId;
101
+ private encryptor?;
102
+ private decryptor?;
103
+ constructor(connection: TCPMTProxy);
104
+ initHeader(): Promise<void>;
105
+ read(n: number): Promise<Buffer>;
106
+ write(data: Buffer): void;
107
+ }
108
+ /**
109
+ * Connection that routes traffic through a Telegram MTProxy server. Connects
110
+ * to the proxy address (not Telegram directly), performs the MTProxy
111
+ * obfuscated handshake, and tunnels MTProto traffic through with AES-CTR
112
+ * encryption. When the secret carries a fake-TLS prefix the underlying
113
+ * socket is first wrapped in a {@link FakeTlsSocket}.
124
114
  *
125
- * Not intended to be used directly — use {@link ConnectionTCPMTProxyAbridged} instead,
126
- * or simply pass an {@link MTProxyType} config to `TelegramClientParams.proxy`
115
+ * Not intended to be used directly — use {@link ConnectionTCPMTProxyAbridged}
116
+ * instead, or pass an {@link MTProxyType} config to `TelegramClientParams.proxy`
127
117
  * and the client will select the correct connection class automatically.
128
118
  */
129
119
  export declare class TCPMTProxy extends ObfuscatedConnection {
130
120
  ObfuscatedIO: typeof MTProxyIO;
131
121
  _secret: Buffer;
132
- constructor({ ip, port, dcId, loggers, proxy, socket, }: TCPMTProxyInterfaceParams);
122
+ _fakeTlsDomain?: string;
123
+ constructor({ dcId, loggers, proxy, socket, }: TCPMTProxyInterfaceParams);
124
+ _initConn(): Promise<void>;
133
125
  }
134
126
  /**
135
- * MTProxy connection using the Abridged packet codec.
136
- *
137
- * This is the connection class automatically selected when `proxy.MTProxy` is `true`.
127
+ * MTProxy connection using the Abridged packet codec — automatically selected
128
+ * when `proxy.MTProxy` is `true`.
138
129
  */
139
130
  export declare class ConnectionTCPMTProxyAbridged extends TCPMTProxy {
140
131
  PacketCodecClass: typeof AbridgedPacketCodec;
@@ -1,136 +1,403 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ConnectionTCPMTProxyAbridged = exports.TCPMTProxy = void 0;
4
+ const node_crypto_1 = require("node:crypto");
4
5
  const Connection_1 = require("./Connection");
5
6
  const TCPAbridged_1 = require("./TCPAbridged");
6
7
  const Helpers_1 = require("../../Helpers");
7
8
  const CTR_1 = require("../../crypto/CTR");
9
+ const SECRET_LEN = 16;
10
+ const PREFIX_FAKE_TLS = 0xee;
11
+ const PREFIX_DD_PADDING = 0xdd;
12
+ function decodeSecret(input) {
13
+ return /^[0-9a-f]+$/i.test(input)
14
+ ? Buffer.from(input, "hex")
15
+ : Buffer.from(input, "base64");
16
+ }
17
+ function parseProxySecret(input) {
18
+ if (!input) {
19
+ throw new Error("MTProxy: secret is required");
20
+ }
21
+ const raw = decodeSecret(input);
22
+ if (raw.length > 1 + SECRET_LEN && raw[0] === PREFIX_FAKE_TLS) {
23
+ return {
24
+ key: Buffer.from(raw.subarray(1, 1 + SECRET_LEN)),
25
+ fakeTlsDomain: raw.subarray(1 + SECRET_LEN).toString("utf8"),
26
+ };
27
+ }
28
+ if (raw.length === 1 + SECRET_LEN && raw[0] === PREFIX_DD_PADDING) {
29
+ return { key: Buffer.from(raw.subarray(1)) };
30
+ }
31
+ if (raw.length === SECRET_LEN) {
32
+ return { key: raw };
33
+ }
34
+ throw new Error(`MTProxy: secret must be ${SECRET_LEN} bytes, ` +
35
+ `${1 + SECRET_LEN} with 0xdd prefix, ` +
36
+ `or 0xee + ${SECRET_LEN} bytes + domain — got ${raw.length} bytes`);
37
+ }
38
+ const OBF_HEADER_LEN = 64;
39
+ const OBF_TAG_OFFSET = 56;
40
+ const OBF_DC_OFFSET = 60;
41
+ /** Header prefixes that collide with HTTP or other protocol sniffers. */
42
+ const FORBIDDEN_HEADER_PREFIXES = [
43
+ Buffer.from("50567247", "hex"),
44
+ Buffer.from("474554", "hex"),
45
+ Buffer.from("504f5354", "hex"),
46
+ Buffer.from("eeeeeeee", "hex"),
47
+ ];
48
+ function pickObfuscationHeader() {
49
+ while (true) {
50
+ const candidate = (0, Helpers_1.generateRandomBytes)(OBF_HEADER_LEN);
51
+ if (candidate[0] === 0xef)
52
+ continue;
53
+ if (candidate.subarray(4, 8).equals(Buffer.alloc(4)))
54
+ continue;
55
+ const head4 = candidate.subarray(0, 4);
56
+ if (FORBIDDEN_HEADER_PREFIXES.some((p) => p.equals(head4)))
57
+ continue;
58
+ return candidate;
59
+ }
60
+ }
8
61
  /**
9
- * Handles the MTProxy obfuscation layer: generates the initial handshake header
10
- * and wraps all reads/writes in AES-CTR encryption keyed by the proxy secret.
62
+ * Generates the MTProxy obfuscation header and wraps every subsequent
63
+ * read/write in AES-CTR. MTProxy derives mirrored CTR streams from the same
64
+ * 64-byte header — bytes 8..56 supply the client→server key/iv, and the
65
+ * reversed copy supplies the server→client side.
66
+ *
11
67
  * @internal
12
68
  */
13
69
  class MTProxyIO {
14
70
  constructor(connection) {
15
- this.header = undefined;
16
- this.connection = connection.socket;
17
- this._packetClass =
71
+ this.stream = connection.socket;
72
+ this.packetCodec =
18
73
  connection.PacketCodecClass;
19
- this._secret = connection._secret;
20
- this._dcId = connection._dcId;
74
+ this.secret = connection._secret;
75
+ this.dcId = connection._dcId;
21
76
  }
22
77
  async initHeader() {
23
- let secret = this._secret;
24
- const isDD = secret.length == 17 && secret[0] == 0xdd;
25
- secret = isDD ? secret.slice(1) : secret;
26
- if (secret.length != 16) {
27
- throw new Error("MTProxy secret must be a hex-string representing 16 bytes");
28
- }
29
- const keywords = [
30
- Buffer.from("50567247", "hex"),
31
- Buffer.from("474554", "hex"),
32
- Buffer.from("504f5354", "hex"),
33
- Buffer.from("eeeeeeee", "hex"),
34
- ];
35
- let random;
36
- // eslint-disable-next-line no-constant-condition
37
- while (true) {
38
- random = (0, Helpers_1.generateRandomBytes)(64);
39
- if (random[0] !== 0xef &&
40
- !random.slice(4, 8).equals(Buffer.alloc(4))) {
41
- let ok = true;
42
- for (const key of keywords) {
43
- if (key.equals(random.slice(0, 4))) {
44
- ok = false;
45
- break;
46
- }
47
- }
48
- if (ok) {
49
- break;
50
- }
51
- }
78
+ if (this.secret.length !== SECRET_LEN) {
79
+ throw new Error(`MTProxy: secret must be ${SECRET_LEN} bytes, got ${this.secret.length}`);
52
80
  }
53
- random = random.toJSON().data;
54
- const randomReversed = Buffer.from(random.slice(8, 56)).reverse();
55
- // Encryption has "continuous buffer" enabled
56
- const encryptKey = await (0, Helpers_1.sha256)(Buffer.concat([Buffer.from(random.slice(8, 40)), secret]));
57
- const encryptIv = Buffer.from(random.slice(40, 56));
58
- const decryptKey = await (0, Helpers_1.sha256)(Buffer.concat([Buffer.from(randomReversed.slice(0, 32)), secret]));
59
- const decryptIv = Buffer.from(randomReversed.slice(32, 48));
60
- const encryptor = new CTR_1.CTR(encryptKey, encryptIv);
61
- const decryptor = new CTR_1.CTR(decryptKey, decryptIv);
62
- random = Buffer.concat([
63
- Buffer.from(random.slice(0, 56)),
64
- this._packetClass.obfuscateTag,
65
- Buffer.from(random.slice(60)),
66
- ]);
67
- const dcIdBytes = Buffer.alloc(2);
68
- dcIdBytes.writeInt8(this._dcId, 0);
69
- random = Buffer.concat([
70
- Buffer.from(random.slice(0, 60)),
71
- dcIdBytes,
72
- Buffer.from(random.slice(62)),
73
- ]);
74
- random = Buffer.concat([
75
- Buffer.from(random.slice(0, 56)),
76
- Buffer.from(encryptor.encrypt(random).slice(56, 64)),
77
- Buffer.from(random.slice(64)),
78
- ]);
79
- this.header = random;
80
- this._encrypt = encryptor;
81
- this._decrypt = decryptor;
81
+ const header = pickObfuscationHeader();
82
+ const reversed = Buffer.from(header.subarray(8, 56)).reverse();
83
+ const encryptKey = await (0, Helpers_1.sha256)(Buffer.concat([header.subarray(8, 40), this.secret]));
84
+ const encryptIv = Buffer.from(header.subarray(40, 56));
85
+ const decryptKey = await (0, Helpers_1.sha256)(Buffer.concat([reversed.subarray(0, 32), this.secret]));
86
+ const decryptIv = Buffer.from(reversed.subarray(32, 48));
87
+ this.encryptor = new CTR_1.CTR(encryptKey, encryptIv);
88
+ this.decryptor = new CTR_1.CTR(decryptKey, decryptIv);
89
+ // Stamp protocol tag and DC id (low byte first, byte 61 zeroed —
90
+ // matches gramjs / official client behavior, even for negative DC ids).
91
+ this.packetCodec.obfuscateTag.copy(header, OBF_TAG_OFFSET);
92
+ header.writeInt8(this.dcId, OBF_DC_OFFSET);
93
+ header[OBF_DC_OFFSET + 1] = 0;
94
+ // Re-encrypt the stamped tail in place — the CTR counter ends up
95
+ // advanced by all 64 bytes, matching the server's view.
96
+ const encryptedTail = this.encryptor
97
+ .encrypt(header)
98
+ .subarray(OBF_TAG_OFFSET, OBF_HEADER_LEN);
99
+ encryptedTail.copy(header, OBF_TAG_OFFSET);
100
+ this.header = header;
82
101
  }
83
102
  async read(n) {
84
- const data = await this.connection.readExactly(n);
85
- return this._decrypt.encrypt(data);
103
+ const data = await this.stream.readExactly(n);
104
+ return this.decryptor.encrypt(data);
86
105
  }
87
106
  write(data) {
88
- this.connection.write(this._encrypt.encrypt(data));
107
+ this.stream.write(this.encryptor.encrypt(data));
108
+ }
109
+ }
110
+ const TLS_RECORD_HEADER_LEN = 5;
111
+ const TLS_RECORD_MAX_PAYLOAD = 16384;
112
+ const TLS_RECORD_HANDSHAKE = 0x16;
113
+ const TLS_RECORD_CHANGE_CIPHER_SPEC = 0x14;
114
+ const TLS_RECORD_APPLICATION_DATA = 0x17;
115
+ const TLS_APP_DATA_PREFIX = Buffer.from([0x17, 0x03, 0x03]);
116
+ const FAKE_TLS_HELLO_SIZE = 517;
117
+ const FAKE_TLS_HELLO_RANDOM_OFFSET = 11;
118
+ const FAKE_TLS_HELLO_RANDOM_LEN = 32;
119
+ /** 5-byte TLS record + 4-byte handshake header + 2-byte version = 114. */
120
+ const FAKE_TLS_FIXED_HEADER_SIZE = 114;
121
+ const FAKE_TLS_RANDOM_TIMESTAMP_XOR_OFFSET = 28;
122
+ const GREASE = 0x0a;
123
+ async function readTlsRecord(stream) {
124
+ const hdr = await stream.readExactly(TLS_RECORD_HEADER_LEN);
125
+ const type = hdr[0];
126
+ const len = hdr.readUInt16BE(3);
127
+ const payload = await stream.readExactly(len);
128
+ return { type, payload };
129
+ }
130
+ function tlsRecordTypeName(type) {
131
+ switch (type) {
132
+ case TLS_RECORD_HANDSHAKE:
133
+ return "Handshake";
134
+ case TLS_RECORD_CHANGE_CIPHER_SPEC:
135
+ return "ChangeCipherSpec";
136
+ case TLS_RECORD_APPLICATION_DATA:
137
+ return "ApplicationData";
138
+ default:
139
+ return `0x${type.toString(16).padStart(2, "0")}`;
140
+ }
141
+ }
142
+ function tlsExtension(type, body) {
143
+ const content = Buffer.isBuffer(body) ? body : Buffer.from(body);
144
+ const out = Buffer.alloc(4 + content.length);
145
+ out.writeUInt16BE(type, 0);
146
+ out.writeUInt16BE(content.length, 2);
147
+ content.copy(out, 4);
148
+ return out;
149
+ }
150
+ function sniExtension(domain) {
151
+ const dom = Buffer.from(domain, "utf8");
152
+ const body = Buffer.alloc(5 + dom.length);
153
+ body.writeUInt16BE(dom.length + 3, 0);
154
+ body.writeUInt8(0x00, 2);
155
+ body.writeUInt16BE(dom.length, 3);
156
+ dom.copy(body, 5);
157
+ return tlsExtension(0x0000, body);
158
+ }
159
+ function keyShareExtension() {
160
+ const x25519Pub = (0, Helpers_1.generateRandomBytes)(32);
161
+ const entries = Buffer.concat([
162
+ Buffer.from([GREASE, GREASE, 0x00, 0x01, 0x00]),
163
+ Buffer.from([0x00, 0x1d, 0x00, 0x20]),
164
+ x25519Pub,
165
+ ]);
166
+ const body = Buffer.alloc(2 + entries.length);
167
+ body.writeUInt16BE(entries.length, 0);
168
+ entries.copy(body, 2);
169
+ return tlsExtension(0x0033, body);
170
+ }
171
+ /**
172
+ * Input-independent extensions in Chrome 105+ order. SNI and key_share are
173
+ * spliced in at runtime (see `buildClientHello`). Layout is intentionally
174
+ * stable for JA3 fingerprint similarity.
175
+ */
176
+ const STATIC_EXTENSIONS = [
177
+ tlsExtension(0x0a0a, []),
178
+ tlsExtension(0x0017, []),
179
+ tlsExtension(0xff01, [0x00]),
180
+ tlsExtension(0x000a, [
181
+ 0x00, 0x08, GREASE, GREASE, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18,
182
+ ]),
183
+ tlsExtension(0x000b, [0x01, 0x00]),
184
+ tlsExtension(0x0023, []),
185
+ tlsExtension(0x0010, [
186
+ 0x00, 0x0c, 0x02, 0x68, 0x32, 0x08, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x31,
187
+ 0x2e, 0x31,
188
+ ]),
189
+ tlsExtension(0x0005, [0x01, 0x00, 0x00, 0x00, 0x00]),
190
+ tlsExtension(0x000d, [
191
+ 0x00, 0x10, 0x04, 0x03, 0x08, 0x04, 0x04, 0x01, 0x05, 0x03, 0x08, 0x05,
192
+ 0x05, 0x01, 0x08, 0x06, 0x06, 0x01,
193
+ ]),
194
+ tlsExtension(0x0012, []),
195
+ tlsExtension(0x002d, [0x01, 0x01]),
196
+ tlsExtension(0x002b, [0x06, GREASE, GREASE, 0x03, 0x04, 0x03, 0x03]),
197
+ tlsExtension(0x001b, [0x02, 0x00, 0x02]),
198
+ tlsExtension(0x0a0a, [0x00]),
199
+ ];
200
+ const STATIC_EXTENSIONS_LEN = STATIC_EXTENSIONS.reduce((s, e) => s + e.length, 0);
201
+ const CIPHER_SUITES = Buffer.from([
202
+ GREASE, GREASE,
203
+ 0x13, 0x01, 0x13, 0x02, 0x13, 0x03,
204
+ 0xc0, 0x2b, 0xc0, 0x2f, 0xc0, 0x2c, 0xc0, 0x30,
205
+ 0xcc, 0xa9, 0xcc, 0xa8,
206
+ 0xc0, 0x13, 0xc0, 0x14,
207
+ 0x00, 0x9c, 0x00, 0x9d, 0x00, 0x2f, 0x00, 0x35,
208
+ ]);
209
+ /**
210
+ * Builds the 517-byte Chrome-like TLS 1.2 ClientHello. The 32-byte `random`
211
+ * field at offset 11 is left zeroed — the caller stamps it with the
212
+ * HMAC-derived value before sending.
213
+ */
214
+ function buildClientHello(domain, sessionId) {
215
+ const sni = sniExtension(domain);
216
+ const keyShare = keyShareExtension();
217
+ let extensionsLen = STATIC_EXTENSIONS_LEN + sni.length + keyShare.length;
218
+ const paddingPayloadLen = FAKE_TLS_HELLO_SIZE - FAKE_TLS_FIXED_HEADER_SIZE - extensionsLen - 4;
219
+ if (paddingPayloadLen < 0) {
220
+ throw new Error(`FakeTLS: domain too long (${domain.length} bytes); ClientHello overflows by ${-paddingPayloadLen} bytes`);
221
+ }
222
+ const padding = tlsExtension(0x0015, Buffer.alloc(paddingPayloadLen));
223
+ extensionsLen += padding.length;
224
+ // SNI follows the leading GREASE; key_share follows the SCT extension.
225
+ const KEY_SHARE_INSERT_AFTER = 11;
226
+ const extensions = [
227
+ STATIC_EXTENSIONS[0],
228
+ sni,
229
+ ...STATIC_EXTENSIONS.slice(1, KEY_SHARE_INSERT_AFTER + 1),
230
+ keyShare,
231
+ ...STATIC_EXTENSIONS.slice(KEY_SHARE_INSERT_AFTER + 1),
232
+ padding,
233
+ ];
234
+ const hello = Buffer.concat([
235
+ Buffer.from([0x16, 0x03, 0x01, 0x02, 0x00]),
236
+ Buffer.from([0x01, 0x00, 0x01, 0xfc]),
237
+ Buffer.from([0x03, 0x03]),
238
+ Buffer.alloc(FAKE_TLS_HELLO_RANDOM_LEN),
239
+ Buffer.from([sessionId.length]),
240
+ sessionId,
241
+ Buffer.from([
242
+ (CIPHER_SUITES.length >> 8) & 0xff,
243
+ CIPHER_SUITES.length & 0xff,
244
+ ]),
245
+ CIPHER_SUITES,
246
+ Buffer.from([0x01, 0x00]),
247
+ Buffer.from([(extensionsLen >> 8) & 0xff, extensionsLen & 0xff]),
248
+ ...extensions,
249
+ ]);
250
+ if (hello.length !== FAKE_TLS_HELLO_SIZE) {
251
+ throw new Error(`FakeTLS: ClientHello size mismatch — got ${hello.length}, expected ${FAKE_TLS_HELLO_SIZE}`);
89
252
  }
253
+ return hello;
90
254
  }
91
255
  /**
92
- * Connection that routes traffic through a Telegram MTProxy server.
256
+ * Wraps a {@link PromisedNetSockets} in MTProto fake-TLS framing. On
257
+ * {@link handshake}: sends a Chrome-like ClientHello with the random field
258
+ * stamped to `HMAC-SHA256(secret, hello)` (last 4 bytes XOR'd with unix
259
+ * time), then verifies the server's random equals
260
+ * `HMAC-SHA256(secret, client_random || server_response_zeroed)`. Post-
261
+ * handshake, all bytes ride inside TLS application_data records.
93
262
  *
94
- * Connects to the proxy address (not Telegram directly), performs the
95
- * MTProxy obfuscated handshake using the provided secret, and then tunnels
96
- * all MTProto traffic through the proxy with AES-CTR encryption.
263
+ * Exposes only the subset of {@link PromisedNetSockets} consumed by
264
+ * {@link MTProxyIO} and the {@link ObfuscatedConnection} base class so it
265
+ * can be hot-swapped in place of the raw socket.
97
266
  *
98
- * Not intended to be used directly — use {@link ConnectionTCPMTProxyAbridged} instead,
99
- * or simply pass an {@link MTProxyType} config to `TelegramClientParams.proxy`
267
+ * @internal
268
+ */
269
+ class FakeTlsSocket {
270
+ constructor(inner, secret, domain) {
271
+ this.clientRandom = Buffer.alloc(0);
272
+ this.readBuf = Buffer.alloc(0);
273
+ if (secret.length !== SECRET_LEN) {
274
+ throw new Error(`FakeTLS: secret must be ${SECRET_LEN} bytes, got ${secret.length}`);
275
+ }
276
+ this.inner = inner;
277
+ this.secret = secret;
278
+ this.domain = domain;
279
+ }
280
+ async handshake() {
281
+ const hello = this.sendClientHello();
282
+ this.inner.write(hello);
283
+ await this.readAndVerifyServerHandshake();
284
+ }
285
+ write(data) {
286
+ for (let off = 0; off < data.length; off += TLS_RECORD_MAX_PAYLOAD) {
287
+ const chunkLen = Math.min(TLS_RECORD_MAX_PAYLOAD, data.length - off);
288
+ const hdr = Buffer.alloc(TLS_RECORD_HEADER_LEN);
289
+ TLS_APP_DATA_PREFIX.copy(hdr, 0);
290
+ hdr.writeUInt16BE(chunkLen, 3);
291
+ this.inner.write(Buffer.concat([hdr, data.subarray(off, off + chunkLen)]));
292
+ }
293
+ }
294
+ async readExactly(n) {
295
+ while (this.readBuf.length < n) {
296
+ const { type, payload } = await readTlsRecord(this.inner);
297
+ if (type !== TLS_RECORD_APPLICATION_DATA)
298
+ continue;
299
+ this.readBuf = Buffer.concat([this.readBuf, payload]);
300
+ }
301
+ const out = Buffer.from(this.readBuf.subarray(0, n));
302
+ this.readBuf = this.readBuf.subarray(n);
303
+ return out;
304
+ }
305
+ async close() {
306
+ await this.inner.close();
307
+ }
308
+ sendClientHello() {
309
+ const sessionId = (0, Helpers_1.generateRandomBytes)(32);
310
+ const hello = buildClientHello(this.domain, sessionId);
311
+ const stamp = (0, node_crypto_1.createHmac)("sha256", this.secret).update(hello).digest();
312
+ const now = Math.floor(Date.now() / 1000) >>> 0;
313
+ const lastWord = (stamp.readUInt32BE(FAKE_TLS_RANDOM_TIMESTAMP_XOR_OFFSET) ^ now) >>>
314
+ 0;
315
+ stamp.writeUInt32BE(lastWord, FAKE_TLS_RANDOM_TIMESTAMP_XOR_OFFSET);
316
+ stamp.copy(hello, FAKE_TLS_HELLO_RANDOM_OFFSET);
317
+ this.clientRandom = stamp;
318
+ return hello;
319
+ }
320
+ // Expects the canonical 3-record server response. Anything else fails
321
+ // loudly rather than silently corrupting the downstream MTProxy stream.
322
+ async readAndVerifyServerHandshake() {
323
+ const expectedTypes = [
324
+ TLS_RECORD_HANDSHAKE,
325
+ TLS_RECORD_CHANGE_CIPHER_SPEC,
326
+ TLS_RECORD_APPLICATION_DATA,
327
+ ];
328
+ const chunks = [];
329
+ let serverRandomOffset = -1;
330
+ let offset = 0;
331
+ for (let i = 0; i < expectedTypes.length; i++) {
332
+ const { type, payload } = await readTlsRecord(this.inner);
333
+ if (type !== expectedTypes[i]) {
334
+ throw new Error(`FakeTLS: expected ${tlsRecordTypeName(expectedTypes[i])} at record ${i}, got ${tlsRecordTypeName(type)}`);
335
+ }
336
+ if (i === 0) {
337
+ serverRandomOffset = offset + FAKE_TLS_HELLO_RANDOM_OFFSET;
338
+ }
339
+ const hdr = Buffer.alloc(TLS_RECORD_HEADER_LEN);
340
+ hdr[0] = type;
341
+ hdr[1] = 0x03;
342
+ hdr[2] = 0x03;
343
+ hdr.writeUInt16BE(payload.length, 3);
344
+ chunks.push(hdr, payload);
345
+ offset += TLS_RECORD_HEADER_LEN + payload.length;
346
+ }
347
+ const serverResp = Buffer.concat(chunks);
348
+ const serverRandom = Buffer.from(serverResp.subarray(serverRandomOffset, serverRandomOffset + FAKE_TLS_HELLO_RANDOM_LEN));
349
+ serverResp.fill(0, serverRandomOffset, serverRandomOffset + FAKE_TLS_HELLO_RANDOM_LEN);
350
+ const expected = (0, node_crypto_1.createHmac)("sha256", this.secret)
351
+ .update(this.clientRandom)
352
+ .update(serverResp)
353
+ .digest();
354
+ if (!expected.equals(serverRandom)) {
355
+ throw new Error("FakeTLS: server HMAC verification failed");
356
+ }
357
+ }
358
+ }
359
+ /**
360
+ * Connection that routes traffic through a Telegram MTProxy server. Connects
361
+ * to the proxy address (not Telegram directly), performs the MTProxy
362
+ * obfuscated handshake, and tunnels MTProto traffic through with AES-CTR
363
+ * encryption. When the secret carries a fake-TLS prefix the underlying
364
+ * socket is first wrapped in a {@link FakeTlsSocket}.
365
+ *
366
+ * Not intended to be used directly — use {@link ConnectionTCPMTProxyAbridged}
367
+ * instead, or pass an {@link MTProxyType} config to `TelegramClientParams.proxy`
100
368
  * and the client will select the correct connection class automatically.
101
369
  */
102
370
  class TCPMTProxy extends Connection_1.ObfuscatedConnection {
103
- constructor({ ip, port, dcId, loggers, proxy, socket, }) {
371
+ constructor({ dcId, loggers, proxy, socket, }) {
104
372
  super({
105
373
  ip: proxy.ip,
106
374
  port: proxy.port,
107
- dcId: dcId,
108
- loggers: loggers,
109
- socket: socket,
110
- proxy: proxy,
375
+ dcId,
376
+ loggers,
377
+ socket,
378
+ proxy,
111
379
  });
112
380
  this.ObfuscatedIO = MTProxyIO;
113
381
  if (!("MTProxy" in proxy)) {
114
- throw new Error("This connection only supports MPTProxies");
115
- }
116
- if (!proxy.secret) {
117
- throw new Error("You need to provide the secret for the MTProxy");
382
+ throw new Error("This connection only supports MTProxies");
118
383
  }
119
- if (proxy.secret && proxy.secret.match(/^[0-9a-f]+$/i)) {
120
- // probably hex
121
- this._secret = Buffer.from(proxy.secret, "hex");
122
- }
123
- else {
124
- // probably b64
125
- this._secret = Buffer.from(proxy.secret, "base64");
384
+ const parsed = parseProxySecret(proxy.secret);
385
+ this._secret = parsed.key;
386
+ this._fakeTlsDomain = parsed.fakeTlsDomain;
387
+ }
388
+ async _initConn() {
389
+ if (this._fakeTlsDomain) {
390
+ const tls = new FakeTlsSocket(this.socket, this._secret, this._fakeTlsDomain);
391
+ await tls.handshake();
392
+ this.socket = tls;
126
393
  }
394
+ await super._initConn();
127
395
  }
128
396
  }
129
397
  exports.TCPMTProxy = TCPMTProxy;
130
398
  /**
131
- * MTProxy connection using the Abridged packet codec.
132
- *
133
- * This is the connection class automatically selected when `proxy.MTProxy` is `true`.
399
+ * MTProxy connection using the Abridged packet codec — automatically selected
400
+ * when `proxy.MTProxy` is `true`.
134
401
  */
135
402
  class ConnectionTCPMTProxyAbridged extends TCPMTProxy {
136
403
  constructor() {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "teleproto",
3
- "version": "1.225.1",
4
- "description": "NodeJS MTProto API Telegram client library,",
3
+ "version": "1.225.3",
4
+ "description": "Telegram MTProto API client library written in TypeScript",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
7
7
  "repository": {
@@ -15,13 +15,11 @@
15
15
  "keywords": [
16
16
  "telegram",
17
17
  "mtproto",
18
- "teleproto",
19
- "client",
20
- "nodejs",
18
+ "userbot",
21
19
  "typescript",
22
- "api"
20
+ "nodejs"
23
21
  ],
24
- "homepage": "https://github.com/sanyok12345/teleproto#readme",
22
+ "homepage": "https://docs.teleproto.dev",
25
23
  "dependencies": {
26
24
  "async-mutex": "^0.3.0",
27
25
  "big-integer": "^1.6.48",
@@ -63,7 +63,7 @@ export declare abstract class Session {
63
63
  * to suit several purposes (e.g. user only provided its ID or wishes
64
64
  * to use a cached username to avoid extra RPC).
65
65
  */
66
- abstract getInputEntity(key: EntityLike): Api.TypeInputPeer;
66
+ abstract getInputEntity(key: EntityLike): Api.TypeInputPeer | Promise<Api.TypeInputPeer>;
67
67
  /**
68
68
  * Returns an ID of the takeout process initialized for this session,
69
69
  * or `None` if there's no were any unfinished takeout requests.
@@ -97,10 +97,10 @@ export declare abstract class Session {
97
97
  */
98
98
  abstract save(): void;
99
99
  /**
100
- * Called upon client.log_out(). Should delete the stored
100
+ * Called upon client.logOut(). Should delete the stored
101
101
  * information from disk since it's not valid anymore.
102
102
  */
103
- abstract delete(): void;
103
+ abstract delete(): void | Promise<void>;
104
104
  /**
105
105
  * Processes the input ``TLObject`` or ``list`` and saves
106
106
  * whatever information is relevant (e.g., ID or access hash).
@@ -115,7 +115,15 @@ class MemorySession extends Abstract_1.Session {
115
115
  close() { }
116
116
  save() { }
117
117
  async load() { }
118
- delete() { }
118
+ delete() {
119
+ this._authKey = undefined;
120
+ this._dcId = 0;
121
+ this._serverAddress = undefined;
122
+ this._port = undefined;
123
+ this._dcAuthKeys.clear();
124
+ this._entities.clear();
125
+ this._updateStates = {};
126
+ }
119
127
  _entityValuesToRow(id, hash, username, phone, name) {
120
128
  // While this is a simple implementation it might be overrode by,
121
129
  // other classes so they don't need to implement the plural form
@@ -24,6 +24,7 @@ export declare class StoreSession extends MemorySession {
24
24
  get testServers(): boolean;
25
25
  set authKey(value: AuthKey | undefined);
26
26
  get authKey(): AuthKey | undefined;
27
+ delete(): void;
27
28
  processEntities(tlo: any): void;
28
29
  getEntityRowsById(id: string | bigInt.BigInteger, exact?: boolean): any;
29
30
  }
@@ -113,6 +113,10 @@ class StoreSession extends Memory_1.MemorySession {
113
113
  get authKey() {
114
114
  return this._authKey;
115
115
  }
116
+ delete() {
117
+ this.store.clearAll();
118
+ super.delete();
119
+ }
116
120
  processEntities(tlo) {
117
121
  const rows = this._entitiesToRows(tlo);
118
122
  if (!rows) {
@@ -28,5 +28,6 @@ export declare class StringSession extends MemorySession {
28
28
  */
29
29
  static decode(x: string): Buffer<ArrayBuffer>;
30
30
  load(): Promise<void>;
31
+ delete(): void;
31
32
  save(): string;
32
33
  }
@@ -88,6 +88,10 @@ class StringSession extends Memory_1.MemorySession {
88
88
  await this._authKey.setKey(this._key);
89
89
  }
90
90
  }
91
+ delete() {
92
+ this._key = undefined;
93
+ super.delete();
94
+ }
91
95
  save() {
92
96
  if (!this.authKey || !this.serverAddress || !this.port) {
93
97
  return "";