@blckrose/baileys 1.1.1 → 1.1.5
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 +1099 -0
- package/lib/Defaults/index.js +4 -2
- package/lib/Signal/libsignal.js +31 -8
- package/lib/Socket/chats.js +102 -6
- package/lib/Socket/groups.js +80 -19
- package/lib/Socket/messages-recv.js +197 -3
- package/lib/Socket/messages-send.js +329 -93
- package/lib/Socket/newsletter.js +40 -1
- package/lib/Types/Newsletter.js +2 -0
- package/lib/Utils/messages-media.js +224 -179
- package/lib/Utils/messages.js +360 -73
- package/package.json +5 -4
package/lib/Defaults/index.js
CHANGED
|
@@ -89,7 +89,8 @@ export const MEDIA_PATH_MAP = {
|
|
|
89
89
|
'product-catalog-image': '/product/image',
|
|
90
90
|
'md-app-state': '',
|
|
91
91
|
'md-msg-hist': '/mms/md-app-state',
|
|
92
|
-
'biz-cover-photo': '/pps/biz-cover-photo'
|
|
92
|
+
'biz-cover-photo': '/pps/biz-cover-photo',
|
|
93
|
+
'sticker-pack': '/mms/sticker'
|
|
93
94
|
};
|
|
94
95
|
export const MEDIA_HKDF_KEY_MAPPING = {
|
|
95
96
|
audio: 'Audio',
|
|
@@ -110,7 +111,8 @@ export const MEDIA_HKDF_KEY_MAPPING = {
|
|
|
110
111
|
'product-catalog-image': '',
|
|
111
112
|
'payment-bg-image': 'Payment Background',
|
|
112
113
|
ptv: 'Video',
|
|
113
|
-
'biz-cover-photo': 'Image'
|
|
114
|
+
'biz-cover-photo': 'Image',
|
|
115
|
+
'sticker-pack': 'Sticker Pack'
|
|
114
116
|
};
|
|
115
117
|
export const MEDIA_KEYS = Object.keys(MEDIA_PATH_MAP);
|
|
116
118
|
export const MIN_PREKEY_COUNT = 5;
|
package/lib/Signal/libsignal.js
CHANGED
|
@@ -3,14 +3,37 @@ import * as libsignal from 'libsignal';
|
|
|
3
3
|
// @ts-ignore
|
|
4
4
|
import { PreKeyWhisperMessage } from 'libsignal/src/protobufs.js';
|
|
5
5
|
import { LRUCache } from 'lru-cache';
|
|
6
|
-
// ── Suppress libsignal "Closing session: SessionEntry {...}"
|
|
7
|
-
|
|
8
|
-
console.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
// ── Suppress libsignal "Closing session: SessionEntry {...}" noise ────────────
|
|
7
|
+
// ES module imports di-hoist, jadi suppressor harus dipasang setelah semua import.
|
|
8
|
+
// Cara paling efektif: patch console + process.stdout setelah module loaded.
|
|
9
|
+
;(function _suppressSessionNoise() {
|
|
10
|
+
const _isNoise = (...args) => {
|
|
11
|
+
if (typeof args[0] === 'string') {
|
|
12
|
+
const s = args[0];
|
|
13
|
+
if (s.startsWith('Closing session') || s.includes('SessionEntry') ||
|
|
14
|
+
s.includes('pendingPreKey') || s.includes('currentRatchet') ||
|
|
15
|
+
s.includes('registrationId:') || s.includes('indexInfo:')) return true;
|
|
16
|
+
}
|
|
17
|
+
if (args[1] && typeof args[1] === 'object' &&
|
|
18
|
+
args[1]?.constructor?.name === 'SessionEntry') return true;
|
|
19
|
+
if (typeof args[0] === 'object' &&
|
|
20
|
+
args[0]?.constructor?.name === 'SessionEntry') return true;
|
|
21
|
+
return false;
|
|
22
|
+
};
|
|
23
|
+
const _wrapFn = (orig) => (...args) => { if (_isNoise(...args)) return; orig(...args); };
|
|
24
|
+
console.log = _wrapFn(console.log.bind(console));
|
|
25
|
+
console.warn = _wrapFn(console.warn.bind(console));
|
|
26
|
+
console.info = _wrapFn(console.info.bind(console));
|
|
27
|
+
// Intercept process.stdout.write untuk kasus libsignal bypass console
|
|
28
|
+
const _origWrite = process.stdout.write.bind(process.stdout);
|
|
29
|
+
process.stdout.write = function(chunk, ...rest) {
|
|
30
|
+
if (typeof chunk === 'string' && (
|
|
31
|
+
chunk.includes('Closing session') || chunk.includes('SessionEntry') ||
|
|
32
|
+
chunk.includes('pendingPreKey') || chunk.includes('currentRatchet')
|
|
33
|
+
)) return true;
|
|
34
|
+
return _origWrite(chunk, ...rest);
|
|
35
|
+
};
|
|
36
|
+
})();
|
|
14
37
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
15
38
|
import { generateSignalPubKey } from '../Utils/index.js';
|
|
16
39
|
import { isHostedLidUser, isHostedPnUser, isLidUser, isPnUser, jidDecode, transferDevice, WAJIDDomains } from '../WABinary/index.js';
|
package/lib/Socket/chats.js
CHANGED
|
@@ -8,7 +8,7 @@ import { chatModificationToAppPatch, decodePatches, decodeSyncdSnapshot, encodeS
|
|
|
8
8
|
import { makeMutex } from '../Utils/make-mutex.js';
|
|
9
9
|
import processMessage from '../Utils/process-message.js';
|
|
10
10
|
import { buildTcTokenFromJid } from '../Utils/tc-token-utils.js';
|
|
11
|
-
import { getBinaryNodeChild, getBinaryNodeChildren, jidDecode, jidNormalizedUser, reduceBinaryNodeToDictionary, S_WHATSAPP_NET } from '../WABinary/index.js';
|
|
11
|
+
import { getBinaryNodeChild, getBinaryNodeChildren, jidDecode, jidEncode, jidNormalizedUser, isLidUser, reduceBinaryNodeToDictionary, S_WHATSAPP_NET } from '../WABinary/index.js';
|
|
12
12
|
import { USyncQuery, USyncUser } from '../WAUSync/index.js';
|
|
13
13
|
import { makeSocket } from './socket.js';
|
|
14
14
|
const MAX_SYNC_ATTEMPTS = 2;
|
|
@@ -152,6 +152,23 @@ export const makeChatsSocket = (config) => {
|
|
|
152
152
|
}
|
|
153
153
|
return botList;
|
|
154
154
|
};
|
|
155
|
+
const getLidUser = async (jid) => {
|
|
156
|
+
if (!jid) throw new Boom('Please input a jid user');
|
|
157
|
+
if (!jid.endsWith('@s.whatsapp.net') && !jid.endsWith('@lid')) {
|
|
158
|
+
throw new Boom('Invalid JID: Not a user JID!');
|
|
159
|
+
}
|
|
160
|
+
const targetJid = jid.includes('@') ? jid : `${jid}@s.whatsapp.net`;
|
|
161
|
+
const usyncQuery = new USyncQuery();
|
|
162
|
+
usyncQuery.protocols.push({
|
|
163
|
+
name: 'lid',
|
|
164
|
+
getQueryElement: () => ({ tag: 'lid', attrs: {}, content: undefined }),
|
|
165
|
+
getUserElement: () => null,
|
|
166
|
+
parser: (node) => node.attrs.val
|
|
167
|
+
});
|
|
168
|
+
usyncQuery.users.push({ id: targetJid });
|
|
169
|
+
const result = await sock.executeUSyncQuery(usyncQuery);
|
|
170
|
+
if (result) return result.list;
|
|
171
|
+
};
|
|
155
172
|
const fetchStatus = async (...jids) => {
|
|
156
173
|
const usyncQuery = new USyncQuery().withStatusProtocol();
|
|
157
174
|
for (const jid of jids) {
|
|
@@ -258,6 +275,24 @@ export const makeChatsSocket = (config) => {
|
|
|
258
275
|
return getBinaryNodeChildren(listNode, 'item').map(n => n.attrs.jid);
|
|
259
276
|
};
|
|
260
277
|
const updateBlockStatus = async (jid, action) => {
|
|
278
|
+
jid = jidNormalizedUser(jid);
|
|
279
|
+
// Jika input adalah LID, resolve ke PN dulu
|
|
280
|
+
if (jid.endsWith('@lid')) {
|
|
281
|
+
try {
|
|
282
|
+
const pn = await signalRepository?.lidMapping?.getPNForLID?.(jid).catch(() => null);
|
|
283
|
+
if (pn) jid = jidNormalizedUser(pn);
|
|
284
|
+
} catch {}
|
|
285
|
+
}
|
|
286
|
+
const dhash = String(Date.now());
|
|
287
|
+
const itemAttrs = {
|
|
288
|
+
dhash,
|
|
289
|
+
action,
|
|
290
|
+
jid, // selalu PN
|
|
291
|
+
};
|
|
292
|
+
// Block: tambah pn_jid juga
|
|
293
|
+
if (action === 'block') {
|
|
294
|
+
itemAttrs.pn_jid = jid;
|
|
295
|
+
}
|
|
261
296
|
await query({
|
|
262
297
|
tag: 'iq',
|
|
263
298
|
attrs: {
|
|
@@ -268,10 +303,7 @@ export const makeChatsSocket = (config) => {
|
|
|
268
303
|
content: [
|
|
269
304
|
{
|
|
270
305
|
tag: 'item',
|
|
271
|
-
attrs:
|
|
272
|
-
action,
|
|
273
|
-
jid
|
|
274
|
-
}
|
|
306
|
+
attrs: itemAttrs
|
|
275
307
|
}
|
|
276
308
|
]
|
|
277
309
|
});
|
|
@@ -803,6 +835,60 @@ export const makeChatsSocket = (config) => {
|
|
|
803
835
|
await Promise.all([fetchProps(), fetchBlocklist(), fetchPrivacySettings()]);
|
|
804
836
|
};
|
|
805
837
|
const upsertMessage = ev.createBufferedFunction(async (msg, type) => {
|
|
838
|
+
// ── Auto-inject isAdmin, isBotAdmin, metadata ─────────────────────────
|
|
839
|
+
try {
|
|
840
|
+
const remoteJid = msg.key?.remoteJid || '';
|
|
841
|
+
const isGroup = remoteJid.endsWith('@g.us');
|
|
842
|
+
if (isGroup) {
|
|
843
|
+
const _normalizeJid = (jid) => {
|
|
844
|
+
if (!jid) return null;
|
|
845
|
+
try { return jidNormalizedUser(jid).split('@')[0]; }
|
|
846
|
+
catch { return String(jid).split('@')[0]; }
|
|
847
|
+
};
|
|
848
|
+
// Fetch metadata (pakai cache jika tersedia)
|
|
849
|
+
let meta = null;
|
|
850
|
+
try { meta = await sock.groupMetadata(remoteJid); } catch {}
|
|
851
|
+
if (meta && Array.isArray(meta.participants)) {
|
|
852
|
+
msg.metadata = meta;
|
|
853
|
+
const botJid = authState.creds?.me?.id;
|
|
854
|
+
const senderJid = msg.key.fromMe
|
|
855
|
+
? authState.creds?.me?.id
|
|
856
|
+
: (msg.key.participant || remoteJid);
|
|
857
|
+
const senderNorm = _normalizeJid(senderJid);
|
|
858
|
+
const botNorm = _normalizeJid(botJid);
|
|
859
|
+
// isAdmin
|
|
860
|
+
msg.isAdmin = meta.participants.some(p => {
|
|
861
|
+
const pid = _normalizeJid(p.jid || p.id || p.lid);
|
|
862
|
+
return pid === senderNorm && (p.admin === 'admin' || p.admin === 'superadmin');
|
|
863
|
+
});
|
|
864
|
+
// isBotAdmin: via m.isBotAdmin jika sudah di-set
|
|
865
|
+
let isBotAdmin = typeof msg.isBotAdmin === 'boolean' ? msg.isBotAdmin : false;
|
|
866
|
+
// fallback via owner
|
|
867
|
+
if (!isBotAdmin) {
|
|
868
|
+
const owners = [meta.owner, meta.subjectOwner, meta.ownerPn]
|
|
869
|
+
.filter(Boolean).map(_normalizeJid);
|
|
870
|
+
if (owners.includes(botNorm)) isBotAdmin = true;
|
|
871
|
+
}
|
|
872
|
+
// fallback via participants
|
|
873
|
+
if (!isBotAdmin) {
|
|
874
|
+
isBotAdmin = meta.participants.some(p => {
|
|
875
|
+
const pid = _normalizeJid(p.jid || p.id || p.lid);
|
|
876
|
+
return pid === botNorm && (p.admin === 'admin' || p.admin === 'superadmin');
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
msg.isBotAdmin = isBotAdmin;
|
|
880
|
+
} else {
|
|
881
|
+
msg.metadata = {};
|
|
882
|
+
msg.isAdmin = false;
|
|
883
|
+
msg.isBotAdmin = false;
|
|
884
|
+
}
|
|
885
|
+
} else {
|
|
886
|
+
msg.metadata = {};
|
|
887
|
+
msg.isAdmin = false;
|
|
888
|
+
msg.isBotAdmin = false;
|
|
889
|
+
}
|
|
890
|
+
} catch {}
|
|
891
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
806
892
|
ev.emit('messages.upsert', { messages: [msg], type });
|
|
807
893
|
if (!!msg.pushName) {
|
|
808
894
|
let jid = msg.key.fromMe ? authState.creds.me.id : msg.key.participant || msg.key.remoteJid;
|
|
@@ -1031,7 +1117,17 @@ export const makeChatsSocket = (config) => {
|
|
|
1031
1117
|
removeMessageLabel,
|
|
1032
1118
|
star,
|
|
1033
1119
|
addOrEditQuickReply,
|
|
1034
|
-
removeQuickReply
|
|
1120
|
+
removeQuickReply,
|
|
1121
|
+
clearMessage: (jid, key, timeStamp) => {
|
|
1122
|
+
return chatModify({
|
|
1123
|
+
delete: true,
|
|
1124
|
+
lastMessages: [{
|
|
1125
|
+
key: key,
|
|
1126
|
+
messageTimestamp: timeStamp
|
|
1127
|
+
}]
|
|
1128
|
+
}, jid);
|
|
1129
|
+
},
|
|
1130
|
+
getLidUser
|
|
1035
1131
|
};
|
|
1036
1132
|
};
|
|
1037
1133
|
//# sourceMappingURL=chats.js.map
|
package/lib/Socket/groups.js
CHANGED
|
@@ -40,28 +40,49 @@ export const makeGroupsSocket = (config) => {
|
|
|
40
40
|
}
|
|
41
41
|
});
|
|
42
42
|
// Update cache saat participant berubah
|
|
43
|
+
// Debounce map to prevent duplicate refresh calls for same group
|
|
44
|
+
const _refreshDebounce = new Map();
|
|
45
|
+
const _refreshGroupMetadata = async (jid) => {
|
|
46
|
+
// Debounce: skip jika refresh sudah dijadwalkan dalam 2 detik terakhir
|
|
47
|
+
if (_refreshDebounce.has(jid)) return;
|
|
48
|
+
_refreshDebounce.set(jid, true);
|
|
49
|
+
setTimeout(() => _refreshDebounce.delete(jid), 2000);
|
|
50
|
+
try {
|
|
51
|
+
const result = await groupQuery(jid, 'get', [{ tag: 'query', attrs: { request: 'interactive' } }]);
|
|
52
|
+
const meta = extractGroupMetadata(result);
|
|
53
|
+
setCachedGroupMetadata(jid, meta);
|
|
54
|
+
// Emit groups.update agar subscriber luar (makeInMemoryStore dll) ikut terupdate
|
|
55
|
+
ev.emit('groups.update', [meta]);
|
|
56
|
+
} catch (e) {
|
|
57
|
+
// Ignore jika gagal (bot mungkin sudah keluar dari grup)
|
|
58
|
+
}
|
|
59
|
+
};
|
|
43
60
|
ev.on('group-participants.update', ({ id, participants, action }) => {
|
|
44
61
|
const entry = groupMetadataCache.get(id);
|
|
45
|
-
if (
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
62
|
+
if (entry && Array.isArray(entry.data?.participants)) {
|
|
63
|
+
// Fast-path: update cache lokal secara optimistis tanpa tunggu network
|
|
64
|
+
const meta = { ...entry.data, participants: [...entry.data.participants] };
|
|
65
|
+
if (action === 'add') {
|
|
66
|
+
const existing = new Set(meta.participants.map(p => p.id));
|
|
67
|
+
for (const jid of participants) {
|
|
68
|
+
if (!existing.has(jid)) meta.participants.push({ id: jid, admin: null });
|
|
69
|
+
}
|
|
70
|
+
} else if (action === 'remove') {
|
|
71
|
+
meta.participants = meta.participants.filter(p => !participants.includes(p.id));
|
|
72
|
+
} else if (action === 'promote') {
|
|
73
|
+
meta.participants = meta.participants.map(p =>
|
|
74
|
+
participants.includes(p.id) ? { ...p, admin: 'admin' } : p
|
|
75
|
+
);
|
|
76
|
+
} else if (action === 'demote') {
|
|
77
|
+
meta.participants = meta.participants.map(p =>
|
|
78
|
+
participants.includes(p.id) ? { ...p, admin: null } : p
|
|
79
|
+
);
|
|
52
80
|
}
|
|
53
|
-
|
|
54
|
-
meta.participants = meta.participants.filter(p => !participants.includes(p.id));
|
|
55
|
-
} else if (action === 'promote') {
|
|
56
|
-
meta.participants = meta.participants.map(p =>
|
|
57
|
-
participants.includes(p.id) ? { ...p, admin: 'admin' } : p
|
|
58
|
-
);
|
|
59
|
-
} else if (action === 'demote') {
|
|
60
|
-
meta.participants = meta.participants.map(p =>
|
|
61
|
-
participants.includes(p.id) ? { ...p, admin: null } : p
|
|
62
|
-
);
|
|
81
|
+
groupMetadataCache.set(id, { data: meta, ts: entry.ts });
|
|
63
82
|
}
|
|
64
|
-
|
|
83
|
+
// Auto-refresh dari network untuk semua aksi participant
|
|
84
|
+
// Ini memastikan data selalu akurat dari server WA
|
|
85
|
+
_refreshGroupMetadata(id);
|
|
65
86
|
});
|
|
66
87
|
// ── End group metadata cache ───────────────────────────────────────────────
|
|
67
88
|
const groupQuery = async (jid, type, content) => query({
|
|
@@ -330,7 +351,47 @@ export const makeGroupsSocket = (config) => {
|
|
|
330
351
|
{ tag: 'membership_approval_mode', attrs: {}, content: [{ tag: 'group_join', attrs: { state: mode } }] }
|
|
331
352
|
]);
|
|
332
353
|
},
|
|
333
|
-
groupFetchAllParticipating
|
|
354
|
+
groupFetchAllParticipating,
|
|
355
|
+
/**
|
|
356
|
+
* Auto detect isAdmin & isBotAdmin dari groupMetadata
|
|
357
|
+
* @param {string} groupJid - JID grup
|
|
358
|
+
* @param {string} senderJid - JID pengirim pesan
|
|
359
|
+
* @returns {Promise<{isAdmin: boolean, isBotAdmin: boolean}>}
|
|
360
|
+
*/
|
|
361
|
+
getAdminStatus: async (groupJid, senderJid) => {
|
|
362
|
+
const normalizeJid = (jid) => {
|
|
363
|
+
if (!jid) return null;
|
|
364
|
+
try {
|
|
365
|
+
return jidNormalizedUser(jid).split('@')[0];
|
|
366
|
+
} catch {
|
|
367
|
+
return String(jid).split('@')[0];
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
const botJid = sock.authState?.creds?.me?.id;
|
|
371
|
+
const meta = await sock.groupMetadata(groupJid).catch(() => null);
|
|
372
|
+
if (!meta || !Array.isArray(meta.participants)) {
|
|
373
|
+
return { isAdmin: false, isBotAdmin: false };
|
|
374
|
+
}
|
|
375
|
+
const senderNorm = normalizeJid(senderJid);
|
|
376
|
+
const botNorm = normalizeJid(botJid);
|
|
377
|
+
const isAdmin = meta.participants.some(p => {
|
|
378
|
+
const pid = normalizeJid(p.jid || p.id || p.lid);
|
|
379
|
+
return pid === senderNorm && (p.admin === 'admin' || p.admin === 'superadmin');
|
|
380
|
+
});
|
|
381
|
+
// Cek isBotAdmin: via participants
|
|
382
|
+
let isBotAdmin = meta.participants.some(p => {
|
|
383
|
+
const pid = normalizeJid(p.jid || p.id || p.lid);
|
|
384
|
+
return pid === botNorm && (p.admin === 'admin' || p.admin === 'superadmin');
|
|
385
|
+
});
|
|
386
|
+
// Fallback: cek via owner/subjectOwner
|
|
387
|
+
if (!isBotAdmin) {
|
|
388
|
+
const owners = [meta.owner, meta.subjectOwner, meta.ownerPn]
|
|
389
|
+
.filter(Boolean)
|
|
390
|
+
.map(normalizeJid);
|
|
391
|
+
if (owners.includes(botNorm)) isBotAdmin = true;
|
|
392
|
+
}
|
|
393
|
+
return { isAdmin, isBotAdmin };
|
|
394
|
+
}
|
|
334
395
|
};
|
|
335
396
|
};
|
|
336
397
|
export const extractGroupMetadata = (result) => {
|
|
@@ -993,7 +993,47 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
993
993
|
}
|
|
994
994
|
return null;
|
|
995
995
|
};
|
|
996
|
+
// Resolve semua contextInfo termasuk yang ada di dalam quotedMessage
|
|
997
|
+
const getAllContextInfos = (content, results = []) => {
|
|
998
|
+
if (!content || typeof content !== 'object') return results;
|
|
999
|
+
if (content.contextInfo) {
|
|
1000
|
+
results.push(content.contextInfo);
|
|
1001
|
+
// Juga cari di dalam quotedMessage secara rekursif
|
|
1002
|
+
if (content.contextInfo.quotedMessage) {
|
|
1003
|
+
getAllContextInfos(content.contextInfo.quotedMessage, results);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
for (const val of Object.values(content)) {
|
|
1007
|
+
if (val && typeof val === 'object' && !results.includes(val)) {
|
|
1008
|
+
getAllContextInfos(val, results);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
return results;
|
|
1012
|
+
};
|
|
996
1013
|
const contextInfo = getContextInfo(msgContent);
|
|
1014
|
+
// Resolve participant LID di semua contextInfo (termasuk quotedMessage)
|
|
1015
|
+
const allContextInfos = getAllContextInfos(msgContent);
|
|
1016
|
+
for (const ctx of allContextInfos) {
|
|
1017
|
+
if (ctx?.participant?.endsWith('@lid')) {
|
|
1018
|
+
try {
|
|
1019
|
+
const pn = await lidMapping.getPNForLID(ctx.participant);
|
|
1020
|
+
if (pn) {
|
|
1021
|
+
logger.debug({ lid: ctx.participant, pn }, 'resolved nested contextInfo.participant LID → PN');
|
|
1022
|
+
ctx.participant = pn;
|
|
1023
|
+
}
|
|
1024
|
+
} catch { }
|
|
1025
|
+
}
|
|
1026
|
+
// Resolve mentionedJid di quotedMessage contextInfo juga
|
|
1027
|
+
if (ctx !== contextInfo && ctx?.mentionedJid?.length) {
|
|
1028
|
+
const lids = ctx.mentionedJid.filter(j => j?.endsWith('@lid'));
|
|
1029
|
+
for (const lid of lids) {
|
|
1030
|
+
try {
|
|
1031
|
+
const pn = await lidMapping.getPNForLID(lid);
|
|
1032
|
+
if (pn) ctx.mentionedJid = ctx.mentionedJid.map(j => j === lid ? pn : j);
|
|
1033
|
+
} catch { }
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
997
1037
|
// resolve contextInfo.participant (sender of quoted message) jika masih LID
|
|
998
1038
|
if (contextInfo?.participant?.endsWith('@lid')) {
|
|
999
1039
|
try {
|
|
@@ -1007,6 +1047,33 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1007
1047
|
}
|
|
1008
1048
|
if (!contextInfo?.mentionedJid?.length)
|
|
1009
1049
|
return;
|
|
1050
|
+
// Fix text yang masih mengandung LID number meskipun mentionedJid sudah resolved ke PN
|
|
1051
|
+
// Kasus: mentionedJid sudah jadi @s.whatsapp.net tapi text masih "@165159209271535" (LID num)
|
|
1052
|
+
const textFieldEarly = getTextField(msgContent);
|
|
1053
|
+
if (textFieldEarly) {
|
|
1054
|
+
let earlyText = textFieldEarly.obj[textFieldEarly.key] || '';
|
|
1055
|
+
// Cek apakah text mengandung @angka panjang (>12 digit = kemungkinan LID number bukan nomor HP)
|
|
1056
|
+
const lidNumPattern = /@(\d{13,20})/g;
|
|
1057
|
+
const lidNumMatches = [...earlyText.matchAll(lidNumPattern)];
|
|
1058
|
+
if (lidNumMatches.length > 0) {
|
|
1059
|
+
// Untuk setiap mentionedJid yang sudah resolved, cek apakah ada LID number di text
|
|
1060
|
+
for (const resolvedJid of contextInfo.mentionedJid) {
|
|
1061
|
+
if (resolvedJid?.endsWith('@lid')) continue; // belum resolved, skip dulu
|
|
1062
|
+
const pnNum = resolvedJid.split('@')[0].split(':')[0];
|
|
1063
|
+
if (!pnNum) continue;
|
|
1064
|
+
// Cari LID number di text yang paling mungkin match (berdasarkan urutan)
|
|
1065
|
+
for (const match of lidNumMatches) {
|
|
1066
|
+
const lidNum = match[1];
|
|
1067
|
+
if (earlyText.includes(`@${lidNum}`)) {
|
|
1068
|
+
earlyText = earlyText.split(`@${lidNum}`).join(`@${pnNum}`);
|
|
1069
|
+
logger.debug({ lidNum, pnNum }, 'replaced LID number in text with PN number');
|
|
1070
|
+
break;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
textFieldEarly.obj[textFieldEarly.key] = earlyText;
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1010
1077
|
const hasLid = contextInfo.mentionedJid.some((j) => j?.endsWith('@lid'));
|
|
1011
1078
|
if (!hasLid)
|
|
1012
1079
|
return;
|
|
@@ -1039,7 +1106,14 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1039
1106
|
if (result?.list) {
|
|
1040
1107
|
const mappings = [];
|
|
1041
1108
|
for (const item of result.list) {
|
|
1042
|
-
|
|
1109
|
+
// item.id bisa berupa PN (@s.whatsapp.net) atau LID (@lid)
|
|
1110
|
+
// stillUnresolved berisi LID. Match berdasarkan numeric prefix
|
|
1111
|
+
const itemNum = (item.id ?? '').split('@')[0].split(':')[0];
|
|
1112
|
+
const lidJid = stillUnresolved.find(l => {
|
|
1113
|
+
if (l === item.id) return true;
|
|
1114
|
+
const lNum = l.split('@')[0].split(':')[0];
|
|
1115
|
+
return itemNum && lNum && itemNum === lNum;
|
|
1116
|
+
});
|
|
1043
1117
|
if (lidJid && item.id && !item.id.endsWith('@lid')) {
|
|
1044
1118
|
resolveMap.set(lidJid, item.id);
|
|
1045
1119
|
mappings.push({ lid: lidJid, pn: item.id });
|
|
@@ -1055,6 +1129,34 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1055
1129
|
logger.debug({ err: e }, 'USync LID resolve failed, using cache only');
|
|
1056
1130
|
}
|
|
1057
1131
|
}
|
|
1132
|
+
// ── Text-based PN extraction ─────────────────────────────────────────────
|
|
1133
|
+
// WA kadang sudah resolve nomor HP di text (@6285133801810) tapi mentionedJid
|
|
1134
|
+
// masih berisi LID. Kita ekstrak semua @nomor dari text lalu matching ke LID
|
|
1135
|
+
// yang masih unresolved berdasarkan urutan kemunculan di mentionedJid.
|
|
1136
|
+
const textField = getTextField(msgContent);
|
|
1137
|
+
const stillUnresolvedAfterUSync = lidJids.filter(l => !resolveMap.has(l));
|
|
1138
|
+
if (stillUnresolvedAfterUSync.length > 0 && textField) {
|
|
1139
|
+
const rawText = textField.obj[textField.key] || '';
|
|
1140
|
+
// Ekstrak semua mention @nomor dari text (hanya angka, min 7 digit)
|
|
1141
|
+
const mentionMatches = [...rawText.matchAll(/@(\d{7,15})/g)].map(m => m[1]);
|
|
1142
|
+
if (mentionMatches.length > 0) {
|
|
1143
|
+
// Match LID ke nomor berdasarkan urutan index di mentionedJid
|
|
1144
|
+
const lidOrder = contextInfo.mentionedJid
|
|
1145
|
+
.map((jid, idx) => ({ jid, idx }))
|
|
1146
|
+
.filter(({ jid }) => stillUnresolvedAfterUSync.includes(jid));
|
|
1147
|
+
for (let i = 0; i < lidOrder.length && i < mentionMatches.length; i++) {
|
|
1148
|
+
const lidJid = lidOrder[i].jid;
|
|
1149
|
+
const phoneNum = mentionMatches[i];
|
|
1150
|
+
const pnJid = `${phoneNum}@s.whatsapp.net`;
|
|
1151
|
+
resolveMap.set(lidJid, pnJid);
|
|
1152
|
+
// Simpan mapping baru ini ke store untuk request berikutnya
|
|
1153
|
+
lidMapping.storeLIDPNMappings([{ lid: lidJid, pn: pnJid }]).catch(() => {});
|
|
1154
|
+
logger.debug({ lid: lidJid, pn: pnJid }, 'text-extracted PN for LID → PN');
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
// ── End text-based PN extraction ─────────────────────────────────────────
|
|
1159
|
+
|
|
1058
1160
|
contextInfo.mentionedJid = contextInfo.mentionedJid.map((jid) => {
|
|
1059
1161
|
if (!jid?.endsWith('@lid'))
|
|
1060
1162
|
return jid;
|
|
@@ -1063,13 +1165,14 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1063
1165
|
logger.debug({ lid: jid, pn: resolved }, 'resolved mentionedJid LID → PN');
|
|
1064
1166
|
return resolved;
|
|
1065
1167
|
}
|
|
1168
|
+
// Tetap kembalikan LID jika benar-benar tidak bisa di-resolve
|
|
1066
1169
|
return jid;
|
|
1067
1170
|
});
|
|
1068
|
-
const textField = getTextField(msgContent);
|
|
1069
1171
|
if (textField) {
|
|
1070
1172
|
let text = textField.obj[textField.key];
|
|
1173
|
+
// Replace LID number di text dengan PN number yang sudah di-resolve
|
|
1071
1174
|
for (const [lidJid, pnJid] of resolveMap) {
|
|
1072
|
-
const lidNum = lidJid.
|
|
1175
|
+
const lidNum = lidJid.split('@')[0].split(':')[0] ?? '';
|
|
1073
1176
|
const pnNum = pnJid.replace('@s.whatsapp.net', '').split(':')[0] ?? '';
|
|
1074
1177
|
if (lidNum && pnNum && text.includes(lidNum)) {
|
|
1075
1178
|
text = text.split(lidNum).join(pnNum);
|
|
@@ -1268,6 +1371,97 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1268
1371
|
}
|
|
1269
1372
|
cleanMessage(msg, authState.creds.me.id, authState.creds.me.lid);
|
|
1270
1373
|
await resolveMentionedLIDs(msg, signalRepository.lidMapping);
|
|
1374
|
+
// ── Post-resolve: fix semua LID yang tersisa di message object ────────
|
|
1375
|
+
// Setelah resolveMentionedLIDs:
|
|
1376
|
+
// 1. Fix text yang masih "@LIDnum" → "@PNnum"
|
|
1377
|
+
// 2. Fix contextInfo.participant yang masih @lid (sender quoted)
|
|
1378
|
+
// 3. Fix mentionedJid & text di dalam quotedMessage
|
|
1379
|
+
try {
|
|
1380
|
+
const _safeSet = (obj, key, val) => {
|
|
1381
|
+
try { obj[key] = val; } catch (_) {
|
|
1382
|
+
// proto readonly fallback
|
|
1383
|
+
const proto = Object.getPrototypeOf(obj);
|
|
1384
|
+
const fresh = Object.assign(Object.create(proto), obj, { [key]: val });
|
|
1385
|
+
// replace reference di parent jika bisa — caller handles
|
|
1386
|
+
return fresh;
|
|
1387
|
+
}
|
|
1388
|
+
return obj;
|
|
1389
|
+
};
|
|
1390
|
+
const _fixTextLid = (innerMsg, mentionedJids, msgTypeLabel) => {
|
|
1391
|
+
const pnNumbers = (mentionedJids || [])
|
|
1392
|
+
.filter(j => j && !j.endsWith('@lid'))
|
|
1393
|
+
.map(j => j.split('@')[0].split(':')[0])
|
|
1394
|
+
.filter(Boolean);
|
|
1395
|
+
if (!pnNumbers.length) return;
|
|
1396
|
+
for (const textKey of ['text', 'caption', 'conversation']) {
|
|
1397
|
+
const originalText = innerMsg[textKey];
|
|
1398
|
+
if (typeof originalText !== 'string') continue;
|
|
1399
|
+
const lidPattern = /@(\d{13,20})/g;
|
|
1400
|
+
let newText = originalText;
|
|
1401
|
+
let match;
|
|
1402
|
+
let pnIdx = 0;
|
|
1403
|
+
while ((match = lidPattern.exec(originalText)) !== null) {
|
|
1404
|
+
if (pnIdx >= pnNumbers.length) break;
|
|
1405
|
+
const lidNum = match[1];
|
|
1406
|
+
const pnNum = pnNumbers[pnIdx++];
|
|
1407
|
+
newText = newText.split(`@${lidNum}`).join(`@${pnNum}`);
|
|
1408
|
+
logger.debug({ lidNum, pnNum, type: msgTypeLabel, textKey }, 'post-resolve: replaced LID num in text');
|
|
1409
|
+
}
|
|
1410
|
+
if (newText !== originalText) _safeSet(innerMsg, textKey, newText);
|
|
1411
|
+
}
|
|
1412
|
+
};
|
|
1413
|
+
const msgObj = msg.message;
|
|
1414
|
+
if (msgObj) {
|
|
1415
|
+
for (const msgType of Object.keys(msgObj)) {
|
|
1416
|
+
const innerMsg = msgObj[msgType];
|
|
1417
|
+
if (!innerMsg || typeof innerMsg !== 'object') continue;
|
|
1418
|
+
const ctxInfo = innerMsg.contextInfo;
|
|
1419
|
+
if (!ctxInfo) continue;
|
|
1420
|
+
// 1. Fix text LID → PN (pesan utama)
|
|
1421
|
+
if (ctxInfo.mentionedJid?.length) {
|
|
1422
|
+
_fixTextLid(innerMsg, ctxInfo.mentionedJid, msgType);
|
|
1423
|
+
}
|
|
1424
|
+
// 2. Fix contextInfo.participant (sender quoted) jika masih LID
|
|
1425
|
+
if (ctxInfo.participant?.endsWith('@lid')) {
|
|
1426
|
+
try {
|
|
1427
|
+
const pn = await signalRepository.lidMapping.getPNForLID(ctxInfo.participant);
|
|
1428
|
+
if (pn) {
|
|
1429
|
+
_safeSet(ctxInfo, 'participant', pn);
|
|
1430
|
+
logger.debug({ lid: ctxInfo.participant, pn }, 'post-resolve: fixed contextInfo.participant LID');
|
|
1431
|
+
}
|
|
1432
|
+
} catch (_) {}
|
|
1433
|
+
}
|
|
1434
|
+
// 3. Fix mentionedJid & text di dalam quotedMessage
|
|
1435
|
+
if (ctxInfo.quotedMessage) {
|
|
1436
|
+
for (const qType of Object.keys(ctxInfo.quotedMessage)) {
|
|
1437
|
+
const qInner = ctxInfo.quotedMessage[qType];
|
|
1438
|
+
if (!qInner || typeof qInner !== 'object') continue;
|
|
1439
|
+
const qCtx = qInner.contextInfo;
|
|
1440
|
+
if (!qCtx?.mentionedJid?.length) continue;
|
|
1441
|
+
// Resolve LID di mentionedJid quoted
|
|
1442
|
+
const resolvedQ = await Promise.all(
|
|
1443
|
+
qCtx.mentionedJid.map(async (jid) => {
|
|
1444
|
+
if (!jid?.endsWith('@lid')) return jid;
|
|
1445
|
+
try {
|
|
1446
|
+
const pn = await signalRepository.lidMapping.getPNForLID(jid);
|
|
1447
|
+
return pn || jid;
|
|
1448
|
+
} catch (_) { return jid; }
|
|
1449
|
+
})
|
|
1450
|
+
);
|
|
1451
|
+
if (resolvedQ.some((j, i) => j !== qCtx.mentionedJid[i])) {
|
|
1452
|
+
_safeSet(qCtx, 'mentionedJid', resolvedQ);
|
|
1453
|
+
logger.debug({ resolvedQ }, 'post-resolve: fixed quotedMessage mentionedJid');
|
|
1454
|
+
}
|
|
1455
|
+
// Fix text LID → PN di quoted
|
|
1456
|
+
_fixTextLid(qInner, resolvedQ, `quoted.${qType}`);
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
} catch (lidFixErr) {
|
|
1462
|
+
logger.debug({ err: lidFixErr }, 'post-resolve LID fix failed (non-critical)');
|
|
1463
|
+
}
|
|
1464
|
+
// ── End post-resolve fix ──────────────────────────────────────────────
|
|
1271
1465
|
await upsertMessage(msg, node.attrs.offline ? 'append' : 'notify');
|
|
1272
1466
|
});
|
|
1273
1467
|
}
|