whatsapp-nodejs 0.0.1-security → 0.0.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.
Potentially problematic release.
This version of whatsapp-nodejs might be problematic. Click here for more details.
- package/.eslintignore +2 -0
- package/.eslintrc.js +59 -0
- package/.prettierrc +11 -0
- package/README.md +51 -3
- package/package.json +97 -3
- package/proto/ClientHello.proto +167 -0
- package/proto/HandshakeMessage.proto +26 -0
- package/proto/Message.proto +795 -0
- package/proto/SessionStructure.proto +113 -0
- package/proto/WhisperTextProtocol.proto +29 -0
- package/src/SocketClient.js +241 -0
- package/src/SocketManager.js +130 -0
- package/src/WASocketClient.js +170 -0
- package/src/Whatsapp.js +169 -0
- package/src/WhatsappServer.js +17 -0
- package/src/bin/decoder.js +1 -0
- package/src/bin/dict.js +1 -0
- package/src/bin/dict1.js +1 -0
- package/src/bin/dict2.js +1 -0
- package/src/bin/encoder.js +1 -0
- package/src/config.js +71 -0
- package/src/db.js +44 -0
- package/src/index.js +7 -0
- package/src/lib/SocketProxy.js +225 -0
- package/src/lib/libsignal-protocol.js +14 -0
- package/src/lib/utils.js +123 -0
- package/src/logger.js +79 -0
- package/src/packet/ProtocolEntity.js +39 -0
- package/src/packet/ProtocolTreeNode.js +202 -0
- package/src/protobuf/pb.js +32415 -0
- package/src/protocol/CipherState.js +48 -0
- package/src/protocol/FallbackPatternModifier.js +48 -0
- package/src/protocol/ForwarderHandshakeState.js +56 -0
- package/src/protocol/HandShake.js +273 -0
- package/src/protocol/HandshakePattern.js +62 -0
- package/src/protocol/HandshakeState.js +223 -0
- package/src/protocol/PKCS7.js +89 -0
- package/src/protocol/SwitchableHandshakeState.js +64 -0
- package/src/protocol/WASymmetricState.js +116 -0
- package/src/protocol/crypto.js +112 -0
- package/src/protocol/handshakepatterns/HandshakePattern.js +62 -0
- package/src/protocol/handshakepatterns/IKHandshakePattern.js +17 -0
- package/src/protocol/handshakepatterns/XXHandshakePattern.js +9 -0
- package/src/schema/account.js +65 -0
- package/src/schema/axolotl.js +8 -0
- package/src/schema/business.js +15 -0
- package/src/schema/common.js +26 -0
- package/src/schema/dayreport.js +21 -0
- package/src/schema/identify.js +49 -0
- package/src/schema/message.js +44 -0
- package/src/schema/prekey.js +51 -0
- package/src/schema/recvmessage.js +33 -0
- package/src/schema/senderkey.js +20 -0
- package/src/schema/session.js +22 -0
- package/src/schema/signedprekeys.js +56 -0
- package/test/WASocketClient.test.js +23 -0
- package/test/handshake.test.js +38 -0
- package/test/logger.test.js +17 -0
- package/test/whatsapp.test.js +17 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
const net = require('net');
|
|
2
|
+
|
|
3
|
+
const numberToHex = num => {
|
|
4
|
+
return Number(`0x${Number(num).toString(16)}`);
|
|
5
|
+
};
|
|
6
|
+
const ERROR_MAP = {
|
|
7
|
+
1: '代理服务器故障',
|
|
8
|
+
2: '代理服务器规则集不允许连接',
|
|
9
|
+
3: '网络无法访问',
|
|
10
|
+
4: '目标服务器无法访问(主机名无效)',
|
|
11
|
+
5: '连接目标服务器被拒绝',
|
|
12
|
+
6: 'TTL已过期',
|
|
13
|
+
7: '不支持的命令',
|
|
14
|
+
8: '不支持的目标服务器地址类型',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
class Socket extends net.Socket {
|
|
18
|
+
constructor(opts = {}) {
|
|
19
|
+
super(opts);
|
|
20
|
+
const { proxy = {} } = opts;
|
|
21
|
+
const { port = 1080, host = '', username = '', password = '', type = 'socks5' } = proxy;
|
|
22
|
+
if (username && !password) throw new Error('You need to pass the password params.');
|
|
23
|
+
this.proxy = proxy;
|
|
24
|
+
this.proxyHost = host;
|
|
25
|
+
this.proxyPort = port;
|
|
26
|
+
this.proxyUsername = username;
|
|
27
|
+
this.proxyPassword = password;
|
|
28
|
+
this.proxyType = type;
|
|
29
|
+
if (!['socks5', 'http'].includes(type)) throw new Error(`Not support proxy type: ${type}`);
|
|
30
|
+
this.isConnected = false;
|
|
31
|
+
|
|
32
|
+
const _on = this.on;
|
|
33
|
+
this._on = _on;
|
|
34
|
+
|
|
35
|
+
this.on = (name, listener) => {
|
|
36
|
+
const isTmp = !!name.match('_tmp');
|
|
37
|
+
_on.call(this, name.replace('_tmp', ''), (...args) => {
|
|
38
|
+
if (this.isConnected) {
|
|
39
|
+
listener.apply(this, args);
|
|
40
|
+
} else if (isTmp) {
|
|
41
|
+
listener.apply(this, args);
|
|
42
|
+
} else if (name === 'connect') {
|
|
43
|
+
listener.apply(this, args);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async connect(opts = {}, listener) {
|
|
50
|
+
if (this.proxyType === 'socks5') {
|
|
51
|
+
await this.connectSocks(opts, listener);
|
|
52
|
+
} else {
|
|
53
|
+
await this.connnectHttp(opts, listener);
|
|
54
|
+
}
|
|
55
|
+
this.on = this._on;
|
|
56
|
+
this.isConnected = true;
|
|
57
|
+
console.log('Socket proxy connect success.');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async connnectHttp(opts = {}, listener) {
|
|
61
|
+
const targetHost = opts.host;
|
|
62
|
+
const targetPort = opts.port;
|
|
63
|
+
|
|
64
|
+
return new Promise((resolve, reject) => {
|
|
65
|
+
console.debug(
|
|
66
|
+
'Start connect target: ',
|
|
67
|
+
`${this.proxyType}://${this.proxyPort}:${this.proxyHost}`,
|
|
68
|
+
this.proxyUsername,
|
|
69
|
+
this.proxyPassword
|
|
70
|
+
);
|
|
71
|
+
super.connect({
|
|
72
|
+
port: this.proxyPort,
|
|
73
|
+
host: this.proxyHost,
|
|
74
|
+
});
|
|
75
|
+
this.once('connect_tmp', () => {
|
|
76
|
+
console.debug('Send Connect method.');
|
|
77
|
+
this.write(`CONNECT ${targetHost}:${targetPort} HTTP/1.1\r\n`);
|
|
78
|
+
this.write('Connection: keep-alive\r\n');
|
|
79
|
+
this.write('Content-Length: 0\r\n');
|
|
80
|
+
if (this.proxyUsername) {
|
|
81
|
+
const base64 = Buffer.from(`${this.proxyUsername}:${this.proxyPassword}`).toString(
|
|
82
|
+
'base64'
|
|
83
|
+
);
|
|
84
|
+
this.write(`Proxy-Authorization: Basic ${base64}\r\n`);
|
|
85
|
+
}
|
|
86
|
+
this.write('\r\n');
|
|
87
|
+
this.once('data_tmp', data => {
|
|
88
|
+
const str = String(data.toString()).trim();
|
|
89
|
+
console.debug(`Recv Connect method: ${str}`);
|
|
90
|
+
if (!str.match(/^HTTP\/1\.1 200/i)) {
|
|
91
|
+
return reject(new Error(str));
|
|
92
|
+
}
|
|
93
|
+
if (typeof listener === 'function') listener();
|
|
94
|
+
console.debug('Connnect socket success.');
|
|
95
|
+
resolve();
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async connectSocks(opts = {}, listener) {
|
|
102
|
+
const host = this.proxyHost || opts.host;
|
|
103
|
+
const port = this.proxyPort || opts.port;
|
|
104
|
+
const params = { ...opts, host, port };
|
|
105
|
+
|
|
106
|
+
return new Promise((resolve, reject) => {
|
|
107
|
+
if (this.proxyHost) {
|
|
108
|
+
console.debug('Start connect proxy: ', `${this.proxyType}://${host}:${port}`);
|
|
109
|
+
} else {
|
|
110
|
+
console.debug('Start connect target: ', `${host}:${port}`);
|
|
111
|
+
}
|
|
112
|
+
super.connect(params);
|
|
113
|
+
this.once('connect_tmp', async () => {
|
|
114
|
+
if (this.proxyHost) {
|
|
115
|
+
console.debug(`Connect proxy success:`, ` ${this.proxyType}://${host}:${port}`);
|
|
116
|
+
} else {
|
|
117
|
+
console.debug('Connect target success: ', `${host}:${port}`);
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
await this.auth();
|
|
121
|
+
await this.connectTarget(opts);
|
|
122
|
+
if (typeof listener === 'function') listener();
|
|
123
|
+
resolve();
|
|
124
|
+
} catch (e) {
|
|
125
|
+
reject(e);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async auth() {
|
|
132
|
+
if (!this.proxyHost) return; // 不需要代理
|
|
133
|
+
/** 第一位 socks 版本,固定 0x05
|
|
134
|
+
// 第二位,支持的验证方法数量,默认支持无密码和有密码两种
|
|
135
|
+
// 第三第四位,
|
|
136
|
+
0x00 不需要认证(常用)
|
|
137
|
+
0x01 GSSAPI认证
|
|
138
|
+
0x02 账号密码认证(常用)
|
|
139
|
+
0x03 - 0x7F IANA分配
|
|
140
|
+
0x80 - 0xFE 私有方法保留
|
|
141
|
+
0xFF 无支持的认证方法
|
|
142
|
+
*/
|
|
143
|
+
return new Promise((resolve, reject) => {
|
|
144
|
+
const head = Buffer.from([0x05, 0x02, 0x00, 0x02]);
|
|
145
|
+
this.write(head);
|
|
146
|
+
this.once('data_tmp', data => {
|
|
147
|
+
if (data.length !== 2) {
|
|
148
|
+
return reject(new Error('Unexpected number of bytes received.'));
|
|
149
|
+
}
|
|
150
|
+
if (data[0] !== 0x05) {
|
|
151
|
+
return reject(new Error(`Unexpected socks version number: ${data[0]}.`));
|
|
152
|
+
}
|
|
153
|
+
const authType = data[1];
|
|
154
|
+
if (authType !== 0x00 && authType !== 0x02)
|
|
155
|
+
return reject(new Error(`Unexpected socks authentication method: ${authType}`));
|
|
156
|
+
if (authType === 0x00) {
|
|
157
|
+
console.debug('Not need auth.');
|
|
158
|
+
return resolve();
|
|
159
|
+
}
|
|
160
|
+
console.debug('Use auth: ', this.proxyUsername, this.proxyPassword);
|
|
161
|
+
const bUsername = Buffer.from(this.proxyUsername);
|
|
162
|
+
let bUser = Buffer.concat([Buffer.from([0x01, bUsername.length]), bUsername]);
|
|
163
|
+
const bPassword = Buffer.from(this.proxyPassword);
|
|
164
|
+
bUser = Buffer.concat([bUser, Buffer.from([bPassword.length]), bPassword]);
|
|
165
|
+
this.write(bUser);
|
|
166
|
+
|
|
167
|
+
this.once('data_tmp', authData => {
|
|
168
|
+
if (authData.length !== 2)
|
|
169
|
+
return reject(new Error('Unexpected number of bytes received.'));
|
|
170
|
+
if (authData[0] !== 0x01)
|
|
171
|
+
return reject(new Error(`Unexpected authentication method code: ${authData[0]}.`));
|
|
172
|
+
if (authData[1] !== 0x00)
|
|
173
|
+
return reject(
|
|
174
|
+
new Error(`Username and password authentication failure: ${authData[1]}.`)
|
|
175
|
+
);
|
|
176
|
+
resolve();
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async connectTarget(opts = {}) {
|
|
183
|
+
if (!this.proxyHost) return; // 不需要代理
|
|
184
|
+
const { host, port } = opts;
|
|
185
|
+
return new Promise((resolve, reject) => {
|
|
186
|
+
let buffer = Buffer.from([0x05, 0x01, 0x00]);
|
|
187
|
+
const targetType = net.isIP(host);
|
|
188
|
+
let hexPort = Number(port).toString(16);
|
|
189
|
+
if (hexPort.length < 4) {
|
|
190
|
+
hexPort = new Array(4 - hexPort.length + 1).join('0') + hexPort;
|
|
191
|
+
}
|
|
192
|
+
if (targetType === 4) {
|
|
193
|
+
buffer = Buffer.concat([
|
|
194
|
+
buffer,
|
|
195
|
+
Buffer.from([0x01]),
|
|
196
|
+
Buffer.from(host.split('.').map(num => numberToHex(num))),
|
|
197
|
+
Buffer.from([Number(`0x${hexPort.substr(0, 2)}`), Number(`0x${hexPort.substr(2)}`)]),
|
|
198
|
+
]);
|
|
199
|
+
} else if (targetType === 0) {
|
|
200
|
+
const bufferHost = Buffer.from(host);
|
|
201
|
+
buffer = Buffer.concat([
|
|
202
|
+
buffer,
|
|
203
|
+
Buffer.from([0x03, bufferHost.length]),
|
|
204
|
+
bufferHost,
|
|
205
|
+
Buffer.from([Number(`0x${hexPort.substr(0, 2)}`), Number(`0x${hexPort.substr(2)}`)]),
|
|
206
|
+
]);
|
|
207
|
+
}
|
|
208
|
+
console.debug('Connnect socket target: ', `${host}:${port}`);
|
|
209
|
+
this.write(buffer);
|
|
210
|
+
this.once('data_tmp', data => {
|
|
211
|
+
const version = data[0];
|
|
212
|
+
if (version !== 0x05)
|
|
213
|
+
return reject(new Error(`Unexpected SOCKS version number: ${version}.`));
|
|
214
|
+
const response = data[1];
|
|
215
|
+
if (response !== 0x00) {
|
|
216
|
+
if (ERROR_MAP[response]) return reject(new Error(ERROR_MAP[response]));
|
|
217
|
+
return reject(new Error('Unknown error'));
|
|
218
|
+
}
|
|
219
|
+
resolve();
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
module.exports = Socket;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
2
|
+
|
|
3
|
+
global.window = {};
|
|
4
|
+
class XMLHttpRequest {}
|
|
5
|
+
global.XMLHttpRequest = XMLHttpRequest;
|
|
6
|
+
const libsignal = require('@privacyresearch/libsignal-protocol-typescript');
|
|
7
|
+
|
|
8
|
+
crypto.getRandomValues = array => {
|
|
9
|
+
const size = array.length;
|
|
10
|
+
return crypto.randomBytes(size);
|
|
11
|
+
};
|
|
12
|
+
// libsignal.setWebCrypto(crypto)
|
|
13
|
+
|
|
14
|
+
module.exports = libsignal;
|
package/src/lib/utils.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
const { v4 } = require('uuid');
|
|
2
|
+
const { SocksProxyAgent } = require('socks-proxy-agent');
|
|
3
|
+
const tunnel = require('tunnel');
|
|
4
|
+
|
|
5
|
+
const uuidv4 = v4;
|
|
6
|
+
const crypto = require('crypto');
|
|
7
|
+
const libsignal = require('./libsignal-protocol');
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
KEY_SIZE: 32,
|
|
11
|
+
fillZero(str, len) {
|
|
12
|
+
if (len && str.length < len) {
|
|
13
|
+
str = new Array(len - str.length + 1).join('0') + str;
|
|
14
|
+
}
|
|
15
|
+
return str;
|
|
16
|
+
},
|
|
17
|
+
getHttpsProxyAgent(proxy) {
|
|
18
|
+
const proxyConfig = {
|
|
19
|
+
host: proxy.host,
|
|
20
|
+
port: proxy.port,
|
|
21
|
+
};
|
|
22
|
+
// http 代理
|
|
23
|
+
if (proxy.type === 'http') {
|
|
24
|
+
if (proxy.userId) {
|
|
25
|
+
proxyConfig.proxyAuth = `${proxy.userId}:${proxy.password}`;
|
|
26
|
+
}
|
|
27
|
+
const agent = tunnel.httpsOverHttp({
|
|
28
|
+
proxy: proxyConfig,
|
|
29
|
+
});
|
|
30
|
+
return agent;
|
|
31
|
+
}
|
|
32
|
+
// socks5 代理
|
|
33
|
+
if (proxy.userId) {
|
|
34
|
+
proxyConfig.userId = proxy.userId;
|
|
35
|
+
proxyConfig.password = proxy.password;
|
|
36
|
+
}
|
|
37
|
+
const agent = new SocksProxyAgent(proxyConfig);
|
|
38
|
+
return agent;
|
|
39
|
+
},
|
|
40
|
+
async sleep(t) {
|
|
41
|
+
return new Promise(resolve => {
|
|
42
|
+
setTimeout(resolve, t);
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
toString(thing) {
|
|
46
|
+
if (typeof thing === 'string') return thing;
|
|
47
|
+
return thing.toString('base64');
|
|
48
|
+
},
|
|
49
|
+
toBuffer(plaintext) {
|
|
50
|
+
return typeof plaintext === 'string'
|
|
51
|
+
? Buffer.from(plaintext, 'base64')
|
|
52
|
+
: Buffer.from(plaintext);
|
|
53
|
+
},
|
|
54
|
+
normalize(number) {
|
|
55
|
+
if (String(number).indexOf('@') !== -1) return number;
|
|
56
|
+
if (String(number).indexOf('-') !== -1) return `${number}@g.us`;
|
|
57
|
+
return `${String(number)}@s.whatsapp.net`;
|
|
58
|
+
},
|
|
59
|
+
generateHeader(num, len = 6) {
|
|
60
|
+
let str = Number(num).toString(16);
|
|
61
|
+
str = new Array(len - str.length + 1).join('0') + str;
|
|
62
|
+
return Buffer.from(str, 'hex');
|
|
63
|
+
},
|
|
64
|
+
generateId(num = 0) {
|
|
65
|
+
let str = Number(num).toString(16);
|
|
66
|
+
if (str.length % 2 === 1) str = `0${str}`;
|
|
67
|
+
return str;
|
|
68
|
+
},
|
|
69
|
+
generateUUID() {
|
|
70
|
+
return uuidv4();
|
|
71
|
+
},
|
|
72
|
+
urandom(len) {
|
|
73
|
+
return crypto.randomBytes(len);
|
|
74
|
+
},
|
|
75
|
+
generateIdentity() {
|
|
76
|
+
return this.urandom(20);
|
|
77
|
+
},
|
|
78
|
+
generateSenderKey() {
|
|
79
|
+
return this.urandom(32);
|
|
80
|
+
},
|
|
81
|
+
generateSenderKeyId() {
|
|
82
|
+
return Math.floor(Math.random() * 2147483647);
|
|
83
|
+
},
|
|
84
|
+
generatePhoneId() {
|
|
85
|
+
return this.generateUUID();
|
|
86
|
+
},
|
|
87
|
+
generateDeviceId() {
|
|
88
|
+
return Buffer.from(this.generateUUID().replace(/-/g, ''), 'hex');
|
|
89
|
+
},
|
|
90
|
+
generateKeyPair() {
|
|
91
|
+
const keyPair = libsignal.curve.generateKeyPair();
|
|
92
|
+
keyPair.pubKey = Buffer.from(keyPair.pubKey);
|
|
93
|
+
keyPair.privKey = Buffer.from(keyPair.privKey);
|
|
94
|
+
return {
|
|
95
|
+
public: keyPair.pubKey.slice(keyPair.pubKey.length - this.KEY_SIZE),
|
|
96
|
+
private: keyPair.privKey.slice(keyPair.privKey.length - this.KEY_SIZE),
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
generateSenderSigningKey() {
|
|
100
|
+
return this.generateKeyPair();
|
|
101
|
+
},
|
|
102
|
+
hash(message, type = 'md5', encode = 'base64') {
|
|
103
|
+
// crypto.createHash('sha256').update(m).digest('base64')
|
|
104
|
+
return crypto
|
|
105
|
+
.createHash(type)
|
|
106
|
+
.update(message)
|
|
107
|
+
.digest(encode);
|
|
108
|
+
},
|
|
109
|
+
hmacHash(key, message, type = 'sha256', encode = 'base64') {
|
|
110
|
+
const hmac = crypto.createHmac(type, key);
|
|
111
|
+
hmac.update(message);
|
|
112
|
+
return hmac.digest(encode); // kXogg18q...
|
|
113
|
+
},
|
|
114
|
+
bufferToArrayBuffer(buffer) {
|
|
115
|
+
if (!buffer) buffer = Buffer.alloc(0);
|
|
116
|
+
const arrayBuffer = new ArrayBuffer(buffer.length);
|
|
117
|
+
const uint8Array = new Uint8Array(arrayBuffer);
|
|
118
|
+
buffer.forEach((val, i) => {
|
|
119
|
+
uint8Array[i] = val;
|
|
120
|
+
});
|
|
121
|
+
return arrayBuffer;
|
|
122
|
+
},
|
|
123
|
+
};
|
package/src/logger.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const bunyan = require('bunyan');
|
|
2
|
+
const bunyanDebugStream = require('bunyan-debug-stream');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fse = require('fs-extra');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
7
|
+
const env = process.env.NODE_ENV;
|
|
8
|
+
const isProduction = env === 'production' && os.platform() !== 'darwin';
|
|
9
|
+
|
|
10
|
+
let logDir = path.join(__dirname, '../logs/');
|
|
11
|
+
|
|
12
|
+
if (isProduction) {
|
|
13
|
+
logDir = `/root/logs/`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
fse.ensureDirSync(logDir);
|
|
17
|
+
|
|
18
|
+
const debugStream = {
|
|
19
|
+
level: 'debug',
|
|
20
|
+
type: 'raw',
|
|
21
|
+
stream: bunyanDebugStream({
|
|
22
|
+
forceColor: true,
|
|
23
|
+
}),
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const debugFileStream = {
|
|
27
|
+
level: 'debug',
|
|
28
|
+
type: 'rotating-file',
|
|
29
|
+
path: path.join(logDir, `whatsapp.debug.log`),
|
|
30
|
+
period: '1d',
|
|
31
|
+
count: 7,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const errorFileStream = {
|
|
35
|
+
level: 'error',
|
|
36
|
+
type: 'rotating-file',
|
|
37
|
+
path: path.join(logDir, `whatsapp.error.log`),
|
|
38
|
+
period: '1d',
|
|
39
|
+
count: 7,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const fatalFileStream = {
|
|
43
|
+
level: 'fatal',
|
|
44
|
+
type: 'rotating-file',
|
|
45
|
+
path: path.join(logDir, `whatsapp.fatal.log`),
|
|
46
|
+
period: '1d',
|
|
47
|
+
count: 7,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const streams = [];
|
|
51
|
+
|
|
52
|
+
if (isProduction) {
|
|
53
|
+
streams.push(errorFileStream);
|
|
54
|
+
} else {
|
|
55
|
+
streams.push(debugStream);
|
|
56
|
+
streams.push(debugFileStream);
|
|
57
|
+
streams.push(errorFileStream);
|
|
58
|
+
streams.push(fatalFileStream);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const logger = bunyan.createLogger({
|
|
62
|
+
name: 'WA',
|
|
63
|
+
streams,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
['fatal', 'error', 'warn', 'info', 'debug', 'trace', 'child'].forEach(method => {
|
|
67
|
+
// eslint-disable-next-line
|
|
68
|
+
global.console[method] = function() {
|
|
69
|
+
// eslint-disable-next-line
|
|
70
|
+
return logger[method].apply(logger, arguments);
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
// eslint-disable-next-line
|
|
74
|
+
console.log = function() {
|
|
75
|
+
// eslint-disable-next-line
|
|
76
|
+
logger.info.apply(logger, arguments);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
module.exports = logger;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const ProtocolTreeNode = require('./ProtocolTreeNode');
|
|
2
|
+
|
|
3
|
+
class ProtocolEntity {
|
|
4
|
+
constructor(tag, attrs, children = [], data = null) {
|
|
5
|
+
this.tag = tag;
|
|
6
|
+
this.attrs = attrs;
|
|
7
|
+
this.children = children;
|
|
8
|
+
this.data = data;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
getTag() {
|
|
12
|
+
return this.tag;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
isType(typ) {
|
|
16
|
+
return this.tag === typ;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
_createProtocolTreeNode(attributes, children = null, data = null) {
|
|
20
|
+
return new ProtocolTreeNode(this.getTag(), attributes, children, data);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
_getCurrentTimestamp() {
|
|
24
|
+
return Math.round(Date.now() / 1000);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
_generateId(short = false) {
|
|
28
|
+
return short ? 'short' : `long`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// eslint-disable-next-line
|
|
32
|
+
toTreeNode() {
|
|
33
|
+
return this._createProtocolTreeNode(this.attrs, this.children, this.data);
|
|
34
|
+
}
|
|
35
|
+
// eslint-disable-next-line
|
|
36
|
+
static fromTreeNode(protocolTreeNode) {}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = ProtocolEntity;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
const WriteEncoder = require('../bin/encoder');
|
|
2
|
+
|
|
3
|
+
class ProtocolTreeNode {
|
|
4
|
+
constructor(tag, attributes = null, children = null, data = null) {
|
|
5
|
+
this.tag = tag;
|
|
6
|
+
this.attributes = attributes || {};
|
|
7
|
+
this.children = children || [];
|
|
8
|
+
this.data = data;
|
|
9
|
+
Object.keys(this.attributes).forEach(key => {
|
|
10
|
+
if (this.attributes[key] === undefined || this.attributes[key] === null) {
|
|
11
|
+
delete this.attributes[key];
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
this.children = this.children.map(child => {
|
|
15
|
+
return new ProtocolTreeNode(child.tag, child.attributes, child.children, child.data);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
toBuffer() {
|
|
20
|
+
try {
|
|
21
|
+
const bytes = new WriteEncoder().protocolTreeNodeToBytes(this);
|
|
22
|
+
return Buffer.from(bytes);
|
|
23
|
+
} catch (e) {
|
|
24
|
+
console.error(`Node 序列化 buffer 失败`, e);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
toString() {
|
|
29
|
+
let out = `<${this.tag}`;
|
|
30
|
+
if (this.attributes) {
|
|
31
|
+
Object.keys(this.attributes).forEach(key => {
|
|
32
|
+
const val = this.attributes[key];
|
|
33
|
+
out += ` ${key}="${val}"`;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
out += '>\n';
|
|
37
|
+
if (this.data) {
|
|
38
|
+
if (this.data instanceof Buffer) {
|
|
39
|
+
out += this.data.toString('utf8');
|
|
40
|
+
} else {
|
|
41
|
+
try {
|
|
42
|
+
out += this.data;
|
|
43
|
+
} catch (e) {
|
|
44
|
+
try {
|
|
45
|
+
out += this.data.toString();
|
|
46
|
+
} catch (err) {
|
|
47
|
+
out += this.data.toString('hex');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
out += `\nHEX3:${Buffer.from(this.data || '').toString('hex')}\n`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
for (let i = 0; i < this.children.length; i++) {
|
|
55
|
+
const c = this.children[i];
|
|
56
|
+
try {
|
|
57
|
+
out += c.toString();
|
|
58
|
+
} catch (e) {
|
|
59
|
+
console.error(e);
|
|
60
|
+
out += '[ENCODED DATA]\n';
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
out += `</${this.tag}>\n`;
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
toJSON() {
|
|
68
|
+
const json = {
|
|
69
|
+
tag: this.tag,
|
|
70
|
+
props: this.attributes,
|
|
71
|
+
children: [],
|
|
72
|
+
};
|
|
73
|
+
Object.keys(this.attributes).forEach(key => {
|
|
74
|
+
let value = this.attributes[key];
|
|
75
|
+
if (value instanceof Buffer) {
|
|
76
|
+
value = value.toString('utf8');
|
|
77
|
+
this.attributes[key] = value;
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
if (this.data !== null) {
|
|
81
|
+
json.data = this.data;
|
|
82
|
+
// if (json.data instanceof Buffer) {
|
|
83
|
+
// json.data = json.data.toString('utf8');
|
|
84
|
+
// }
|
|
85
|
+
}
|
|
86
|
+
for (let i = 0; i < this.children.length; i++) {
|
|
87
|
+
const c = this.children[i];
|
|
88
|
+
try {
|
|
89
|
+
json.children.push(c.toJSON());
|
|
90
|
+
} catch (e) {
|
|
91
|
+
console.error(e);
|
|
92
|
+
json.children.push('[ENCODED DATA]');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return json;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
toSimpleJSON() {
|
|
99
|
+
const json = {};
|
|
100
|
+
json[this.tag] = {};
|
|
101
|
+
Object.keys(this.attributes).forEach(key => {
|
|
102
|
+
let value = this.attributes[key];
|
|
103
|
+
if (value instanceof Buffer) {
|
|
104
|
+
value = value.toString('utf8');
|
|
105
|
+
}
|
|
106
|
+
json[this.tag][key] = value;
|
|
107
|
+
});
|
|
108
|
+
try {
|
|
109
|
+
for (let i = 0; i < this.children.length; i++) {
|
|
110
|
+
const c = this.children[i].toSimpleJSON();
|
|
111
|
+
if (!c) continue;
|
|
112
|
+
let tag = Object.keys(c)[0];
|
|
113
|
+
const child = c[tag];
|
|
114
|
+
if (typeof this.attributes[tag] !== 'undefined') {
|
|
115
|
+
tag = `${tag}_entity`;
|
|
116
|
+
}
|
|
117
|
+
if (json[this.tag][tag]) {
|
|
118
|
+
if (Array.isArray(json[this.tag][tag])) {
|
|
119
|
+
json[this.tag][tag].push(child);
|
|
120
|
+
} else {
|
|
121
|
+
json[this.tag][tag] = [json[this.tag][tag], child];
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
json[this.tag][tag] = child;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
} catch (e) {
|
|
128
|
+
console.error(e);
|
|
129
|
+
}
|
|
130
|
+
if (this.data !== null && typeof this.data !== 'undefined') {
|
|
131
|
+
json[this.tag].entity_value = this.data;
|
|
132
|
+
if (json[this.tag].entity_value instanceof Buffer) {
|
|
133
|
+
json[this.tag].entity_value = json[this.tag].entity_value.toString('utf8');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return json || {};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
setData(data) {
|
|
140
|
+
this.data = data;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
getData() {
|
|
144
|
+
return this.data;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
getChild(identifier) {
|
|
148
|
+
if (typeof identifier === 'number') {
|
|
149
|
+
if (this.children.length > identifier) return this.children[identifier];
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
for (let i = 0; i < this.children.length; i++) {
|
|
153
|
+
const c = this.children[i];
|
|
154
|
+
if (c.tag === identifier) return c;
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
addChild(childNode) {
|
|
160
|
+
this.children.push(childNode);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
addChildren(children) {
|
|
164
|
+
children.forEach(child => {
|
|
165
|
+
this.addChild(child);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
hasChildren() {
|
|
170
|
+
return this.children.length > 0;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
getAllChildren(tag = null) {
|
|
174
|
+
if (!tag) return this.children;
|
|
175
|
+
return this.children.filter(child => child.tag === tag);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
getAttributeValue(key) {
|
|
179
|
+
return this.attributes[key];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
getAttr(key) {
|
|
183
|
+
return this.getAttributeValue(key);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
setAttr(key, value) {
|
|
187
|
+
this.setAttribute(key, value);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
removeAttr(key) {
|
|
191
|
+
delete this.attributes[key];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
removeAttribute(key) {
|
|
195
|
+
delete this.attributes[key];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
setAttribute(key, value) {
|
|
199
|
+
this.attributes[key] = value;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
module.exports = ProtocolTreeNode;
|