@heavstaltech/baileys 2.3.4 → 3.2.4
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 +226 -53
- package/WAProto/index.js +14270 -302
- package/engine-requirements.js +10 -0
- package/lib/Defaults/baileys-version.json +1 -1
- package/lib/Defaults/index.js +118 -79
- package/lib/Defaults/phonenumber-mcc.json +223 -0
- package/lib/Signal/Group/ciphertext-message.d.ts +9 -0
- package/lib/Signal/Group/ciphertext-message.js +15 -0
- package/lib/Signal/Group/group-session-builder.d.ts +14 -0
- package/lib/Signal/Group/group-session-builder.js +64 -0
- package/lib/Signal/Group/group_cipher.d.ts +17 -0
- package/lib/Signal/Group/group_cipher.js +96 -0
- package/lib/Signal/Group/index.d.ts +11 -0
- package/lib/Signal/Group/index.js +57 -0
- package/lib/Signal/Group/keyhelper.d.ts +10 -0
- package/lib/Signal/Group/keyhelper.js +55 -0
- package/lib/Signal/Group/queue-job.d.ts +1 -0
- package/lib/Signal/Group/queue-job.js +57 -0
- package/lib/Signal/Group/sender-chain-key.d.ts +13 -0
- package/lib/Signal/Group/sender-chain-key.js +34 -0
- package/lib/Signal/Group/sender-key-distribution-message.d.ts +16 -0
- package/lib/Signal/Group/sender-key-distribution-message.js +66 -0
- package/lib/Signal/Group/sender-key-message.d.ts +18 -0
- package/lib/Signal/Group/sender-key-message.js +69 -0
- package/lib/Signal/Group/sender-key-name.d.ts +17 -0
- package/lib/Signal/Group/sender-key-name.js +51 -0
- package/lib/Signal/Group/sender-key-record.d.ts +30 -0
- package/lib/Signal/Group/sender-key-record.js +53 -0
- package/lib/Signal/Group/sender-key-state.d.ts +38 -0
- package/lib/Signal/Group/sender-key-state.js +99 -0
- package/lib/Signal/Group/sender-message-key.d.ts +11 -0
- package/{WASignalGroup/sender_message_key.js → lib/Signal/Group/sender-message-key.js} +6 -16
- package/lib/Signal/libsignal.js +51 -29
- package/lib/Socket/business.d.ts +43 -42
- package/lib/Socket/chats.d.ts +222 -36
- package/lib/Socket/chats.js +186 -153
- package/lib/Socket/dugong.d.ts +254 -0
- package/lib/Socket/dugong.js +484 -0
- package/lib/Socket/groups.d.ts +7 -7
- package/lib/Socket/groups.js +37 -35
- package/lib/Socket/index.d.ts +52 -51
- package/lib/Socket/index.js +1 -0
- package/lib/Socket/messages-recv.d.ts +37 -34
- package/lib/Socket/messages-recv.js +175 -37
- package/lib/Socket/messages-send.d.ts +12 -18
- package/lib/Socket/messages-send.js +396 -574
- package/lib/Socket/newsletter.d.ts +28 -26
- package/lib/Socket/newsletter.js +140 -25
- package/lib/Socket/registration.d.ts +52 -49
- package/lib/Socket/registration.js +7 -7
- package/lib/Socket/socket.d.ts +0 -1
- package/lib/Socket/socket.js +47 -198
- package/lib/Socket/usync.d.ts +10 -11
- package/lib/Store/make-cache-manager-store.d.ts +1 -2
- package/lib/Store/make-in-memory-store.d.ts +2 -2
- package/lib/Store/make-in-memory-store.js +1 -5
- package/lib/Store/make-ordered-dictionary.js +2 -2
- package/lib/Types/Auth.d.ts +1 -0
- package/lib/Types/Call.d.ts +1 -1
- package/lib/Types/Chat.d.ts +7 -12
- package/lib/Types/Events.d.ts +2 -17
- package/lib/Types/GroupMetadata.d.ts +2 -3
- package/lib/Types/Label.d.ts +0 -11
- package/lib/Types/Label.js +1 -1
- package/lib/Types/LabelAssociation.js +1 -1
- package/lib/Types/Message.d.ts +10 -170
- package/lib/Types/Newsletter.d.ts +97 -86
- package/lib/Types/Newsletter.js +38 -32
- package/lib/Types/Socket.d.ts +2 -7
- package/lib/Types/index.d.ts +0 -9
- package/lib/Types/index.js +1 -1
- package/lib/Utils/auth-utils.js +14 -35
- package/lib/Utils/business.d.ts +1 -1
- package/lib/Utils/business.js +2 -2
- package/lib/Utils/chat-utils.d.ts +12 -11
- package/lib/Utils/chat-utils.js +36 -52
- package/lib/Utils/crypto.d.ts +16 -15
- package/lib/Utils/crypto.js +26 -74
- package/lib/Utils/decode-wa-message.d.ts +0 -17
- package/lib/Utils/decode-wa-message.js +17 -53
- package/lib/Utils/event-buffer.js +7 -10
- package/lib/Utils/generics.d.ts +17 -13
- package/lib/Utils/generics.js +79 -58
- package/lib/Utils/history.d.ts +2 -6
- package/lib/Utils/history.js +6 -4
- package/lib/Utils/logger.d.ts +3 -1
- package/lib/Utils/lt-hash.js +12 -12
- package/lib/Utils/make-mutex.d.ts +2 -2
- package/lib/Utils/messages-media.d.ts +28 -25
- package/lib/Utils/messages-media.js +201 -103
- package/lib/Utils/messages.js +36 -473
- package/lib/Utils/noise-handler.d.ts +5 -4
- package/lib/Utils/noise-handler.js +14 -19
- package/lib/Utils/process-message.d.ts +5 -5
- package/lib/Utils/process-message.js +23 -75
- package/lib/Utils/signal.d.ts +1 -2
- package/lib/Utils/signal.js +26 -32
- package/lib/Utils/use-multi-file-auth-state.d.ts +1 -0
- package/lib/Utils/use-multi-file-auth-state.js +66 -242
- package/lib/Utils/validate-connection.d.ts +1 -1
- package/lib/Utils/validate-connection.js +88 -64
- package/lib/WABinary/constants.d.ts +27 -24
- package/lib/WABinary/decode.d.ts +2 -1
- package/lib/WABinary/decode.js +11 -23
- package/lib/WABinary/encode.d.ts +2 -1
- package/lib/WABinary/encode.js +147 -134
- package/lib/WABinary/generic-utils.d.ts +5 -2
- package/lib/WABinary/generic-utils.js +125 -37
- package/lib/WABinary/jid-utils.d.ts +1 -1
- package/lib/WAM/BinaryInfo.d.ts +11 -2
- package/lib/WAM/encode.d.ts +2 -1
- package/lib/WAUSync/Protocols/USyncStatusProtocol.js +3 -3
- package/lib/WAUSync/USyncUser.d.ts +2 -0
- package/lib/index.d.ts +12 -0
- package/lib/index.js +12 -0
- package/package.json +102 -98
- package/WAProto/index.d.ts +0 -50383
- package/WASignalGroup/GroupProtocol.js +0 -1697
- package/WASignalGroup/ciphertext_message.js +0 -16
- package/WASignalGroup/group_cipher.js +0 -120
- package/WASignalGroup/group_session_builder.js +0 -46
- package/WASignalGroup/index.js +0 -5
- package/WASignalGroup/keyhelper.js +0 -21
- package/WASignalGroup/protobufs.js +0 -3
- package/WASignalGroup/queue_job.js +0 -69
- package/WASignalGroup/sender_chain_key.js +0 -50
- package/WASignalGroup/sender_key_distribution_message.js +0 -78
- package/WASignalGroup/sender_key_message.js +0 -92
- package/WASignalGroup/sender_key_name.js +0 -70
- package/WASignalGroup/sender_key_record.js +0 -56
- package/WASignalGroup/sender_key_state.js +0 -129
- package/lib/Utils/use-single-file-auth-state.d.ts +0 -12
- package/lib/Utils/use-single-file-auth-state.js +0 -75
|
@@ -3,13 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.makeRegistrationSocket = void 0;
|
|
7
|
-
exports.registrationParams = registrationParams;
|
|
8
|
-
exports.mobileRegisterCode = mobileRegisterCode;
|
|
9
|
-
exports.mobileRegisterExists = mobileRegisterExists;
|
|
10
|
-
exports.mobileRegister = mobileRegister;
|
|
11
|
-
exports.mobileRegisterEncrypt = mobileRegisterEncrypt;
|
|
12
|
-
exports.mobileRegisterFetch = mobileRegisterFetch;
|
|
6
|
+
exports.mobileRegisterFetch = exports.mobileRegisterEncrypt = exports.mobileRegister = exports.mobileRegisterExists = exports.mobileRegisterCode = exports.registrationParams = exports.makeRegistrationSocket = void 0;
|
|
13
7
|
/* eslint-disable camelcase */
|
|
14
8
|
const axios_1 = __importDefault(require("axios"));
|
|
15
9
|
const Defaults_1 = require("../Defaults");
|
|
@@ -95,6 +89,7 @@ function registrationParams(params) {
|
|
|
95
89
|
fraud_checkpoint_code: params.captcha,
|
|
96
90
|
};
|
|
97
91
|
}
|
|
92
|
+
exports.registrationParams = registrationParams;
|
|
98
93
|
/**
|
|
99
94
|
* Requests a registration code for the given phone number.
|
|
100
95
|
*/
|
|
@@ -113,12 +108,14 @@ function mobileRegisterCode(params, fetchOptions) {
|
|
|
113
108
|
...fetchOptions,
|
|
114
109
|
});
|
|
115
110
|
}
|
|
111
|
+
exports.mobileRegisterCode = mobileRegisterCode;
|
|
116
112
|
function mobileRegisterExists(params, fetchOptions) {
|
|
117
113
|
return mobileRegisterFetch('/exist', {
|
|
118
114
|
params: registrationParams(params),
|
|
119
115
|
...fetchOptions
|
|
120
116
|
});
|
|
121
117
|
}
|
|
118
|
+
exports.mobileRegisterExists = mobileRegisterExists;
|
|
122
119
|
/**
|
|
123
120
|
* Registers the phone number on whatsapp with the received OTP code.
|
|
124
121
|
*/
|
|
@@ -129,6 +126,7 @@ async function mobileRegister(params, fetchOptions) {
|
|
|
129
126
|
...fetchOptions,
|
|
130
127
|
});
|
|
131
128
|
}
|
|
129
|
+
exports.mobileRegister = mobileRegister;
|
|
132
130
|
/**
|
|
133
131
|
* Encrypts the given string as AEAD aes-256-gcm with the public whatsapp key and a random keypair.
|
|
134
132
|
*/
|
|
@@ -138,6 +136,7 @@ function mobileRegisterEncrypt(data) {
|
|
|
138
136
|
const buffer = (0, crypto_1.aesEncryptGCM)(Buffer.from(data), new Uint8Array(key), Buffer.alloc(12), Buffer.alloc(0));
|
|
139
137
|
return Buffer.concat([Buffer.from(keypair.public), buffer]).toString('base64url');
|
|
140
138
|
}
|
|
139
|
+
exports.mobileRegisterEncrypt = mobileRegisterEncrypt;
|
|
141
140
|
async function mobileRegisterFetch(path, opts = {}) {
|
|
142
141
|
let url = `${Defaults_1.MOBILE_REGISTRATION_ENDPOINT}${path}`;
|
|
143
142
|
if (opts.params) {
|
|
@@ -164,3 +163,4 @@ async function mobileRegisterFetch(path, opts = {}) {
|
|
|
164
163
|
}
|
|
165
164
|
return json;
|
|
166
165
|
}
|
|
166
|
+
exports.mobileRegisterFetch = mobileRegisterFetch;
|
package/lib/Socket/socket.d.ts
CHANGED
package/lib/Socket/socket.js
CHANGED
|
@@ -16,25 +16,20 @@ const Client_1 = require("./Client");
|
|
|
16
16
|
* - simple queries (no retry mechanism, wait for connection establishment)
|
|
17
17
|
* - listen to messages and emit events
|
|
18
18
|
* - query phone connection
|
|
19
|
-
*/
|
|
19
|
+
*/
|
|
20
20
|
const makeSocket = (config) => {
|
|
21
21
|
var _a, _b;
|
|
22
22
|
const { waWebSocketUrl, connectTimeoutMs, logger, keepAliveIntervalMs, browser, auth: authState, printQRInTerminal, defaultQueryTimeoutMs, transactionOpts, qrTimeout, makeSignalRepository, } = config;
|
|
23
|
-
|
|
24
|
-
config.mobile = config.mobile || url.protocol === 'tcp:';
|
|
25
|
-
if (config.mobile && url.protocol !== 'tcp:') {
|
|
26
|
-
url = new url_1.URL(`tcp://${Defaults_1.MOBILE_ENDPOINT}:${Defaults_1.MOBILE_PORT}`);
|
|
27
|
-
}
|
|
28
|
-
if (!config.mobile && url.protocol === 'wss' && ((_a = authState === null || authState === void 0 ? void 0 : authState.creds) === null || _a === void 0 ? void 0 : _a.routingInfo)) {
|
|
29
|
-
url.searchParams.append('ED', authState.creds.routingInfo.toString('base64url'));
|
|
30
|
-
}
|
|
23
|
+
const url = typeof waWebSocketUrl === 'string' ? new url_1.URL(waWebSocketUrl) : waWebSocketUrl;
|
|
31
24
|
if (config.mobile || url.protocol === 'tcp:') {
|
|
32
|
-
throw new boom_1.Boom('Mobile API is not supported anymore', {
|
|
25
|
+
throw new boom_1.Boom('Mobile API is not supported anymore', {
|
|
26
|
+
statusCode: Types_1.DisconnectReason.loggedOut
|
|
27
|
+
});
|
|
33
28
|
}
|
|
34
29
|
if (url.protocol === 'wss' && ((_a = authState === null || authState === void 0 ? void 0 : authState.creds) === null || _a === void 0 ? void 0 : _a.routingInfo)) {
|
|
35
30
|
url.searchParams.append('ED', authState.creds.routingInfo.toString('base64url'));
|
|
36
31
|
}
|
|
37
|
-
const ws =
|
|
32
|
+
const ws = new Client_1.WebSocketClient(url, config);
|
|
38
33
|
ws.connect();
|
|
39
34
|
const ev = (0, Utils_1.makeEventBuffer)(logger);
|
|
40
35
|
/** ephemeral key pair used to encrypt/decrypt communication. Unique for each connection */
|
|
@@ -42,8 +37,7 @@ const makeSocket = (config) => {
|
|
|
42
37
|
/** WA noise protocol wrapper */
|
|
43
38
|
const noise = (0, Utils_1.makeNoiseHandler)({
|
|
44
39
|
keyPair: ephemeralKeyPair,
|
|
45
|
-
NOISE_HEADER:
|
|
46
|
-
mobile: config.mobile,
|
|
40
|
+
NOISE_HEADER: Defaults_1.NOISE_WA_HEADER,
|
|
47
41
|
logger,
|
|
48
42
|
routingInfo: (_b = authState === null || authState === void 0 ? void 0 : authState.creds) === null || _b === void 0 ? void 0 : _b.routingInfo
|
|
49
43
|
});
|
|
@@ -86,6 +80,25 @@ const makeSocket = (config) => {
|
|
|
86
80
|
/** log & process any unexpected errors */
|
|
87
81
|
const onUnexpectedError = (err, msg) => {
|
|
88
82
|
logger.error({ err }, `unexpected error in '${msg}'`);
|
|
83
|
+
const message = (err && ((err.stack || err.message) || String(err))).toLowerCase();
|
|
84
|
+
// auto recover from cryptographic desyncs by re-uploading prekeys
|
|
85
|
+
if (message.includes('bad mac') || (message.includes('mac') && message.includes('invalid'))) {
|
|
86
|
+
try {
|
|
87
|
+
uploadPreKeysToServerIfRequired(true)
|
|
88
|
+
.catch(e => logger.warn({ e }, 'failed to re-upload prekeys after bad mac'));
|
|
89
|
+
}
|
|
90
|
+
catch (_e) {
|
|
91
|
+
// ignore
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// gently back off when encountering rate limits (429)
|
|
95
|
+
if (message.includes('429') || message.includes('rate limit')) {
|
|
96
|
+
const wait = Math.min(30000, (config.backoffDelayMs || 5000));
|
|
97
|
+
logger.info({ wait }, 'backing off due to rate limit');
|
|
98
|
+
setTimeout(() => {
|
|
99
|
+
// intentionally empty; wait to delay further sends
|
|
100
|
+
}, wait);
|
|
101
|
+
}
|
|
89
102
|
};
|
|
90
103
|
/** await the next incoming message */
|
|
91
104
|
const awaitNextMessage = async (sendMsg) => {
|
|
@@ -122,7 +135,7 @@ const makeSocket = (config) => {
|
|
|
122
135
|
let onRecv;
|
|
123
136
|
let onErr;
|
|
124
137
|
try {
|
|
125
|
-
|
|
138
|
+
const result = await (0, Utils_1.promiseTimeout)(timeoutMs, (resolve, reject) => {
|
|
126
139
|
onRecv = resolve;
|
|
127
140
|
onErr = err => {
|
|
128
141
|
reject(err || new boom_1.Boom('Connection Closed', { statusCode: Types_1.DisconnectReason.connectionClosed }));
|
|
@@ -131,6 +144,7 @@ const makeSocket = (config) => {
|
|
|
131
144
|
ws.on('close', onErr); // if the socket closes, you'll never receive the message
|
|
132
145
|
ws.off('error', onErr);
|
|
133
146
|
});
|
|
147
|
+
return result;
|
|
134
148
|
}
|
|
135
149
|
finally {
|
|
136
150
|
ws.off(`TAG:${msgId}`, onRecv);
|
|
@@ -144,9 +158,10 @@ const makeSocket = (config) => {
|
|
|
144
158
|
node.attrs.id = generateMessageTag();
|
|
145
159
|
}
|
|
146
160
|
const msgId = node.attrs.id;
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
161
|
+
const [result] = await Promise.all([
|
|
162
|
+
waitForMessage(msgId, timeoutMs),
|
|
163
|
+
sendNode(node)
|
|
164
|
+
]);
|
|
150
165
|
if ('tag' in result) {
|
|
151
166
|
(0, WABinary_1.assertNodeErrorFree)(result);
|
|
152
167
|
}
|
|
@@ -165,10 +180,7 @@ const makeSocket = (config) => {
|
|
|
165
180
|
logger.trace({ handshake }, 'handshake recv from WA');
|
|
166
181
|
const keyEnc = await noise.processHandshake(handshake, creds.noiseKey);
|
|
167
182
|
let node;
|
|
168
|
-
if (
|
|
169
|
-
node = (0, Utils_1.generateMobileNode)(config);
|
|
170
|
-
}
|
|
171
|
-
else if (!creds.me) {
|
|
183
|
+
if (!creds.me) {
|
|
172
184
|
node = (0, Utils_1.generateRegistrationNode)(creds, config);
|
|
173
185
|
logger.info({ node }, 'not logged in, attempting registration...');
|
|
174
186
|
}
|
|
@@ -183,7 +195,7 @@ const makeSocket = (config) => {
|
|
|
183
195
|
payload: payloadEnc,
|
|
184
196
|
},
|
|
185
197
|
}).finish());
|
|
186
|
-
|
|
198
|
+
noise.finishInit();
|
|
187
199
|
startKeepAliveRequest();
|
|
188
200
|
};
|
|
189
201
|
const getAvailablePreKeysOnServer = async () => {
|
|
@@ -371,17 +383,24 @@ const makeSocket = (config) => {
|
|
|
371
383
|
}
|
|
372
384
|
end(new boom_1.Boom(msg || 'Intentional Logout', { statusCode: Types_1.DisconnectReason.loggedOut }));
|
|
373
385
|
};
|
|
374
|
-
|
|
386
|
+
|
|
387
|
+
/** This method was created by snowi, and implemented by KyuuRzy */
|
|
388
|
+
/** hey bro, if you delete this text */
|
|
389
|
+
/** you are the most cursed human being who likes to claim other people's property 😹🙌🏻 *//////////
|
|
390
|
+
const requestPairingCode = async (phoneNumber, pairKey = "HEAV-STAL") => {
|
|
375
391
|
if (pairKey) {
|
|
376
|
-
|
|
392
|
+
authState.creds.pairingCode = pairKey.toUpperCase();
|
|
377
393
|
} else {
|
|
378
394
|
authState.creds.pairingCode = (0, Utils_1.bytesToCrockford)((0, crypto_1.randomBytes)(5));
|
|
379
395
|
}
|
|
396
|
+
|
|
380
397
|
authState.creds.me = {
|
|
381
398
|
id: (0, WABinary_1.jidEncode)(phoneNumber, 's.whatsapp.net'),
|
|
382
399
|
name: '~'
|
|
383
400
|
};
|
|
401
|
+
|
|
384
402
|
ev.emit('creds.update', authState.creds);
|
|
403
|
+
|
|
385
404
|
await sendNode({
|
|
386
405
|
tag: 'iq',
|
|
387
406
|
attrs: {
|
|
@@ -396,7 +415,6 @@ const makeSocket = (config) => {
|
|
|
396
415
|
attrs: {
|
|
397
416
|
jid: authState.creds.me.id,
|
|
398
417
|
stage: 'companion_hello',
|
|
399
|
-
// eslint-disable-next-line camelcase
|
|
400
418
|
should_show_push_notification: 'true'
|
|
401
419
|
},
|
|
402
420
|
content: [
|
|
@@ -423,14 +441,15 @@ const makeSocket = (config) => {
|
|
|
423
441
|
{
|
|
424
442
|
tag: 'link_code_pairing_nonce',
|
|
425
443
|
attrs: {},
|
|
426
|
-
content:
|
|
444
|
+
content: "0"
|
|
427
445
|
}
|
|
428
446
|
]
|
|
429
447
|
}
|
|
430
448
|
]
|
|
431
449
|
});
|
|
450
|
+
|
|
432
451
|
return authState.creds.pairingCode;
|
|
433
|
-
}
|
|
452
|
+
}
|
|
434
453
|
async function generatePairingKey() {
|
|
435
454
|
const salt = (0, crypto_1.randomBytes)(32);
|
|
436
455
|
const randomIv = (0, crypto_1.randomBytes)(16);
|
|
@@ -470,174 +489,4 @@ const makeSocket = (config) => {
|
|
|
470
489
|
// the server terminated the connection
|
|
471
490
|
ws.on('CB:xmlstreamend', () => end(new boom_1.Boom('Connection Terminated by Server', { statusCode: Types_1.DisconnectReason.connectionClosed })));
|
|
472
491
|
// QR gen
|
|
473
|
-
ws.on('CB:iq,type:set,pair-device', async (
|
|
474
|
-
const iq = {
|
|
475
|
-
tag: 'iq',
|
|
476
|
-
attrs: {
|
|
477
|
-
to: WABinary_1.S_WHATSAPP_NET,
|
|
478
|
-
type: 'result',
|
|
479
|
-
id: stanza.attrs.id,
|
|
480
|
-
}
|
|
481
|
-
};
|
|
482
|
-
await sendNode(iq);
|
|
483
|
-
const pairDeviceNode = (0, WABinary_1.getBinaryNodeChild)(stanza, 'pair-device');
|
|
484
|
-
const refNodes = (0, WABinary_1.getBinaryNodeChildren)(pairDeviceNode, 'ref');
|
|
485
|
-
const noiseKeyB64 = Buffer.from(creds.noiseKey.public).toString('base64');
|
|
486
|
-
const identityKeyB64 = Buffer.from(creds.signedIdentityKey.public).toString('base64');
|
|
487
|
-
const advB64 = creds.advSecretKey;
|
|
488
|
-
let qrMs = qrTimeout || 60000; // time to let a QR live
|
|
489
|
-
const genPairQR = () => {
|
|
490
|
-
if (!ws.isOpen) {
|
|
491
|
-
return;
|
|
492
|
-
}
|
|
493
|
-
const refNode = refNodes.shift();
|
|
494
|
-
if (!refNode) {
|
|
495
|
-
end(new boom_1.Boom('QR refs attempts ended', { statusCode: Types_1.DisconnectReason.timedOut }));
|
|
496
|
-
return;
|
|
497
|
-
}
|
|
498
|
-
const ref = refNode.content.toString('utf-8');
|
|
499
|
-
const qr = [ref, noiseKeyB64, identityKeyB64, advB64].join(',');
|
|
500
|
-
ev.emit('connection.update', { qr });
|
|
501
|
-
qrTimer = setTimeout(genPairQR, qrMs);
|
|
502
|
-
qrMs = qrTimeout || 20000; // shorter subsequent qrs
|
|
503
|
-
};
|
|
504
|
-
genPairQR();
|
|
505
|
-
});
|
|
506
|
-
// device paired for the first time
|
|
507
|
-
// if device pairs successfully, the server asks to restart the connection
|
|
508
|
-
ws.on('CB:iq,,pair-success', async (stanza) => {
|
|
509
|
-
logger.debug('pair success recv');
|
|
510
|
-
try {
|
|
511
|
-
const { reply, creds: updatedCreds } = (0, Utils_1.configureSuccessfulPairing)(stanza, creds);
|
|
512
|
-
logger.info({ me: updatedCreds.me, platform: updatedCreds.platform }, 'pairing configured successfully, expect to restart the connection...');
|
|
513
|
-
ev.emit('creds.update', updatedCreds);
|
|
514
|
-
ev.emit('connection.update', { isNewLogin: true, qr: undefined });
|
|
515
|
-
await sendNode(reply);
|
|
516
|
-
}
|
|
517
|
-
catch (error) {
|
|
518
|
-
logger.info({ trace: error.stack }, 'error in pairing');
|
|
519
|
-
end(error);
|
|
520
|
-
}
|
|
521
|
-
});
|
|
522
|
-
// login complete
|
|
523
|
-
ws.on('CB:success', async (node) => {
|
|
524
|
-
try {
|
|
525
|
-
await uploadPreKeysToServerIfRequired();
|
|
526
|
-
await sendPassiveIq('active');
|
|
527
|
-
logger.info('opened connection to WA');
|
|
528
|
-
clearTimeout(qrTimer); // will never happen in all likelyhood -- but just in case WA sends success on first try
|
|
529
|
-
ev.emit('creds.update', { me: { ...authState.creds.me, lid: node.attrs.lid } });
|
|
530
|
-
ev.emit('connection.update', { connection: 'open' });
|
|
531
|
-
}
|
|
532
|
-
catch (err) {
|
|
533
|
-
logger.error({ err }, 'error opening connection');
|
|
534
|
-
end(err);
|
|
535
|
-
}
|
|
536
|
-
});
|
|
537
|
-
ws.on('CB:stream:error', (node) => {
|
|
538
|
-
logger.error({ node }, 'stream errored out');
|
|
539
|
-
const { reason, statusCode } = (0, Utils_1.getErrorCodeFromStreamError)(node);
|
|
540
|
-
end(new boom_1.Boom(`Stream Errored (${reason})`, { statusCode, data: node }));
|
|
541
|
-
});
|
|
542
|
-
// stream fail, possible logout
|
|
543
|
-
ws.on('CB:failure', (node) => {
|
|
544
|
-
const reason = +(node.attrs.reason || 500);
|
|
545
|
-
end(new boom_1.Boom('Connection Failure', { statusCode: reason, data: node.attrs }));
|
|
546
|
-
});
|
|
547
|
-
ws.on('CB:ib,,downgrade_webclient', () => {
|
|
548
|
-
end(new boom_1.Boom('Multi-device beta not joined', { statusCode: Types_1.DisconnectReason.multideviceMismatch }));
|
|
549
|
-
});
|
|
550
|
-
ws.on('CB:ib,,offline_preview', (node) => {
|
|
551
|
-
logger.info('offline preview received', JSON.stringify(node));
|
|
552
|
-
sendNode({
|
|
553
|
-
tag: 'ib',
|
|
554
|
-
attrs: {},
|
|
555
|
-
content: [{ tag: 'offline_batch', attrs: { count: '100' } }]
|
|
556
|
-
});
|
|
557
|
-
});
|
|
558
|
-
ws.on('CB:ib,,edge_routing', (node) => {
|
|
559
|
-
const edgeRoutingNode = (0, WABinary_1.getBinaryNodeChild)(node, 'edge_routing');
|
|
560
|
-
const routingInfo = (0, WABinary_1.getBinaryNodeChild)(edgeRoutingNode, 'routing_info');
|
|
561
|
-
if (routingInfo === null || routingInfo === void 0 ? void 0 : routingInfo.content) {
|
|
562
|
-
authState.creds.routingInfo = Buffer.from(routingInfo === null || routingInfo === void 0 ? void 0 : routingInfo.content);
|
|
563
|
-
ev.emit('creds.update', authState.creds);
|
|
564
|
-
}
|
|
565
|
-
});
|
|
566
|
-
let didStartBuffer = false;
|
|
567
|
-
process.nextTick(() => {
|
|
568
|
-
var _a;
|
|
569
|
-
if ((_a = creds.me) === null || _a === void 0 ? void 0 : _a.id) {
|
|
570
|
-
// start buffering important events
|
|
571
|
-
// if we're logged in
|
|
572
|
-
ev.buffer();
|
|
573
|
-
didStartBuffer = true;
|
|
574
|
-
}
|
|
575
|
-
ev.emit('connection.update', { connection: 'connecting', receivedPendingNotifications: false, qr: undefined });
|
|
576
|
-
});
|
|
577
|
-
// called when all offline notifs are handled
|
|
578
|
-
ws.on('CB:ib,,offline', (node) => {
|
|
579
|
-
const child = (0, WABinary_1.getBinaryNodeChild)(node, 'offline');
|
|
580
|
-
const offlineNotifs = +((child === null || child === void 0 ? void 0 : child.attrs.count) || 0);
|
|
581
|
-
logger.info(`handled ${offlineNotifs} offline messages/notifications`);
|
|
582
|
-
if (didStartBuffer) {
|
|
583
|
-
ev.flush();
|
|
584
|
-
logger.trace('flushed events for initial buffer');
|
|
585
|
-
}
|
|
586
|
-
ev.emit('connection.update', { receivedPendingNotifications: true });
|
|
587
|
-
});
|
|
588
|
-
// update credentials when required
|
|
589
|
-
ev.on('creds.update', update => {
|
|
590
|
-
var _a, _b;
|
|
591
|
-
const name = (_a = update.me) === null || _a === void 0 ? void 0 : _a.name;
|
|
592
|
-
// if name has just been received
|
|
593
|
-
if (((_b = creds.me) === null || _b === void 0 ? void 0 : _b.name) !== name) {
|
|
594
|
-
logger.debug({ name }, 'updated pushName');
|
|
595
|
-
sendNode({
|
|
596
|
-
tag: 'presence',
|
|
597
|
-
attrs: { name: name }
|
|
598
|
-
})
|
|
599
|
-
.catch(err => {
|
|
600
|
-
logger.warn({ trace: err.stack }, 'error in sending presence update on name change');
|
|
601
|
-
});
|
|
602
|
-
}
|
|
603
|
-
Object.assign(creds, update);
|
|
604
|
-
});
|
|
605
|
-
if (printQRInTerminal) {
|
|
606
|
-
(0, Utils_1.printQRIfNecessaryListener)(ev, logger);
|
|
607
|
-
}
|
|
608
|
-
return {
|
|
609
|
-
type: 'md',
|
|
610
|
-
ws,
|
|
611
|
-
ev,
|
|
612
|
-
authState: { creds, keys },
|
|
613
|
-
signalRepository,
|
|
614
|
-
get user() {
|
|
615
|
-
return authState.creds.me;
|
|
616
|
-
},
|
|
617
|
-
generateMessageTag,
|
|
618
|
-
query,
|
|
619
|
-
waitForMessage,
|
|
620
|
-
waitForSocketOpen,
|
|
621
|
-
sendRawMessage,
|
|
622
|
-
sendNode,
|
|
623
|
-
logout,
|
|
624
|
-
end,
|
|
625
|
-
onUnexpectedError,
|
|
626
|
-
uploadPreKeys,
|
|
627
|
-
uploadPreKeysToServerIfRequired,
|
|
628
|
-
requestPairingCode,
|
|
629
|
-
/** Waits for the connection to WA to reach a state */
|
|
630
|
-
waitForConnectionUpdate: (0, Utils_1.bindWaitForConnectionUpdate)(ev),
|
|
631
|
-
sendWAMBuffer,
|
|
632
|
-
};
|
|
633
|
-
};
|
|
634
|
-
exports.makeSocket = makeSocket;
|
|
635
|
-
/**
|
|
636
|
-
* map the websocket error to the right type
|
|
637
|
-
* so it can be retried by the caller
|
|
638
|
-
* */
|
|
639
|
-
function mapWebSocketError(handler) {
|
|
640
|
-
return (error) => {
|
|
641
|
-
handler(new boom_1.Boom(`WebSocket Error (${error === null || error === void 0 ? void 0 : error.message})`, { statusCode: (0, Utils_1.getCodeFromWSError)(error), data: error }));
|
|
642
|
-
};
|
|
643
|
-
}
|
|
492
|
+
ws.on('CB:iq,type:set,pair-device', async (s
|
package/lib/Socket/usync.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
1
|
import { Boom } from '@hapi/boom';
|
|
3
2
|
import { SocketConfig } from '../Types';
|
|
4
3
|
import { BinaryNode } from '../WABinary';
|
|
@@ -6,12 +5,12 @@ import { USyncQuery } from '../WAUSync';
|
|
|
6
5
|
export declare const makeUSyncSocket: (config: SocketConfig) => {
|
|
7
6
|
executeUSyncQuery: (usyncQuery: USyncQuery) => Promise<import("../WAUSync").USyncQueryResult | undefined>;
|
|
8
7
|
type: "md";
|
|
9
|
-
ws:
|
|
8
|
+
ws: import("./Client").WebSocketClient;
|
|
10
9
|
ev: import("../Types").BaileysEventEmitter & {
|
|
11
|
-
process(handler: (events: Partial<import("../Types").BaileysEventMap>) => void | Promise<void>): () => void;
|
|
10
|
+
process(handler: (events: Partial<import("../Types").BaileysEventMap>) => void | Promise<void>): (() => void);
|
|
12
11
|
buffer(): void;
|
|
13
|
-
createBufferedFunction<A extends any[], T>(work: (...args: A) => Promise<T>): (...args: A) => Promise<T
|
|
14
|
-
flush(force?: boolean
|
|
12
|
+
createBufferedFunction<A extends any[], T>(work: (...args: A) => Promise<T>): ((...args: A) => Promise<T>);
|
|
13
|
+
flush(force?: boolean): boolean;
|
|
15
14
|
isBuffering(): boolean;
|
|
16
15
|
};
|
|
17
16
|
authState: {
|
|
@@ -21,17 +20,17 @@ export declare const makeUSyncSocket: (config: SocketConfig) => {
|
|
|
21
20
|
signalRepository: import("../Types").SignalRepository;
|
|
22
21
|
user: import("../Types").Contact | undefined;
|
|
23
22
|
generateMessageTag: () => string;
|
|
24
|
-
query: (node: BinaryNode, timeoutMs?: number
|
|
25
|
-
waitForMessage: <
|
|
23
|
+
query: (node: BinaryNode, timeoutMs?: number) => Promise<BinaryNode>;
|
|
24
|
+
waitForMessage: <T>(msgId: string, timeoutMs?: number | undefined) => Promise<any>;
|
|
26
25
|
waitForSocketOpen: () => Promise<void>;
|
|
27
26
|
sendRawMessage: (data: Uint8Array | Buffer) => Promise<void>;
|
|
28
27
|
sendNode: (frame: BinaryNode) => Promise<void>;
|
|
29
|
-
logout: (msg?: string
|
|
28
|
+
logout: (msg?: string) => Promise<void>;
|
|
30
29
|
end: (error: Error | undefined) => void;
|
|
31
|
-
onUnexpectedError: (err: Error | Boom
|
|
30
|
+
onUnexpectedError: (err: Error | Boom, msg: string) => void;
|
|
32
31
|
uploadPreKeys: (count?: number) => Promise<void>;
|
|
33
32
|
uploadPreKeysToServerIfRequired: () => Promise<void>;
|
|
34
|
-
requestPairingCode: (phoneNumber: string,
|
|
35
|
-
waitForConnectionUpdate: (check: (u: Partial<import("../Types").ConnectionState>) => boolean | undefined
|
|
33
|
+
requestPairingCode: (phoneNumber: string, customPairingCode?: string) => Promise<string>;
|
|
34
|
+
waitForConnectionUpdate: (check: (u: Partial<import("../Types").ConnectionState>) => Promise<boolean | undefined>, timeoutMs?: number) => Promise<void>;
|
|
36
35
|
sendWAMBuffer: (wamBuffer: Buffer) => Promise<BinaryNode>;
|
|
37
36
|
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { Store } from 'cache-manager';
|
|
2
1
|
import { AuthenticationCreds } from '../Types';
|
|
3
|
-
declare const makeCacheManagerAuthState: (store:
|
|
2
|
+
declare const makeCacheManagerAuthState: (store: Storage, sessionKey: string) => Promise<{
|
|
4
3
|
clearState: () => Promise<void>;
|
|
5
4
|
saveCreds: () => Promise<void>;
|
|
6
5
|
state: {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type KeyedDB from '@adiwajshing/keyed-db';
|
|
2
2
|
import type { Comparable } from '@adiwajshing/keyed-db/lib/Types';
|
|
3
|
+
import type { Logger } from 'pino';
|
|
3
4
|
import { proto } from '../../WAProto';
|
|
4
5
|
import type makeMDSocket from '../Socket';
|
|
5
6
|
import type { BaileysEventEmitter, Chat, ConnectionState, Contact, GroupMetadata, PresenceData, WAMessage, WAMessageCursor, WAMessageKey } from '../Types';
|
|
6
7
|
import { Label } from '../Types/Label';
|
|
7
8
|
import { LabelAssociation } from '../Types/LabelAssociation';
|
|
8
|
-
import { ILogger } from '../Utils/logger';
|
|
9
9
|
import { ObjectRepository } from './object-repository';
|
|
10
10
|
type WASocket = ReturnType<typeof makeMDSocket>;
|
|
11
11
|
export declare const waChatKey: (pin: boolean) => {
|
|
@@ -17,7 +17,7 @@ export declare const waLabelAssociationKey: Comparable<LabelAssociation, string>
|
|
|
17
17
|
export type BaileysInMemoryStoreConfig = {
|
|
18
18
|
chatKey?: Comparable<Chat, string>;
|
|
19
19
|
labelAssociationKey?: Comparable<LabelAssociation, string>;
|
|
20
|
-
logger?:
|
|
20
|
+
logger?: Logger;
|
|
21
21
|
socket?: WASocket;
|
|
22
22
|
};
|
|
23
23
|
declare const _default: (config: BaileysInMemoryStoreConfig) => {
|
|
@@ -74,11 +74,7 @@ exports.default = (config) => {
|
|
|
74
74
|
ev.on('connection.update', update => {
|
|
75
75
|
Object.assign(state, update);
|
|
76
76
|
});
|
|
77
|
-
ev.on('messaging-history.set', ({ chats: newChats, contacts: newContacts, messages: newMessages, isLatest
|
|
78
|
-
if (syncType === WAProto_1.proto.HistorySync.HistorySyncType.ON_DEMAND) {
|
|
79
|
-
return; // FOR NOW,
|
|
80
|
-
//TODO: HANDLE
|
|
81
|
-
}
|
|
77
|
+
ev.on('messaging-history.set', ({ chats: newChats, contacts: newContacts, messages: newMessages, isLatest }) => {
|
|
82
78
|
if (isLatest) {
|
|
83
79
|
chats.clear();
|
|
84
80
|
for (const id in messages) {
|
|
@@ -56,9 +56,9 @@ function makeOrderedDictionary(idGetter) {
|
|
|
56
56
|
},
|
|
57
57
|
clear: () => {
|
|
58
58
|
array.splice(0, array.length);
|
|
59
|
-
|
|
59
|
+
Object.keys(dict).forEach(key => {
|
|
60
60
|
delete dict[key];
|
|
61
|
-
}
|
|
61
|
+
});
|
|
62
62
|
},
|
|
63
63
|
filter: (contain) => {
|
|
64
64
|
let i = 0;
|
package/lib/Types/Auth.d.ts
CHANGED
package/lib/Types/Call.d.ts
CHANGED
package/lib/Types/Chat.d.ts
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import type { proto } from '../../WAProto';
|
|
2
2
|
import type { AccountSettings } from './Auth';
|
|
3
3
|
import type { BufferedEventData } from './Events';
|
|
4
|
-
import type { LabelActionBody } from './Label';
|
|
5
4
|
import type { ChatLabelAssociationActionBody } from './LabelAssociation';
|
|
6
5
|
import type { MessageLabelAssociationActionBody } from './LabelAssociation';
|
|
7
|
-
import type { MinimalMessage
|
|
6
|
+
import type { MinimalMessage } from './Message';
|
|
8
7
|
/** privacy settings in WhatsApp Web */
|
|
9
8
|
export type WAPrivacyValue = 'all' | 'contacts' | 'contact_blacklist' | 'none';
|
|
10
9
|
export type WAPrivacyOnlineValue = 'all' | 'match_last_seen';
|
|
11
|
-
export type WAPrivacyGroupAddValue = 'all' | 'contacts' | 'contact_blacklist';
|
|
12
10
|
export type WAReadReceiptsValue = 'all' | 'none';
|
|
13
|
-
export type WAPrivacyCallValue = 'all' | 'known';
|
|
14
11
|
/** set of statuses visible to other people; see updatePresence() in WhatsAppWeb.Send */
|
|
15
12
|
export type WAPresence = 'unavailable' | 'available' | 'composing' | 'recording' | 'paused';
|
|
16
13
|
export declare const ALL_WA_PATCH_NAMES: readonly ["critical_block", "critical_unblock_low", "regular_high", "regular_low", "regular"];
|
|
@@ -62,12 +59,12 @@ export type ChatModification = {
|
|
|
62
59
|
/** mute for duration, or provide timestamp of mute to remove*/
|
|
63
60
|
mute: number | null;
|
|
64
61
|
} | {
|
|
65
|
-
clear:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
62
|
+
clear: 'all' | {
|
|
63
|
+
messages: {
|
|
64
|
+
id: string;
|
|
65
|
+
fromMe?: boolean;
|
|
66
|
+
timestamp: number;
|
|
67
|
+
}[];
|
|
71
68
|
};
|
|
72
69
|
} | {
|
|
73
70
|
star: {
|
|
@@ -83,8 +80,6 @@ export type ChatModification = {
|
|
|
83
80
|
} | {
|
|
84
81
|
delete: true;
|
|
85
82
|
lastMessages: LastMessageList;
|
|
86
|
-
} | {
|
|
87
|
-
addLabel: LabelActionBody;
|
|
88
83
|
} | {
|
|
89
84
|
addChatLabel: ChatLabelAssociationActionBody;
|
|
90
85
|
} | {
|
package/lib/Types/Events.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { AuthenticationCreds } from './Auth';
|
|
|
4
4
|
import { WACallEvent } from './Call';
|
|
5
5
|
import { Chat, ChatUpdate, PresenceData } from './Chat';
|
|
6
6
|
import { Contact } from './Contact';
|
|
7
|
-
import { GroupMetadata, ParticipantAction
|
|
7
|
+
import { GroupMetadata, ParticipantAction } from './GroupMetadata';
|
|
8
8
|
import { Label } from './Label';
|
|
9
9
|
import { LabelAssociation } from './LabelAssociation';
|
|
10
10
|
import { MessageUpsertType, MessageUserReceiptUpdate, WAMessage, WAMessageKey, WAMessageUpdate } from './Message';
|
|
@@ -19,10 +19,7 @@ export type BaileysEventMap = {
|
|
|
19
19
|
chats: Chat[];
|
|
20
20
|
contacts: Contact[];
|
|
21
21
|
messages: WAMessage[];
|
|
22
|
-
isLatest
|
|
23
|
-
progress?: number | null;
|
|
24
|
-
syncType?: proto.HistorySync.HistorySyncType;
|
|
25
|
-
peerDataRequestSessionId?: string | null;
|
|
22
|
+
isLatest: boolean;
|
|
26
23
|
};
|
|
27
24
|
/** upsert chats */
|
|
28
25
|
'chats.upsert': Chat[];
|
|
@@ -61,12 +58,10 @@ export type BaileysEventMap = {
|
|
|
61
58
|
/**
|
|
62
59
|
* add/update the given messages. If they were received while the connection was online,
|
|
63
60
|
* the update will have type: "notify"
|
|
64
|
-
* if requestId is provided, then the messages was received from the phone due to it being unavailable
|
|
65
61
|
* */
|
|
66
62
|
'messages.upsert': {
|
|
67
63
|
messages: WAMessage[];
|
|
68
64
|
type: MessageUpsertType;
|
|
69
|
-
requestId?: string;
|
|
70
65
|
};
|
|
71
66
|
/** message was reacted to. If reaction was removed -- then "reaction.text" will be falsey */
|
|
72
67
|
'messages.reaction': {
|
|
@@ -83,13 +78,6 @@ export type BaileysEventMap = {
|
|
|
83
78
|
participants: string[];
|
|
84
79
|
action: ParticipantAction;
|
|
85
80
|
};
|
|
86
|
-
'group.join-request': {
|
|
87
|
-
id: string;
|
|
88
|
-
author: string;
|
|
89
|
-
participant: string;
|
|
90
|
-
action: RequestJoinAction;
|
|
91
|
-
method: RequestJoinMethod;
|
|
92
|
-
};
|
|
93
81
|
'blocklist.set': {
|
|
94
82
|
blocklist: string[];
|
|
95
83
|
};
|
|
@@ -118,9 +106,6 @@ export type BufferedEventData = {
|
|
|
118
106
|
};
|
|
119
107
|
empty: boolean;
|
|
120
108
|
isLatest: boolean;
|
|
121
|
-
progress?: number | null;
|
|
122
|
-
syncType?: proto.HistorySync.HistorySyncType;
|
|
123
|
-
peerDataRequestSessionId?: string;
|
|
124
109
|
};
|
|
125
110
|
chatUpserts: {
|
|
126
111
|
[jid: string]: Chat;
|
|
@@ -4,13 +4,12 @@ export type GroupParticipant = (Contact & {
|
|
|
4
4
|
isSuperAdmin?: boolean;
|
|
5
5
|
admin?: 'admin' | 'superadmin' | null;
|
|
6
6
|
});
|
|
7
|
-
export type ParticipantAction = 'add' | 'remove' | 'promote' | 'demote'
|
|
8
|
-
export type RequestJoinAction = 'created' | 'revoked' | 'rejected';
|
|
9
|
-
export type RequestJoinMethod = 'invite_link' | 'linked_group_join' | 'non_admin_add' | undefined;
|
|
7
|
+
export type ParticipantAction = 'add' | 'remove' | 'promote' | 'demote';
|
|
10
8
|
export interface GroupMetadata {
|
|
11
9
|
id: string;
|
|
12
10
|
owner: string | undefined;
|
|
13
11
|
subject: string;
|
|
12
|
+
addressingMode: "pn" | "lid";
|
|
14
13
|
/** group subject owner */
|
|
15
14
|
subjectOwner?: string;
|
|
16
15
|
/** group subject modification date */
|
package/lib/Types/Label.d.ts
CHANGED
|
@@ -10,17 +10,6 @@ export interface Label {
|
|
|
10
10
|
/** WhatsApp has 5 predefined labels (New customer, New order & etc) */
|
|
11
11
|
predefinedId?: string;
|
|
12
12
|
}
|
|
13
|
-
export interface LabelActionBody {
|
|
14
|
-
id: string;
|
|
15
|
-
/** Label name */
|
|
16
|
-
name?: string;
|
|
17
|
-
/** Label color ID */
|
|
18
|
-
color?: number;
|
|
19
|
-
/** Is label has been deleted */
|
|
20
|
-
deleted?: boolean;
|
|
21
|
-
/** WhatsApp has 5 predefined labels (New customer, New order & etc) */
|
|
22
|
-
predefinedId?: number;
|
|
23
|
-
}
|
|
24
13
|
/** WhatsApp has 20 predefined colors */
|
|
25
14
|
export declare enum LabelColor {
|
|
26
15
|
Color1 = 0,
|