webmaxsocket 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,13 +2,16 @@
2
2
 
3
3
  ## 📖 Описание / Description
4
4
 
5
- **WebMaxSocket** — async Node.js библиотека для работы с внутренним API мессенджера Max. Позволяет создавать WebSocket соединение с **QR-кодом авторизацией**.
5
+ **WebMaxSocket** — async Node.js библиотека для работы с внутренним API мессенджера Max. Поддерживает **QR-код авторизацию**, **Token авторизацию**, и работу через **WebSocket** (WEB) или **TCP Socket** (IOS/ANDROID).
6
6
 
7
7
  ## ✨ Особенности / Features
8
8
 
9
9
  - ✅ **QR-код авторизация** / QR code authentication
10
- - ✅ **WebSocket соединение** / WebSocket connection
10
+ - ✅ **QR для привязки устройства** (`showLinkDeviceQR`) после входа по SMS/TCP тот же сценарий, что «Профиль → Устройства → Подключить устройство» в приложении
11
+ - ✅ **Token авторизация** / Token authentication
12
+ - ✅ **Два транспорта:** WebSocket (WEB) и TCP Socket (IOS/ANDROID)
11
13
  - ✅ **Автоматическое сохранение сессий** / Automatic session storage
14
+ - ✅ **Автовыбор транспорта** после QR-авторизации (переход на TCP)
12
15
  - ✅ **Отправка и получение сообщений** / Send and receive messages
13
16
  - ✅ **Редактирование и удаление сообщений** / Edit and delete messages
14
17
  - ✅ **Event-driven архитектура** / Event-driven architecture
@@ -21,6 +24,16 @@
21
24
  npm install webmaxsocket
22
25
  ```
23
26
 
27
+ ### Зависимости для Socket транспорта (IOS/ANDROID)
28
+
29
+ Для работы с TCP Socket транспортом требуется библиотека `lz4`. Если при установке возникают проблемы с `node-gyp`:
30
+
31
+ ```bash
32
+ npm install lz4 --ignore-scripts
33
+ ```
34
+
35
+ **Примечание:** Для обычной QR-авторизации (WEB) дополнительные зависимости не нужны. Socket транспорт используется только после сохранения сессии или при явном указании `deviceType: 'IOS'`/`'ANDROID'`.
36
+
24
37
  ## 🚀 Быстрый старт / Quick Start
25
38
 
26
39
  ### Базовый пример / Basic Example
@@ -63,28 +76,111 @@ main().catch(console.error);
63
76
 
64
77
  ### Авторизация / Authentication
65
78
 
66
- При первом запуске вы увидите QR-код в консоли:
79
+ #### Способ 1: QR-код (рекомендуется для первого запуска)
67
80
 
68
- On first run, you'll see a QR code in the console:
81
+ При первом запуске вы увидите QR-код в консоли:
69
82
 
70
83
  ```
71
84
  🔐 АВТОРИЗАЦИЯ ЧЕРЕЗ QR-КОД
72
85
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
73
86
 
74
- 📱 Откройте приложение Max на телефоне
75
- ➡️ Настройки → Устройства → Подключить устройство
87
+ 📱 На телефоне: Профиль Устройства / Безопасность → Подключить устройство
76
88
  📸 Отсканируйте QR-код
77
89
 
78
90
  █████████████████████████████
79
- █████████████████████████████
80
- ████ ▄▄▄▄▄ █▀█ █▄▄█ ▄▄▄▄▄ ████
81
- ████ █ █ █▀▀▀█ ▄█ █ █ ████
82
91
  ...
83
92
  ```
84
93
 
85
- После сканирования токен сохраняется автоматически.
94
+ После сканирования:
95
+ - Токен и clientSessionId сохраняются автоматически
96
+ - При следующем запуске клиент **автоматически переключится на TCP Socket** для стабильности
97
+ - Повторная авторизация не требуется
98
+
99
+ #### Способ 2: SMS авторизация (IOS/ANDROID)
100
+
101
+ Авторизация по номеру телефона с кодом из SMS:
102
+
103
+ ```javascript
104
+ const client = new WebMaxClient({
105
+ name: 'my_session',
106
+ deviceType: 'IOS' // Обязательно для SMS авторизации
107
+ });
108
+
109
+ await client.connect();
110
+
111
+ // Запрос кода
112
+ const authSession = await client.authorizeBySMS('+79001234567');
113
+
114
+ // Получаем код из SMS и отправляем
115
+ const code = '123456'; // Код из SMS
116
+ await authSession.sendCode(code);
117
+
118
+ // Завершаем запуск
119
+ await client.triggerHandlers(client.handlers.START);
120
+ ```
121
+
122
+ Или запустите готовый пример:
123
+
124
+ ```bash
125
+ node example-sms.js
126
+ node example-sms.js +79001234567 # с номером в аргументе
127
+ ```
128
+
129
+ #### QR после входа: привязка второго устройства (IOS/ANDROID)
130
+
131
+ Когда вы уже авторизованы по **TCP** (SMS и сохранённая сессия), запрос **`GET_QR` на том же соединении недоступен** (ответ сервера: недопустимое состояние сессии). Для сценария как в приложении — **показать QR, телефон сканирует** — используйте метод **`showLinkDeviceQR()`**: библиотека открывает **отдельное краткоживущее WebSocket-подключение** (как у [web.max.ru](https://web.max.ru)), запрашивает QR, печатает его в консоль и при необходимости ждёт сканирования.
132
+
133
+ Требования: активное соединение и **`isAuthorized`** (обычно после `await client.start()`).
134
+
135
+ ```javascript
136
+ await client.start();
137
+
138
+ // Показать QR и ждать, пока отсканируют в приложении Max на телефоне
139
+ await client.showLinkDeviceQR();
140
+
141
+ // Только показать QR и вернуть данные (без ожидания скана)
142
+ const data = await client.showLinkDeviceQR({ waitForScan: false });
143
+ // data: { qrLink, trackId, pollingInterval, expiresAt }
144
+ ```
145
+
146
+ Опции: `waitForScan` (по умолчанию `true`), `small` — компактный QR в терминале.
147
+
148
+ **Версия клиента:** для выдачи QR сервер ожидает актуальный **`appVersion`** в User-Agent (не ниже **25.12.13**). В конструкторе по умолчанию используется **25.12.14**; при необходимости передайте `appVersion: '25.21.3'` или новее.
86
149
 
87
- After scanning, the token is saved automatically.
150
+ Если сервер отвечает **`qr_login.disabled`**, проверьте версию приложения в опциях, откройте [web.max.ru](https://web.max.ru) в браузере или войдите на втором устройстве по номеру телефона.
151
+
152
+ #### Способ 3: Token авторизация
153
+
154
+ Если у вас уже есть токен (от другого сервиса/приложения):
155
+
156
+ ```javascript
157
+ const client = new WebMaxClient({
158
+ name: 'my_session',
159
+ token: 'An_Sx6HQ9HDiftNkVBNf6Q5PG5D8Oyj...', // Ваш токен
160
+ configPath: 'config/myconfig.json', // Или из файла
161
+ saveToken: true
162
+ });
163
+
164
+ await client.start();
165
+ ```
166
+
167
+ Формат конфига (`config/default.json`):
168
+ ```json
169
+ {
170
+ "token": "An_Sx6HQ9HDiftNk...",
171
+ "ua": "Mozilla/5.0 (iPhone...)",
172
+ "device_type": 2,
173
+ "deviceType": "IOS"
174
+ }
175
+ ```
176
+
177
+ #### Транспорты
178
+
179
+ - **WEB** (`deviceType: 'WEB'` или `device_type: 1`) → WebSocket (ws-api.oneme.ru)
180
+ - **IOS** (`deviceType: 'IOS'` или `device_type: 2`) → TCP Socket (api.oneme.ru)
181
+ - **ANDROID** (`deviceType: 'ANDROID'` или `device_type: 3`) → TCP Socket (api.oneme.ru)
182
+
183
+ Клиент **автоматически выбирает** правильный транспорт на основе сохраненного deviceType.
88
184
 
89
185
  ## API
90
186
 
@@ -97,10 +193,23 @@ After scanning, the token is saved automatically.
97
193
  ```javascript
98
194
  const client = new WebMaxClient({
99
195
  name: 'session', // Имя сессии (для сохранения авторизации)
100
- phone: '+1234567890', // Номер телефона
196
+ token: 'An_Sx6H...', // Токен авторизации (опционально)
197
+ configPath: 'myconfig', // Путь к config файлу (опционально)
198
+ deviceType: 'WEB', // Тип устройства: 'WEB', 'IOS', 'ANDROID', 'DESKTOP' (опционально)
199
+ saveToken: true, // Сохранять токен в сессию (по умолчанию true)
200
+ debug: false, // Отладочный режим (опционально)
101
201
  apiUrl: 'wss://...', // URL WebSocket API (опционально)
102
202
  maxReconnectAttempts: 5,// Максимальное количество попыток переподключения
103
- reconnectDelay: 3000 // Задержка между попытками переподключения (мс)
203
+ reconnectDelay: 3000, // Задержка между попытками переподключения (мс)
204
+ // User-Agent / клиент (важно для GET_QR, см. showLinkDeviceQR):
205
+ appVersion: '25.12.14', // Рекомендуется ≥ 25.12.13 для запроса QR
206
+ ua: 'Mozilla/5.0 ...', // или headerUserAgent
207
+ osVersion: 'Windows 11',
208
+ screen: '1920x1080 1.0x',
209
+ timezone: 'Europe/Moscow',
210
+ locale: 'ru',
211
+ buildNumber: 0x97cb, // опционально
212
+ clientSessionId: 1 // опционально
104
213
  });
105
214
  ```
106
215
 
@@ -114,9 +223,42 @@ const client = new WebMaxClient({
114
223
  await client.start();
115
224
  ```
116
225
 
226
+ ##### `authorizeBySMS(phone)`
227
+
228
+ Авторизация по номеру телефона через SMS (только для IOS/ANDROID).
229
+
230
+ ```javascript
231
+ // Подключаемся
232
+ await client.connect();
233
+
234
+ // Запрашиваем код
235
+ const authSession = await client.authorizeBySMS('+79001234567');
236
+
237
+ // Вводим код из SMS
238
+ await authSession.sendCode('123456');
239
+ ```
240
+
241
+ ##### `showLinkDeviceQR(options)`
242
+
243
+ Показать в консоли **QR-код для привязки устройства** (как в приложении Max: телефон сканирует QR). Нужна **уже выполненная авторизация** (`start()` или `connect` + `sync`).
244
+
245
+ - Для **WEB** запрос выполняется по текущему WebSocket.
246
+ - Для **IOS/ANDROID** после входа по TCP используется **второе** WebSocket-подключение без повторного `LOGIN` на той сессии (иначе `GET_QR` на том же TCP недоступен).
247
+
248
+ ```javascript
249
+ await client.showLinkDeviceQR();
250
+ await client.showLinkDeviceQR({ waitForScan: false, small: false });
251
+ ```
252
+
253
+ Возвращает `Promise<{ qrLink, trackId, pollingInterval, expiresAt }>`.
254
+
255
+ ##### `requestQR()`, `checkQRStatus(trackId)`, `loginByQR(trackId)`, `authorizeByQR()`
256
+
257
+ Низкоуровневые шаги QR-авторизации для **WEB** (первый вход без SMS). Обычно достаточно `start()` без токена или `authorizeByQR()`.
258
+
117
259
  ##### `sendMessage(options)`
118
260
 
119
- Отправляет сообщение в чат.
261
+ Отправляет сообщение в чат с уведомлением (notify: true).
120
262
 
121
263
  ```javascript
122
264
  const message = await client.sendMessage({
@@ -128,6 +270,20 @@ const message = await client.sendMessage({
128
270
  });
129
271
  ```
130
272
 
273
+ ##### `sendMessageChannel(options)`
274
+
275
+ Отправляет сообщение в канал без уведомления (notify: false).
276
+
277
+ ```javascript
278
+ const message = await client.sendMessageChannel({
279
+ chatId: 123,
280
+ text: 'Сообщение в канал',
281
+ cid: Date.now(),
282
+ replyTo: null, // ID сообщения для ответа (опционально)
283
+ attachments: [] // Вложения (опционально)
284
+ });
285
+ ```
286
+
131
287
  ##### `editMessage(options)`
132
288
 
133
289
  Редактирует сообщение.
@@ -362,22 +518,98 @@ ChatActions.RECORDING_VOICE // Записывает голосовое
362
518
  ChatActions.RECORDING_VIDEO // Записывает видео
363
519
  ```
364
520
 
521
+ ### MaxSocketTransport
522
+
523
+ Низкоуровневый TCP Socket транспорт для IOS/ANDROID (api.oneme.ru).
524
+
525
+ #### Прямое использование (advanced)
526
+
527
+ ```javascript
528
+ const { MaxSocketTransport } = require('webmaxsocket');
529
+
530
+ const transport = new MaxSocketTransport({
531
+ deviceType: 'IOS',
532
+ ua: 'Mozilla/5.0 (iPhone...)',
533
+ deviceId: 'your-device-id',
534
+ debug: true
535
+ });
536
+
537
+ await transport.connect();
538
+ await transport.handshake(userAgentPayload);
539
+ const syncData = await transport.sync(token, userAgent);
540
+ ```
541
+
542
+ **Примечание:** В большинстве случаев используйте `WebMaxClient`, который автоматически выбирает нужный транспорт.
543
+
544
+ ## 📚 Примеры
545
+
546
+ ### Пример 1: QR-авторизация (example.js)
547
+
548
+ ```bash
549
+ node example.js
550
+ ```
551
+
552
+ Первый запуск - QR-авторизация, повторные запуски - автоматический вход через TCP Socket.
553
+
554
+ ### Пример 2: Token авторизация (example-token.js)
555
+
556
+ ```bash
557
+ # Через config файл
558
+ node example-token.js
559
+ node example-token.js myconfig # config/myconfig.json
560
+
561
+ # Через переменную окружения
562
+ TOKEN="ваш_токен" node example-token.js
563
+ ```
564
+
565
+ ### Пример 3: SMS авторизация (example-sms.js)
566
+
567
+ ```bash
568
+ # Интерактивный ввод номера
569
+ node example-sms.js
570
+
571
+ # С номером в аргументе
572
+ node example-sms.js +79001234567
573
+ ```
574
+
575
+ ### Пример 4: IOS/ANDROID Socket (example-ios.js)
576
+
577
+ ```bash
578
+ # С готовым конфигом
579
+ node example-ios.js
580
+
581
+ # С отладкой
582
+ node example-ios.js --debug
583
+ ```
584
+
585
+ ### Пример 5: QR для второго устройства после SMS
586
+
587
+ После успешного `start()` с сохранённой сессией IOS/Android вызовите `showLinkDeviceQR()` (см. раздел **«QR после входа»** выше).
588
+
365
589
  ## Структура проекта
366
590
 
367
591
  ```
368
592
  webmaxsocket/
369
593
  ├── lib/
370
594
  │ ├── client.js # Основной клиент
595
+ │ ├── socketTransport.js # TCP Socket транспорт
371
596
  │ ├── session.js # Управление сессиями
597
+ │ ├── userAgent.js # UserAgent генератор
598
+ │ ├── opcodes.js # Протокол опкоды
372
599
  │ ├── constants.js # Константы
373
600
  │ └── entities/
374
601
  │ ├── User.js # Класс пользователя
375
602
  │ ├── Message.js # Класс сообщения
376
603
  │ ├── ChatAction.js # Класс действия в чате
377
604
  │ └── index.js # Экспорт сущностей
605
+ ├── config/ # Конфигурационные файлы
606
+ │ └── example.json # Пример конфига
378
607
  ├── sessions/ # Директория с сохраненными сессиями
379
608
  ├── index.js # Точка входа
380
- ├── example.js # Пример использования
609
+ ├── example.js # QR-авторизация
610
+ ├── example-token.js # Token авторизация
611
+ ├── example-sms.js # SMS авторизация
612
+ ├── example-ios.js # IOS/ANDROID Socket
381
613
  ├── package.json
382
614
  └── README.md
383
615
  ```
@@ -408,4 +640,56 @@ try {
408
640
  } catch (error) {
409
641
  console.error('Ошибка:', error.message);
410
642
  }
411
- ```
643
+ ```
644
+
645
+ ## 🔧 Отладка / Debug
646
+
647
+ Для включения отладочного вывода:
648
+
649
+ ```javascript
650
+ const client = new WebMaxClient({
651
+ name: 'my_session',
652
+ debug: true // или process.env.DEBUG = '1'
653
+ });
654
+ ```
655
+
656
+ Или через переменную окружения:
657
+
658
+ ```bash
659
+ DEBUG=1 node example.js
660
+ ```
661
+
662
+ ## 💡 Важные замечания
663
+
664
+ 1. **TCP Socket после QR-авторизации:** После первой успешной QR-авторизации клиент автоматически сохраняет `clientSessionId` и переключается на TCP Socket транспорт при следующем запуске для повышения стабильности.
665
+
666
+ 2. **QR для нового устройства после входа по SMS/TCP:** Используйте `showLinkDeviceQR()`. Это не отдельный опкод в протоколе, а тот же `GET_QR`, что и у веб-клиента; для уже залогиненного TCP-сокета запрос выполняется через **эфемерное WebSocket-подключение** (временный файл сессии `_link_qr_*` удаляется после завершения).
667
+
668
+ 3. **Версия `appVersion` и QR:** Слишком старая версия в User-Agent может привести к ответу `qr_login.disabled` на `GET_QR`. Задайте в конструкторе актуальную строку (по умолчанию **25.12.14**).
669
+
670
+ 4. **Разница между sendMessage и sendMessageChannel:**
671
+ - `sendMessage()` - отправка с уведомлением (notify: true) для обычных чатов
672
+ - `sendMessageChannel()` - отправка без уведомления (notify: false) для каналов
673
+
674
+ 5. **Автоматический выбор транспорта:** Клиент автоматически определяет какой транспорт использовать на основе `deviceType` в сессии или config файле.
675
+
676
+ ## 🔗 Ссылки / Links
677
+
678
+ - [GitHub Repository](https://github.com/Tellarion/webmaxsocket)
679
+ - [NPM Package](https://www.npmjs.com/package/webmaxsocket)
680
+
681
+ ## 📄 Лицензия / License
682
+
683
+ MIT License - see LICENSE file for details
684
+
685
+ ## 👤 Автор / Author
686
+
687
+ Tellarion - [tellarion.dev](https://tellarion.dev)
688
+
689
+ ## 💝 Поддержка / Support
690
+
691
+ Если вам нравится эта библиотека и вы хотите поддержать разработку:
692
+
693
+ **USDT (TRC20):** `TXfs1iVbp2aLd3rbc4cenVzMoTevP5RbBE`
694
+
695
+ Спасибо за вашу поддержку!
@@ -0,0 +1,6 @@
1
+ {
2
+ "token": "YOUR_TOKEN_HERE",
3
+ "ua": "Mozilla/5.0 (iPhone15,2; CPU iPhone OS 18_6_2 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/602.1.50",
4
+ "device_type": 2,
5
+ "deviceType": "IOS"
6
+ }
package/example-ios.js ADDED
@@ -0,0 +1,186 @@
1
+ /**
2
+ * IOS/ANDROID: вход через Socket (api.oneme.ru), не WebSocket
3
+ * WEB: через WebSocket (ws-api.oneme.ru)
4
+ *
5
+ * С готовым конфигом: node example-ios.js
6
+ * Без токена — запрос телефона и SMS
7
+ */
8
+
9
+ const path = require('path');
10
+ const fs = require('fs');
11
+ const { WebMaxClient, MaxSocketTransport } = require('./index');
12
+ const { UserAgentPayload } = require('./lib/userAgent');
13
+ const { Opcode } = require('./lib/opcodes');
14
+
15
+ const argv = process.argv.slice(2).filter((a) => a !== '--debug' && a !== '-d');
16
+ if (process.argv.includes('--debug') || process.argv.includes('-d')) process.env.DEBUG = '1';
17
+ const CONFIG_NAME = argv[0] || process.env.CONFIG || 'default';
18
+
19
+ function loadConfig() {
20
+ const base = path.join(process.cwd(), 'config');
21
+ const p = path.join(base, CONFIG_NAME + (CONFIG_NAME.endsWith('.json') ? '' : '.json'));
22
+ if (!fs.existsSync(p)) return null;
23
+ return JSON.parse(fs.readFileSync(p, 'utf8'));
24
+ }
25
+
26
+ function configToUserAgent(config) {
27
+ const dt = config.deviceType || (config.device_type === 2 ? 'IOS' : config.device_type === 3 ? 'ANDROID' : 'WEB');
28
+ return new UserAgentPayload({
29
+ deviceType: dt,
30
+ locale: config.locale || 'ru',
31
+ deviceLocale: config.deviceLocale || config.locale || 'ru',
32
+ osVersion: config.osVersion || '18.6.2',
33
+ deviceName: config.deviceName || 'Safari',
34
+ headerUserAgent: config.headerUserAgent || config.ua || '',
35
+ appVersion: config.appVersion || '25.12.14',
36
+ screen: config.screen || '390x844 3.0x',
37
+ timezone: config.timezone || 'Europe/Moscow',
38
+ buildNumber: config.buildNumber,
39
+ clientSessionId: config.clientSessionId,
40
+ release: config.release
41
+ });
42
+ }
43
+
44
+ function useSocket(config) {
45
+ const dt = config.deviceType || config.device_type;
46
+ return dt === 'IOS' || dt === 'ANDROID' || dt === 2 || dt === 3;
47
+ }
48
+
49
+ async function main() {
50
+ const config = loadConfig();
51
+ const hasToken = config && config.token && config.token.length >= 50;
52
+
53
+ if (hasToken && useSocket(config)) {
54
+ console.log('🚀 Socket (api.oneme.ru) — IOS/ANDROID\n');
55
+ const ua = configToUserAgent(config);
56
+ const transport = new MaxSocketTransport({
57
+ deviceType: config.deviceType || (config.device_type === 2 ? 'IOS' : 'ANDROID'),
58
+ ua: config.headerUserAgent || config.ua,
59
+ deviceId: config.deviceId || require('uuid').v4(),
60
+ debug: process.env.DEBUG === '1'
61
+ });
62
+
63
+ transport.onNotification = (data) => {
64
+ if (data.opcode === Opcode.NOTIF_MESSAGE && data.payload) {
65
+ const m = data.payload;
66
+ if (m.senderId !== transport.me?.id) {
67
+ console.log('💬', (m.sender?.firstName || 'User') + ':', m.text || '[вложение]');
68
+ }
69
+ }
70
+ };
71
+
72
+ await transport.connect();
73
+ await transport.handshake(ua);
74
+
75
+ const syncResp = await transport.sync(config.token, ua.toJSON());
76
+
77
+ if (process.env.DEBUG === '1') {
78
+ const s = JSON.stringify(syncResp, null, 2);
79
+ console.log('\n📋 Sync (сырой):', s.slice(0, 3000) + (s.length > 3000 ? '\n...' : ''));
80
+ }
81
+ console.log('\n📋 Ключи sync:', Object.keys(syncResp || {}).join(', '));
82
+
83
+ const contact = syncResp?.profile?.contact ?? syncResp?.profile?.user ?? syncResp?.contact;
84
+ if (contact) {
85
+ const names = contact.names || [];
86
+ const name = Array.isArray(names) ? names[0] : names;
87
+ const first = name?.firstName ?? name?.name ?? '';
88
+ const last = name?.lastName ?? '';
89
+ transport.me = {
90
+ id: contact.id,
91
+ firstname: first,
92
+ lastname: last,
93
+ fullname: [first, last].filter(Boolean).join(' ') || 'User'
94
+ };
95
+ console.log('\n👤 Профиль:');
96
+ console.log(' ID:', contact.id);
97
+ console.log(' Имя:', transport.me.fullname);
98
+ console.log(' Телефон:', contact.phone ? '+' + contact.phone : '—');
99
+ }
100
+
101
+ const syncChats = syncResp?.chats ?? syncResp?.chatList ?? syncResp?.list ?? [];
102
+ const chats = await transport.getChats();
103
+ const allChats = syncChats.length ? syncChats : chats;
104
+ console.log('\n📂 Диалоги (' + allChats.length + '):');
105
+ allChats.slice(0, 20).forEach((c, i) => {
106
+ const t = c.title ?? c.name ?? c.chat?.title ?? ('Chat ' + (c.id ?? c.chatId ?? i));
107
+ const lastMsg = c.lastMessage?.text ?? c.lastMessage?.message ?? '—';
108
+ console.log(` ${i + 1}. ${t} — ${String(lastMsg).slice(0, 35)}`);
109
+ });
110
+ if (allChats.length > 20) console.log(' ... и ещё', allChats.length - 20);
111
+
112
+ const contacts = syncResp?.contacts || [];
113
+ if (contacts.length) {
114
+ console.log('\n📇 Контакты:', contacts.length);
115
+ }
116
+
117
+ console.log('\n🤖 Socket работает (Ctrl+C — выход)\n');
118
+ process.on('SIGINT', () => { transport.close(); process.exit(0); });
119
+ return;
120
+ }
121
+
122
+ if (hasToken) {
123
+ const client = new WebMaxClient({
124
+ name: 'web_session',
125
+ configPath: CONFIG_NAME,
126
+ saveToken: false,
127
+ debug: process.env.DEBUG === '1'
128
+ });
129
+ client.onStart(async () => {
130
+ if (client.me) console.log('\n✅ Вход:', client.me.fullname || client.me.firstname, '(ID:', client.me.id + ')\n');
131
+ try { console.log('📂 Диалогов:', (await client.getChats()).length); } catch (e) { console.log('⚠️', e.message); }
132
+ });
133
+ client.onMessage((m) => { if (m.senderId !== client.me?.id) console.log('💬', m.getSenderName() + ':', m.text); });
134
+ client.onError((e) => console.error('❌', e.message));
135
+ await client.start();
136
+ console.log('🤖 Бот работает (Ctrl+C — выход)\n');
137
+ return;
138
+ }
139
+
140
+ const readline = require('readline');
141
+ const { v4: uuidv4 } = require('uuid');
142
+ const IOS_UA = 'Mozilla/5.0 (iPhone15,2; CPU iPhone OS 18_6_2 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/602.1.50';
143
+
144
+ function ask(q) {
145
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
146
+ return new Promise((r) => rl.question(q, (a) => { rl.close(); r(a.trim()); }));
147
+ }
148
+
149
+ console.log('📱 Получение токена по телефону (Socket)\n');
150
+ const phone = await ask('Номер (+79xxxxxxxxx): ');
151
+ if (!/^\+?\d{10,15}$/.test(phone.replace(/\s/g, ''))) { console.error('❌ Неверный формат'); process.exit(1); }
152
+ let clean = phone.replace(/\D/g, '');
153
+ if (clean.startsWith('8') && clean.length === 11) clean = '7' + clean.slice(1);
154
+ else if (clean.startsWith('9') && clean.length === 10) clean = '7' + clean;
155
+ const norm = '+' + clean;
156
+
157
+ const transport = new MaxSocketTransport({ deviceType: 'IOS', ua: IOS_UA, deviceId: uuidv4(), debug: process.env.DEBUG === '1' });
158
+ await transport.connect();
159
+ await transport.handshake(new UserAgentPayload({ deviceType: 'IOS', headerUserAgent: IOS_UA, appVersion: '25.12.14', osVersion: '18.6.2', deviceName: 'Safari', screen: '390x844 3.0x', locale: 'ru', deviceLocale: 'ru', timezone: 'Europe/Moscow' }));
160
+
161
+ console.log('📤 Запрос кода...');
162
+ const tempToken = await transport.requestCode(norm);
163
+ if (!tempToken) throw new Error('Не получен временный токен');
164
+ const code = await ask('Код из SMS (6 цифр): ');
165
+ if (!/^\d{6}$/.test(code)) throw new Error('Неверный код');
166
+
167
+ const authResp = await transport.sendCode(tempToken, code);
168
+ const token = authResp?.tokenAttrs?.LOGIN?.token;
169
+ if (!token) throw new Error(authResp?.passwordChallenge ? '2FA не поддерживается' : 'Токен не получен');
170
+ await transport.close();
171
+
172
+ const cfg = { token, ua: IOS_UA, device_type: 2, deviceType: 'IOS' };
173
+ const dir = path.join(process.cwd(), 'config');
174
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
175
+ fs.writeFileSync(path.join(dir, CONFIG_NAME + '.json'), JSON.stringify(cfg, null, 2), 'utf8');
176
+ console.log('\n✅ Конфиг сохранён:', path.join(dir, CONFIG_NAME + '.json'));
177
+ console.log(' Запуск: node example-ios.js', CONFIG_NAME);
178
+ }
179
+
180
+ main().catch((e) => { console.error('❌', e.message); process.exit(1); });
181
+ process.on('SIGINT', () => {
182
+ console.log('\n\n👋 Завершение работы...');
183
+ console.log('\n💝 Нравится библиотека? Поддержите разработку:');
184
+ console.log(' USDT (TRC20): TXfs1iVbp2aLd3rbc4cenVzMoTevP5RbBE');
185
+ process.exit(0);
186
+ });