webmaxsocket 1.0.0 → 1.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/README.md +239 -13
- 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 +345 -44
- package/lib/opcodes.js +3 -1
- package/lib/socketTransport.js +296 -0
- package/lib/userAgent.js +8 -4
- package/package.json +16 -3
package/lib/client.js
CHANGED
|
@@ -1,13 +1,35 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
1
3
|
const WebSocket = require('ws');
|
|
2
4
|
const EventEmitter = require('events');
|
|
3
5
|
const { v4: uuidv4 } = require('uuid');
|
|
4
6
|
const qrcode = require('qrcode-terminal');
|
|
5
7
|
const SessionManager = require('./session');
|
|
8
|
+
const { MaxSocketTransport } = require('./socketTransport');
|
|
6
9
|
const { Message, ChatAction, User } = require('./entities');
|
|
7
10
|
const { EventTypes, ChatActions } = require('./constants');
|
|
8
11
|
const { Opcode, DeviceType, getOpcodeName } = require('./opcodes');
|
|
9
12
|
const { UserAgentPayload } = require('./userAgent');
|
|
10
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Загружает конфиг: { token, agent }
|
|
16
|
+
*/
|
|
17
|
+
function loadSessionConfig(configPath) {
|
|
18
|
+
let resolved;
|
|
19
|
+
if (path.isAbsolute(configPath)) {
|
|
20
|
+
resolved = configPath;
|
|
21
|
+
} else if (!/[\\/]/.test(configPath) && !configPath.endsWith('.json')) {
|
|
22
|
+
resolved = path.join(process.cwd(), 'config', `${configPath}.json`);
|
|
23
|
+
} else {
|
|
24
|
+
resolved = path.join(process.cwd(), configPath);
|
|
25
|
+
}
|
|
26
|
+
if (!fs.existsSync(resolved)) {
|
|
27
|
+
throw new Error(`Конфиг не найден: ${resolved}`);
|
|
28
|
+
}
|
|
29
|
+
const data = fs.readFileSync(resolved, 'utf8');
|
|
30
|
+
return JSON.parse(data);
|
|
31
|
+
}
|
|
32
|
+
|
|
11
33
|
/**
|
|
12
34
|
* Основной клиент для работы с API Max
|
|
13
35
|
*/
|
|
@@ -18,20 +40,53 @@ class WebMaxClient extends EventEmitter {
|
|
|
18
40
|
this.phone = options.phone || null;
|
|
19
41
|
this.sessionName = options.name || options.session || 'default';
|
|
20
42
|
this.apiUrl = options.apiUrl || 'wss://ws-api.oneme.ru/websocket';
|
|
43
|
+
|
|
44
|
+
// Загрузка из config — token, ua (agent), device_type
|
|
45
|
+
let token = options.token || null;
|
|
46
|
+
let agent = options.ua || options.agent || options.headerUserAgent || null;
|
|
47
|
+
let configObj = {};
|
|
48
|
+
const configPath = options.configPath || options.config;
|
|
49
|
+
if (configPath) {
|
|
50
|
+
configObj = loadSessionConfig(configPath);
|
|
51
|
+
token = token || configObj.token || null;
|
|
52
|
+
agent = agent || configObj.agent || configObj.ua || configObj.headerUserAgent || null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
this._providedToken = token;
|
|
56
|
+
this._saveTokenToSession = options.saveToken !== false;
|
|
21
57
|
this.origin = 'https://web.max.ru';
|
|
22
58
|
this.session = new SessionManager(this.sessionName);
|
|
23
59
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
60
|
+
const deviceTypeMap = { 1: 'WEB', 2: 'IOS', 3: 'ANDROID' };
|
|
61
|
+
const rawDeviceType = options.deviceType ?? configObj.device_type ?? configObj.deviceType ?? this.session.get('deviceType');
|
|
62
|
+
const deviceType = deviceTypeMap[rawDeviceType] || rawDeviceType || 'WEB';
|
|
63
|
+
const uaString = agent || configObj.headerUserAgent || configObj.ua || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36';
|
|
64
|
+
const webDefaults = {
|
|
65
|
+
deviceType: deviceType,
|
|
66
|
+
locale: configObj.locale || 'ru',
|
|
67
|
+
deviceLocale: configObj.deviceLocale || configObj.locale || 'ru',
|
|
68
|
+
osVersion: configObj.osVersion || (deviceType === 'IOS' ? '18.6.2' : deviceType === 'ANDROID' ? '14' : 'Linux'),
|
|
69
|
+
deviceName: configObj.deviceName || (deviceType === 'IOS' ? 'Safari' : deviceType === 'ANDROID' ? 'Chrome' : 'Chrome'),
|
|
70
|
+
headerUserAgent: uaString,
|
|
71
|
+
appVersion: configObj.appVersion || '25.10.5',
|
|
72
|
+
screen: configObj.screen || (deviceType === 'IOS' ? '390x844 3.0x' : deviceType === 'ANDROID' ? '360x780 3.0x' : '1080x1920 1.0x'),
|
|
73
|
+
timezone: configObj.timezone || 'Europe/Moscow',
|
|
74
|
+
buildNumber: configObj.buildNumber,
|
|
75
|
+
clientSessionId: configObj.clientSessionId || this.session.get('clientSessionId'),
|
|
76
|
+
release: configObj.release
|
|
77
|
+
};
|
|
78
|
+
this._handshakeUserAgent = new UserAgentPayload(webDefaults);
|
|
79
|
+
this.userAgent = this._handshakeUserAgent;
|
|
28
80
|
|
|
29
|
-
// Device ID
|
|
30
81
|
this.deviceId = options.deviceId || this.session.get('deviceId') || uuidv4();
|
|
31
82
|
if (!this.session.get('deviceId')) {
|
|
32
83
|
this.session.set('deviceId', this.deviceId);
|
|
33
84
|
}
|
|
34
85
|
|
|
86
|
+
// Определяем тип транспорта: Socket для IOS/ANDROID, WebSocket для WEB
|
|
87
|
+
this._useSocketTransport = (deviceType === 'IOS' || deviceType === 'ANDROID');
|
|
88
|
+
this._socketTransport = null;
|
|
89
|
+
|
|
35
90
|
this.ws = null;
|
|
36
91
|
this.me = null;
|
|
37
92
|
this.isConnected = false;
|
|
@@ -55,6 +110,7 @@ class WebMaxClient extends EventEmitter {
|
|
|
55
110
|
|
|
56
111
|
this.messageQueue = [];
|
|
57
112
|
this.pendingRequests = new Map();
|
|
113
|
+
this.debug = options.debug || process.env.DEBUG === '1';
|
|
58
114
|
}
|
|
59
115
|
|
|
60
116
|
/**
|
|
@@ -135,22 +191,35 @@ class WebMaxClient extends EventEmitter {
|
|
|
135
191
|
try {
|
|
136
192
|
console.log('🚀 Запуск WebMax клиента...');
|
|
137
193
|
|
|
138
|
-
// Подключаемся к WebSocket
|
|
194
|
+
// Подключаемся к WebSocket или Socket
|
|
139
195
|
await this.connect();
|
|
140
196
|
|
|
141
|
-
//
|
|
142
|
-
const
|
|
197
|
+
// Приоритет: 1) переданный токен, 2) сохранённая сессия, 3) QR-авторизация
|
|
198
|
+
const tokenToUse = this._providedToken || this.session.get('token');
|
|
143
199
|
|
|
144
|
-
if (
|
|
145
|
-
|
|
146
|
-
|
|
200
|
+
if (tokenToUse) {
|
|
201
|
+
if (this._providedToken) {
|
|
202
|
+
console.log('✅ Вход по токену (token auth)');
|
|
203
|
+
if (this._saveTokenToSession) {
|
|
204
|
+
this.session.set('token', this._providedToken);
|
|
205
|
+
this.session.set('deviceId', this.deviceId);
|
|
206
|
+
}
|
|
207
|
+
} else {
|
|
208
|
+
console.log('✅ Найдена сохраненная сессия');
|
|
209
|
+
}
|
|
210
|
+
this._token = tokenToUse;
|
|
147
211
|
|
|
148
212
|
try {
|
|
149
213
|
await this.sync();
|
|
150
214
|
this.isAuthorized = true;
|
|
151
215
|
} catch (error) {
|
|
152
|
-
|
|
216
|
+
const wasTokenAuth = !!this._providedToken;
|
|
153
217
|
this.session.clear();
|
|
218
|
+
this._providedToken = null;
|
|
219
|
+
if (wasTokenAuth) {
|
|
220
|
+
throw new Error(`Токен недействителен или сессия истекла. Обновите токен в config. (${error.message})`);
|
|
221
|
+
}
|
|
222
|
+
console.log('⚠️ Сессия истекла, требуется повторная авторизация');
|
|
154
223
|
await this.authorize();
|
|
155
224
|
}
|
|
156
225
|
} else {
|
|
@@ -288,12 +357,25 @@ class WebMaxClient extends EventEmitter {
|
|
|
288
357
|
throw new Error('Токен не получен из ответа');
|
|
289
358
|
}
|
|
290
359
|
|
|
360
|
+
// Сохраняем токен и все данные сессии для TCP подключения
|
|
291
361
|
this.session.set('token', token);
|
|
292
362
|
this.session.set('deviceId', this.deviceId);
|
|
363
|
+
this.session.set('clientSessionId', this.userAgent.clientSessionId);
|
|
364
|
+
this.session.set('deviceType', 'IOS'); // Переключаемся на IOS для TCP при следующем запуске
|
|
365
|
+
this.session.set('headerUserAgent', this.userAgent.headerUserAgent);
|
|
366
|
+
this.session.set('appVersion', this.userAgent.appVersion);
|
|
367
|
+
this.session.set('osVersion', this.userAgent.osVersion);
|
|
368
|
+
this.session.set('deviceName', this.userAgent.deviceName);
|
|
369
|
+
this.session.set('screen', this.userAgent.screen);
|
|
370
|
+
this.session.set('timezone', this.userAgent.timezone);
|
|
371
|
+
this.session.set('locale', this.userAgent.locale);
|
|
372
|
+
this.session.set('buildNumber', this.userAgent.buildNumber);
|
|
373
|
+
|
|
293
374
|
this.isAuthorized = true;
|
|
294
375
|
this._token = token;
|
|
295
376
|
|
|
296
377
|
console.log('✅ Авторизация через QR-код успешна!');
|
|
378
|
+
console.log('💡 При следующем запуске будет использоваться TCP Socket транспорт');
|
|
297
379
|
|
|
298
380
|
// Выполняем sync
|
|
299
381
|
await this.sync();
|
|
@@ -305,11 +387,105 @@ class WebMaxClient extends EventEmitter {
|
|
|
305
387
|
}
|
|
306
388
|
|
|
307
389
|
/**
|
|
308
|
-
* Авторизация
|
|
390
|
+
* Авторизация по номеру телефона через SMS (для IOS/ANDROID)
|
|
309
391
|
*/
|
|
310
|
-
async
|
|
311
|
-
|
|
312
|
-
|
|
392
|
+
async authorizeBySMS(phone) {
|
|
393
|
+
if (!this._useSocketTransport) {
|
|
394
|
+
throw new Error('SMS авторизация доступна только для IOS/ANDROID (используйте deviceType: "IOS" или "ANDROID")');
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
try {
|
|
398
|
+
console.log('📱 Авторизация по номеру телефона...');
|
|
399
|
+
|
|
400
|
+
// Нормализация номера телефона
|
|
401
|
+
let cleanPhone = phone.replace(/\D/g, '');
|
|
402
|
+
if (cleanPhone.startsWith('8') && cleanPhone.length === 11) {
|
|
403
|
+
cleanPhone = '7' + cleanPhone.slice(1);
|
|
404
|
+
} else if (cleanPhone.startsWith('9') && cleanPhone.length === 10) {
|
|
405
|
+
cleanPhone = '7' + cleanPhone;
|
|
406
|
+
}
|
|
407
|
+
const normalizedPhone = '+' + cleanPhone;
|
|
408
|
+
|
|
409
|
+
console.log(`📤 Запрос кода на номер: ${normalizedPhone}`);
|
|
410
|
+
|
|
411
|
+
if (!this._socketTransport) {
|
|
412
|
+
throw new Error('Socket транспорт не инициализирован');
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Запрос кода
|
|
416
|
+
const tempToken = await this._socketTransport.requestCode(normalizedPhone);
|
|
417
|
+
|
|
418
|
+
if (!tempToken) {
|
|
419
|
+
throw new Error('Не получен временный токен');
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
console.log('✅ Код отправлен! Ожидаем ввода кода...');
|
|
423
|
+
|
|
424
|
+
return {
|
|
425
|
+
tempToken,
|
|
426
|
+
phone: normalizedPhone,
|
|
427
|
+
sendCode: async (code) => {
|
|
428
|
+
console.log('🔐 Проверка кода...');
|
|
429
|
+
|
|
430
|
+
const authResponse = await this._socketTransport.sendCode(tempToken, code);
|
|
431
|
+
|
|
432
|
+
if (authResponse?.passwordChallenge) {
|
|
433
|
+
throw new Error('2FA не поддерживается');
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const token = authResponse?.tokenAttrs?.LOGIN?.token;
|
|
437
|
+
|
|
438
|
+
if (!token) {
|
|
439
|
+
throw new Error('Токен не получен из ответа');
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Сохраняем сессию
|
|
443
|
+
this.session.set('token', token);
|
|
444
|
+
this.session.set('deviceId', this.deviceId);
|
|
445
|
+
this.session.set('clientSessionId', this.userAgent.clientSessionId);
|
|
446
|
+
this.session.set('deviceType', this.userAgent.deviceType);
|
|
447
|
+
this.session.set('headerUserAgent', this.userAgent.headerUserAgent);
|
|
448
|
+
this.session.set('appVersion', this.userAgent.appVersion);
|
|
449
|
+
this.session.set('osVersion', this.userAgent.osVersion);
|
|
450
|
+
this.session.set('deviceName', this.userAgent.deviceName);
|
|
451
|
+
this.session.set('screen', this.userAgent.screen);
|
|
452
|
+
this.session.set('timezone', this.userAgent.timezone);
|
|
453
|
+
this.session.set('locale', this.userAgent.locale);
|
|
454
|
+
this.session.set('buildNumber', this.userAgent.buildNumber);
|
|
455
|
+
|
|
456
|
+
this.isAuthorized = true;
|
|
457
|
+
this._token = token;
|
|
458
|
+
|
|
459
|
+
console.log('✅ Авторизация по SMS успешна!');
|
|
460
|
+
|
|
461
|
+
// Выполняем sync
|
|
462
|
+
await this.sync();
|
|
463
|
+
|
|
464
|
+
return token;
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
} catch (error) {
|
|
469
|
+
console.error('Ошибка SMS авторизации:', error);
|
|
470
|
+
throw error;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Авторизация пользователя (QR-код для WEB, SMS для IOS/ANDROID)
|
|
476
|
+
*/
|
|
477
|
+
async authorize(phone = null) {
|
|
478
|
+
if (this._useSocketTransport && phone) {
|
|
479
|
+
// SMS авторизация для IOS/ANDROID
|
|
480
|
+
console.log('🔐 Авторизация через SMS');
|
|
481
|
+
return await this.authorizeBySMS(phone);
|
|
482
|
+
} else if (this._useSocketTransport && !phone) {
|
|
483
|
+
throw new Error('Для IOS/ANDROID требуется номер телефона. Используйте: authorize("+79001234567")');
|
|
484
|
+
} else {
|
|
485
|
+
// QR авторизация для WEB
|
|
486
|
+
console.log('🔐 Авторизация через QR-код');
|
|
487
|
+
await this.authorizeByQR();
|
|
488
|
+
}
|
|
313
489
|
}
|
|
314
490
|
|
|
315
491
|
|
|
@@ -332,14 +508,16 @@ class WebMaxClient extends EventEmitter {
|
|
|
332
508
|
contactsSync: 0,
|
|
333
509
|
presenceSync: 0,
|
|
334
510
|
draftsSync: 0,
|
|
335
|
-
chatsCount: 40
|
|
336
|
-
userAgent: this.userAgent.toJSON()
|
|
511
|
+
chatsCount: 40
|
|
337
512
|
};
|
|
513
|
+
payload.userAgent = this.userAgent.toJSON();
|
|
338
514
|
|
|
339
515
|
const response = await this.sendAndWait(Opcode.LOGIN, payload);
|
|
340
516
|
|
|
341
517
|
if (response.payload && response.payload.error) {
|
|
342
|
-
|
|
518
|
+
const err = response.payload.error;
|
|
519
|
+
const msg = typeof err === 'string' ? err : (response.payload.localizedMessage || JSON.stringify(err));
|
|
520
|
+
throw new Error(msg);
|
|
343
521
|
}
|
|
344
522
|
|
|
345
523
|
// Сохраняем информацию о пользователе
|
|
@@ -421,17 +599,58 @@ class WebMaxClient extends EventEmitter {
|
|
|
421
599
|
|
|
422
600
|
|
|
423
601
|
/**
|
|
424
|
-
* Установка WebSocket
|
|
602
|
+
* Установка соединения (WebSocket или Socket)
|
|
425
603
|
*/
|
|
426
604
|
async connect() {
|
|
605
|
+
if (this._useSocketTransport) {
|
|
606
|
+
return this._connectSocket();
|
|
607
|
+
} else {
|
|
608
|
+
return this._connectWebSocket();
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* Подключение через TCP Socket (для IOS/ANDROID)
|
|
614
|
+
*/
|
|
615
|
+
async _connectSocket() {
|
|
616
|
+
if (this._socketTransport && this._socketTransport.socket && !this._socketTransport.socket.destroyed) {
|
|
617
|
+
this.isConnected = true;
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
this._socketTransport = new MaxSocketTransport({
|
|
622
|
+
deviceId: this.deviceId,
|
|
623
|
+
deviceType: this.userAgent.deviceType,
|
|
624
|
+
ua: this.userAgent.headerUserAgent,
|
|
625
|
+
debug: this.debug
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
this._socketTransport.onNotification = (data) => {
|
|
629
|
+
this.handleSocketNotification(data);
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
await this._socketTransport.connect();
|
|
633
|
+
await this._socketTransport.handshake(this.userAgent);
|
|
634
|
+
|
|
635
|
+
this.isConnected = true;
|
|
636
|
+
this.reconnectAttempts = 0;
|
|
637
|
+
this.emit('connected');
|
|
638
|
+
|
|
639
|
+
console.log('TCP Socket соединение установлено');
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* Установка WebSocket соединения (для WEB)
|
|
644
|
+
*/
|
|
645
|
+
async _connectWebSocket() {
|
|
427
646
|
if (this.ws && this.isConnected) {
|
|
428
647
|
return;
|
|
429
648
|
}
|
|
430
649
|
|
|
431
650
|
return new Promise((resolve, reject) => {
|
|
432
651
|
const headers = {
|
|
433
|
-
'
|
|
434
|
-
'
|
|
652
|
+
'Origin': this.origin,
|
|
653
|
+
'User-Agent': this._handshakeUserAgent.headerUserAgent
|
|
435
654
|
};
|
|
436
655
|
|
|
437
656
|
this.ws = new WebSocket(this.apiUrl, {
|
|
@@ -445,7 +664,6 @@ class WebMaxClient extends EventEmitter {
|
|
|
445
664
|
this.emit('connected');
|
|
446
665
|
|
|
447
666
|
try {
|
|
448
|
-
// Выполняем handshake
|
|
449
667
|
await this.handshake();
|
|
450
668
|
resolve();
|
|
451
669
|
} catch (error) {
|
|
@@ -466,6 +684,12 @@ class WebMaxClient extends EventEmitter {
|
|
|
466
684
|
this.ws.on('close', () => {
|
|
467
685
|
console.log('WebSocket соединение закрыто');
|
|
468
686
|
this.isConnected = false;
|
|
687
|
+
const err = new Error('Соединение закрыто');
|
|
688
|
+
for (const [, pending] of this.pendingRequests) {
|
|
689
|
+
if (pending.timeoutId) clearTimeout(pending.timeoutId);
|
|
690
|
+
pending.reject(err);
|
|
691
|
+
}
|
|
692
|
+
this.pendingRequests.clear();
|
|
469
693
|
this.triggerHandlers(EventTypes.DISCONNECT);
|
|
470
694
|
this.handleReconnect();
|
|
471
695
|
});
|
|
@@ -480,7 +704,7 @@ class WebMaxClient extends EventEmitter {
|
|
|
480
704
|
|
|
481
705
|
const payload = {
|
|
482
706
|
deviceId: this.deviceId,
|
|
483
|
-
userAgent: this.
|
|
707
|
+
userAgent: this._handshakeUserAgent.toJSON()
|
|
484
708
|
};
|
|
485
709
|
|
|
486
710
|
const response = await this.sendAndWait(Opcode.SESSION_INIT, payload);
|
|
@@ -493,6 +717,41 @@ class WebMaxClient extends EventEmitter {
|
|
|
493
717
|
return response;
|
|
494
718
|
}
|
|
495
719
|
|
|
720
|
+
/**
|
|
721
|
+
* Обработка уведомлений от Socket транспорта
|
|
722
|
+
*/
|
|
723
|
+
async handleSocketNotification(data) {
|
|
724
|
+
try {
|
|
725
|
+
if (this.debug && data.opcode !== Opcode.PING) {
|
|
726
|
+
const payload = data.payload?.error ? ` error=${JSON.stringify(data.payload.error)}` : '';
|
|
727
|
+
console.log(`📥 ${getOpcodeName(data.opcode)} (seq=${data.seq})${payload}`);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
switch (data.opcode) {
|
|
731
|
+
case Opcode.NOTIF_MESSAGE:
|
|
732
|
+
await this.handleNewMessage(data.payload);
|
|
733
|
+
break;
|
|
734
|
+
|
|
735
|
+
case Opcode.NOTIF_MSG_DELETE:
|
|
736
|
+
await this.handleRemovedMessage(data.payload);
|
|
737
|
+
break;
|
|
738
|
+
|
|
739
|
+
case Opcode.NOTIF_CHAT:
|
|
740
|
+
await this.handleChatAction(data.payload);
|
|
741
|
+
break;
|
|
742
|
+
|
|
743
|
+
case Opcode.PING:
|
|
744
|
+
break;
|
|
745
|
+
|
|
746
|
+
default:
|
|
747
|
+
this.emit('raw_message', data);
|
|
748
|
+
}
|
|
749
|
+
} catch (error) {
|
|
750
|
+
console.error('Ошибка при обработке Socket уведомления:', error);
|
|
751
|
+
await this.triggerHandlers(EventTypes.ERROR, error);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
|
|
496
755
|
/**
|
|
497
756
|
* Обработка переподключения
|
|
498
757
|
*/
|
|
@@ -510,16 +769,16 @@ class WebMaxClient extends EventEmitter {
|
|
|
510
769
|
}
|
|
511
770
|
|
|
512
771
|
/**
|
|
513
|
-
* Обработка входящих сообщений
|
|
772
|
+
* Обработка входящих сообщений (WebSocket)
|
|
514
773
|
*/
|
|
515
774
|
async handleMessage(data) {
|
|
516
775
|
try {
|
|
517
776
|
const message = JSON.parse(data.toString());
|
|
518
777
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
778
|
+
if (this.debug && message.opcode !== Opcode.PING) {
|
|
779
|
+
const payload = message.payload?.error ? ` error=${JSON.stringify(message.payload.error)}` : '';
|
|
780
|
+
console.log(`📥 ${getOpcodeName(message.opcode)} (seq=${message.seq})${payload}`);
|
|
781
|
+
}
|
|
523
782
|
|
|
524
783
|
// Обработка ответов на запросы по seq
|
|
525
784
|
if (message.seq && this.pendingRequests.has(message.seq)) {
|
|
@@ -549,7 +808,6 @@ class WebMaxClient extends EventEmitter {
|
|
|
549
808
|
break;
|
|
550
809
|
|
|
551
810
|
case Opcode.PING:
|
|
552
|
-
// Отвечаем на ping (без логирования)
|
|
553
811
|
await this.sendPong();
|
|
554
812
|
break;
|
|
555
813
|
|
|
@@ -629,21 +887,25 @@ class WebMaxClient extends EventEmitter {
|
|
|
629
887
|
}
|
|
630
888
|
|
|
631
889
|
/**
|
|
632
|
-
* Отправка запроса
|
|
890
|
+
* Отправка запроса и ожидание ответа
|
|
633
891
|
*/
|
|
634
|
-
sendAndWait(opcode, payload, cmd = 0, timeout = 20000) {
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
return;
|
|
639
|
-
}
|
|
892
|
+
async sendAndWait(opcode, payload, cmd = 0, timeout = 20000) {
|
|
893
|
+
if (!this.isConnected) {
|
|
894
|
+
throw new Error('Соединение не установлено');
|
|
895
|
+
}
|
|
640
896
|
|
|
897
|
+
// Используем Socket транспорт для IOS/ANDROID
|
|
898
|
+
if (this._useSocketTransport && this._socketTransport) {
|
|
899
|
+
return await this._socketTransport.sendAndWait(opcode, payload, cmd, timeout);
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// WebSocket транспорт для WEB
|
|
903
|
+
return new Promise((resolve, reject) => {
|
|
641
904
|
const message = this.makeMessage(opcode, payload, cmd);
|
|
642
905
|
const seq = message.seq;
|
|
643
906
|
|
|
644
907
|
this.pendingRequests.set(seq, { resolve, reject });
|
|
645
908
|
|
|
646
|
-
// Таймаут для запроса
|
|
647
909
|
const timeoutId = setTimeout(() => {
|
|
648
910
|
if (this.pendingRequests.has(seq)) {
|
|
649
911
|
this.pendingRequests.delete(seq);
|
|
@@ -651,19 +913,14 @@ class WebMaxClient extends EventEmitter {
|
|
|
651
913
|
}
|
|
652
914
|
}, timeout);
|
|
653
915
|
|
|
654
|
-
// Сохраняем timeoutId чтобы можно было отменить
|
|
655
916
|
this.pendingRequests.get(seq).timeoutId = timeoutId;
|
|
656
917
|
|
|
657
|
-
// Отладочное логирование (раскомментируйте при необходимости)
|
|
658
|
-
// if (opcode !== Opcode.PING) {
|
|
659
|
-
// console.log(`📤 Отправка: ${getOpcodeName(opcode)} (seq=${seq})`);
|
|
660
|
-
// }
|
|
661
918
|
this.ws.send(JSON.stringify(message));
|
|
662
919
|
});
|
|
663
920
|
}
|
|
664
921
|
|
|
665
922
|
/**
|
|
666
|
-
* Отправка сообщения
|
|
923
|
+
* Отправка сообщения (с уведомлением)
|
|
667
924
|
*/
|
|
668
925
|
async sendMessage(options) {
|
|
669
926
|
if (typeof options === 'string') {
|
|
@@ -672,6 +929,37 @@ class WebMaxClient extends EventEmitter {
|
|
|
672
929
|
|
|
673
930
|
const { chatId, text, cid, replyTo, attachments } = options;
|
|
674
931
|
|
|
932
|
+
const payload = {
|
|
933
|
+
chatId: chatId,
|
|
934
|
+
message: {
|
|
935
|
+
text: text || '',
|
|
936
|
+
cid: cid || Date.now(),
|
|
937
|
+
elements: [],
|
|
938
|
+
attaches: attachments || [],
|
|
939
|
+
link: replyTo ? { type: 'REPLY', messageId: replyTo } : null
|
|
940
|
+
},
|
|
941
|
+
notify: true
|
|
942
|
+
};
|
|
943
|
+
|
|
944
|
+
const response = await this.sendAndWait(Opcode.MSG_SEND, payload);
|
|
945
|
+
|
|
946
|
+
if (response.payload && response.payload.message) {
|
|
947
|
+
return new Message(response.payload.message, this);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
return response.payload;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
/**
|
|
954
|
+
* Отправка сообщения в канал (без уведомления)
|
|
955
|
+
*/
|
|
956
|
+
async sendMessageChannel(options) {
|
|
957
|
+
if (typeof options === 'string') {
|
|
958
|
+
throw new Error('sendMessageChannel требует объект с параметрами: { chatId, text, cid }');
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
const { chatId, text, cid, replyTo, attachments } = options;
|
|
962
|
+
|
|
675
963
|
const payload = {
|
|
676
964
|
chatId: chatId,
|
|
677
965
|
message: {
|
|
@@ -769,6 +1057,10 @@ class WebMaxClient extends EventEmitter {
|
|
|
769
1057
|
* Получение списка чатов
|
|
770
1058
|
*/
|
|
771
1059
|
async getChats(marker = 0) {
|
|
1060
|
+
if (this._useSocketTransport && this._socketTransport) {
|
|
1061
|
+
return await this._socketTransport.getChats(marker);
|
|
1062
|
+
}
|
|
1063
|
+
|
|
772
1064
|
const payload = {
|
|
773
1065
|
marker: marker
|
|
774
1066
|
};
|
|
@@ -782,6 +1074,11 @@ class WebMaxClient extends EventEmitter {
|
|
|
782
1074
|
* Получение истории сообщений
|
|
783
1075
|
*/
|
|
784
1076
|
async getHistory(chatId, from = Date.now(), backward = 200, forward = 0) {
|
|
1077
|
+
if (this._useSocketTransport && this._socketTransport) {
|
|
1078
|
+
const messages = await this._socketTransport.getHistory(chatId, from, backward, forward);
|
|
1079
|
+
return messages.map(msg => new Message(msg, this));
|
|
1080
|
+
}
|
|
1081
|
+
|
|
785
1082
|
const payload = {
|
|
786
1083
|
chatId: chatId,
|
|
787
1084
|
from: from,
|
|
@@ -819,6 +1116,10 @@ class WebMaxClient extends EventEmitter {
|
|
|
819
1116
|
* Остановка клиента
|
|
820
1117
|
*/
|
|
821
1118
|
async stop() {
|
|
1119
|
+
if (this._socketTransport) {
|
|
1120
|
+
await this._socketTransport.close();
|
|
1121
|
+
this._socketTransport = null;
|
|
1122
|
+
}
|
|
822
1123
|
if (this.ws) {
|
|
823
1124
|
this.ws.close();
|
|
824
1125
|
this.ws = null;
|
package/lib/opcodes.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Opcodes для протокола Max API
|
|
3
|
-
* Портировано из PyMax
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
5
|
const Opcode = {
|
|
@@ -10,6 +9,8 @@ const Opcode = {
|
|
|
10
9
|
LOG: 5,
|
|
11
10
|
SESSION_INIT: 6,
|
|
12
11
|
PROFILE: 16,
|
|
12
|
+
AUTH_REQUEST: 17,
|
|
13
|
+
AUTH: 18,
|
|
13
14
|
LOGIN: 19,
|
|
14
15
|
LOGOUT: 20,
|
|
15
16
|
SYNC: 21,
|
|
@@ -40,6 +41,7 @@ const Opcode = {
|
|
|
40
41
|
NOTIF_MESSAGE: 128,
|
|
41
42
|
NOTIF_CHAT: 135,
|
|
42
43
|
NOTIF_ATTACH: 136,
|
|
44
|
+
NOTIF_MSG_DELETE: 154,
|
|
43
45
|
NOTIF_MSG_REACTIONS_CHANGED: 155,
|
|
44
46
|
MSG_REACTION: 178,
|
|
45
47
|
MSG_CANCEL_REACTION: 179,
|