max-account-api 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,328 @@
1
+ # max-account-api
2
+
3
+ > ⚠️ **Неофициальная библиотека.** Не аффилирована с MAX / VK. Используйте на свой страх и риск — ваш аккаунт может быть помечен или заблокирован за нестандартную активность.
4
+
5
+ Node.js-клиент для **личного** MAX-аккаунта — залогиньтесь по номеру телефона или QR-коду и автоматизируйте всё что делает обычный пользователь: получать сообщения, отвечать, читать, реагировать, редактировать, отправлять фото / файлы / стикеры / опросы, создавать группы, и т.д.
6
+
7
+ Сложные части (TLS + msgpack + LZ4 бинарный протокол для phone-auth, WebSocket для всего остального, корреляция фреймов, ping / heartbeat, авто-реконнект, ack серверных push-ев) спрятаны за маленьким event-driven API.
8
+
9
+ ```ts
10
+ import { MaxClient } from 'max-account-api';
11
+
12
+ const client = await MaxClient.loginWithPhone({
13
+ phone: '+79991234567',
14
+ getSmsCode: async () => prompt('SMS code: '),
15
+ });
16
+
17
+ client.on('message', (m) => console.log(`[${m.chatId}] ${m.fromId}: ${m.text}`));
18
+ await client.sendMessage(0, 'Привет в Избранное!');
19
+ ```
20
+
21
+ ## Возможности
22
+
23
+ | | |
24
+ |---|---|
25
+ | 📱 **Phone-auth + auto QR-bind** | Один метод `loginWithPhone({phone, getSmsCode, getPassword?})` — SMS-код, опционально облачный пароль (2FA), под капотом mobile binary protocol + WS QR-bind. Web-токен сохраняется автоматически. |
26
+ | 📷 **QR-логин** (как раньше) | Терминал-QR + событие со ссылкой. Старый flow `new MaxClient().start()` тоже работает. |
27
+ | 💾 **Гибкая сессия** | По умолчанию — `./.max-session.json`. Можно отдать кастомный `SessionStore` или инжектить `deviceId`+`loginToken` из env. |
28
+ | 🧩 **Несколько инстансов в процессе** | Каждый со своим store / тoken pair'ом. |
29
+ | 📨 **Сообщения** | Текст, реплаи, форварды, цитаты, редактирование, удаление, реакции, голосование в опросах. |
30
+ | 📎 **Вложения** | Фото, файлы, видео, стикеры, контакты, опросы — с end-to-end upload. |
31
+ | 👥 **Чаты / группы** | Создание групп / каналов, добавление / удаление / промоут участников, переименование, аватары, инвайт-линки, mute / unmute, pin, политика реакций. |
32
+ | 📞 **Звонки** | Signalling для 1:1 audio / video (медиа через отдельный `videowebrtc.okcdn.ru`). |
33
+ | 🔁 **Auto-reconnect** | Тихий повторный handshake при обрыве. |
34
+ | 📡 **Server push** | Подтверждается автоматически — вы получаете `'message'`, `'chatUpdate'`, `'presence'`, `'readByOther'`, и т.д. |
35
+ | 🔐 **Управление сессией** | Список активных сессий, logout, 2FA включить / выключить, смена пароля. |
36
+ | 📚 **Полная TypeScript-типизация** | На каждый запрос и ответ. 160+ опкодов с каноническими именами, извлечёнными из декомпилированного MAX-клиента. |
37
+ | 🪟 **Запасной выход** | `client.raw(opcode, payload)` для любого опкода которого нет в high-level API. |
38
+
39
+ ## Установка
40
+
41
+ ```bash
42
+ npm install max-account-api
43
+ ```
44
+
45
+ Требуется Node.js 18+.
46
+
47
+ ## Быстрый старт
48
+
49
+ ### Вход по номеру телефона (рекомендуется)
50
+
51
+ ```ts
52
+ import { MaxClient } from 'max-account-api';
53
+ import * as readline from 'node:readline/promises';
54
+ import { stdin as input, stdout as output } from 'node:process';
55
+
56
+ const rl = readline.createInterface({ input, output });
57
+
58
+ const client = await MaxClient.loginWithPhone({
59
+ phone: await rl.question('Phone (+79991234567): '),
60
+ getSmsCode: () => rl.question('SMS code: '),
61
+ // Опционально: вызовется только если у аккаунта 2FA облачный пароль
62
+ getPassword: (challenge) =>
63
+ rl.question(`2FA password (hint: ${challenge.hint}): `),
64
+ sessionFile: './.max-session.json',
65
+ });
66
+
67
+ console.log('Logged in as', client.getMe()?.names?.[0]?.name);
68
+
69
+ client.on('message', async (m) => {
70
+ if (m.fromId !== client.getMe()?.id) {
71
+ await client.reply(m, `Эхо: ${m.text}`);
72
+ }
73
+ });
74
+
75
+ rl.close();
76
+ // keep process alive
77
+ await new Promise(() => {});
78
+ ```
79
+
80
+ На первом запуске MAX пришлёт SMS, после ввода кода + (если есть) 2FA пароля библиотека сохранит **оба** токена (mobile + web) в session-файл. На последующих запусках — тихий reconnect, ничего вводить не надо.
81
+
82
+ > **Регион:** SMS-доставка и phone-auth заблокированы серверной геолокацией. Запускайте из РФ или через VPN с RU-IP, иначе получите `service.unavailable` / `phone.region.unsupported`.
83
+
84
+ ### Вход по QR-коду (без номера)
85
+
86
+ ```ts
87
+ const client = new MaxClient();
88
+ client.on('qr', ({ link, expiresAt }) => {
89
+ console.log('Откройте/отсканируйте:', link);
90
+ console.log('Истекает в', new Date(expiresAt).toLocaleString());
91
+ });
92
+ await client.start();
93
+ ```
94
+
95
+ QR печатается в терминал автоматически. После сканирования мобильным MAX в консоль выводятся `deviceId` + `loginToken` — сохраните их в env.
96
+
97
+ ### Переиспользование токена (silent login)
98
+
99
+ ```ts
100
+ const client = new MaxClient({
101
+ deviceId: process.env.MAX_DEVICE_ID,
102
+ loginToken: process.env.MAX_LOGIN_TOKEN,
103
+ });
104
+ await client.start();
105
+ ```
106
+
107
+ Если оба значения корректны — никакого QR / SMS не будет.
108
+
109
+ ### Несколько инстансов в одном процессе
110
+
111
+ ```ts
112
+ const a = new MaxClient({ deviceId: process.env.A_ID, loginToken: process.env.A_TOK });
113
+ const b = new MaxClient({ deviceId: process.env.B_ID, loginToken: process.env.B_TOK });
114
+ await Promise.all([a.start(), b.start()]);
115
+ ```
116
+
117
+ ## API по разделам
118
+
119
+ Полный справочник: [`docs/API.md`](docs/API.md). Основные группы:
120
+
121
+ ### Сообщения
122
+
123
+ ```ts
124
+ await client.sendMessage(chatId, 'привет');
125
+ await client.sendReply(chatId, replyToMessageId, 'ответ на сообщение');
126
+ await client.reply(incoming, 'эхо'); // ответить на полученное
127
+ await client.editMessage(chatId, msgId, 'новый текст');
128
+ await client.deleteMessages(chatId, [msgId1, msgId2], /*forAll*/ true);
129
+ await client.forwardMessage(toChatId, fromChatId, msgId, 'опциональный комментарий');
130
+ await client.sendTyping(chatId);
131
+ await client.readMessage(chatId, msgId);
132
+ await client.getHistory(chatId, { backward: 50 });
133
+ await client.searchMessages(chatId, 'query');
134
+ ```
135
+
136
+ ### Реакции
137
+
138
+ ```ts
139
+ await client.setReaction(chatId, msgId, '👍');
140
+ await client.removeReaction(chatId, msgId);
141
+ const reactions = await client.getReactions(chatId, [msgId1, msgId2]);
142
+ ```
143
+
144
+ ### Вложения
145
+
146
+ ```ts
147
+ const photoToken = await client.uploadPhotoBytes({ data: pngBytes, filename: 'pic.png' });
148
+ await client.sendPhoto(chatId, photoToken, 'подпись');
149
+
150
+ const file = await client.uploadFileBytes({ data: pdfBytes, filename: 'doc.pdf' });
151
+ await client.sendFile(chatId, file.fileId, 'договор');
152
+
153
+ await client.sendVoice(chatId, { data: oggBytes, filename: 'voice.ogg' }, { duration: 12 });
154
+ await client.sendVideo(chatId, { data: mp4Bytes, filename: 'clip.mp4' }, 'смотри ролик');
155
+ await client.sendVideoNote(chatId, { data: mp4Bytes }); // круглый кружок
156
+
157
+ await client.sendSticker(chatId, stickerId);
158
+ await client.sendContact(chatId, contactId);
159
+ await client.sendLocation(chatId, 55.7558, 37.6173, { name: 'Кремль' });
160
+ await client.sendPoll(chatId, 'Куда едем?', ['Сочи', 'Питер', 'Казань']);
161
+ ```
162
+
163
+ > `sendVoice` / `sendVideo` / `sendVideoNote` требуют `mobileLoginToken` (т.е. сессию, заведённую через `MaxClient.loginWithPhone()`); через QR-only login они недоступны.
164
+
165
+ ### Группы / каналы
166
+
167
+ ```ts
168
+ const group = await client.createGroup('Тестовая группа', [userId1, userId2]);
169
+ await client.addGroupParticipants(group.chatId, [userId3]);
170
+ await client.setGroupAdmin(group.chatId, userId1);
171
+ await client.renameGroup(group.chatId, 'Новое имя');
172
+ await client.setChatAvatar(group.chatId, photoToken);
173
+ await client.pinMessage(group.chatId, msgId);
174
+ await client.leaveChat(group.chatId);
175
+ await client.resolveChatLink('https://max.ru/somegroup'); // op 57 — фактически вступает в чат
176
+ ```
177
+
178
+ ### Чаты
179
+
180
+ ```ts
181
+ const chats = client.getChats(); // снимок при логине
182
+ const fresh = await client.getChatsByIds([id1, id2]);
183
+ await client.muteChat(chatId, /*muteUntilMs*/ -1);
184
+ await client.unmuteChat(chatId);
185
+ await client.clearChatHistory(chatId); // только история
186
+ await client.deleteChat(chatId); // полностью удалить
187
+ await client.subscribeChat(chatId, true);
188
+ ```
189
+
190
+ ### Контакты
191
+
192
+ ```ts
193
+ const me = client.getMe();
194
+ const c = await client.getContactInfo(userId);
195
+ await client.addContactByPhone('+79991234567', 'Имя', 'Фамилия');
196
+ await client.lookupContactByPhone('+79991234567'); // без сохранения
197
+ await client.renameContact(userId, 'Новое имя');
198
+ await client.blockContact(userId);
199
+ await client.unblockContact(userId);
200
+ await client.removeContact(userId);
201
+ const blocked = await client.listBlockedContacts();
202
+ const presence = await client.getPresence([userId]);
203
+ ```
204
+
205
+ ### Профиль
206
+
207
+ ```ts
208
+ await client.setProfileName('Имя', 'Фамилия');
209
+ await client.setProfileDescription('обо мне');
210
+ await client.setProfileAvatar(photoToken);
211
+ await client.updateProfile({ firstName: 'Имя', description: 'обо мне' });
212
+ ```
213
+
214
+ ### Сессии / безопасность
215
+
216
+ ```ts
217
+ const sessions = await client.listSessions();
218
+ await client.logout(); // op 20 + локально стереть токен
219
+ await client.enable2faPassword({
220
+ password: 'NewSecurePass123',
221
+ hint: 'мамин день рождения',
222
+ recoveryEmail: 'me@example.com',
223
+ getCode: async ({ codeLength }) => prompt(`Code from email (${codeLength}): `),
224
+ });
225
+ await client.disable2faPassword(currentPassword);
226
+ ```
227
+
228
+ ### Папки
229
+
230
+ ```ts
231
+ const folders = await client.getFolders(['folder-id-1']);
232
+ await client.upsertFolder({ title: 'Боты', filters: [10], /* ... */ });
233
+ await client.reorderFolders(['id1', 'id2', 'id3']);
234
+ ```
235
+
236
+ ### Поиск
237
+
238
+ ```ts
239
+ const result = await client.globalSearch('текст');
240
+ const channels = await client.globalSearchByType('канал', 'CHANNELS');
241
+ const inChat = await client.searchMessages(chatId, 'слово');
242
+ ```
243
+
244
+ ### Запасной выход
245
+
246
+ ```ts
247
+ // Любой опкод которого нет в high-level API:
248
+ const res = await client.raw<MyResponse>(Opcode.SOME_OP, { /* payload */ });
249
+ ```
250
+
251
+ ## События
252
+
253
+ ```ts
254
+ client.on('qr', ({ link, expiresAt }) => …); // QR готов
255
+ client.on('login', (me) => …); // QR подтверждён, профиль есть
256
+ client.on('ready', () => …); // полная инициализация
257
+ client.on('message', (m) => …); // входящее сообщение
258
+ client.on('chatUpdate', (chat) => …); // chat info изменился
259
+ client.on('readByOther', (mark) => …); // кто-то прочитал твоё сообщение
260
+ client.on('presence', (p) => …); // online/offline у контакта
261
+ client.on('contactUpdate', (contact) => …);
262
+ client.on('memberLeave', (notif) => …);
263
+ client.on('stickerRecentsUpdate', (push) => …);
264
+ client.on('fileUploadDone', (push) => …);
265
+ client.on('configChanged', (hash) => …);
266
+ client.on('raw', (frame) => …); // любой WS-фрейм (отладка)
267
+ client.on('reconnect', (attempt) => …);
268
+ client.on('close', (code, reason) => …);
269
+ client.on('error', (err) => …);
270
+ ```
271
+
272
+ ## Документация
273
+
274
+ | Файл | Что внутри |
275
+ |---|---|
276
+ | [`docs/API.md`](docs/API.md) | Полный справочник по `MaxClient` (каждый метод + типы аргументов / возвращаемых значений) |
277
+ | [`docs/ROADMAP.md`](docs/ROADMAP.md) | Что реализовано, известные ограничения |
278
+
279
+ ## Примеры
280
+
281
+ В [`examples/`](examples/) — десяток мини-сценариев (echo-бот, отправка фото / файла / голоса / видео-кружка / локации, реакции, опросы, группы, поиск, контакты, …):
282
+
283
+ ```bash
284
+ npm run example:phone # tsx examples/phone-bot.ts — phone-login + echo
285
+ npm run example:echo # tsx examples/echo-bot.ts — QR-логин + echo
286
+ npx tsx examples/send-photo.ts ./pic.png 0
287
+ npx tsx examples/send-voice.ts ./voice.ogg 12 0
288
+ ```
289
+
290
+ Подробнее — [`examples/README.md`](examples/README.md).
291
+
292
+ ## Безопасность сессии
293
+
294
+ Файл `.max-session*.json` содержит **LOGIN-токен** — это эквивалент пароля от вашего MAX-аккаунта. Никогда не коммитьте, не передавайте в логи / Slack / GitHub Issues. В `.gitignore` уже добавлено правило `.max-session*.json`.
295
+
296
+ Если токен скомпрометирован — `await client.logout()` или войдите в **mobile MAX** → Настройки → Устройства → завершите сессию.
297
+
298
+ Для multi-tenant / production хранилищ реализуйте свой `SessionStore`:
299
+
300
+ ```ts
301
+ import type { SessionStore, StoredSession } from 'max-account-api';
302
+
303
+ class VaultSessionStore implements SessionStore {
304
+ async load(): Promise<StoredSession> { /* read from secrets vault */ }
305
+ async save(s: StoredSession): Promise<void> { /* write back */ }
306
+ }
307
+
308
+ const client = new MaxClient({ session: new VaultSessionStore() });
309
+ ```
310
+
311
+ ## Что не покрыто / ограничения
312
+
313
+ - **Медиа звонков** (`videowebrtc.okcdn.ru/ws2`) — есть только signalling (`startCall`), сами RTP / WebRTC потоки вне scope библиотеки.
314
+ - **Live streams** — есть `chatLivestreamInfo` (op 62), но запуск стрима не реализован.
315
+ - **Mini-apps / WebView** — есть `openBotMiniApp` (op 160), сам WebApp / WebView пользователь рендерит сам.
316
+ - **SMS phone-auth** — заблокирован сервером для не-RU IP, нужен RU-IP или VPN.
317
+
318
+ Полная таблица — [`docs/ROADMAP.md`](docs/ROADMAP.md).
319
+
320
+ ## Дисклеймер
321
+
322
+ Это пользовательский эксперимент по реверс-инжинирингу. Протокол не публичный; опкоды наблюдены через официальный веб-клиент и Android-клиент (v26.15.1), могут быть изменены MAX без предупреждения. Автор не несёт ответственности за блокировку аккаунтов в результате использования библиотеки.
323
+
324
+ **Не используйте её для спама, скрейпинга, массовых рассылок или иных нарушений условий использования MAX.**
325
+
326
+ ## Лицензия
327
+
328
+ MIT
package/dist/auth.d.ts ADDED
@@ -0,0 +1,85 @@
1
+ import type { Transport } from './transport.js';
2
+ import type { RawTransport } from './raw-transport.js';
3
+ /** Transport that can speak the mobile binary protocol (phone-auth). */
4
+ type PhoneAuthTransport = RawTransport;
5
+ import type { PhoneAuthStartResponse, PhoneAuthVerifyResponse, AuthEmailSendCodeResponse, AuthEmailVerifyCodeResponse, AuthPasswordCommitRequest, AuthPasswordInfoResponse, AuthPasswordVerifyResponse, AuthQrPasswordLoginResponse, AuthTrackStartResponse, HelloResponse, LoginResponse, PasswordChallenge, QrLoginResponse, UserAgent } from './types.js';
6
+ export declare const DEFAULT_USER_AGENT: UserAgent;
7
+ export interface QrEmitter {
8
+ emitQr(info: {
9
+ link: string;
10
+ expiresAt: number;
11
+ }): void;
12
+ printToTerminal: boolean;
13
+ /**
14
+ * Called when the QR scan succeeds but the account has 2FA cloud password
15
+ * enabled — caller must return the password (string) or `null` to abort.
16
+ * If absent, `loginViaQr` throws when 2FA is required.
17
+ */
18
+ resolvePassword?: (challenge: PasswordChallenge) => Promise<string | null>;
19
+ }
20
+ export declare function hello(transport: Transport, deviceId: string, userAgent?: UserAgent): Promise<HelloResponse>;
21
+ /**
22
+ * Run QR login flow:
23
+ * 1. opcode 288 → get qrLink + trackId + pollingInterval
24
+ * 2. emit qrLink to user (terminal QR + event)
25
+ * 3. poll opcode 289 with trackId until loginAvailable=true or expiry
26
+ * 4. opcode 291 → returns LOGIN token + profile
27
+ */
28
+ export declare function loginViaQr(transport: Transport, emitter: QrEmitter): Promise<QrLoginResponse>;
29
+ /**
30
+ * Submit login token (received from QR or persisted from previous session)
31
+ * to obtain full app state (profile, chats, contacts, config).
32
+ */
33
+ export declare function loginWithToken(transport: Transport, token: string, opts?: {
34
+ chatsCount?: number;
35
+ interactive?: boolean;
36
+ }): Promise<LoginResponse>;
37
+ /** Op 112: open a fresh auth track. Returns `trackId` used by all `auth*` ops. */
38
+ export declare function startAuthTrack(transport: Transport, type?: number): Promise<AuthTrackStartResponse>;
39
+ /** Op 104: returns whether 2FA is set on the account and the recovery email (if any). */
40
+ export declare function getPasswordInfo(transport: Transport, trackId: string): Promise<AuthPasswordInfoResponse>;
41
+ /** Op 107: pre-flight password strength check. Throws server `password.invalid` for weak. */
42
+ export declare function validatePassword(transport: Transport, trackId: string, password: string): Promise<void>;
43
+ /** Op 108: stash the password hint (shown later as a reminder). */
44
+ export declare function setPasswordHint(transport: Transport, trackId: string, hint: string): Promise<void>;
45
+ /** Op 109: ask the server to email a verification code to `email`. */
46
+ export declare function sendEmailCode(transport: Transport, trackId: string, email: string): Promise<AuthEmailSendCodeResponse>;
47
+ /** Op 110: confirm the code mailed by `sendEmailCode`. */
48
+ export declare function verifyEmailCode(transport: Transport, trackId: string, verifyCode: string): Promise<AuthEmailVerifyCodeResponse>;
49
+ /**
50
+ * Op 111: commit a 2FA cloud password change.
51
+ * - to ENABLE: `{password, hint, expectedCapabilities:[0,3,4]}` after a verified
52
+ * email track (ops 109/110 done).
53
+ * - to DISABLE: `{remove2fa:true, expectedCapabilities:[5]}` after `verifyPassword`.
54
+ */
55
+ export declare function commitPasswordChange(transport: Transport, req: AuthPasswordCommitRequest): Promise<void>;
56
+ /** Op 113: prove ownership of the account with the existing 2FA password. */
57
+ export declare function verifyPassword(transport: Transport, trackId: string, password: string): Promise<AuthPasswordVerifyResponse>;
58
+ /** Op 115: exchange a `passwordChallenge.trackId` + 2FA password for a LOGIN token. */
59
+ export declare function completeQrPasswordLogin(transport: Transport, trackId: string, password: string): Promise<AuthQrPasswordLoginResponse>;
60
+ /** Op 20: invalidate the current login token server-side. */
61
+ export declare function logout(transport: Transport): Promise<void>;
62
+ /**
63
+ * Op 17: request an SMS verification code.
64
+ *
65
+ * IMPORTANT: this op is **mobile-only**. The web WebSocket endpoint refuses
66
+ * `userAgent.deviceType:'ANDROID'` with close code 1008 ("deviceType forbidden"),
67
+ * and a WEB hello returns `phone-auth-enabled:false`. You must use
68
+ * `RawTransport` (TLS to `api.oneme.ru:443`) with an ANDROID/IOS userAgent.
69
+ *
70
+ * `type='START_AUTH'` for a fresh request; `type='RESEND'` to re-send.
71
+ * Returns a short-lived `token` that must be passed to `verifyPhoneAuthCode`.
72
+ */
73
+ export declare function sendPhoneAuthCode(transport: PhoneAuthTransport, phone: string, type?: 'START_AUTH' | 'RESEND'): Promise<PhoneAuthStartResponse>;
74
+ /**
75
+ * Op 18: submit the SMS code received after `sendPhoneAuthCode`.
76
+ * On success returns `tokenAttrs.LOGIN.token` (use with `loginWithToken`).
77
+ * If 2FA cloud password is set, returns `passwordChallenge` instead — pass to
78
+ * `completeQrPasswordLogin` (op 115, same shape as QR flow).
79
+ * On wrong code the server throws `{error:'verify.code.wrong'}`.
80
+ *
81
+ * Mobile-only — see `sendPhoneAuthCode` for the transport requirement.
82
+ */
83
+ export declare function verifyPhoneAuthCode(transport: PhoneAuthTransport, verifyCode: string, token: string): Promise<PhoneAuthVerifyResponse>;
84
+ export {};
85
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
package/dist/auth.js ADDED
@@ -0,0 +1,204 @@
1
+ import qrTerminal from 'qrcode-terminal';
2
+ import { Opcode } from './opcodes.js';
3
+ export const DEFAULT_USER_AGENT = {
4
+ deviceType: 'WEB',
5
+ locale: 'ru',
6
+ deviceLocale: 'ru',
7
+ osVersion: 'macOS',
8
+ deviceName: 'Chrome',
9
+ headerUserAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36',
10
+ appVersion: '26.5.5',
11
+ screen: '720x1280 1.0x',
12
+ timezone: 'Europe/Moscow',
13
+ };
14
+ function formatExpiry(expiresAt) {
15
+ const ms = expiresAt - Date.now();
16
+ const inSec = Math.max(0, Math.round(ms / 1000));
17
+ const d = new Date(expiresAt);
18
+ const local = d.toLocaleString(undefined, {
19
+ hour: '2-digit',
20
+ minute: '2-digit',
21
+ second: '2-digit',
22
+ timeZoneName: 'short',
23
+ });
24
+ return { local, inSec };
25
+ }
26
+ export async function hello(transport, deviceId, userAgent = DEFAULT_USER_AGENT) {
27
+ const req = { userAgent, deviceId };
28
+ return transport.request(Opcode.HELLO, req);
29
+ }
30
+ /**
31
+ * Run QR login flow:
32
+ * 1. opcode 288 → get qrLink + trackId + pollingInterval
33
+ * 2. emit qrLink to user (terminal QR + event)
34
+ * 3. poll opcode 289 with trackId until loginAvailable=true or expiry
35
+ * 4. opcode 291 → returns LOGIN token + profile
36
+ */
37
+ export async function loginViaQr(transport, emitter) {
38
+ const start = await transport.request(Opcode.QR_START);
39
+ emitter.emitQr({ link: start.qrLink, expiresAt: start.expiresAt });
40
+ if (emitter.printToTerminal) {
41
+ qrTerminal.generate(start.qrLink, { small: true });
42
+ const { local, inSec } = formatExpiry(start.expiresAt);
43
+ // eslint-disable-next-line no-console
44
+ console.log(`\n[max-account-api] Отсканируйте QR или откройте ссылку:\n ${start.qrLink}\n` +
45
+ `Код истечёт через ${inSec} с (в ${local}).\n`);
46
+ }
47
+ const interval = Math.max(1000, start.pollingInterval ?? 5000);
48
+ const deadline = start.expiresAt;
49
+ while (Date.now() < deadline) {
50
+ const poll = await transport.request(Opcode.QR_POLL, {
51
+ trackId: start.trackId,
52
+ });
53
+ if (poll.status?.loginAvailable) {
54
+ const res = await transport.request(Opcode.QR_LOGIN, {
55
+ trackId: start.trackId,
56
+ });
57
+ // Account with 2FA cloud password: tokenAttrs is `{}`, passwordChallenge is set.
58
+ if (!res.tokenAttrs?.LOGIN && res.passwordChallenge) {
59
+ if (!emitter.resolvePassword) {
60
+ throw new Error('Аккаунт защищён облачным паролем (2FA). Передайте `resolvePassword` в loginViaQr или используйте `client.completeQrPasswordLogin`.');
61
+ }
62
+ const pw = await emitter.resolvePassword(res.passwordChallenge);
63
+ if (pw === null || pw === undefined) {
64
+ throw new Error('2FA-пароль не предоставлен — вход отменён.');
65
+ }
66
+ const final = await transport.request(Opcode.AUTH_QR_PASSWORD_LOGIN, { trackId: res.passwordChallenge.trackId, password: pw });
67
+ return {
68
+ tokenAttrs: final.tokenAttrs,
69
+ ...(final.profile ? { profile: final.profile } : {}),
70
+ };
71
+ }
72
+ return res;
73
+ }
74
+ await sleep(interval);
75
+ }
76
+ if (emitter.printToTerminal) {
77
+ // eslint-disable-next-line no-console
78
+ console.log('\n[max-account-api] QR-код истёк. Перезапустите скрипт.\n');
79
+ }
80
+ throw new Error('QR-код истёк до сканирования. Перезапустите скрипт.');
81
+ }
82
+ /**
83
+ * Submit login token (received from QR or persisted from previous session)
84
+ * to obtain full app state (profile, chats, contacts, config).
85
+ */
86
+ export async function loginWithToken(transport, token, opts = {}) {
87
+ return transport.request(Opcode.LOGIN, {
88
+ token,
89
+ chatsCount: opts.chatsCount ?? 40,
90
+ interactive: opts.interactive ?? true,
91
+ chatsSync: 0,
92
+ contactsSync: 0,
93
+ presenceSync: -1,
94
+ draftsSync: 0,
95
+ });
96
+ }
97
+ function sleep(ms) {
98
+ return new Promise((r) => setTimeout(r, ms));
99
+ }
100
+ // --------------------------------------------------------------------------
101
+ // Cloud-password / email auth track (ops 104, 107–113)
102
+ // --------------------------------------------------------------------------
103
+ /** Op 112: open a fresh auth track. Returns `trackId` used by all `auth*` ops. */
104
+ export async function startAuthTrack(transport, type = 0) {
105
+ return transport.request(Opcode.AUTH_TRACK_START, {
106
+ type,
107
+ });
108
+ }
109
+ /** Op 104: returns whether 2FA is set on the account and the recovery email (if any). */
110
+ export async function getPasswordInfo(transport, trackId) {
111
+ return transport.request(Opcode.AUTH_PASSWORD_INFO, {
112
+ trackId,
113
+ });
114
+ }
115
+ /** Op 107: pre-flight password strength check. Throws server `password.invalid` for weak. */
116
+ export async function validatePassword(transport, trackId, password) {
117
+ await transport.request(Opcode.AUTH_PASSWORD_VALIDATE, {
118
+ trackId,
119
+ password,
120
+ });
121
+ }
122
+ /** Op 108: stash the password hint (shown later as a reminder). */
123
+ export async function setPasswordHint(transport, trackId, hint) {
124
+ await transport.request(Opcode.AUTH_PASSWORD_HINT, {
125
+ trackId,
126
+ hint,
127
+ });
128
+ }
129
+ /** Op 109: ask the server to email a verification code to `email`. */
130
+ export async function sendEmailCode(transport, trackId, email) {
131
+ return transport.request(Opcode.AUTH_EMAIL_SEND_CODE, {
132
+ trackId,
133
+ email,
134
+ });
135
+ }
136
+ /** Op 110: confirm the code mailed by `sendEmailCode`. */
137
+ export async function verifyEmailCode(transport, trackId, verifyCode) {
138
+ return transport.request(Opcode.AUTH_EMAIL_VERIFY_CODE, {
139
+ trackId,
140
+ verifyCode,
141
+ });
142
+ }
143
+ /**
144
+ * Op 111: commit a 2FA cloud password change.
145
+ * - to ENABLE: `{password, hint, expectedCapabilities:[0,3,4]}` after a verified
146
+ * email track (ops 109/110 done).
147
+ * - to DISABLE: `{remove2fa:true, expectedCapabilities:[5]}` after `verifyPassword`.
148
+ */
149
+ export async function commitPasswordChange(transport, req) {
150
+ await transport.request(Opcode.AUTH_PASSWORD_COMMIT, req);
151
+ }
152
+ /** Op 113: prove ownership of the account with the existing 2FA password. */
153
+ export async function verifyPassword(transport, trackId, password) {
154
+ return transport.request(Opcode.AUTH_PASSWORD_VERIFY, {
155
+ trackId,
156
+ password,
157
+ });
158
+ }
159
+ /** Op 115: exchange a `passwordChallenge.trackId` + 2FA password for a LOGIN token. */
160
+ export async function completeQrPasswordLogin(transport, trackId, password) {
161
+ return transport.request(Opcode.AUTH_QR_PASSWORD_LOGIN, {
162
+ trackId,
163
+ password,
164
+ });
165
+ }
166
+ /** Op 20: invalidate the current login token server-side. */
167
+ export async function logout(transport) {
168
+ await transport.request(Opcode.LOGOUT);
169
+ }
170
+ // --------------------------------------------------------------------------
171
+ // Phone auth (ops 17, 18) — Android mobile flow
172
+ // --------------------------------------------------------------------------
173
+ /**
174
+ * Op 17: request an SMS verification code.
175
+ *
176
+ * IMPORTANT: this op is **mobile-only**. The web WebSocket endpoint refuses
177
+ * `userAgent.deviceType:'ANDROID'` with close code 1008 ("deviceType forbidden"),
178
+ * and a WEB hello returns `phone-auth-enabled:false`. You must use
179
+ * `RawTransport` (TLS to `api.oneme.ru:443`) with an ANDROID/IOS userAgent.
180
+ *
181
+ * `type='START_AUTH'` for a fresh request; `type='RESEND'` to re-send.
182
+ * Returns a short-lived `token` that must be passed to `verifyPhoneAuthCode`.
183
+ */
184
+ export async function sendPhoneAuthCode(transport, phone, type = 'START_AUTH') {
185
+ // Observed sub-type hints embedded by mobile client: 21 for START_AUTH, 17 for RESEND.
186
+ const hint = type === 'START_AUTH' ? 21 : 17;
187
+ return transport.request(Opcode.PHONE_AUTH_START, { type, phone }, { prependOpHint: hint });
188
+ }
189
+ /**
190
+ * Op 18: submit the SMS code received after `sendPhoneAuthCode`.
191
+ * On success returns `tokenAttrs.LOGIN.token` (use with `loginWithToken`).
192
+ * If 2FA cloud password is set, returns `passwordChallenge` instead — pass to
193
+ * `completeQrPasswordLogin` (op 115, same shape as QR flow).
194
+ * On wrong code the server throws `{error:'verify.code.wrong'}`.
195
+ *
196
+ * Mobile-only — see `sendPhoneAuthCode` for the transport requirement.
197
+ */
198
+ export async function verifyPhoneAuthCode(transport, verifyCode, token) {
199
+ // Mobile op 18 observed:
200
+ // header flags byte = 0x02
201
+ // body = msgpack int (-31) followed by map {verifyCode, token, type:'CHECK_CODE'}
202
+ return transport.request(Opcode.PHONE_AUTH_VERIFY, { verifyCode, token, authTokenType: 'CHECK_CODE' }, { prependOpHint: 0xad, flags: 1 });
203
+ }
204
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}