@bruch/max-client 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +168 -0
  3. package/UPSTREAM.md +10 -0
  4. package/dist/api/account.d.ts +24 -0
  5. package/dist/api/account.d.ts.map +1 -0
  6. package/dist/api/auth.d.ts +57 -0
  7. package/dist/api/auth.d.ts.map +1 -0
  8. package/dist/api/bots.d.ts +8 -0
  9. package/dist/api/bots.d.ts.map +1 -0
  10. package/dist/api/chats.d.ts +41 -0
  11. package/dist/api/chats.d.ts.map +1 -0
  12. package/dist/api/facade.d.ts +19 -0
  13. package/dist/api/facade.d.ts.map +1 -0
  14. package/dist/api/index.d.ts +10 -0
  15. package/dist/api/index.d.ts.map +1 -0
  16. package/dist/api/messages.d.ts +35 -0
  17. package/dist/api/messages.d.ts.map +1 -0
  18. package/dist/api/response.d.ts +7 -0
  19. package/dist/api/response.d.ts.map +1 -0
  20. package/dist/api/runtime.d.ts +19 -0
  21. package/dist/api/runtime.d.ts.map +1 -0
  22. package/dist/api/uploads.d.ts +23 -0
  23. package/dist/api/uploads.d.ts.map +1 -0
  24. package/dist/api/users.d.ts +25 -0
  25. package/dist/api/users.d.ts.map +1 -0
  26. package/dist/app.d.ts +55 -0
  27. package/dist/app.d.ts.map +1 -0
  28. package/dist/auth/flows.d.ts +27 -0
  29. package/dist/auth/flows.d.ts.map +1 -0
  30. package/dist/auth/index.d.ts +3 -0
  31. package/dist/auth/index.d.ts.map +1 -0
  32. package/dist/auth/index.js +109 -0
  33. package/dist/auth/index.js.map +11 -0
  34. package/dist/auth/providers.d.ts +11 -0
  35. package/dist/auth/providers.d.ts.map +1 -0
  36. package/dist/client.d.ts +139 -0
  37. package/dist/client.d.ts.map +1 -0
  38. package/dist/config.d.ts +80 -0
  39. package/dist/config.d.ts.map +1 -0
  40. package/dist/connection/connection.d.ts +25 -0
  41. package/dist/connection/connection.d.ts.map +1 -0
  42. package/dist/connection/index.d.ts +4 -0
  43. package/dist/connection/index.d.ts.map +1 -0
  44. package/dist/connection/pending.d.ts +9 -0
  45. package/dist/connection/pending.d.ts.map +1 -0
  46. package/dist/connection/readers.d.ts +17 -0
  47. package/dist/connection/readers.d.ts.map +1 -0
  48. package/dist/dispatch/dispatcher.d.ts +16 -0
  49. package/dist/dispatch/dispatcher.d.ts.map +1 -0
  50. package/dist/dispatch/index.d.ts +3 -0
  51. package/dist/dispatch/index.d.ts.map +1 -0
  52. package/dist/dispatch/router.d.ts +64 -0
  53. package/dist/dispatch/router.d.ts.map +1 -0
  54. package/dist/errors.d.ts +24 -0
  55. package/dist/errors.d.ts.map +1 -0
  56. package/dist/files/file.d.ts +29 -0
  57. package/dist/files/file.d.ts.map +1 -0
  58. package/dist/files/index.d.ts +2 -0
  59. package/dist/files/index.d.ts.map +1 -0
  60. package/dist/files/index.js +95 -0
  61. package/dist/files/index.js.map +10 -0
  62. package/dist/formatting/index.d.ts +2 -0
  63. package/dist/formatting/index.d.ts.map +1 -0
  64. package/dist/formatting/markdown.d.ts +13 -0
  65. package/dist/formatting/markdown.d.ts.map +1 -0
  66. package/dist/index.d.ts +15 -0
  67. package/dist/index.d.ts.map +1 -0
  68. package/dist/index.js +3967 -0
  69. package/dist/index.js.map +48 -0
  70. package/dist/logging.d.ts +10 -0
  71. package/dist/logging.d.ts.map +1 -0
  72. package/dist/protocol/compression.d.ts +8 -0
  73. package/dist/protocol/compression.d.ts.map +1 -0
  74. package/dist/protocol/enums.d.ts +175 -0
  75. package/dist/protocol/enums.d.ts.map +1 -0
  76. package/dist/protocol/index.d.ts +6 -0
  77. package/dist/protocol/index.d.ts.map +1 -0
  78. package/dist/protocol/index.js +438 -0
  79. package/dist/protocol/index.js.map +13 -0
  80. package/dist/protocol/tcp.d.ts +35 -0
  81. package/dist/protocol/tcp.d.ts.map +1 -0
  82. package/dist/protocol/types.d.ts +38 -0
  83. package/dist/protocol/types.d.ts.map +1 -0
  84. package/dist/protocol/websocket.d.ts +7 -0
  85. package/dist/protocol/websocket.d.ts.map +1 -0
  86. package/dist/session/index.d.ts +3 -0
  87. package/dist/session/index.d.ts.map +1 -0
  88. package/dist/session/index.js +179 -0
  89. package/dist/session/index.js.map +12 -0
  90. package/dist/session/store.d.ts +18 -0
  91. package/dist/session/store.d.ts.map +1 -0
  92. package/dist/session/types.d.ts +29 -0
  93. package/dist/session/types.d.ts.map +1 -0
  94. package/dist/telemetry/index.d.ts +4 -0
  95. package/dist/telemetry/index.d.ts.map +1 -0
  96. package/dist/telemetry/index.js +831 -0
  97. package/dist/telemetry/index.js.map +17 -0
  98. package/dist/telemetry/navigation.d.ts +41 -0
  99. package/dist/telemetry/navigation.d.ts.map +1 -0
  100. package/dist/telemetry/payloads.d.ts +30 -0
  101. package/dist/telemetry/payloads.d.ts.map +1 -0
  102. package/dist/telemetry/service.d.ts +53 -0
  103. package/dist/telemetry/service.d.ts.map +1 -0
  104. package/dist/transport/base.d.ts +18 -0
  105. package/dist/transport/base.d.ts.map +1 -0
  106. package/dist/transport/index.d.ts +4 -0
  107. package/dist/transport/index.d.ts.map +1 -0
  108. package/dist/transport/tcp.d.ts +28 -0
  109. package/dist/transport/tcp.d.ts.map +1 -0
  110. package/dist/transport/websocket.d.ts +14 -0
  111. package/dist/transport/websocket.d.ts.map +1 -0
  112. package/dist/types/attachments.d.ts +18 -0
  113. package/dist/types/attachments.d.ts.map +1 -0
  114. package/dist/types/index.d.ts +4 -0
  115. package/dist/types/index.d.ts.map +1 -0
  116. package/dist/types/index.js +376 -0
  117. package/dist/types/index.js.map +12 -0
  118. package/dist/types/models.d.ts +210 -0
  119. package/dist/types/models.d.ts.map +1 -0
  120. package/dist/types/utils.d.ts +10 -0
  121. package/dist/types/utils.d.ts.map +1 -0
  122. package/package.json +95 -0
  123. package/src/api/account.ts +93 -0
  124. package/src/api/auth.ts +312 -0
  125. package/src/api/bots.ts +21 -0
  126. package/src/api/chats.ts +234 -0
  127. package/src/api/facade.ts +28 -0
  128. package/src/api/index.ts +9 -0
  129. package/src/api/messages.ts +230 -0
  130. package/src/api/response.ts +24 -0
  131. package/src/api/runtime.ts +27 -0
  132. package/src/api/uploads.ts +91 -0
  133. package/src/api/users.ts +68 -0
  134. package/src/app.ts +271 -0
  135. package/src/auth/flows.ts +103 -0
  136. package/src/auth/index.ts +2 -0
  137. package/src/auth/providers.ts +30 -0
  138. package/src/client.ts +506 -0
  139. package/src/config.ts +161 -0
  140. package/src/connection/connection.ts +93 -0
  141. package/src/connection/index.ts +3 -0
  142. package/src/connection/pending.ts +49 -0
  143. package/src/connection/readers.ts +33 -0
  144. package/src/dispatch/dispatcher.ts +194 -0
  145. package/src/dispatch/index.ts +2 -0
  146. package/src/dispatch/router.ts +149 -0
  147. package/src/errors.ts +41 -0
  148. package/src/files/file.ts +92 -0
  149. package/src/files/index.ts +1 -0
  150. package/src/formatting/index.ts +1 -0
  151. package/src/formatting/markdown.ts +123 -0
  152. package/src/index.ts +15 -0
  153. package/src/logging.ts +67 -0
  154. package/src/protocol/compression.ts +69 -0
  155. package/src/protocol/enums.ts +175 -0
  156. package/src/protocol/index.ts +5 -0
  157. package/src/protocol/tcp.ts +139 -0
  158. package/src/protocol/types.ts +45 -0
  159. package/src/protocol/websocket.ts +47 -0
  160. package/src/session/index.ts +2 -0
  161. package/src/session/store.ts +154 -0
  162. package/src/session/types.ts +45 -0
  163. package/src/telemetry/index.ts +3 -0
  164. package/src/telemetry/navigation.ts +160 -0
  165. package/src/telemetry/payloads.ts +84 -0
  166. package/src/telemetry/service.ts +194 -0
  167. package/src/transport/base.ts +43 -0
  168. package/src/transport/index.ts +3 -0
  169. package/src/transport/tcp.ts +296 -0
  170. package/src/transport/websocket.ts +63 -0
  171. package/src/types/attachments.ts +24 -0
  172. package/src/types/index.ts +3 -0
  173. package/src/types/models.ts +461 -0
  174. package/src/types/utils.ts +45 -0
@@ -0,0 +1,10 @@
1
+ export type MaxId = bigint;
2
+ export declare function toMaxId(value: unknown, field?: string): MaxId;
3
+ export declare function optionalMaxId(value: unknown, field?: string): MaxId | null;
4
+ export declare function asRecord(value: unknown, field?: string): Record<string, unknown>;
5
+ export declare function optionalRecord(value: unknown): Record<string, unknown> | null;
6
+ export declare function asString(value: unknown, fallback?: string): string;
7
+ export declare function asNumber(value: unknown, fallback?: number): number;
8
+ export declare function asBoolean(value: unknown, fallback?: boolean): boolean;
9
+ export declare function asArray(value: unknown): unknown[];
10
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/types/utils.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,KAAK,GAAG,MAAM,CAAC;AAE3B,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,SAAO,GAAG,KAAK,CAK3D;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,SAAO,GAAG,KAAK,GAAG,IAAI,CAExE;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,SAAY,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAKnF;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAI7E;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,SAAK,GAAG,MAAM,CAE9D;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,SAAI,GAAG,MAAM,CAM7D;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,UAAQ,GAAG,OAAO,CAEnE;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,EAAE,CAEjD"}
package/package.json ADDED
@@ -0,0 +1,95 @@
1
+ {
2
+ "name": "@bruch/max-client",
3
+ "version": "0.1.0",
4
+ "description": "Unofficial Bun/TypeScript client for the Max messenger internal API",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "engines": {
8
+ "bun": ">=1.3.0"
9
+ },
10
+ "packageManager": "bun@1.3.0",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "bun": "./src/index.ts",
15
+ "default": "./dist/index.js"
16
+ },
17
+ "./auth": {
18
+ "types": "./dist/auth/index.d.ts",
19
+ "bun": "./src/auth/index.ts",
20
+ "default": "./dist/auth/index.js"
21
+ },
22
+ "./files": {
23
+ "types": "./dist/files/index.d.ts",
24
+ "bun": "./src/files/index.ts",
25
+ "default": "./dist/files/index.js"
26
+ },
27
+ "./protocol": {
28
+ "types": "./dist/protocol/index.d.ts",
29
+ "bun": "./src/protocol/index.ts",
30
+ "default": "./dist/protocol/index.js"
31
+ },
32
+ "./session": {
33
+ "types": "./dist/session/index.d.ts",
34
+ "bun": "./src/session/index.ts",
35
+ "default": "./dist/session/index.js"
36
+ },
37
+ "./telemetry": {
38
+ "types": "./dist/telemetry/index.d.ts",
39
+ "bun": "./src/telemetry/index.ts",
40
+ "default": "./dist/telemetry/index.js"
41
+ },
42
+ "./types": {
43
+ "types": "./dist/types/index.d.ts",
44
+ "bun": "./src/types/index.ts",
45
+ "default": "./dist/types/index.js"
46
+ }
47
+ },
48
+ "files": [
49
+ "dist",
50
+ "src",
51
+ "README.md",
52
+ "LICENSE",
53
+ "UPSTREAM.md"
54
+ ],
55
+ "scripts": {
56
+ "build": "bun run clean && bun build src/index.ts src/auth/index.ts src/files/index.ts src/protocol/index.ts src/session/index.ts src/telemetry/index.ts src/types/index.ts --outdir dist --target bun --format esm --packages external --sourcemap=external && tsc -p tsconfig.build.json",
57
+ "clean": "rm -rf dist",
58
+ "typecheck": "tsc --noEmit",
59
+ "lint": "eslint .",
60
+ "format": "prettier --write .",
61
+ "format:check": "prettier --check .",
62
+ "test": "bun test --coverage",
63
+ "test:unit": "bun test",
64
+ "test:real": "bun run scripts/real-test.ts",
65
+ "pack:check": "bun pm pack --dry-run",
66
+ "check": "bun run lint && bun run format:check && bun run typecheck && bun run test && bun run build && bun run pack:check"
67
+ },
68
+ "dependencies": {
69
+ "@msgpack/msgpack": "^3.1.3",
70
+ "lossless-json": "^4.3.0",
71
+ "qrcode": "^1.5.4"
72
+ },
73
+ "devDependencies": {
74
+ "@eslint/js": "^10.0.1",
75
+ "@types/bun": "^1.3.14",
76
+ "@types/qrcode": "^1.5.6",
77
+ "eslint": "^10.7.0",
78
+ "eslint-config-prettier": "^10.1.8",
79
+ "prettier": "^3.9.5",
80
+ "typescript": "^6.0.3",
81
+ "typescript-eslint": "^8.64.0"
82
+ },
83
+ "publishConfig": {
84
+ "access": "public"
85
+ },
86
+ "keywords": [
87
+ "max",
88
+ "messenger",
89
+ "api",
90
+ "bun",
91
+ "typescript",
92
+ "websocket",
93
+ "tcp"
94
+ ]
95
+ }
@@ -0,0 +1,93 @@
1
+ import type { Photo } from "../files/index.ts";
2
+ import { Opcode } from "../protocol/index.ts";
3
+ import { Profile, asString, type MaxId } from "../types/index.ts";
4
+ import { payload, requiredItem } from "./response.ts";
5
+ import type { ApiRuntime } from "./runtime.ts";
6
+ import type { UploadApi } from "./uploads.ts";
7
+
8
+ export class AccountApi {
9
+ constructor(
10
+ private readonly app: ApiRuntime,
11
+ private readonly uploads: UploadApi,
12
+ ) {}
13
+
14
+ async requestProfilePhotoUploadUrl(): Promise<string> {
15
+ return asString(
16
+ requiredItem(await this.app.invoke(Opcode.PHOTO_UPLOAD, { count: 1, profile: true }), "url"),
17
+ );
18
+ }
19
+
20
+ async changeProfile(options: {
21
+ firstName: string;
22
+ lastName?: string | null;
23
+ description?: string | null;
24
+ photo?: Photo | null;
25
+ photoToken?: string | null;
26
+ }): Promise<boolean> {
27
+ let photoToken = options.photoToken ?? null;
28
+ if (options.photo) {
29
+ const uploaded = await this.uploads.uploadPhoto(options.photo, true);
30
+ if (uploaded._type === "PHOTO") photoToken = uploaded.photoToken;
31
+ }
32
+ const frame = await this.app.invoke(Opcode.PROFILE, {
33
+ firstName: options.firstName,
34
+ lastName: options.lastName ?? null,
35
+ description: options.description ?? null,
36
+ photoToken,
37
+ avatarType: "USER_AVATAR",
38
+ });
39
+ this.app.me = Profile.fromPayload(requiredItem(frame, "profile"));
40
+ return true;
41
+ }
42
+
43
+ async createFolder(
44
+ title: string,
45
+ chatInclude: MaxId[],
46
+ filters: unknown[] = [],
47
+ ): Promise<Record<string, unknown>> {
48
+ return payload(
49
+ await this.app.invoke(Opcode.FOLDERS_UPDATE, {
50
+ id: crypto.randomUUID(),
51
+ title,
52
+ include: chatInclude,
53
+ filters,
54
+ }),
55
+ );
56
+ }
57
+ async getFolders(folderSync = 0): Promise<Record<string, unknown>> {
58
+ return payload(await this.app.invoke(Opcode.FOLDERS_GET, { folderSync }));
59
+ }
60
+ async updateFolder(
61
+ folderId: string,
62
+ title: string,
63
+ chatInclude: MaxId[] = [],
64
+ filters: unknown[] = [],
65
+ options: unknown[] = [],
66
+ ): Promise<Record<string, unknown>> {
67
+ return payload(
68
+ await this.app.invoke(Opcode.FOLDERS_UPDATE, {
69
+ id: folderId,
70
+ title,
71
+ include: chatInclude,
72
+ filters,
73
+ options,
74
+ }),
75
+ );
76
+ }
77
+ async deleteFolder(folderId: string): Promise<Record<string, unknown>> {
78
+ return payload(await this.app.invoke(Opcode.FOLDERS_DELETE, { folderIds: [folderId] }));
79
+ }
80
+ async closeAllSessions(): Promise<boolean> {
81
+ if (!this.app.session) return false;
82
+ const data = payload(await this.app.invoke(Opcode.SESSIONS_CLOSE, {}));
83
+ const token = asString(data.token);
84
+ if (!token) return false;
85
+ await this.app.store.updateToken(this.app.session.token, token);
86
+ this.app.session = { ...this.app.session, token };
87
+ return true;
88
+ }
89
+ async logout(): Promise<boolean> {
90
+ await this.app.invoke(Opcode.LOGOUT, {});
91
+ return true;
92
+ }
93
+ }
@@ -0,0 +1,312 @@
1
+ import { DeviceType, type UserAgent } from "../config.ts";
2
+ import { Opcode } from "../protocol/index.ts";
3
+ import { defaultSyncState, resolveSync, type SyncState } from "../session/index.ts";
4
+ import {
5
+ Chat,
6
+ Message,
7
+ Profile,
8
+ User,
9
+ asArray,
10
+ asNumber,
11
+ asRecord,
12
+ asString,
13
+ toMaxId,
14
+ } from "../types/index.ts";
15
+ import { payload, requiredItem } from "./response.ts";
16
+ import type { ApiRuntime } from "./runtime.ts";
17
+
18
+ export interface PasswordChallenge {
19
+ trackId: string;
20
+ hint: string | null;
21
+ }
22
+ export interface CodeResult {
23
+ loginToken: string | null;
24
+ registerToken: string | null;
25
+ passwordChallenge: PasswordChallenge | null;
26
+ }
27
+ export interface QrRequest {
28
+ expiresAt: number;
29
+ pollingInterval: number;
30
+ qrLink: string;
31
+ trackId: string;
32
+ ttl: number;
33
+ }
34
+ export interface LoginResult {
35
+ chats: Chat[];
36
+ profile: Profile;
37
+ messages: Map<bigint, Message[]>;
38
+ contacts: Array<User | null>;
39
+ token: string | null;
40
+ time: number | null;
41
+ configHash: string | number | null;
42
+ }
43
+
44
+ export class AuthApi {
45
+ constructor(private readonly app: ApiRuntime) {}
46
+
47
+ async requestCode(phone: string): Promise<Record<string, unknown>> {
48
+ const frame = await this.app.invoke(Opcode.AUTH_REQUEST, {
49
+ phone,
50
+ type: "START_AUTH",
51
+ language: "ru",
52
+ });
53
+ return payload(frame);
54
+ }
55
+
56
+ async sendCode(token: string, verifyCode: string): Promise<CodeResult> {
57
+ const frame = await this.app.invoke(Opcode.AUTH, {
58
+ token,
59
+ verifyCode,
60
+ authTokenType: "CHECK_CODE",
61
+ });
62
+ return parseCodeResult(payload(frame));
63
+ }
64
+
65
+ async checkPassword(
66
+ trackId: string,
67
+ password: string,
68
+ ): Promise<CodeResult & { error: string | null }> {
69
+ const frame = await this.app.invoke(Opcode.AUTH_LOGIN_CHECK_PASSWORD, { trackId, password });
70
+ const data = payload(frame);
71
+ return { ...parseCodeResult(data), error: typeof data.error === "string" ? data.error : null };
72
+ }
73
+
74
+ async requestQr(): Promise<QrRequest> {
75
+ const data = payload(await this.app.invoke(Opcode.GET_QR, {}));
76
+ return {
77
+ expiresAt: asNumber(data.expiresAt),
78
+ pollingInterval: asNumber(data.pollingInterval),
79
+ qrLink: asString(data.qrLink),
80
+ trackId: asString(data.trackId),
81
+ ttl: asNumber(data.ttl),
82
+ };
83
+ }
84
+
85
+ async checkQr(trackId: string): Promise<{ expiresAt: number; loginAvailable: boolean }> {
86
+ const data = asRecord(
87
+ requiredItem(await this.app.invoke(Opcode.GET_QR_STATUS, { trackId }), "status"),
88
+ );
89
+ return { expiresAt: asNumber(data.expiresAt), loginAvailable: data.loginAvailable === true };
90
+ }
91
+
92
+ async confirmQr(trackId: string): Promise<CodeResult> {
93
+ return parseCodeResult(payload(await this.app.invoke(Opcode.LOGIN_BY_QR, { trackId })));
94
+ }
95
+
96
+ async confirmRegistration(
97
+ firstName: string,
98
+ lastName: string | null,
99
+ token: string,
100
+ ): Promise<string> {
101
+ const data = payload(
102
+ await this.app.invoke(Opcode.AUTH_CONFIRM, {
103
+ firstName,
104
+ lastName,
105
+ token,
106
+ tokenType: "REGISTER",
107
+ }),
108
+ );
109
+ return asString(data.token);
110
+ }
111
+
112
+ async handshake(deviceId: string): Promise<void> {
113
+ const { userAgent, mtInstanceId, clientSessionId } = this.app.config.device;
114
+ if (userAgent.deviceType === DeviceType.WEB) {
115
+ await this.app.invoke(Opcode.SESSION_INIT, {
116
+ userAgent: webUserAgent(userAgent),
117
+ deviceId,
118
+ });
119
+ } else {
120
+ await this.app.invoke(Opcode.SESSION_INIT, {
121
+ mt_instanceid: mtInstanceId,
122
+ userAgent,
123
+ clientSessionId,
124
+ deviceId,
125
+ });
126
+ }
127
+ }
128
+
129
+ async login(): Promise<LoginResult> {
130
+ const session = this.app.session;
131
+ if (!session) throw new Error("No session available for login");
132
+ const sync = resolveSync(session.sync, this.app.config.sync);
133
+ const userAgent = this.app.config.device.userAgent;
134
+ const loginPayload =
135
+ userAgent.deviceType === DeviceType.WEB
136
+ ? {
137
+ token: session.token,
138
+ chatsCount: 40,
139
+ interactive: true,
140
+ chatsSync: sync.chatsSync,
141
+ contactsSync: sync.contactsSync,
142
+ presenceSync: sync.presenceSync,
143
+ draftsSync: sync.draftsSync,
144
+ }
145
+ : {
146
+ userAgent,
147
+ token: session.token,
148
+ chatsSync: sync.chatsSync,
149
+ contactsSync: sync.contactsSync,
150
+ draftsSync: sync.draftsSync,
151
+ presenceSync: sync.presenceSync,
152
+ interactive: true,
153
+ exp: { chatsCountGroups: Uint8Array.from([0x0a, 0x32]) },
154
+ configHash: sync.configHash,
155
+ };
156
+ const result = parseLogin(payload(await this.app.invoke(Opcode.LOGIN, loginPayload)), this.app);
157
+ const updatedSync: SyncState = defaultSyncState({
158
+ chatsSync: result.time ?? session.sync.chatsSync,
159
+ contactsSync: result.time ?? session.sync.contactsSync,
160
+ draftsSync: result.time ?? session.sync.draftsSync,
161
+ presenceSync: result.time ?? session.sync.presenceSync,
162
+ configHash: result.configHash ?? session.sync.configHash,
163
+ });
164
+ this.app.session = {
165
+ ...session,
166
+ mtInstanceId: this.app.config.device.mtInstanceId,
167
+ sync: updatedSync,
168
+ };
169
+ await this.app.store.saveSession(this.app.session);
170
+ return result;
171
+ }
172
+
173
+ async createTrack(): Promise<string> {
174
+ const data = payload(await this.app.invoke(Opcode.AUTH_CREATE_TRACK, { type: 0 }));
175
+ const trackId = asString(data.trackId);
176
+ if (!trackId) throw new Error("Failed to create auth track");
177
+ return trackId;
178
+ }
179
+
180
+ async setTwoFactor(options: {
181
+ password: string;
182
+ hint?: string;
183
+ email?: string;
184
+ emailCodeProvider?: (email: string) => string | Promise<string>;
185
+ }): Promise<boolean> {
186
+ const trackId = await this.createTrack();
187
+ await this.app.invoke(Opcode.AUTH_VALIDATE_PASSWORD, { trackId, password: options.password });
188
+ const capabilities = [1];
189
+ if (options.email !== undefined) {
190
+ await this.app.invoke(Opcode.AUTH_VERIFY_EMAIL, { trackId, email: options.email });
191
+ if (!options.emailCodeProvider) throw new Error("emailCodeProvider is required");
192
+ const verifyCode = await options.emailCodeProvider(options.email);
193
+ await this.app.invoke(Opcode.AUTH_CHECK_EMAIL, { trackId, verifyCode });
194
+ capabilities.push(3);
195
+ }
196
+ if (options.hint !== undefined) {
197
+ await this.app.invoke(Opcode.AUTH_VALIDATE_HINT, { trackId, hint: options.hint });
198
+ capabilities.push(2);
199
+ }
200
+ await this.app.invoke(Opcode.AUTH_SET_2FA, {
201
+ trackId,
202
+ password: options.password,
203
+ hint: options.hint ?? null,
204
+ expectedCapabilities: capabilities,
205
+ });
206
+ return true;
207
+ }
208
+
209
+ async removeTwoFactor(password: string): Promise<boolean> {
210
+ const trackId = await this.createTrack();
211
+ await this.app.invoke(Opcode.AUTH_CHECK_PASSWORD, { trackId, password });
212
+ await this.app.invoke(Opcode.AUTH_SET_2FA, {
213
+ trackId,
214
+ remove2fa: true,
215
+ expectedCapabilities: [4],
216
+ });
217
+ return true;
218
+ }
219
+
220
+ async changePassword(oldPassword: string, newPassword: string): Promise<boolean> {
221
+ const trackId = await this.createTrack();
222
+ await this.app.invoke(Opcode.AUTH_CHECK_PASSWORD, { trackId, password: oldPassword });
223
+ await this.app.invoke(Opcode.AUTH_VALIDATE_PASSWORD, { trackId, password: newPassword });
224
+ await this.app.invoke(Opcode.AUTH_SET_2FA, {
225
+ trackId,
226
+ password: newPassword,
227
+ hint: null,
228
+ expectedCapabilities: [5],
229
+ });
230
+ return true;
231
+ }
232
+
233
+ async authorizeQrLogin(qrLink: string): Promise<boolean> {
234
+ await this.app.invoke(Opcode.AUTH_QR_APPROVE, { qrLink });
235
+ return true;
236
+ }
237
+
238
+ async checkTwoFactor(): Promise<boolean> {
239
+ return this.app.me?.profileOptions?.includes(2) ?? false;
240
+ }
241
+ }
242
+
243
+ function parseCodeResult(data: Record<string, unknown>): CodeResult {
244
+ const attrs = data.tokenAttrs ? asRecord(data.tokenAttrs) : {};
245
+ const login = attrs.LOGIN ? asRecord(attrs.LOGIN) : null;
246
+ const register = attrs.REGISTER ? asRecord(attrs.REGISTER) : null;
247
+ const challenge = data.passwordChallenge ? asRecord(data.passwordChallenge) : null;
248
+ return {
249
+ loginToken: login ? asString(login.token) || null : null,
250
+ registerToken: register ? asString(register.token) || null : null,
251
+ passwordChallenge: challenge
252
+ ? {
253
+ trackId: asString(challenge.trackId),
254
+ hint: typeof challenge.hint === "string" ? challenge.hint : null,
255
+ }
256
+ : null,
257
+ };
258
+ }
259
+
260
+ function parseLogin(data: Record<string, unknown>, app: ApiRuntime): LoginResult {
261
+ const profile = Profile.fromPayload(data.profile);
262
+ app.bindUser(profile.contact);
263
+ const chats = asArray(data.chats).map((item) => app.bindChat(Chat.fromPayload(item)));
264
+ const contacts = asArray(data.contacts).map((item) =>
265
+ item ? app.bindUser(User.fromPayload(item)) : null,
266
+ );
267
+ const messages = new Map<bigint, Message[]>();
268
+ if (data.messages && typeof data.messages === "object") {
269
+ for (const [chatId, items] of Object.entries(data.messages)) {
270
+ messages.set(
271
+ toMaxId(chatId),
272
+ asArray(items).map((item) => app.bindMessage(Message.fromPayload(item))),
273
+ );
274
+ }
275
+ }
276
+ const config = data.config ? asRecord(data.config) : null;
277
+ return {
278
+ chats,
279
+ profile,
280
+ messages,
281
+ contacts,
282
+ token: typeof data.token === "string" ? data.token : null,
283
+ time: data.time === null || data.time === undefined ? null : asNumber(data.time),
284
+ configHash:
285
+ typeof config?.hash === "string" || typeof config?.hash === "number" ? config.hash : null,
286
+ };
287
+ }
288
+
289
+ function webUserAgent(agent: UserAgent): Partial<UserAgent> {
290
+ const {
291
+ deviceType,
292
+ locale,
293
+ deviceLocale,
294
+ osVersion,
295
+ deviceName,
296
+ headerUserAgent,
297
+ appVersion,
298
+ screen,
299
+ timezone,
300
+ } = agent;
301
+ return {
302
+ deviceType,
303
+ locale,
304
+ deviceLocale,
305
+ osVersion,
306
+ deviceName,
307
+ appVersion,
308
+ screen,
309
+ timezone,
310
+ ...(headerUserAgent ? { headerUserAgent } : {}),
311
+ };
312
+ }
@@ -0,0 +1,21 @@
1
+ import { Opcode } from "../protocol/index.ts";
2
+ import type { MaxId } from "../types/index.ts";
3
+ import { payload } from "./response.ts";
4
+ import type { ApiRuntime } from "./runtime.ts";
5
+
6
+ export class BotsApi {
7
+ constructor(private readonly app: ApiRuntime) {}
8
+ async getBotInitData(
9
+ botId: MaxId,
10
+ chatId?: MaxId | null,
11
+ startParam?: string | null,
12
+ ): Promise<Record<string, unknown>> {
13
+ return payload(
14
+ await this.app.invoke(Opcode.WEB_APP_INIT_DATA, {
15
+ botId,
16
+ chatId: chatId ?? null,
17
+ startParam: startParam ?? null,
18
+ }),
19
+ );
20
+ }
21
+ }