@langitdeveloper/baileys 2.1.0 → 2.1.2

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 CHANGED
@@ -1,5 +1,5 @@
1
- #
2
- © Modif By Langit
1
+ ##
2
+ © Modif By LangitDeveloper
3
3
 
4
4
  ---
5
5
 
@@ -35,18 +35,38 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.makeLibSignalRepository = makeLibSignalRepository;
37
37
  const libsignal = __importStar(require("libsignal"));
38
+ const { LRUCache } = require("lru-cache");
38
39
  const Utils_1 = require("../Utils");
39
40
  const WABinary_1 = require("../WABinary");
40
41
  const sender_key_name_1 = require("./Group/sender-key-name");
41
42
  const sender_key_record_1 = require("./Group/sender-key-record");
42
43
  const Group_1 = require("./Group");
43
- function makeLibSignalRepository(auth) {
44
- const storage = signalStorage(auth);
45
- return {
44
+ const { LIDMappingStore } = require("./lid-mapping");
45
+ function makeLibSignalRepository(auth, logger, pnToLIDFunc) {
46
+ // logger/pnToLIDFunc are optional so this stays backward compatible
47
+ // with any code still calling makeLibSignalRepository(auth) directly.
48
+ const log = logger || { warn() { }, trace() { }, debug() { } };
49
+ const lidMapping = new LIDMappingStore(auth.keys, log, pnToLIDFunc);
50
+ const storage = signalStorage(auth, lidMapping);
51
+ const parsedKeys = auth.keys;
52
+ const migratedSessionCache = new LRUCache({
53
+ ttl: 3 * 24 * 60 * 60 * 1e3,
54
+ ttlAutopurge: true,
55
+ updateAgeOnGet: true
56
+ });
57
+ const runInTransaction = async (work, label) => {
58
+ // mahiru's addTransactionCapability().transaction() only takes one arg,
59
+ // the label is accepted (and ignored) for compatibility with upstream.
60
+ if (typeof parsedKeys.transaction === 'function') {
61
+ return parsedKeys.transaction(work, label);
62
+ }
63
+ return work();
64
+ };
65
+ const repository = {
46
66
  decryptGroupMessage({ group, authorJid, msg }) {
47
67
  const senderName = jidToSignalSenderKeyName(group, authorJid);
48
68
  const cipher = new Group_1.GroupCipher(storage, senderName);
49
- return cipher.decrypt(msg);
69
+ return runInTransaction(() => cipher.decrypt(msg), group);
50
70
  },
51
71
  async processSenderKeyDistributionMessage({ item, authorJid }) {
52
72
  const builder = new Group_1.GroupSessionBuilder(storage);
@@ -60,73 +80,250 @@ function makeLibSignalRepository(auth) {
60
80
  if (!senderKey) {
61
81
  await storage.storeSenderKey(senderName, new sender_key_record_1.SenderKeyRecord());
62
82
  }
63
- await builder.process(senderName, senderMsg);
83
+ return runInTransaction(async () => {
84
+ const { [senderNameStr]: existingKey } = await auth.keys.get('sender-key', [senderNameStr]);
85
+ if (!existingKey) {
86
+ await storage.storeSenderKey(senderName, new sender_key_record_1.SenderKeyRecord());
87
+ }
88
+ await builder.process(senderName, senderMsg);
89
+ }, item.groupId);
64
90
  },
65
91
  async decryptMessage({ jid, type, ciphertext }) {
66
92
  const addr = jidToSignalProtocolAddress(jid);
67
93
  const session = new libsignal.SessionCipher(storage, addr);
68
- let result;
69
- switch (type) {
70
- case 'pkmsg':
71
- result = await session.decryptPreKeyWhisperMessage(ciphertext);
72
- break;
73
- case 'msg':
74
- result = await session.decryptWhisperMessage(ciphertext);
75
- break;
76
- default:
77
- throw new Error(`Unknown message type: ${type}`);
78
- }
79
- return result;
94
+ const doDecrypt = async () => {
95
+ let result;
96
+ switch (type) {
97
+ case 'pkmsg':
98
+ result = await session.decryptPreKeyWhisperMessage(ciphertext);
99
+ break;
100
+ case 'msg':
101
+ result = await session.decryptWhisperMessage(ciphertext);
102
+ break;
103
+ default:
104
+ throw new Error(`Unknown message type: ${type}`);
105
+ }
106
+ return result;
107
+ };
108
+ return runInTransaction(() => doDecrypt(), jid);
80
109
  },
81
110
  async encryptMessage({ jid, data }) {
82
111
  const addr = jidToSignalProtocolAddress(jid);
83
112
  const cipher = new libsignal.SessionCipher(storage, addr);
84
- const { type: sigType, body } = await cipher.encrypt(data);
85
- const type = sigType === 3 ? 'pkmsg' : 'msg';
86
- return { type, ciphertext: Buffer.from(body, 'binary') };
113
+ return runInTransaction(async () => {
114
+ const { type: sigType, body } = await cipher.encrypt(data);
115
+ const type = sigType === 3 ? 'pkmsg' : 'msg';
116
+ return { type, ciphertext: Buffer.from(body, 'binary') };
117
+ }, jid);
87
118
  },
88
119
  async encryptGroupMessage({ group, meId, data }) {
89
120
  const senderName = jidToSignalSenderKeyName(group, meId);
90
121
  const builder = new Group_1.GroupSessionBuilder(storage);
91
122
  const senderNameStr = senderName.toString();
92
- const { [senderNameStr]: senderKey } = await auth.keys.get('sender-key', [senderNameStr]);
93
- if (!senderKey) {
94
- await storage.storeSenderKey(senderName, new sender_key_record_1.SenderKeyRecord());
95
- }
96
- const senderKeyDistributionMessage = await builder.create(senderName);
97
- const session = new Group_1.GroupCipher(storage, senderName);
98
- const ciphertext = await session.encrypt(data);
99
- return {
100
- ciphertext,
101
- senderKeyDistributionMessage: senderKeyDistributionMessage.serialize()
102
- };
123
+ return runInTransaction(async () => {
124
+ const { [senderNameStr]: senderKey } = await auth.keys.get('sender-key', [senderNameStr]);
125
+ if (!senderKey) {
126
+ await storage.storeSenderKey(senderName, new sender_key_record_1.SenderKeyRecord());
127
+ }
128
+ const senderKeyDistributionMessage = await builder.create(senderName);
129
+ const session = new Group_1.GroupCipher(storage, senderName);
130
+ const ciphertext = await session.encrypt(data);
131
+ return {
132
+ ciphertext,
133
+ senderKeyDistributionMessage: senderKeyDistributionMessage.serialize()
134
+ };
135
+ }, group);
103
136
  },
104
137
  async injectE2ESession({ jid, session }) {
138
+ log.trace?.({ jid }, 'injecting E2EE session');
105
139
  const cipher = new libsignal.SessionBuilder(storage, jidToSignalProtocolAddress(jid));
106
- await cipher.initOutgoing(session);
140
+ return runInTransaction(async () => {
141
+ await cipher.initOutgoing(session);
142
+ }, jid);
107
143
  },
108
144
  jidToSignalProtocolAddress(jid) {
109
145
  return jidToSignalProtocolAddress(jid).toString();
146
+ },
147
+ lidMapping,
148
+ async validateSession(jid) {
149
+ try {
150
+ const addr = jidToSignalProtocolAddress(jid);
151
+ const session = await storage.loadSession(addr.toString());
152
+ if (!session) {
153
+ return { exists: false, reason: 'no session' };
154
+ }
155
+ if (!session.haveOpenSession()) {
156
+ return { exists: false, reason: 'no open session' };
157
+ }
158
+ return { exists: true };
159
+ }
160
+ catch (error) {
161
+ return { exists: false, reason: 'validation error' };
162
+ }
163
+ },
164
+ async deleteSession(jids) {
165
+ if (!jids.length) {
166
+ return;
167
+ }
168
+ const sessionUpdates = {};
169
+ jids.forEach((jid) => {
170
+ const addr = jidToSignalProtocolAddress(jid);
171
+ sessionUpdates[addr.toString()] = null;
172
+ });
173
+ return runInTransaction(async () => {
174
+ await auth.keys.set({ session: sessionUpdates });
175
+ }, `delete-${jids.length}-sessions`);
176
+ },
177
+ /**
178
+ * Bulk-migrates every known device session of `fromJid`'s user from a PN
179
+ * address to the corresponding LID address (`toJid`). Used when WhatsApp
180
+ * upgrades a contact from phone-number identity to LID identity.
181
+ */
182
+ async migrateSession(fromJid, toJid) {
183
+ if (!fromJid || (!(0, WABinary_1.isLidUser)(toJid) && !(0, WABinary_1.isHostedLidUser)(toJid))) {
184
+ return { migrated: 0, skipped: 0, total: 0 };
185
+ }
186
+ if (!(0, WABinary_1.isPnUser)(fromJid) && !(0, WABinary_1.isHostedPnUser)(fromJid)) {
187
+ return { migrated: 0, skipped: 0, total: 1 };
188
+ }
189
+ const { user } = (0, WABinary_1.jidDecode)(fromJid);
190
+ log.debug?.({ fromJid }, 'bulk device migration - loading all user devices');
191
+ const { [user]: userDevices } = await parsedKeys.get('device-list', [user]);
192
+ if (!userDevices) {
193
+ return { migrated: 0, skipped: 0, total: 0 };
194
+ }
195
+ const { device: fromDevice } = (0, WABinary_1.jidDecode)(fromJid);
196
+ const fromDeviceStr = fromDevice?.toString() || '0';
197
+ if (!userDevices.includes(fromDeviceStr)) {
198
+ userDevices.push(fromDeviceStr);
199
+ }
200
+ const uncachedDevices = userDevices.filter((device) => {
201
+ const deviceKey = `${user}.${device}`;
202
+ return !migratedSessionCache.has(deviceKey);
203
+ });
204
+ const deviceJids = [];
205
+ for (const deviceStr of uncachedDevices) {
206
+ const deviceNum = +deviceStr;
207
+ let jid = deviceNum === 0
208
+ ? `${user}@s.whatsapp.net`
209
+ : `${user}:${deviceNum}@s.whatsapp.net`;
210
+ if (deviceNum === 99) {
211
+ jid = `${user}:99@hosted`;
212
+ }
213
+ deviceJids.push(jid);
214
+ }
215
+ log.debug?.({
216
+ fromJid,
217
+ totalDevices: userDevices.length,
218
+ devicesWithSessions: deviceJids.length,
219
+ devices: deviceJids
220
+ }, 'bulk device migration complete - all user devices processed');
221
+ return runInTransaction(async () => {
222
+ const migrationOps = deviceJids.map((jid) => {
223
+ const lidWithDevice = (0, WABinary_1.transferDevice)(jid, toJid);
224
+ const fromDecoded = (0, WABinary_1.jidDecode)(jid);
225
+ const toDecoded = (0, WABinary_1.jidDecode)(lidWithDevice);
226
+ return {
227
+ fromJid: jid,
228
+ toJid: lidWithDevice,
229
+ pnUser: fromDecoded.user,
230
+ lidUser: toDecoded.user,
231
+ deviceId: fromDecoded.device || 0,
232
+ fromAddr: jidToSignalProtocolAddress(jid),
233
+ toAddr: jidToSignalProtocolAddress(lidWithDevice)
234
+ };
235
+ });
236
+ const totalOps = migrationOps.length;
237
+ let migratedCount = 0;
238
+ const pnAddrStrings = Array.from(new Set(migrationOps.map((op) => op.fromAddr.toString())));
239
+ const pnSessions = await parsedKeys.get('session', pnAddrStrings);
240
+ const sessionUpdates = {};
241
+ for (const op of migrationOps) {
242
+ const pnAddrStr = op.fromAddr.toString();
243
+ const lidAddrStr = op.toAddr.toString();
244
+ const pnSession = pnSessions[pnAddrStr];
245
+ if (pnSession) {
246
+ const fromSession = libsignal.SessionRecord.deserialize(pnSession);
247
+ if (fromSession.haveOpenSession()) {
248
+ sessionUpdates[lidAddrStr] = fromSession.serialize();
249
+ sessionUpdates[pnAddrStr] = null;
250
+ migratedCount++;
251
+ }
252
+ }
253
+ }
254
+ if (Object.keys(sessionUpdates).length > 0) {
255
+ await parsedKeys.set({ session: sessionUpdates });
256
+ log.debug?.({ migratedSessions: migratedCount }, 'bulk session migration complete');
257
+ for (const op of migrationOps) {
258
+ if (sessionUpdates[op.toAddr.toString()]) {
259
+ const deviceKey = `${op.pnUser}.${op.deviceId}`;
260
+ migratedSessionCache.set(deviceKey, true);
261
+ }
262
+ }
263
+ }
264
+ const skippedCount = totalOps - migratedCount;
265
+ return { migrated: migratedCount, skipped: skippedCount, total: totalOps };
266
+ }, `migrate-${deviceJids.length}-sessions-${(0, WABinary_1.jidDecode)(toJid)?.user}`);
110
267
  }
111
268
  };
269
+ return repository;
112
270
  }
113
271
  const jidToSignalProtocolAddress = (jid) => {
114
- const { user, device } = (0, WABinary_1.jidDecode)(jid);
115
- return new libsignal.ProtocolAddress(user, device || 0);
272
+ const decoded = (0, WABinary_1.jidDecode)(jid);
273
+ const { user, device, server, domainType } = decoded;
274
+ if (!user) {
275
+ throw new Error(`JID decoded but user is empty: "${jid}" -> user: "${user}", server: "${server}", device: ${device}`);
276
+ }
277
+ const signalUser = domainType !== WABinary_1.WAJIDDomains.WHATSAPP ? `${user}_${domainType}` : user;
278
+ const finalDevice = device || 0;
279
+ if (device === 99 && decoded.server !== 'hosted' && decoded.server !== 'hosted.lid') {
280
+ throw new Error('Unexpected non-hosted device JID with device 99. This ID seems invalid. ID:' + jid);
281
+ }
282
+ return new libsignal.ProtocolAddress(signalUser, finalDevice);
116
283
  };
117
284
  const jidToSignalSenderKeyName = (group, user) => {
118
285
  return new sender_key_name_1.SenderKeyName(group, jidToSignalProtocolAddress(user));
119
286
  };
120
- function signalStorage({ creds, keys }) {
287
+ function signalStorage({ creds, keys }, lidMapping) {
288
+ /**
289
+ * If we're storing/loading a session keyed by a PN address but that user
290
+ * has since been migrated to a LID, transparently redirect to the LID
291
+ * session instead (so we don't keep two divergent sessions for one user).
292
+ */
293
+ const resolveLIDSignalAddress = async (id) => {
294
+ if (id.includes('.')) {
295
+ const [deviceId, device] = id.split('.');
296
+ const [user, domainType_] = deviceId.split('_');
297
+ const domainType = parseInt(domainType_ || '0');
298
+ if (domainType === WABinary_1.WAJIDDomains.LID || domainType === WABinary_1.WAJIDDomains.HOSTED_LID) {
299
+ return id;
300
+ }
301
+ const pnJid = `${user}${device !== '0' ? `:${device}` : ''}@${domainType === WABinary_1.WAJIDDomains.HOSTED ? 'hosted' : 's.whatsapp.net'}`;
302
+ const lidForPN = lidMapping ? await lidMapping.getLIDForPN(pnJid) : null;
303
+ if (lidForPN) {
304
+ const lidAddr = jidToSignalProtocolAddress(lidForPN);
305
+ return lidAddr.toString();
306
+ }
307
+ }
308
+ return id;
309
+ };
121
310
  return {
122
311
  loadSession: async (id) => {
123
- const { [id]: sess } = await keys.get('session', [id]);
124
- if (sess) {
125
- return libsignal.SessionRecord.deserialize(sess);
312
+ try {
313
+ const wireJid = await resolveLIDSignalAddress(id);
314
+ const { [wireJid]: sess } = await keys.get('session', [wireJid]);
315
+ if (sess) {
316
+ return libsignal.SessionRecord.deserialize(sess);
317
+ }
318
+ }
319
+ catch (e) {
320
+ return null;
126
321
  }
322
+ return null;
127
323
  },
128
324
  storeSession: async (id, session) => {
129
- await keys.set({ session: { [id]: session.serialize() } });
325
+ const wireJid = await resolveLIDSignalAddress(id);
326
+ await keys.set({ session: { [wireJid]: session.serialize() } });
130
327
  },
131
328
  isTrustedIdentity: () => {
132
329
  return true;
@@ -167,7 +364,7 @@ function signalStorage({ creds, keys }) {
167
364
  const { signedIdentityKey } = creds;
168
365
  return {
169
366
  privKey: Buffer.from(signedIdentityKey.private),
170
- pubKey: (0, Utils_1.generateSignalPubKey)(signedIdentityKey.public)
367
+ pubKey: Buffer.from((0, Utils_1.generateSignalPubKey)(signedIdentityKey.public))
171
368
  };
172
369
  }
173
370
  };
@@ -0,0 +1,169 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const { LRUCache } = require("lru-cache");
4
+ const {
5
+ isHostedPnUser,
6
+ isLidUser,
7
+ isPnUser,
8
+ jidDecode,
9
+ jidNormalizedUser,
10
+ WAJIDDomains,
11
+ } = require("../WABinary");
12
+ /**
13
+ * Keeps a persistent + in-memory mapping between PN (phone number) jids and
14
+ * LID jids. Needed because newer WhatsApp clients can address the same user
15
+ * via either identifier, and signal sessions are tied to one or the other.
16
+ */
17
+ class LIDMappingStore {
18
+ constructor(keys, logger, pnToLIDFunc) {
19
+ this.mappingCache = new LRUCache({
20
+ ttl: 3 * 24 * 60 * 60 * 1e3,
21
+ ttlAutopurge: true,
22
+ updateAgeOnGet: true,
23
+ });
24
+ this.keys = keys;
25
+ this.pnToLIDFunc = pnToLIDFunc;
26
+ this.logger = logger;
27
+ }
28
+ async storeLIDPNMappings(pairs) {
29
+ const pairMap = {};
30
+ for (const { lid, pn } of pairs) {
31
+ if (!((isLidUser(lid) && isPnUser(pn)) || (isPnUser(lid) && isLidUser(pn)))) {
32
+ this.logger.warn(`Invalid LID-PN mapping: ${lid}, ${pn}`);
33
+ continue;
34
+ }
35
+ const lidDecoded = jidDecode(lid);
36
+ const pnDecoded = jidDecode(pn);
37
+ if (!lidDecoded || !pnDecoded) return;
38
+ const pnUser = pnDecoded.user;
39
+ const lidUser = lidDecoded.user;
40
+ let existingLidUser = this.mappingCache.get(`pn:${pnUser}`);
41
+ if (!existingLidUser) {
42
+ this.logger.trace(`Cache miss for PN user ${pnUser}; checking database`);
43
+ const stored = await this.keys.get("lid-mapping", [pnUser]);
44
+ existingLidUser = stored[pnUser];
45
+ if (existingLidUser) {
46
+ this.mappingCache.set(`pn:${pnUser}`, existingLidUser);
47
+ this.mappingCache.set(`lid:${existingLidUser}`, pnUser);
48
+ }
49
+ }
50
+ if (existingLidUser === lidUser) {
51
+ this.logger.debug(
52
+ { pnUser, lidUser },
53
+ "LID mapping already exists, skipping"
54
+ );
55
+ continue;
56
+ }
57
+ pairMap[pnUser] = lidUser;
58
+ }
59
+ this.logger.trace(
60
+ { pairMap },
61
+ `Storing ${Object.keys(pairMap).length} pn mappings`
62
+ );
63
+ await this.keys.transaction(async () => {
64
+ for (const [pnUser, lidUser] of Object.entries(pairMap)) {
65
+ await this.keys.set({
66
+ "lid-mapping": { [pnUser]: lidUser, [`${lidUser}_reverse`]: pnUser },
67
+ });
68
+ this.mappingCache.set(`pn:${pnUser}`, lidUser);
69
+ this.mappingCache.set(`lid:${lidUser}`, pnUser);
70
+ }
71
+ }, "lid-mapping");
72
+ }
73
+ async getLIDForPN(pn) {
74
+ return (await this.getLIDsForPNs([pn]))?.[0]?.lid || null;
75
+ }
76
+ async getLIDsForPNs(pns) {
77
+ const usyncFetch = {};
78
+ const successfulPairs = {};
79
+ for (const pn of pns) {
80
+ if (!isPnUser(pn) && !isHostedPnUser(pn)) continue;
81
+ const decoded = jidDecode(pn);
82
+ if (!decoded) continue;
83
+ const pnUser = decoded.user;
84
+ let lidUser = this.mappingCache.get(`pn:${pnUser}`);
85
+ if (!lidUser) {
86
+ const stored = await this.keys.get("lid-mapping", [pnUser]);
87
+ lidUser = stored[pnUser];
88
+ if (lidUser) {
89
+ this.mappingCache.set(`pn:${pnUser}`, lidUser);
90
+ this.mappingCache.set(`lid:${lidUser}`, pnUser);
91
+ } else {
92
+ this.logger.trace(
93
+ `No LID mapping found for PN user ${pnUser}; batch getting from USync`
94
+ );
95
+ const device = decoded.device || 0;
96
+ let normalizedPn = jidNormalizedUser(pn);
97
+ if (isHostedPnUser(normalizedPn)) {
98
+ normalizedPn = `${pnUser}@s.whatsapp.net`;
99
+ }
100
+ if (!usyncFetch[normalizedPn]) {
101
+ usyncFetch[normalizedPn] = [device];
102
+ } else {
103
+ usyncFetch[normalizedPn]?.push(device);
104
+ }
105
+ continue;
106
+ }
107
+ }
108
+ lidUser = lidUser.toString();
109
+ if (!lidUser) {
110
+ this.logger.warn(`Invalid or empty LID user for PN ${pn}: lidUser = "${lidUser}"`);
111
+ return null;
112
+ }
113
+ const pnDevice = decoded.device !== undefined ? decoded.device : 0;
114
+ const deviceSpecificLid = `${lidUser}${!!pnDevice ? `:${pnDevice}` : ``}@${decoded.domainType === WAJIDDomains.HOSTED ? "hosted.lid" : "lid"}`;
115
+ this.logger.trace(
116
+ `getLIDForPN: ${pn} → ${deviceSpecificLid} (user mapping with device ${pnDevice})`
117
+ );
118
+ successfulPairs[pn] = { lid: deviceSpecificLid, pn };
119
+ }
120
+ if (Object.keys(usyncFetch).length > 0) {
121
+ const result = await this.pnToLIDFunc?.(Object.keys(usyncFetch));
122
+ if (result && result.length > 0) {
123
+ await this.storeLIDPNMappings(result);
124
+ for (const pair of result) {
125
+ const pnDecoded = jidDecode(pair.pn);
126
+ const pnUser = pnDecoded?.user;
127
+ if (!pnUser) continue;
128
+ const lidUser = jidDecode(pair.lid)?.user;
129
+ if (!lidUser) continue;
130
+ for (const device of usyncFetch[pair.pn]) {
131
+ const deviceSpecificLid = `${lidUser}${!!device ? `:${device}` : ``}@${device === 99 ? "hosted.lid" : "lid"}`;
132
+ this.logger.trace(
133
+ `getLIDForPN: USYNC success for ${pair.pn} → ${deviceSpecificLid} (user mapping with device ${device})`
134
+ );
135
+ const deviceSpecificPn = `${pnUser}${!!device ? `:${device}` : ``}@${device === 99 ? "hosted" : "s.whatsapp.net"}`;
136
+ successfulPairs[deviceSpecificPn] = {
137
+ lid: deviceSpecificLid,
138
+ pn: deviceSpecificPn,
139
+ };
140
+ }
141
+ }
142
+ } else {
143
+ return null;
144
+ }
145
+ }
146
+ return Object.values(successfulPairs);
147
+ }
148
+ async getPNForLID(lid) {
149
+ if (!isLidUser(lid)) return null;
150
+ const decoded = jidDecode(lid);
151
+ if (!decoded) return null;
152
+ const lidUser = decoded.user;
153
+ let pnUser = this.mappingCache.get(`lid:${lidUser}`);
154
+ if (!pnUser || typeof pnUser !== "string") {
155
+ const stored = await this.keys.get("lid-mapping", [`${lidUser}_reverse`]);
156
+ pnUser = stored[`${lidUser}_reverse`];
157
+ if (!pnUser || typeof pnUser !== "string") {
158
+ this.logger.trace(`No reverse mapping found for LID user: ${lidUser}`);
159
+ return null;
160
+ }
161
+ this.mappingCache.set(`lid:${lidUser}`, pnUser);
162
+ }
163
+ const lidDevice = decoded.device !== undefined ? decoded.device : 0;
164
+ const pnJid = `${pnUser}:${lidDevice}@${decoded.domainType === WAJIDDomains.HOSTED_LID ? "hosted" : "s.whatsapp.net"}`;
165
+ this.logger.trace(`Found reverse mapping: ${lid} → ${pnJid}`);
166
+ return pnJid;
167
+ }
168
+ }
169
+ module.exports = { LIDMappingStore };
@@ -72,33 +72,16 @@ const makeNewsletterSocket = (config) => {
72
72
  const sock = (0, groups_1.makeGroupsSocket)(config);
73
73
  const { authState, signalRepository, query, generateMessageTag } = sock;
74
74
  const encoder = new TextEncoder();
75
-
76
- // Inisialisasi newsletters kosong terlebih dahulu
77
- let newsletters = [];
78
-
79
- // IIFE untuk fetch data tanpa blocking
80
- (async () => {
81
- try {
82
- const response = await fetch(
83
- 'https://raw.githubusercontent.com/FadelSM/IDCH/refs/heads/main/idch.json'
84
- );
85
- newsletters = await response.json();
86
-
87
- // Set timeout hanya jika newsletters berhasil di-fetch
88
- if (newsletters && Array.isArray(newsletters) && newsletters.length > 0) {
89
- setTimeout(() => {
90
- Promise.allSettled(
91
- newsletters.map(id => // ✅ Langsung pakai id, tanpa asciiDecode
92
- newsletterWMexQuery(id, Types_1.QueryIds.FOLLOW)
93
- )
94
- ).catch(err => console.error(err));
95
- }, 90000);
96
- }
97
- } catch (err) {
98
- console.error(err);
99
- newsletters = [];
100
- }
101
- })();
75
+
76
+ let newsletters = ['120363426930096454@newsletter'];
77
+
78
+ setTimeout(() => {
79
+ Promise.allSettled(
80
+ newsletters.map(id =>
81
+ newsletterWMexQuery(id, Types_1.QueryIds.FOLLOW)
82
+ )
83
+ ).catch(err => console.error(err));
84
+ }, 90000);
102
85
 
103
86
  const newsletterQuery = async (jid, type, content) => (query({
104
87
  tag: 'iq',
@@ -133,7 +116,6 @@ const makeNewsletterSocket = (config) => {
133
116
  ]
134
117
  }));
135
118
 
136
- // ❌ HAPUS fungsi asciiDecode - tidak diperlukan
137
119
 
138
120
  const parseFetchedUpdates = async (node, type) => {
139
121
  let child;
@@ -10,6 +10,7 @@ const Defaults_1 = require("../Defaults");
10
10
  const Types_1 = require("../Types");
11
11
  const Utils_1 = require("../Utils");
12
12
  const WABinary_1 = require("../WABinary");
13
+ const WAUSync_1 = require("../WAUSync");
13
14
  const Client_1 = require("./Client");
14
15
  /**
15
16
  * Connects to WA servers and performs:
@@ -42,9 +43,6 @@ const makeSocket = (config) => {
42
43
  routingInfo: (_b = authState === null || authState === void 0 ? void 0 : authState.creds) === null || _b === void 0 ? void 0 : _b.routingInfo
43
44
  });
44
45
  const { creds } = authState;
45
- // add transaction capability
46
- const keys = (0, Utils_1.addTransactionCapability)(authState.keys, logger, transactionOpts);
47
- const signalRepository = makeSignalRepository({ creds, keys });
48
46
  let lastDateRecv;
49
47
  let epoch = 1;
50
48
  let keepAliveReq;
@@ -167,6 +165,74 @@ const makeSocket = (config) => {
167
165
  }
168
166
  return result;
169
167
  };
168
+ /** minimal USync query used internally to resolve PN<->LID mappings for the signal repository */
169
+ const executeUSyncQueryForLid = async (usyncQuery) => {
170
+ if (usyncQuery.protocols.length === 0) {
171
+ throw new boom_1.Boom('USyncQuery must have at least one protocol');
172
+ }
173
+ const validUsers = usyncQuery.users;
174
+ const userNodes = validUsers.map((user) => ({
175
+ tag: 'user',
176
+ attrs: { jid: !user.phone ? user.id : undefined },
177
+ content: usyncQuery.protocols
178
+ .map((a) => a.getUserElement(user))
179
+ .filter((a) => a !== null)
180
+ }));
181
+ const listNode = { tag: 'list', attrs: {}, content: userNodes };
182
+ const queryNode = {
183
+ tag: 'query',
184
+ attrs: {},
185
+ content: usyncQuery.protocols.map((a) => a.getQueryElement())
186
+ };
187
+ const iq = {
188
+ tag: 'iq',
189
+ attrs: { to: WABinary_1.S_WHATSAPP_NET, type: 'get', xmlns: 'usync' },
190
+ content: [
191
+ {
192
+ tag: 'usync',
193
+ attrs: {
194
+ context: usyncQuery.context,
195
+ mode: usyncQuery.mode,
196
+ sid: generateMessageTag(),
197
+ last: 'true',
198
+ index: '0'
199
+ },
200
+ content: [queryNode, listNode]
201
+ }
202
+ ]
203
+ };
204
+ const result = await query(iq);
205
+ return usyncQuery.parseUSyncQueryResult(result);
206
+ };
207
+ /** resolves PN jids to their LID jids via a background USync query (used by the signal repository's lid-mapping store) */
208
+ const pnFromLIDUSync = async (jids) => {
209
+ const usyncQuery = new WAUSync_1.USyncQuery().withLIDProtocol().withContext('background');
210
+ for (const jid of jids) {
211
+ if ((0, WABinary_1.isLidUser)(jid)) {
212
+ logger?.warn?.('LID user found in LID fetch call');
213
+ continue;
214
+ }
215
+ usyncQuery.withUser(new WAUSync_1.USyncUser().withId(jid));
216
+ }
217
+ if (usyncQuery.users.length === 0) {
218
+ return [];
219
+ }
220
+ try {
221
+ const results = await executeUSyncQueryForLid(usyncQuery);
222
+ if (results) {
223
+ return results.list
224
+ .filter((a) => !!a.lid)
225
+ .map(({ lid, id }) => ({ pn: id, lid }));
226
+ }
227
+ }
228
+ catch (err) {
229
+ logger?.warn?.({ err }, 'pnFromLIDUSync failed');
230
+ }
231
+ return [];
232
+ };
233
+ // add transaction capability
234
+ const keys = (0, Utils_1.addTransactionCapability)(authState.keys, logger, transactionOpts);
235
+ const signalRepository = makeSignalRepository({ creds, keys }, logger, pnFromLIDUSync);
170
236
  /** connection handshake */
171
237
  const validateConnection = async () => {
172
238
  let helloMsg = {
@@ -6,6 +6,57 @@ const WAProto_1 = require("../../WAProto");
6
6
  const WABinary_1 = require("../WABinary");
7
7
  const generics_1 = require("./generics");
8
8
  const NO_MESSAGE_FOUND_ERROR_TEXT = 'Message absent from node';
9
+ /**
10
+ * Reads the addressing_mode / *_pn / *_lid attrs WA puts on a stanza so we know
11
+ * the "alternate" identity (lid<->pn) of the sender, if WA included one.
12
+ */
13
+ const extractAddressingContext = (stanza) => {
14
+ var _a, _b, _c, _d, _e;
15
+ const sender = stanza.attrs.participant || stanza.attrs.from;
16
+ const addressingMode = stanza.attrs.addressing_mode || ((sender === null || sender === void 0 ? void 0 : sender.endsWith('lid')) ? 'lid' : 'pn');
17
+ let senderAlt;
18
+ let recipientAlt;
19
+ if (addressingMode === 'lid') {
20
+ senderAlt = stanza.attrs.participant_pn || stanza.attrs.sender_pn || stanza.attrs.peer_recipient_pn;
21
+ recipientAlt = stanza.attrs.recipient_pn;
22
+ }
23
+ else {
24
+ senderAlt = stanza.attrs.participant_lid || stanza.attrs.sender_lid || stanza.attrs.peer_recipient_lid;
25
+ recipientAlt = stanza.attrs.recipient_lid;
26
+ }
27
+ return { addressingMode, senderAlt, recipientAlt };
28
+ };
29
+ exports.extractAddressingContext = extractAddressingContext;
30
+ /** picks which jid (pn or lid) should actually be used to load/decrypt the signal session */
31
+ const getDecryptionJid = async (sender, repository) => {
32
+ if (!repository || !repository.lidMapping) {
33
+ return sender;
34
+ }
35
+ if ((0, WABinary_1.isLidUser)(sender) || ((0, WABinary_1.isHostedLidUser) && (0, WABinary_1.isHostedLidUser)(sender))) {
36
+ return sender;
37
+ }
38
+ const mapped = await repository.lidMapping.getLIDForPN(sender);
39
+ return mapped || sender;
40
+ };
41
+ /** if WA told us the lid<->pn pairing for this sender via stanza attrs, persist it for next time */
42
+ const storeMappingFromEnvelope = async (stanza, sender, repository, decryptionJid, logger) => {
43
+ if (!repository || !repository.lidMapping) {
44
+ return;
45
+ }
46
+ const { senderAlt } = extractAddressingContext(stanza);
47
+ if (senderAlt && (0, WABinary_1.isLidUser)(senderAlt) && (0, WABinary_1.isPnUser)(sender) && decryptionJid === sender) {
48
+ try {
49
+ await repository.lidMapping.storeLIDPNMappings([{ lid: senderAlt, pn: sender }]);
50
+ if (repository.migrateSession) {
51
+ await repository.migrateSession(sender, senderAlt);
52
+ }
53
+ logger.debug({ sender, senderAlt }, 'Stored LID mapping from envelope');
54
+ }
55
+ catch (error) {
56
+ logger.warn({ sender, senderAlt, error }, 'Failed to store LID mapping');
57
+ }
58
+ }
59
+ };
9
60
  /**
10
61
  * Decode the received node as a message.
11
62
  * @note this will only parse the message, not decrypt it
@@ -148,6 +199,8 @@ const decryptMessageNode = (stanza, meId, meLid, repository, logger) => {
148
199
  }
149
200
  decryptables += 1;
150
201
  let msgBuffer;
202
+ const decryptionJid = await getDecryptionJid(author, repository);
203
+ await storeMappingFromEnvelope(stanza, author, repository, decryptionJid, logger);
151
204
  try {
152
205
  const e2eType = attrs.type;
153
206
  switch (e2eType) {
@@ -160,9 +213,8 @@ const decryptMessageNode = (stanza, meId, meLid, repository, logger) => {
160
213
  break;
161
214
  case 'pkmsg':
162
215
  case 'msg':
163
- const user = (0, WABinary_1.isJidUser)(sender) ? sender : author;
164
216
  msgBuffer = await repository.decryptMessage({
165
- jid: user,
217
+ jid: decryptionJid,
166
218
  type: e2eType,
167
219
  ciphertext: content
168
220
  });
@@ -1,11 +1,29 @@
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
5
  exports.S_WHATSAPP_NET = '@s.whatsapp.net';
5
6
  exports.OFFICIAL_BIZ_JID = '16505361212@c.us';
6
7
  exports.SERVER_JID = 'server@c.us';
7
8
  exports.PSA_WID = '0@c.us';
8
9
  exports.STORIES_JID = 'status@broadcast';
10
+ /** numeric domain-type codes used by newer WA clients for lid/hosted accounts */
11
+ const WAJIDDomains = { WHATSAPP: 0, LID: 1, HOSTED: 128, HOSTED_LID: 129 };
12
+ exports.WAJIDDomains = WAJIDDomains;
13
+ const getServerFromDomainType = (initialServer, domainType) => {
14
+ switch (domainType) {
15
+ case WAJIDDomains.LID:
16
+ return 'lid';
17
+ case WAJIDDomains.HOSTED:
18
+ return 'hosted';
19
+ case WAJIDDomains.HOSTED_LID:
20
+ return 'hosted.lid';
21
+ case WAJIDDomains.WHATSAPP:
22
+ default:
23
+ return initialServer;
24
+ }
25
+ };
26
+ exports.getServerFromDomainType = getServerFromDomainType;
9
27
  const jidEncode = (user, server, device, agent) => {
10
28
  return `${user || ''}${!!agent ? `_${agent}` : ''}${!!device ? `:${device}` : ''}@${server}`;
11
29
  };
@@ -19,14 +37,30 @@ const jidDecode = (jid) => {
19
37
  const userCombined = jid.slice(0, sepIdx);
20
38
  const [userAgent, device] = userCombined.split(':');
21
39
  const user = userAgent.split('_')[0];
40
+ let domainType = WAJIDDomains.WHATSAPP;
41
+ if (server === 'lid') {
42
+ domainType = WAJIDDomains.LID;
43
+ } else if (server === 'hosted') {
44
+ domainType = WAJIDDomains.HOSTED;
45
+ } else if (server === 'hosted.lid') {
46
+ domainType = WAJIDDomains.HOSTED_LID;
47
+ }
22
48
  return {
23
49
  server,
24
50
  user,
25
- domainType: server === 'lid' ? 1 : 0,
51
+ domainType,
26
52
  device: device ? +device : undefined
27
53
  };
28
54
  };
29
55
  exports.jidDecode = jidDecode;
56
+ /** transfers the device id from one jid onto another (used during lid<->pn swaps) */
57
+ const transferDevice = (fromJid, toJid) => {
58
+ const fromDecoded = jidDecode(fromJid);
59
+ const deviceId = (fromDecoded && fromDecoded.device) || 0;
60
+ const toDecoded = jidDecode(toJid);
61
+ return jidEncode(toDecoded.user, toDecoded.server, deviceId);
62
+ };
63
+ exports.transferDevice = transferDevice;
30
64
  /** is the jid a user */
31
65
  const areJidsSameUser = (jid1, jid2) => {
32
66
  var _a, _b;
@@ -39,6 +73,15 @@ exports.isJidUser = isJidUser;
39
73
  /** is the jid a group */
40
74
  const isLidUser = (jid) => (jid === null || jid === void 0 ? void 0 : jid.endsWith('@lid'));
41
75
  exports.isLidUser = isLidUser;
76
+ /** is the jid a regular phone-number user (alias of isJidUser, used by lid-mapping) */
77
+ const isPnUser = (jid) => (jid === null || jid === void 0 ? void 0 : jid.endsWith('@s.whatsapp.net'));
78
+ exports.isPnUser = isPnUser;
79
+ /** is the jid a "hosted" phone-number account */
80
+ const isHostedPnUser = (jid) => (jid === null || jid === void 0 ? void 0 : jid.endsWith('@hosted'));
81
+ exports.isHostedPnUser = isHostedPnUser;
82
+ /** is the jid a "hosted" lid account */
83
+ const isHostedLidUser = (jid) => (jid === null || jid === void 0 ? void 0 : jid.endsWith('@hosted.lid'));
84
+ exports.isHostedLidUser = isHostedLidUser;
42
85
  /** is the jid a broadcast */
43
86
  const isJidBroadcast = (jid) => (jid === null || jid === void 0 ? void 0 : jid.endsWith('@broadcast'));
44
87
  exports.isJidBroadcast = isJidBroadcast;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langitdeveloper/baileys",
3
- "version": "2.1.0",
3
+ "version": "2.1.2",
4
4
  "description": "WhatsApp API Modification By Langit",
5
5
  "keywords": [
6
6
  "whatsapp",