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.
@@ -0,0 +1,724 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import { type SessionStore } from './storage.js';
3
+ import type { AuthPasswordInfoResponse, CallHistoryEntry, PasswordChallenge, VideoUploadInfo, WebTokenResponse, ActiveSession, AttachFilterType, BotMiniAppOpenResponse, CallCallerParams, CallStartResponse, ChatFolder, ChatReactionsSettings, ChatReactionsSettingsRequest, ChatSettingsOptions, ChatSettingsRequest, ClientEvents, CommonChatsResponse, ComplainsSyncResponse, ContactActionRequest, ContactActionResponse, ContactAddByPhoneResponse, ContactInfo, ContactsListByStatusRequest, DeleteMessagesResponse, EventLogItem, ExternalVideoResolveResponse, FileDownloadUrlResponse, FileUploadInfo, FolderUpsertRequest, FolderUpsertResponse, FoldersOrderResponse, GlobalSearchHit, GlobalSearchResponse, GlobalSearchSection, IncomingMessage, MaxChat, MaxMessage, MemberEntry, MessageAttach, MessageElement, MessageSearchHit, OutgoingMessage, PollInfo, PollState, ProfileUpdateRequest, ReactionInfo, SendMessageResponse, SettingsRequest, SettingsResponse, StickerPackActionResponse, TypingRequest, UserAgent, UserSettings } from './types.js';
4
+ export interface ClientOptions {
5
+ /** WebSocket endpoint. Defaults to wss://ws-api.oneme.ru/websocket */
6
+ url?: string;
7
+ /**
8
+ * Session store for persisting deviceId + tokens.
9
+ * Default: FileSessionStore('./.max-session.json').
10
+ * Override to support multiple instances or custom secret stores.
11
+ */
12
+ session?: SessionStore;
13
+ /**
14
+ * Inline credentials. If set, overrides the session store and uses an
15
+ * in-memory store seeded with these values. Use this to run several
16
+ * MaxClient instances in one process or to load creds from env / a vault.
17
+ *
18
+ * If only deviceId is provided → first run will perform QR login.
19
+ * If both are provided → silent login with the stored token.
20
+ */
21
+ deviceId?: string;
22
+ loginToken?: string;
23
+ /**
24
+ * Path of the default file store. Ignored when `session` or `loginToken`
25
+ * is provided. Useful if you want a separate file per instance.
26
+ */
27
+ sessionFile?: string;
28
+ /** UA spoof. Override only if you need to mimic a different device. */
29
+ userAgent?: UserAgent;
30
+ /** Print QR to stdout via qrcode-terminal. Default true. */
31
+ printQr?: boolean;
32
+ /**
33
+ * Print the deviceId + loginToken to stdout right after a fresh QR login,
34
+ * so the user can copy them into env / config for later reuse. Default true.
35
+ */
36
+ printCredentialsAfterLogin?: boolean;
37
+ /** Auto-mark incoming messages as read. Default false. */
38
+ autoRead?: boolean;
39
+ /** Number of chats to preload on login. Default 40. */
40
+ chatsCount?: number;
41
+ /** Heartbeat interval in ms. Default 30_000. */
42
+ pingIntervalMs?: number;
43
+ /** Per-request timeout in ms. Default 20_000. */
44
+ requestTimeoutMs?: number;
45
+ /**
46
+ * Called during QR login when the account has a 2FA cloud password.
47
+ * Return the password to finish op 115 → token. Return `null` to abort.
48
+ * Without this callback, accounts with 2FA throw at login time.
49
+ */
50
+ resolvePassword?: (challenge: PasswordChallenge) => Promise<string | null>;
51
+ }
52
+ /**
53
+ * Options for {@link MaxClient.loginWithPhone}. Inherits everything from
54
+ * {@link ClientOptions} plus phone-specific fields.
55
+ */
56
+ export interface PhoneLoginOptions {
57
+ /**
58
+ * Phone number in E.164 format, e.g. `+79991234567`. Pass a callback
59
+ * (`() => Promise<string>`) to defer the prompt — it'll only be called on
60
+ * a fresh login, never on silent reconnect.
61
+ */
62
+ phone: string | (() => Promise<string>);
63
+ /**
64
+ * Called when an SMS code is needed. Return the digits the user received.
65
+ * Throw / return empty string to abort the login.
66
+ */
67
+ getSmsCode: () => Promise<string>;
68
+ /**
69
+ * Called when the account has a 2FA cloud password. Receives the masked
70
+ * email + hint from the challenge. Return the password, or `null` to abort.
71
+ * If absent and the account has 2FA → `loginWithPhone()` throws.
72
+ *
73
+ * The callback is invoked at most once per `loginWithPhone()` call — both
74
+ * the mobile-side op 115 and the web-side op 115 (if any) reuse the same
75
+ * cached value.
76
+ */
77
+ getPassword?: (challenge: PasswordChallenge) => Promise<string | null>;
78
+ /**
79
+ * Hex-dump the mobile binary TX/RX to stderr (helpful for debugging
80
+ * region-gated server errors). Default false.
81
+ */
82
+ debug?: boolean;
83
+ }
84
+ export declare interface MaxClient {
85
+ on<E extends keyof ClientEvents>(event: E, listener: ClientEvents[E]): this;
86
+ once<E extends keyof ClientEvents>(event: E, listener: ClientEvents[E]): this;
87
+ off<E extends keyof ClientEvents>(event: E, listener: ClientEvents[E]): this;
88
+ emit<E extends keyof ClientEvents>(event: E, ...args: Parameters<ClientEvents[E]>): boolean;
89
+ }
90
+ /**
91
+ * High-level MAX account client.
92
+ *
93
+ * ```ts
94
+ * const client = new MaxClient();
95
+ * client.on('message', (m) => console.log(m.fromId, m.text));
96
+ * await client.start();
97
+ * await client.sendMessage(0, 'hello, saved messages!');
98
+ * ```
99
+ */
100
+ export declare class MaxClient extends EventEmitter {
101
+ private readonly url;
102
+ private readonly session;
103
+ private readonly userAgent;
104
+ private readonly printQr;
105
+ private readonly printCredentialsAfterLogin;
106
+ private readonly autoRead;
107
+ private readonly chatsCount;
108
+ private readonly resolvePassword?;
109
+ private transport;
110
+ private state;
111
+ private me;
112
+ private chats;
113
+ constructor(opts?: ClientOptions);
114
+ private static resolveSession;
115
+ /** Connect, handshake, login (via QR if no token stored), and emit `ready`. */
116
+ start(): Promise<void>;
117
+ /** Disconnect and stop reconnects. */
118
+ stop(): Promise<void>;
119
+ /** Logged-in account info. Available after `ready`. */
120
+ getMe(): ContactInfo | null;
121
+ /** Chats snapshot (last seen by the server during login). */
122
+ getChats(): MaxChat[];
123
+ /**
124
+ * Send a text message to a chat. Use `chatId = 0` for Saved Messages.
125
+ */
126
+ sendMessage(chatId: number, text: string, opts?: Partial<OutgoingMessage>): Promise<MaxMessage>;
127
+ /** Reply helper — sends to the same chat the incoming message came from. */
128
+ reply(incoming: IncomingMessage, text: string): Promise<MaxMessage>;
129
+ /**
130
+ * Send a quoted reply to a specific message. Server attaches the original
131
+ * message envelope automatically based on `messageId`.
132
+ */
133
+ sendReply(chatId: number, replyToMessageId: string, text: string): Promise<MaxMessage>;
134
+ /**
135
+ * Set (or replace) your reaction on a message. Re-calling with a different
136
+ * emoji upserts — there is no separate "change" call.
137
+ */
138
+ setReaction(chatId: number, messageId: string, emoji: string): Promise<ReactionInfo>;
139
+ /** Fetch reactions for a batch of messages in one chat. */
140
+ getReactions(chatId: number, messageIds: string[]): Promise<Record<string, ReactionInfo>>;
141
+ /**
142
+ * Detailed reaction listing for a single message. `count` is the number of
143
+ * reactor entries to fetch (default 100 in the web client).
144
+ */
145
+ listReactions(chatId: number, messageId: string, count?: number): Promise<ReactionInfo>;
146
+ /** Remove your reaction from a message. */
147
+ removeReaction(chatId: number, messageId: string): Promise<ReactionInfo>;
148
+ /**
149
+ * Delete one or more messages.
150
+ * - `forMe=true` → delete only on this device (own messages, any time).
151
+ * - `forMe=false` → delete for all participants. Requires admin rights for
152
+ * foreign messages in groups, or being the author within edit window.
153
+ */
154
+ deleteMessages(chatId: number, messageIds: string[], forMe?: boolean): Promise<DeleteMessagesResponse>;
155
+ /** Edit your own message. 7-day window per server config (`edit-timeout`). */
156
+ editMessage(chatId: number, messageId: string, text: string, opts?: {
157
+ elements?: MessageElement[];
158
+ attachments?: MessageAttach[];
159
+ }): Promise<MaxMessage>;
160
+ /**
161
+ * Forward a message into another chat. Pass `toChatId=0` for Saved Messages.
162
+ * Optional `comment` adds a leading text message before the forward.
163
+ */
164
+ forwardMessage(toChatId: number, sourceChatId: number, messageId: string, comment?: string): Promise<MaxMessage>;
165
+ /**
166
+ * Create a new group chat with given participants. Returns the SendMessage
167
+ * response: `chatId` is the new group's id, `chat` carries the freshly
168
+ * created group snapshot.
169
+ */
170
+ createGroup(title: string, userIds: number[], opts?: {
171
+ chatType?: 'CHAT' | 'CHANNEL';
172
+ }): Promise<SendMessageResponse & {
173
+ chat?: MaxChat;
174
+ }>;
175
+ /** Add members to a group. */
176
+ addGroupParticipants(chatId: number, userIds: number[], showHistory?: boolean): Promise<MaxChat>;
177
+ /**
178
+ * Remove a member from the group. `cleanMsgPeriod` (seconds) wipes that much
179
+ * of the user's recent history, 0 keeps everything.
180
+ */
181
+ removeGroupParticipant(chatId: number, userId: number, cleanMsgPeriod?: number): Promise<MaxChat>;
182
+ /** Promote a user to admin. `permissions` is a bitmask, 255 = full admin. */
183
+ setGroupAdmin(chatId: number, userId: number, permissions?: number): Promise<MaxChat>;
184
+ /** Demote an admin back to a regular member. */
185
+ removeGroupAdmin(chatId: number, userId: number): Promise<MaxChat>;
186
+ /**
187
+ * Update group settings (op 55). Pass any subset:
188
+ * `options` — boolean flags (ALL_CAN_PIN_MESSAGE, ONLY_ADMIN_CAN_CALL, …).
189
+ * `theme` / `description` — rename / set bio.
190
+ * `pinMessageId` / `notifyPin` — pin a message.
191
+ */
192
+ updateChatSettings(chatId: number, patch: Omit<ChatSettingsRequest, 'chatId'>): Promise<MaxChat>;
193
+ /** Convenience: rename group + optionally update description. */
194
+ renameGroup(chatId: number, title: string, description?: string): Promise<MaxChat>;
195
+ /** Set group permission flags. Merges with existing options server-side. */
196
+ setGroupOptions(chatId: number, options: ChatSettingsOptions): Promise<MaxChat>;
197
+ /** Pin a message in chat. `notify=true` posts a service message. */
198
+ pinMessage(chatId: number, messageId: string, notify?: boolean): Promise<MaxChat>;
199
+ /**
200
+ * Configure per-chat reaction policy.
201
+ * - disable: `setChatReactionsPolicy(id, { value: false })`
202
+ * - allow N: `setChatReactionsPolicy(id, { value: true, included: true, reactionIds: ['👍','❤️'], count: 2 })`
203
+ * - allow all:`setChatReactionsPolicy(id, { value: true, included: false, count: 2 })`
204
+ */
205
+ setChatReactionsPolicy(chatId: number, cfg: Omit<ChatReactionsSettingsRequest, 'chatId'>): Promise<ChatReactionsSettings>;
206
+ /** Chats you share with the given user. */
207
+ getCommonChats(userId: number): Promise<CommonChatsResponse>;
208
+ /** Search messages inside a chat by text. */
209
+ searchMessages(chatId: number, query: string, count?: number): Promise<MessageSearchHit[]>;
210
+ /**
211
+ * Mark a chat as "opened" / focused. Web client sends this around search
212
+ * sessions and chat focus changes; server replies with empty payload.
213
+ */
214
+ openChat(chatId: number): Promise<void>;
215
+ /** Mark a message as read. */
216
+ readMessage(chatId: number, messageId: string, mark?: number): Promise<void>;
217
+ /** Send "typing…" presence to a chat. Repeats are needed to keep it active. */
218
+ sendTyping(chatId: number, type?: TypingRequest['type']): Promise<void>;
219
+ /** Fetch message history from a chat. */
220
+ getHistory(chatId: number, opts?: {
221
+ from?: number;
222
+ backward?: number;
223
+ forward?: number;
224
+ }): Promise<MaxMessage[]>;
225
+ /** Fetch chats by ids. */
226
+ getChatsByIds(chatIds: number[]): Promise<MaxChat[]>;
227
+ /** Subscribe / unsubscribe to push events inside a chat. */
228
+ subscribeChat(chatId: number, subscribe: boolean): Promise<void>;
229
+ /** Escape hatch: send any opcode and get its response. */
230
+ raw<T = unknown>(opcode: number, payload?: unknown): Promise<T>;
231
+ /** Update own profile fields. Pass any subset. */
232
+ updateProfile(patch: ProfileUpdateRequest): Promise<ContactInfo>;
233
+ /** Convenience: rename own account. */
234
+ setProfileName(firstName: string, lastName?: string): Promise<ContactInfo>;
235
+ /** Set profile bio. */
236
+ setProfileDescription(description: string): Promise<ContactInfo>;
237
+ /**
238
+ * Set own avatar from a previously uploaded photo. Get `photoToken` from
239
+ * `uploadPhotoBytes()` (high-level) or `requestPhotoUploadUrl()` + HTTP POST.
240
+ */
241
+ setProfileAvatar(photoToken: string, firstName?: string, lastName?: string): Promise<ContactInfo>;
242
+ /** Raw settings setter — pass any subset of `settings.user` / `settings.chats`. */
243
+ updateSettings(patch: SettingsRequest['settings']): Promise<SettingsResponse>;
244
+ /**
245
+ * Mute / unmute a chat.
246
+ * - `muteUntilMs` omitted or `-1` → mute forever
247
+ * - `0` → unmute
248
+ * - timestamp (ms) → mute until that moment
249
+ */
250
+ muteChat(chatId: number, muteUntilMs?: number): Promise<SettingsResponse>;
251
+ /** Convenience: unmute chat. */
252
+ unmuteChat(chatId: number): Promise<SettingsResponse>;
253
+ /** Update privacy / push / safety toggles on the user level. */
254
+ setUserSettings(user: UserSettings): Promise<SettingsResponse>;
255
+ /** List active login sessions across devices. */
256
+ listSessions(): Promise<ActiveSession[]>;
257
+ /** Low-level contact action. Use the named helpers below for clarity. */
258
+ contactAction(req: ContactActionRequest): Promise<ContactActionResponse>;
259
+ /** Remove a saved contact. */
260
+ removeContact(contactId: number): Promise<void>;
261
+ /** Rename a contact in your address book (does not affect their profile). */
262
+ renameContact(contactId: number, firstName: string, lastName?: string): Promise<ContactInfo | undefined>;
263
+ blockContact(contactId: number): Promise<void>;
264
+ unblockContact(contactId: number): Promise<void>;
265
+ /**
266
+ * List your contacts filtered by status. `status:'BLOCKED'` is the block list.
267
+ * Paginate with `from` (offset).
268
+ */
269
+ listContactsByStatus(status: ContactsListByStatusRequest['status'], opts?: {
270
+ count?: number;
271
+ from?: number;
272
+ }): Promise<ContactInfo[]>;
273
+ /** Get the list of users you've blocked. */
274
+ listBlockedContacts(opts?: {
275
+ count?: number;
276
+ from?: number;
277
+ }): Promise<ContactInfo[]>;
278
+ /**
279
+ * Add a contact by phone number (E.164). Throws if the phone is not registered.
280
+ * `new=false` in response means the contact already existed.
281
+ */
282
+ addContactByPhone(phone: string, firstName?: string, lastName?: string): Promise<ContactAddByPhoneResponse>;
283
+ /** Batch-fetch contact info by ids (op 32). */
284
+ getContactsInfo(contactIds: number[]): Promise<ContactInfo[]>;
285
+ /** Convenience: fetch a single contact by id. */
286
+ getContactInfo(contactId: number): Promise<ContactInfo | undefined>;
287
+ /** Get last-seen / online status for one or more contacts (op 35). */
288
+ getPresence(contactIds: number[]): Promise<{
289
+ presence: Record<string, {
290
+ seen: number;
291
+ status: number;
292
+ }>;
293
+ time: number;
294
+ }>;
295
+ /**
296
+ * Subscribe to presence push updates for a user (op 177). Server then sends
297
+ * op 132 frames whenever their `seen`/`status` change. Pass `time:0` for
298
+ * "from now"; positive timestamp resumes from a previous cursor.
299
+ */
300
+ subscribePresence(userId: number, time?: number): Promise<void>;
301
+ /** Resolve a phone number to a contact without saving. */
302
+ lookupContactByPhone(phone: string): Promise<ContactInfo>;
303
+ /**
304
+ * Fetch surrounding messages filtered by attach type. Used by photo/file
305
+ * viewers to walk media in the chat.
306
+ */
307
+ getHistoryByAttach(chatId: number, messageId: string, attachTypes: AttachFilterType[], opts?: {
308
+ forward?: number;
309
+ backward?: number;
310
+ }): Promise<MaxMessage[]>;
311
+ /**
312
+ * Delete (or wipe) a chat. `forAll=true` removes for every participant.
313
+ * `lastEventTimeMs` defaults to "now" (deletes everything up to current).
314
+ */
315
+ deleteChat(chatId: number, forAll?: boolean, lastEventTimeMs?: number): Promise<void>;
316
+ /** Leave a group / channel. Server posts a CONTROL `leave` message. */
317
+ leaveChat(chatId: number): Promise<MaxMessage>;
318
+ /** Paginated participants list. Use `type:'ADMIN'` to list admins only. */
319
+ listMembers(chatId: number, opts?: {
320
+ type?: 'MEMBER' | 'ADMIN';
321
+ marker?: number;
322
+ count?: number;
323
+ }): Promise<MemberEntry[]>;
324
+ /** View counts for channel posts. */
325
+ getMessageStats(chatId: number, messageIds: string[]): Promise<Record<string, {
326
+ views: number;
327
+ }>>;
328
+ /** Cross-section global search. Paginate via `marker`. */
329
+ globalSearch(query: string, opts?: {
330
+ count?: number;
331
+ marker?: string;
332
+ }): Promise<GlobalSearchResponse>;
333
+ /** Single-section search (returns full chat objects in a flat array). */
334
+ globalSearchByType(query: string, type: GlobalSearchSection, count?: number): Promise<GlobalSearchHit[]>;
335
+ /**
336
+ * Initiate a 1:1 audio or video call to a single user. Returns the WebRTC
337
+ * endpoint info (`internalCallerParams` parsed from JSON) — connect to
338
+ * that WebSocket separately to actually carry media.
339
+ *
340
+ * NOTE: this opens the call signalling slot only; full call media goes over
341
+ * `wss://videowebrtc.okcdn.ru/ws2` and is out of scope of this library.
342
+ */
343
+ startCall(calleeId: number, opts?: {
344
+ isVideo?: boolean;
345
+ deviceId?: string;
346
+ conversationId?: string;
347
+ }): Promise<{
348
+ conversationId: string;
349
+ caller: CallCallerParams;
350
+ raw: CallStartResponse;
351
+ }>;
352
+ /** Allocate one or more photo upload slots. Returns the HTTPS upload URL. */
353
+ requestPhotoUploadUrl(count?: number): Promise<string>;
354
+ /** Allocate file upload slot(s). Returns `{url, fileId, token}` per slot. */
355
+ requestFileUploadUrl(count?: number): Promise<FileUploadInfo[]>;
356
+ /** Resolve a CDN download URL for a file attach. */
357
+ getFileDownloadUrl(fileId: number, chatId: number, messageId: string): Promise<FileDownloadUrlResponse>;
358
+ /** Resolve playable URLs for an external (e.g. OK / VK) video attachment. */
359
+ resolveExternalVideo(videoId: string, token: string, chatId: number, messageId: string): Promise<ExternalVideoResolveResponse>;
360
+ /**
361
+ * Upload a photo end-to-end and return its photoToken. Combines op 80 with
362
+ * the HTTPS multipart POST. Uses global `fetch` (Node 18+).
363
+ */
364
+ uploadPhotoBytes(file: {
365
+ data: Uint8Array | ArrayBuffer | Blob;
366
+ filename: string;
367
+ contentType?: string;
368
+ }): Promise<string>;
369
+ /**
370
+ * Upload a file end-to-end and return `{fileId, token}` ready to be used as
371
+ * a `_type:'FILE'` attach. Awaits the op 136 push so caller knows the file
372
+ * is fully ingested before sending the message.
373
+ */
374
+ uploadFileBytes(file: {
375
+ data: Uint8Array | ArrayBuffer | Blob;
376
+ filename: string;
377
+ }): Promise<FileUploadInfo>;
378
+ /** Send a photo message. `photoToken` from `uploadPhotoBytes()`. */
379
+ sendPhoto(chatId: number, photoToken: string, caption?: string): Promise<MaxMessage>;
380
+ /** Send a file message. `fileId` from `uploadFileBytes()`. */
381
+ sendFile(chatId: number, fileId: number | string, caption?: string): Promise<MaxMessage>;
382
+ /** Send a sticker. `stickerId` from sticker pack listings (op 26). */
383
+ sendSticker(chatId: number, stickerId: number | string): Promise<MaxMessage>;
384
+ /** Share a contact. */
385
+ sendContact(chatId: number, contactId: number, caption?: string): Promise<MaxMessage>;
386
+ /** Add a sticker pack to favourites. */
387
+ favoriteStickerPack(packId: number | string): Promise<StickerPackActionResponse>;
388
+ /** Remove a sticker pack from favourites. */
389
+ unfavoriteStickerPack(packId: number | string): Promise<StickerPackActionResponse>;
390
+ /**
391
+ * Create a poll in a chat.
392
+ * - `settings` bitmask: 4 = anonymous, 12 = anon + visible voters list.
393
+ * - default = anonymous.
394
+ */
395
+ sendPoll(chatId: number, title: string, answers: string[], settings?: number): Promise<MaxMessage>;
396
+ /** Cast or change a vote. Pass multiple `answerIds` for multi-choice polls. */
397
+ votePoll(chatId: number, messageId: string, pollId: number | string, answerIds: number[]): Promise<PollState>;
398
+ /** Fetch current state of one or more polls. */
399
+ getPolls(chatId: number, polls: Array<{
400
+ messageId: string;
401
+ pollId: number | string;
402
+ }>): Promise<PollInfo[]>;
403
+ /** Open a bot mini-app and obtain its launch URL with signed WebAppData. */
404
+ openBotMiniApp(botId: number, chatId: number): Promise<BotMiniAppOpenResponse>;
405
+ /** Fetch the localised report-reason catalogue (used to build report dialogs). */
406
+ getComplainReasons(complainSync?: number): Promise<ComplainsSyncResponse>;
407
+ /** Fetch specific folders by id. */
408
+ getFolders(folderIds: string[]): Promise<ChatFolder[]>;
409
+ /** Create or update a folder. Pass id from a previous folder for update. */
410
+ upsertFolder(folder: FolderUpsertRequest): Promise<FolderUpsertResponse>;
411
+ /**
412
+ * Reorder folders by sending the new id sequence.
413
+ *
414
+ * Op 275 (FOLDERS_REORDER) — earlier versions of this file used op 276,
415
+ * which is actually FOLDERS_DELETE. The fix follows the canonical opcode
416
+ * names extracted from the Android client.
417
+ */
418
+ reorderFolders(folderIds: string[]): Promise<FoldersOrderResponse>;
419
+ /** Read reactions policy for many chats at once. */
420
+ getChatReactionsPolicies(chatIds: number[]): Promise<ChatReactionsSettings[]>;
421
+ /** Set / replace a chat (group or channel) avatar from an uploaded photo token. */
422
+ setChatAvatar(chatId: number, photoToken: string): Promise<MaxChat>;
423
+ /** Rotate the private invite link of a group / channel. */
424
+ revokeChatInviteLink(chatId: number): Promise<MaxChat>;
425
+ /** Toggle "join requires admin approval" on a channel. */
426
+ setChannelJoinRequest(chatId: number, enabled: boolean): Promise<MaxChat>;
427
+ /** Unpin pinned message (sample observation: empty string clears it). */
428
+ unpinMessage(chatId: number): Promise<MaxChat>;
429
+ /** Send analytics events to the server. Fire-and-forget. */
430
+ logEvents(events: EventLogItem[]): void;
431
+ /**
432
+ * Round-trip ping (op 1). Earlier versions used op 200 which is actually
433
+ * PROFILE_DELETE_TIME; the canonical keep-alive op in the Android client
434
+ * is the standard PING.
435
+ */
436
+ keepalive(): Promise<{
437
+ timestamp: number;
438
+ }>;
439
+ /**
440
+ * Log out server-side (op 20) and wipe local session (deviceId is kept,
441
+ * loginToken cleared so next `start()` triggers a fresh QR login).
442
+ * Closes the websocket on the way out.
443
+ */
444
+ logout(): Promise<void>;
445
+ /**
446
+ * Refresh the short-lived web token (op 158). MAX rotates this every
447
+ * ~14 minutes — call before the `token_refresh_ts` timestamp.
448
+ */
449
+ refreshWebToken(): Promise<WebTokenResponse>;
450
+ /** Open a new auth track (op 112) used by all 2FA / email-recovery ops. */
451
+ startAuthTrack(type?: number): Promise<string>;
452
+ /** Op 104: report whether a 2FA password is set, and the recovery email. */
453
+ getPasswordInfo(trackId: string): Promise<AuthPasswordInfoResponse['password']>;
454
+ /**
455
+ * Enable 2FA (cloud password). Requires email ownership confirmation:
456
+ * 1. server emails a code to `recoveryEmail` (op 109)
457
+ * 2. caller resolves it via `getCode(blockingDuration, codeLength)`
458
+ * 3. code verified (op 110), password committed (op 111)
459
+ */
460
+ enable2faPassword(opts: {
461
+ password: string;
462
+ hint: string;
463
+ recoveryEmail: string;
464
+ getCode: (info: {
465
+ blockingDuration: number;
466
+ codeLength: number;
467
+ }) => Promise<string>;
468
+ }): Promise<void>;
469
+ /** Disable 2FA cloud password (op 113 verify → op 111 with `remove2fa:true`). */
470
+ disable2faPassword(currentPassword: string): Promise<void>;
471
+ /**
472
+ * Re-prove ownership of the 2FA password mid-session (op 113). Server may
473
+ * require this before sensitive settings changes.
474
+ */
475
+ verify2faPassword(password: string): Promise<string>;
476
+ /**
477
+ * Manually finish a 2FA-gated QR login (op 115). Use when you bypass the
478
+ * `resolvePassword` callback and prefer to drive the flow yourself.
479
+ */
480
+ completeQrPasswordLogin(challenge: PasswordChallenge, password: string): Promise<string>;
481
+ /**
482
+ * Clear chat history (op 52). Unlike `deleteChat` (op 54), the chat itself
483
+ * stays in the list; only messages up to `lastEventTimeMs` are wiped.
484
+ */
485
+ clearChatHistory(chatId: number, forAll?: boolean, lastEventTimeMs?: number): Promise<void>;
486
+ /**
487
+ * Incremental chat-list sync (op 53). Pass the largest `modified` timestamp
488
+ * you've seen; server returns chats updated after `marker`.
489
+ */
490
+ syncChats(marker: number): Promise<MaxChat[]>;
491
+ /**
492
+ * Resolve a public `https://max.ru/<slug>` or invite link to a `MaxChat`
493
+ * snapshot (op 57 — `CHAT_JOIN`). Canonical op 57 actually performs a join
494
+ * server-side; if you only want to peek without joining, use
495
+ * `raw(Opcode.CHAT_CHECK_LINK, { link })` (op 56) instead.
496
+ */
497
+ resolveChatLink(link: string): Promise<MaxChat>;
498
+ /**
499
+ * Server-side URL preview (op 70). Web client calls this when the user
500
+ * pastes a link into the composer; result is a `SHARE` attach you can
501
+ * pass straight to `sendMessage(..., { attaches })`.
502
+ */
503
+ previewLink(text: string): Promise<MessageAttach[]>;
504
+ /** Recent calls history (op 79). Newest first when `forward:false`. */
505
+ getCallHistory(opts?: {
506
+ count?: number;
507
+ forward?: boolean;
508
+ marker?: number;
509
+ }): Promise<CallHistoryEntry[]>;
510
+ /**
511
+ * Allocate upload slot(s) (op 82). The same opcode serves video AND audio —
512
+ * pass `{uploaderType, type}` to select the CDN.
513
+ *
514
+ * From on-the-wire capture of `ru.oneme.app` v26.15.1:
515
+ * - `{uploaderType:0, type:1}` → video (OK-CDN)
516
+ * - `{uploaderType:1, type:2}` → audio / voice note (`au.oneme.ru/uploadAudio`)
517
+ *
518
+ * **Transport routing**: when `{uploaderType, type}` is provided we route
519
+ * through a short-lived mobile binary connection — the WS transport
520
+ * ignores these fields and always hands back a video URL. Without them,
521
+ * we keep the historical WS path (cheap, no extra TLS handshake).
522
+ */
523
+ requestVideoUploadUrl(count?: number, opts?: {
524
+ uploaderType?: number;
525
+ type?: number;
526
+ }): Promise<VideoUploadInfo[]>;
527
+ /**
528
+ * Open a short-lived mobile binary session (HELLO + LOGIN with the saved
529
+ * `mobileLoginToken`), run `fn`, and close. Used for opcodes whose audio
530
+ * / video variant is mobile-only (e.g. op 82 with `{uploaderType:1, type:2}`).
531
+ *
532
+ * Throws if the session has no `mobileLoginToken` — i.e. the user never
533
+ * logged in via phone-auth on this client.
534
+ */
535
+ private runOnMobileTransport;
536
+ /**
537
+ * Send a regular video message (any aspect, any length within MAX limits).
538
+ *
539
+ * Like {@link sendVoice}, the whole upload + MSG_SEND flow runs in a
540
+ * single short-lived mobile-binary session — WS-issued tokens are not
541
+ * valid for the mobile-side VIDEO attach lookup.
542
+ *
543
+ * Wire format (captured from `ru.oneme.app` v26.15.1):
544
+ * `{_type:'VIDEO', token, thumbhash?}`
545
+ */
546
+ sendVideo(chatId: number, video: {
547
+ data: Uint8Array | ArrayBuffer | Blob;
548
+ filename?: string;
549
+ }, caption?: string, opts?: {
550
+ cid?: number;
551
+ thumbhash?: Uint8Array;
552
+ }): Promise<MaxMessage>;
553
+ /**
554
+ * Send a circular "video note" — short clip, square aspect, ≤ 60 s, no
555
+ * caption. Same wire-format as {@link sendVideo} but with the
556
+ * `videoType: 1` flag that makes the recipient client render the
557
+ * round-clip UI.
558
+ */
559
+ sendVideoNote(chatId: number, video: {
560
+ data: Uint8Array | ArrayBuffer | Blob;
561
+ filename?: string;
562
+ }, opts?: {
563
+ cid?: number;
564
+ thumbhash?: Uint8Array;
565
+ }): Promise<MaxMessage>;
566
+ /**
567
+ * Low-level: upload video bytes via op 82 + OK-CDN POST. Returns
568
+ * `{token}`. The token is bound to the mobile session that issued it —
569
+ * prefer {@link sendVideo} / {@link sendVideoNote} for the full E2E flow.
570
+ */
571
+ uploadVideoBytes(file: {
572
+ data: Uint8Array | ArrayBuffer | Blob;
573
+ filename?: string;
574
+ }): Promise<{
575
+ token: string;
576
+ }>;
577
+ private sendVideoInternal;
578
+ /**
579
+ * Send a voice / audio message.
580
+ *
581
+ * The whole flow (request audio upload URL → POST raw bytes →
582
+ * MSG_SEND with the AUDIO attach) runs on a short-lived mobile binary
583
+ * connection. WS-side tokens issued by `op 82 {uploaderType:1, type:2}`
584
+ * are **not** valid for WS `op 64` — the back-end keeps separate session
585
+ * scopes, so we have to stay on mobile binary for the whole exchange.
586
+ *
587
+ * Wire format (captured from `ru.oneme.app` v26.15.1):
588
+ * `{_type:'AUDIO', token, duration, wave?}`
589
+ *
590
+ * @param chatId target chat. `0` for Saved Messages.
591
+ * @param audio `{data, filename?}` — bytes of the audio (any opus / ogg /
592
+ * mp3 / m4a / aac). WAV PCM is rejected by the server
593
+ * (`video.not.supported`).
594
+ * @param opts `durationMs` (**milliseconds** — the wire field is ms!)
595
+ * OR `duration` (seconds — auto-multiplied by 1000); `wave`
596
+ * — raw 80-byte waveform buffer (omit for flat waveform);
597
+ * `cid` — client-side message id (auto-generated otherwise).
598
+ */
599
+ sendVoice(chatId: number, audio: {
600
+ data: Uint8Array | ArrayBuffer | Blob;
601
+ filename?: string;
602
+ }, opts?: {
603
+ duration?: number;
604
+ durationMs?: number;
605
+ wave?: Uint8Array;
606
+ cid?: number;
607
+ }): Promise<MaxMessage>;
608
+ /**
609
+ * Retry helper for the `errors.process.attachment.video.not.ready` race
610
+ * that occurs when MSG_SEND lands before the server finishes ingesting
611
+ * the just-uploaded media. Attempts at 500ms, 1s, 2s, 4s (≈ 8s total).
612
+ */
613
+ private retryNotReady;
614
+ /**
615
+ * Upload an audio file via op 82 (audio variant) and return `{token}`.
616
+ * **NOTE:** the returned `token` is bound to a mobile session — it is NOT
617
+ * accepted by WS `op 64`. Use {@link sendVoice} for the full E2E send;
618
+ * this method is kept for parity / low-level callers.
619
+ */
620
+ uploadAudioBytes(file: {
621
+ data: Uint8Array | ArrayBuffer | Blob;
622
+ filename: string;
623
+ }): Promise<{
624
+ token: string;
625
+ }>;
626
+ /**
627
+ * Send a geolocation message. Coordinates are validated server-side —
628
+ * pass valid WGS-84 lat/lng. Optional `name`/`address` show as a labelled
629
+ * preview alongside the map.
630
+ */
631
+ sendLocation(chatId: number, latitude: number, longitude: number, opts?: {
632
+ name?: string;
633
+ address?: string;
634
+ }): Promise<MaxMessage>;
635
+ /**
636
+ * Fetch a single message by id from a chat (op 71 — MSG_GET).
637
+ * Returns `undefined` if the message is not found or already deleted.
638
+ */
639
+ getMessage(chatId: number, messageId: string): Promise<MaxMessage | undefined>;
640
+ /**
641
+ * Search your contacts by free-text (name / phone fragment) — op 37.
642
+ * Server returns the best matches sorted by relevance.
643
+ *
644
+ * The server may key the array under different names depending on the
645
+ * route (`contacts` for the contact-book search, `users` for the global
646
+ * directory fallback). We coalesce both into a single array so callers
647
+ * don't have to branch.
648
+ */
649
+ searchContacts(query: string, count?: number): Promise<ContactInfo[]>;
650
+ /**
651
+ * Get the list of contacts you share with another user (op 38).
652
+ * Different from {@link getCommonChats} — this is users you both know,
653
+ * not chats you're both in.
654
+ */
655
+ getMutualContacts(userId: number): Promise<ContactInfo[]>;
656
+ /**
657
+ * Request audio-to-text transcription of a voice / audio message (op 202).
658
+ * The actual transcription is computed asynchronously; the response is the
659
+ * task acknowledgement. The finished transcript arrives via push op 293
660
+ * (`NOTIF_TRANSCRIPTION`) which is forwarded as the `'raw'` event for now.
661
+ */
662
+ transcribeMedia(chatId: number, messageId: string): Promise<void>;
663
+ /**
664
+ * Fetch a bot's metadata + description (op 145).
665
+ * `botId` is the user id of the bot account.
666
+ */
667
+ getBotInfo(botId: number): Promise<unknown>;
668
+ /**
669
+ * List a bot's `/`-commands as configured by its owner via BotFather-like
670
+ * flow (op 144).
671
+ */
672
+ getBotCommands(botId: number, chatId?: number): Promise<unknown>;
673
+ /**
674
+ * Save a local draft to the server-side so it syncs across your devices
675
+ * (op 176). `text` may be empty to clear; pass `cid` if echoing a message
676
+ * that's being drafted.
677
+ */
678
+ saveDraft(chatId: number, text: string, opts?: {
679
+ cid?: number;
680
+ }): Promise<void>;
681
+ /**
682
+ * Discard the server-side draft for a chat (op 177).
683
+ */
684
+ discardDraft(chatId: number): Promise<void>;
685
+ /**
686
+ * Close a specific active session by id (op 97).
687
+ * Use {@link listSessions} to discover ids. The current session can't be
688
+ * closed via this op — use {@link logout} instead.
689
+ */
690
+ closeSession(sessionId: number | string): Promise<void>;
691
+ /**
692
+ * One-shot phone-number login with automatic web QR-bind.
693
+ *
694
+ * On first call:
695
+ * 1. opens a mobile binary transport (TLS to api.oneme.ru:443)
696
+ * 2. requests an SMS code (op 17) → calls `getSmsCode()`
697
+ * 3. submits the code (op 18) → mobile LOGIN token (or 2FA challenge)
698
+ * 4. if 2FA: calls `getPassword()` → submits via op 115
699
+ * 5. completes the mobile session (op 19)
700
+ * 6. opens a web WS and does QR-bind via op 290 ↔ ops 288/289/291
701
+ * (mobile transport plays the role of the "scanning device" — no
702
+ * camera, no other phone needed)
703
+ * 7. saves both tokens to the session store and returns a ready
704
+ * {@link MaxClient} connected via WS.
705
+ *
706
+ * On subsequent calls: finds the saved web token and silently reconnects;
707
+ * `getSmsCode` / `getPassword` are never invoked.
708
+ *
709
+ * ```ts
710
+ * const client = await MaxClient.loginWithPhone({
711
+ * phone: '+79991234567',
712
+ * getSmsCode: async () => readline('SMS code: '),
713
+ * getPassword: async (challenge) => readline(`2FA (hint: ${challenge.hint}): `),
714
+ * sessionFile: './.max-session.json',
715
+ * });
716
+ * client.on('message', m => console.log(m.fromId, m.text));
717
+ * await client.sendMessage(0, 'hi from phone-login');
718
+ * ```
719
+ */
720
+ static loginWithPhone(opts: PhoneLoginOptions & Omit<ClientOptions, 'deviceId' | 'loginToken'>): Promise<MaxClient>;
721
+ private handlePush;
722
+ private rehandshake;
723
+ }
724
+ //# sourceMappingURL=client.d.ts.map