@langitdeveloper/baileys 2.2.1 → 2.2.3
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/lib/Socket/messages-send.js +87 -1
- package/lib/Types/index.js +2 -0
- package/lib/Utils/bot-toolkit.js +21 -39
- package/lib/WABinary/generic-utils.js +33 -7
- package/lib/WABinary/jid-utils.js +18 -1
- package/lib/WAUSync/Protocols/USyncContactProtocol.js +9 -1
- package/lib/WAUSync/USyncUser.js +6 -0
- package/package.json +12 -1
|
@@ -14,6 +14,7 @@ const link_preview_1 = require("../Utils/link-preview");
|
|
|
14
14
|
const WABinary_1 = require("../WABinary");
|
|
15
15
|
const newsletter_1 = require("./newsletter");
|
|
16
16
|
const WAUSync_1 = require("../WAUSync");
|
|
17
|
+
const { isTcTokenExpired, shouldSendNewTcToken, resolveTcTokenJid, resolveIssuanceJid, buildMergedTcTokenIndexWrite, storeTcTokensFromIqResult } = require("../Utils/tc-token-utils");
|
|
17
18
|
const kikyy = require('./dugong');
|
|
18
19
|
const makeMessagesSocket = (config) => {
|
|
19
20
|
const {
|
|
@@ -155,7 +156,12 @@ const makeMessagesSocket = (config) => {
|
|
|
155
156
|
for (const jid of toFetch) {
|
|
156
157
|
query.withUser(new WAUSync_1.USyncUser().withId(jid))
|
|
157
158
|
}
|
|
158
|
-
|
|
159
|
+
let result = await executeUSyncQuery(query)
|
|
160
|
+
if (!result) {
|
|
161
|
+
// stability fix: query device kadang gagal transient (timeout/network), coba sekali lagi sebelum nyerah
|
|
162
|
+
logger.debug({ toFetch }, 'usync device query empty, retrying once')
|
|
163
|
+
result = await executeUSyncQuery(query)
|
|
164
|
+
}
|
|
159
165
|
if (result) {
|
|
160
166
|
const extracted = Utils_1.extractDeviceJids(result?.list, authState.creds.me.id, ignoreZeroDevices)
|
|
161
167
|
const deviceMap = {}
|
|
@@ -333,6 +339,15 @@ const makeMessagesSocket = (config) => {
|
|
|
333
339
|
const additionalDevices = await getUSyncDevices(participantsList, !!useUserDevicesCache, false)
|
|
334
340
|
devices.push(...additionalDevices)
|
|
335
341
|
}
|
|
342
|
+
// fallback: kalo server ga ngasih addressingMode di group metadata (kadang kosong/null),
|
|
343
|
+
// tebak dari suffix jid participant sendiri drpd langsung default ke pn - lebih akurat pas grup lid-only
|
|
344
|
+
if (groupData && !groupData.addressingMode && groupData.participants?.length) {
|
|
345
|
+
const lidCount = groupData.participants.filter(p => WABinary_1.isLidUser(p.id)).length
|
|
346
|
+
if (lidCount > 0 && lidCount === groupData.participants.length) {
|
|
347
|
+
groupData.addressingMode = 'lid'
|
|
348
|
+
logger.trace({ jid }, 'inferred addressingMode=lid from participant jid suffix (fallback)')
|
|
349
|
+
}
|
|
350
|
+
}
|
|
336
351
|
const patched = await patchMessageBeforeSending(message, devices.map(d => WABinary_1.jidEncode(d.user, isLid ? 'lid' : 's.whatsapp.net', d.device)));
|
|
337
352
|
const bytes = Utils_1.encodeWAMessage(patched);
|
|
338
353
|
const { ciphertext, senderKeyDistributionMessage } = await signalRepository.encryptGroupMessage({
|
|
@@ -518,8 +533,59 @@ const makeMessagesSocket = (config) => {
|
|
|
518
533
|
if (!didPushAdditional && additionalNodes && additionalNodes.length > 0) {
|
|
519
534
|
stanza.content.push(...additionalNodes);
|
|
520
535
|
}
|
|
536
|
+
// TC (Trusted Contact) token: attach our stored token for this peer (if any
|
|
537
|
+
// and not expired) onto the outgoing stanza - mirrors WA Web's reliability
|
|
538
|
+
// mechanism for 1:1 chats. Ported from official Baileys v7.
|
|
539
|
+
const is1on1Send = isPrivate && !isStatus;
|
|
540
|
+
const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping);
|
|
541
|
+
let tcTokenJid = jid;
|
|
542
|
+
let existingTokenEntry;
|
|
543
|
+
if (is1on1Send) {
|
|
544
|
+
try {
|
|
545
|
+
tcTokenJid = await resolveTcTokenJid(jid, getLIDForPN);
|
|
546
|
+
const contactTcTokenData = await authState.keys.get('tctoken', [tcTokenJid]);
|
|
547
|
+
existingTokenEntry = contactTcTokenData[tcTokenJid];
|
|
548
|
+
if (existingTokenEntry?.token?.length && !isTcTokenExpired(existingTokenEntry?.timestamp)) {
|
|
549
|
+
stanza.content.push({
|
|
550
|
+
tag: 'tctoken',
|
|
551
|
+
attrs: { t: String(existingTokenEntry.timestamp) },
|
|
552
|
+
content: existingTokenEntry.token
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
catch (err) {
|
|
557
|
+
logger.debug({ err }, 'tctoken attach failed, sending without it');
|
|
558
|
+
}
|
|
559
|
+
}
|
|
521
560
|
logger.debug({ msgId }, `sending message to ${participants.length} devices`);
|
|
522
561
|
await sendNode(stanza);
|
|
562
|
+
// Fire-and-forget: after sending, issue our own token to this contact so
|
|
563
|
+
// future messages from them get the reliability benefit too. Skipped for
|
|
564
|
+
// protocol messages, PSA, and bot/MetaAI contacts (matches WA Web behavior).
|
|
565
|
+
if (is1on1Send) {
|
|
566
|
+
try {
|
|
567
|
+
const isProtocolMsg = !!Types_1.WAProto.Message.fromObject(message)?.protocolMessage;
|
|
568
|
+
const isBotOrPSA = jid === WABinary_1.PSA_WID || WABinary_1.isJidBot(jid) || WABinary_1.isJidMetaAI(jid);
|
|
569
|
+
if (!isProtocolMsg && !isBotOrPSA && shouldSendNewTcToken(existingTokenEntry?.senderTimestamp)) {
|
|
570
|
+
const issueTimestamp = (0, Utils_1.unixTimestampSeconds)();
|
|
571
|
+
const getPNForLID = signalRepository.lidMapping.getPNForLID.bind(signalRepository.lidMapping);
|
|
572
|
+
resolveIssuanceJid(jid, !!sock.serverProps?.lidTrustedTokenIssueToLid, getLIDForPN, getPNForLID)
|
|
573
|
+
.then((issueJid) => sock.issuePrivacyTokens ? sock.issuePrivacyTokens([issueJid], issueTimestamp) : null)
|
|
574
|
+
.then(async (result) => {
|
|
575
|
+
if (!result) {
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
await storeTcTokensFromIqResult({ result, fallbackJid: tcTokenJid, keys: authState.keys, getLIDForPN });
|
|
579
|
+
const indexWrite = await buildMergedTcTokenIndexWrite(authState.keys, [tcTokenJid]);
|
|
580
|
+
await authState.keys.set({ tctoken: indexWrite });
|
|
581
|
+
})
|
|
582
|
+
.catch((err) => logger.debug({ err }, 'tctoken issuance failed'));
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
catch (err) {
|
|
586
|
+
logger.debug({ err }, 'tctoken post-send issuance setup failed');
|
|
587
|
+
}
|
|
588
|
+
}
|
|
523
589
|
});
|
|
524
590
|
message = Types_1.WAProto.Message.fromObject(message)
|
|
525
591
|
const messageJSON = {
|
|
@@ -653,11 +719,31 @@ const makeMessagesSocket = (config) => {
|
|
|
653
719
|
});
|
|
654
720
|
return result;
|
|
655
721
|
}
|
|
722
|
+
/** issues our own TC (Trusted Contact) tokens to the given jids - used internally after sending, also callable directly */
|
|
723
|
+
const issuePrivacyTokens = async (jids, timestamp) => {
|
|
724
|
+
const t = (timestamp !== undefined ? timestamp : (0, Utils_1.unixTimestampSeconds)()).toString();
|
|
725
|
+
const result = await query({
|
|
726
|
+
tag: 'iq',
|
|
727
|
+
attrs: { to: WABinary_1.S_WHATSAPP_NET, type: 'set', xmlns: 'privacy' },
|
|
728
|
+
content: [
|
|
729
|
+
{
|
|
730
|
+
tag: 'tokens',
|
|
731
|
+
attrs: {},
|
|
732
|
+
content: jids.map((jid) => ({
|
|
733
|
+
tag: 'token',
|
|
734
|
+
attrs: { jid: WABinary_1.jidNormalizedUser(jid), t, type: 'trusted_contact' }
|
|
735
|
+
}))
|
|
736
|
+
}
|
|
737
|
+
]
|
|
738
|
+
});
|
|
739
|
+
return result;
|
|
740
|
+
};
|
|
656
741
|
const waUploadToServer = (0, Utils_1.getWAUploadToServer)(config, refreshMediaConn);
|
|
657
742
|
const rahmi = new kikyy(Utils_1, waUploadToServer, relayMessage, config, sock);
|
|
658
743
|
const waitForMsgMediaUpdate = (0, Utils_1.bindWaitForEvent)(ev, 'messages.media-update');
|
|
659
744
|
return {
|
|
660
745
|
...sock,
|
|
746
|
+
issuePrivacyTokens,
|
|
661
747
|
getPrivacyTokens,
|
|
662
748
|
assertSessions,
|
|
663
749
|
relayMessage,
|
package/lib/Types/index.js
CHANGED
|
@@ -40,4 +40,6 @@ var DisconnectReason;
|
|
|
40
40
|
DisconnectReason[DisconnectReason["multideviceMismatch"] = 411] = "multideviceMismatch";
|
|
41
41
|
DisconnectReason[DisconnectReason["forbidden"] = 403] = "forbidden";
|
|
42
42
|
DisconnectReason[DisconnectReason["unavailableService"] = 503] = "unavailableService";
|
|
43
|
+
// akun kena restrict/timelock dari WA (guess, blm 100% kekonfirm formatnya di MD protocol)
|
|
44
|
+
DisconnectReason[DisconnectReason["restrictedAccess"] = 463] = "restrictedAccess";
|
|
43
45
|
})(DisconnectReason = exports.DisconnectReason || (exports.DisconnectReason = {}));
|
package/lib/Utils/bot-toolkit.js
CHANGED
|
@@ -27,6 +27,18 @@ const makeBotToolkit = (conn, logger) => {
|
|
|
27
27
|
const groupMetaCache = new Map(); // jid -> { data, fetchedAt }
|
|
28
28
|
const GROUP_META_TTL_MS = 60 * 1000;
|
|
29
29
|
return {
|
|
30
|
+
/**
|
|
31
|
+
* Releases bot-toolkit's own internal state (poll trackers, group
|
|
32
|
+
* metadata cache, dedup/rate-limit maps). Call this when a session
|
|
33
|
+
* ends/logs out, alongside the socket's own cleanup, so nothing in
|
|
34
|
+
* this toolkit keeps holding memory for a dead connection.
|
|
35
|
+
*/
|
|
36
|
+
destroy() {
|
|
37
|
+
seenMessageIds.clear();
|
|
38
|
+
rateLimitBuckets.clear();
|
|
39
|
+
groupMetaCache.clear();
|
|
40
|
+
pollTallies.clear();
|
|
41
|
+
},
|
|
30
42
|
/**
|
|
31
43
|
* Checks if a user is an admin/superadmin in a group, using the
|
|
32
44
|
* cached metadata getter above so repeated checks (every message in
|
|
@@ -429,45 +441,15 @@ const makeBotToolkit = (conn, logger) => {
|
|
|
429
441
|
}
|
|
430
442
|
rateLimitBuckets.set(bucketKey, now);
|
|
431
443
|
return false;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
'Jawab singkat, bahasa Indonesia santai, fokus ke: kemungkinan sebab error, dan baris/fungsi mana yang perlu dicek.',
|
|
442
|
-
'Jangan ngarang nama file/fungsi yang gak disebutkan user.'
|
|
443
|
-
];
|
|
444
|
-
if (errorText) {
|
|
445
|
-
promptParts.push(`Error yang dialami:\n${errorText}`);
|
|
446
|
-
}
|
|
447
|
-
if (code) {
|
|
448
|
-
promptParts.push(`Kode yang dipakai user:\n\`\`\`js\n${code}\n\`\`\``);
|
|
449
|
-
}
|
|
450
|
-
const res = await fetch('https://api.anthropic.com/v1/messages', {
|
|
451
|
-
method: 'POST',
|
|
452
|
-
headers: {
|
|
453
|
-
'content-type': 'application/json',
|
|
454
|
-
'x-api-key': key,
|
|
455
|
-
'anthropic-version': '2023-06-01'
|
|
456
|
-
},
|
|
457
|
-
body: JSON.stringify({
|
|
458
|
-
model,
|
|
459
|
-
max_tokens: 1000,
|
|
460
|
-
messages: [{ role: 'user', content: promptParts.join('\n\n') }]
|
|
461
|
-
})
|
|
462
|
-
});
|
|
463
|
-
if (!res.ok) {
|
|
464
|
-
const text = await res.text().catch(() => '');
|
|
465
|
-
throw new Error(`aiMahiru: Anthropic API error ${res.status}: ${text}`);
|
|
466
|
-
}
|
|
467
|
-
const data = await res.json();
|
|
468
|
-
const textBlock = (data.content || []).find((c) => c.type === 'text');
|
|
469
|
-
return (textBlock === null || textBlock === void 0 ? void 0 : textBlock.text) || '(no response)';
|
|
470
|
-
}
|
|
444
|
+
},
|
|
445
|
+
/**
|
|
446
|
+
* Asks an Anthropic model to help debug an error / snippet against
|
|
447
|
+
* THIS fork's actual Baileys source, so suggestions are grounded in
|
|
448
|
+
* what's really in your codebase instead of generic upstream advice.
|
|
449
|
+
* Wire this up to whatever command prefix you like in your own bot
|
|
450
|
+
* dispatcher (e.g. `.aimahiru`) - this function only does the actual
|
|
451
|
+
* call + prompt shaping, not command parsing.
|
|
452
|
+
*/
|
|
471
453
|
};
|
|
472
454
|
};
|
|
473
455
|
exports.makeBotToolkit = makeBotToolkit;
|
|
@@ -5,11 +5,38 @@ const boom_1 = require("@hapi/boom");
|
|
|
5
5
|
const WAProto_1 = require("../../WAProto");
|
|
6
6
|
const Utils_1 = require("../Utils")
|
|
7
7
|
// some extra useful utilities
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
// caches children-by-tag lookups per node so repeatedly calling
|
|
9
|
+
// getBinaryNodeChildren/getBinaryNodeChild on the same node (very common when
|
|
10
|
+
// parsing a stanza with multiple helper calls) doesn't re-scan node.content
|
|
11
|
+
// every time. WeakMap means cached entries disappear once the node itself is
|
|
12
|
+
// garbage collected - no manual cleanup needed.
|
|
13
|
+
const childrenByTagCache = new WeakMap();
|
|
14
|
+
const getChildrenByTag = (node) => {
|
|
15
|
+
if (!Array.isArray(node?.content)) {
|
|
16
|
+
return null;
|
|
11
17
|
}
|
|
12
|
-
|
|
18
|
+
let byTag = childrenByTagCache.get(node);
|
|
19
|
+
if (!byTag) {
|
|
20
|
+
byTag = new Map();
|
|
21
|
+
for (const item of node.content) {
|
|
22
|
+
const tag = item?.tag;
|
|
23
|
+
if (!tag) {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
let list = byTag.get(tag);
|
|
27
|
+
if (!list) {
|
|
28
|
+
list = [];
|
|
29
|
+
byTag.set(tag, list);
|
|
30
|
+
}
|
|
31
|
+
list.push(item);
|
|
32
|
+
}
|
|
33
|
+
childrenByTagCache.set(node, byTag);
|
|
34
|
+
}
|
|
35
|
+
return byTag;
|
|
36
|
+
};
|
|
37
|
+
const getBinaryNodeChildren = (node, childTag) => {
|
|
38
|
+
const byTag = getChildrenByTag(node);
|
|
39
|
+
return byTag?.get(childTag) || [];
|
|
13
40
|
}
|
|
14
41
|
exports.getBinaryNodeChildren = getBinaryNodeChildren;
|
|
15
42
|
const getAllBinaryNodeChildren = ({ content }) => {
|
|
@@ -20,9 +47,8 @@ const getAllBinaryNodeChildren = ({ content }) => {
|
|
|
20
47
|
}
|
|
21
48
|
exports.getAllBinaryNodeChildren = getAllBinaryNodeChildren;
|
|
22
49
|
const getBinaryNodeChild = (node, childTag) => {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
50
|
+
const byTag = getChildrenByTag(node);
|
|
51
|
+
return byTag?.get(childTag)?.[0];
|
|
26
52
|
}
|
|
27
53
|
exports.getBinaryNodeChild = getBinaryNodeChild;
|
|
28
54
|
const getBinaryNodeChildBuffer = (node, childTag) => {
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.jidNormalizedUser = exports.isJidNewsLetter = exports.isJidStatusBroadcast = exports.isJidGroup = exports.isJidBroadcast = exports.isLidUser = exports.isJidUser = exports.areJidsSameUser = exports.jidDecode = exports.jidEncode = exports.STORIES_JID = exports.PSA_WID = exports.SERVER_JID = exports.OFFICIAL_BIZ_JID = exports.S_WHATSAPP_NET = void 0;
|
|
4
|
-
exports.isPnUser = exports.isHostedPnUser = exports.isHostedLidUser = exports.WAJIDDomains = exports.getServerFromDomainType = exports.transferDevice = void 0;
|
|
4
|
+
exports.isPnUser = exports.isHostedPnUser = exports.isHostedLidUser = exports.WAJIDDomains = exports.getServerFromDomainType = exports.transferDevice = exports.isJidBot = void 0;
|
|
5
|
+
/** matches WA's official bot phone-number patterns (used to gate TC token issuance away from bots) */
|
|
6
|
+
const botRegexp = /^1313555\d{4}$|^131655500\d{2}$/;
|
|
7
|
+
const isJidBot = (jid) => !!(jid && botRegexp.test(jid.split('@')[0]) && jid.endsWith('@c.us'));
|
|
8
|
+
exports.isJidBot = isJidBot;
|
|
5
9
|
exports.S_WHATSAPP_NET = '@s.whatsapp.net';
|
|
6
10
|
exports.OFFICIAL_BIZ_JID = '16505361212@c.us';
|
|
7
11
|
exports.SERVER_JID = 'server@c.us';
|
|
@@ -94,6 +98,19 @@ exports.isJidStatusBroadcast = isJidStatusBroadcast;
|
|
|
94
98
|
/** is the jid the newsletter */
|
|
95
99
|
const isJidNewsLetter = (jid) => (jid === null || jid === void 0 ? void 0 : jid.endsWith('newsletter'));
|
|
96
100
|
exports.isJidNewsLetter = isJidNewsLetter;
|
|
101
|
+
/**
|
|
102
|
+
* SPECULATIVE - WA Username / BSUID support (rollout mulai Jul 2026, blm masuk ID, blm ada di protokol MD resmi).
|
|
103
|
+
* BSUID format yg kekonfirm sejauh ini (Cloud API): "US.13491208655302741918" -> kode negara + "." + digit panjang.
|
|
104
|
+
* Belum jelas apakah MD protocol bakal pake domain baru (mis. "@username"/"@bsuid") atau tetap "@s.whatsapp.net"
|
|
105
|
+
* dgn field tambahan. Ini cuma placeholder detector, JANGAN dipake buat encode/kirim sampe ada konfirmasi real traffic.
|
|
106
|
+
*/
|
|
107
|
+
const bsuidRegexp = /^[A-Z]{2}\.\d{6,}$/;
|
|
108
|
+
/** cek apakah string berbentuk BSUID (country-code prefixed id), bukan jid biasa */
|
|
109
|
+
const isBsuid = (id) => !!(id && bsuidRegexp.test(id.split('@')[0]));
|
|
110
|
+
exports.isBsuid = isBsuid;
|
|
111
|
+
/** cek apakah jid pake domain "username" (tebakan, blm ada konfirmasi format resmi dari WA MD protocol) */
|
|
112
|
+
const isUsernameJid = (jid) => (jid === null || jid === void 0 ? void 0 : jid.endsWith('@username'));
|
|
113
|
+
exports.isUsernameJid = isUsernameJid;
|
|
97
114
|
const jidNormalizedUser = (jid) => {
|
|
98
115
|
const result = (0, exports.jidDecode)(jid);
|
|
99
116
|
if (!result) {
|
|
@@ -13,7 +13,15 @@ class USyncContactProtocol {
|
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
15
|
getUserElement(user) {
|
|
16
|
-
//
|
|
16
|
+
// kalo query by username, kirim type='username' + content=username (bukan phone)
|
|
17
|
+
// NOTE: speculative, blm ada konfirmasi format node asli dari traffic MD WA yg beneran punya username
|
|
18
|
+
if (user.type === 'username' && user.username) {
|
|
19
|
+
return {
|
|
20
|
+
tag: 'contact',
|
|
21
|
+
attrs: { type: 'username' },
|
|
22
|
+
content: user.username,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
17
25
|
return {
|
|
18
26
|
tag: 'contact',
|
|
19
27
|
attrs: {},
|
package/lib/WAUSync/USyncUser.js
CHANGED
|
@@ -22,5 +22,11 @@ class USyncUser {
|
|
|
22
22
|
this.personaId = personaId;
|
|
23
23
|
return this;
|
|
24
24
|
}
|
|
25
|
+
// speculative - buat query usync by username (fitur WA username, blm live di ID)
|
|
26
|
+
withUsername(username) {
|
|
27
|
+
this.username = username;
|
|
28
|
+
this.type = 'username';
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
25
31
|
}
|
|
26
32
|
exports.USyncUser = USyncUser;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@langitdeveloper/baileys",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.3",
|
|
4
4
|
"description": "WhatsApp API Modification By Langit",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"whatsapp",
|
|
@@ -14,6 +14,17 @@
|
|
|
14
14
|
"author": "Adhiraj Singh",
|
|
15
15
|
"main": "lib/index.js",
|
|
16
16
|
"types": "lib/index.d.ts",
|
|
17
|
+
"type": "commonjs",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./lib/index.d.ts",
|
|
21
|
+
"require": "./lib/index.js",
|
|
22
|
+
"import": "./lib/index.js",
|
|
23
|
+
"default": "./lib/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./package.json": "./package.json",
|
|
26
|
+
"./*": "./lib/*"
|
|
27
|
+
},
|
|
17
28
|
"files": [
|
|
18
29
|
"lib/*",
|
|
19
30
|
"WAProto/*.js",
|