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 +300 -16
- package/config/example.json +6 -0
- package/example-ios.js +186 -0
- package/example-sms.js +131 -0
- package/example-token.js +100 -0
- package/example.js +2 -0
- package/index.js +2 -0
- package/lib/client.js +510 -54
- package/lib/opcodes.js +3 -1
- package/lib/socketTransport.js +296 -0
- package/lib/userAgent.js +8 -4
- package/package.json +16 -3
package/README.md
CHANGED
|
@@ -2,13 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## 📖 Описание / Description
|
|
4
4
|
|
|
5
|
-
**WebMaxSocket** — async Node.js библиотека для работы с внутренним API мессенджера Max.
|
|
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
|
-
- ✅ **
|
|
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
|
-
|
|
79
|
+
#### Способ 1: QR-код (рекомендуется для первого запуска)
|
|
67
80
|
|
|
68
|
-
|
|
81
|
+
При первом запуске вы увидите QR-код в консоли:
|
|
69
82
|
|
|
70
83
|
```
|
|
71
84
|
🔐 АВТОРИЗАЦИЯ ЧЕРЕЗ QR-КОД
|
|
72
85
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
73
86
|
|
|
74
|
-
📱
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
Спасибо за вашу поддержку!
|
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
|
+
});
|