@kelvdra/baileys 1.0.4 → 1.0.5-rc.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.
Files changed (46) hide show
  1. package/LICENSE +21 -0
  2. package/WAProto/index.js +65472 -137440
  3. package/lib/Defaults/index.d.ts +1 -1
  4. package/lib/Defaults/index.js +22 -3
  5. package/lib/Socket/chats.js +12 -13
  6. package/lib/Socket/groups.js +321 -18
  7. package/lib/Socket/hydra.js +521 -0
  8. package/lib/Socket/messages-recv.js +736 -324
  9. package/lib/Socket/messages-send.js +526 -113
  10. package/lib/Socket/mex.js +44 -6
  11. package/lib/Socket/newsletter.d.ts +16 -9
  12. package/lib/Socket/newsletter.js +259 -70
  13. package/lib/Types/GroupMetadata.d.ts +16 -0
  14. package/lib/Types/Mex.d.ts +141 -0
  15. package/lib/Types/Mex.js +37 -0
  16. package/lib/Types/State.js +54 -1
  17. package/lib/Utils/auth-utils.js +12 -1
  18. package/lib/Utils/chat-utils.js +36 -2
  19. package/lib/Utils/companion-reg-client-utils.d.ts +17 -0
  20. package/lib/Utils/companion-reg-client-utils.js +35 -0
  21. package/lib/Utils/decode-wa-message.js +23 -4
  22. package/lib/Utils/generics.js +4 -1
  23. package/lib/Utils/identity-change-handler.d.ts +44 -0
  24. package/lib/Utils/identity-change-handler.js +50 -0
  25. package/lib/Utils/index.js +1 -1
  26. package/lib/Utils/message-retry-manager.js +25 -1
  27. package/lib/Utils/messages-media.js +162 -43
  28. package/lib/Utils/messages.d.ts +1 -1
  29. package/lib/Utils/messages.js +230 -9
  30. package/lib/Utils/offline-node-processor.d.ts +17 -0
  31. package/lib/Utils/offline-node-processor.js +40 -0
  32. package/lib/Utils/reporting-utils.d.ts +11 -0
  33. package/lib/Utils/reporting-utils.js +258 -0
  34. package/lib/Utils/signal.js +45 -1
  35. package/lib/Utils/stanza-ack.d.ts +11 -0
  36. package/lib/Utils/stanza-ack.js +38 -0
  37. package/lib/Utils/sync-action-utils.d.ts +19 -0
  38. package/lib/Utils/sync-action-utils.js +49 -0
  39. package/lib/Utils/tc-token-utils.d.ts +37 -0
  40. package/lib/Utils/tc-token-utils.js +163 -0
  41. package/lib/WAUSync/Protocols/USyncUsernameProtocol.d.ts +10 -0
  42. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +25 -0
  43. package/lib/WAUSync/Protocols/index.d.ts +1 -0
  44. package/lib/WAUSync/Protocols/index.js +1 -0
  45. package/lib/WAUSync/USyncQuery.js +5 -1
  46. package/package.json +3 -1
@@ -59,4 +59,4 @@ export declare const DEFAULT_CACHE_TTLS: {
59
59
  CALL_OFFER: number;
60
60
  USER_DEVICES: number;
61
61
  };
62
- //# sourceMappingURL=index.d.ts.map
62
+ //# sourceMappingURL=index.d.ts.map
@@ -2,14 +2,15 @@ import { proto } from '../../WAProto/index.js';
2
2
  import { makeLibSignalRepository } from '../Signal/libsignal.js';
3
3
  import { Browsers } from '../Utils/browser-utils.js';
4
4
  import logger from '../Utils/logger.js';
5
- const version = [2, 3000, 1027934701];
6
- import PHONENUMBER_MCC from './phonenumber-mcc.json' with { type: 'json' };
7
- export PHONENUMBER_MCC;
5
+ const version = [2, 3000, 1033105955];
6
+ import PHONENUMBER_MCC_1 from './phonenumber-mcc.json' with { type: 'json' };
7
+ export const PHONENUMBER_MCC = PHONENUMBER_MCC_1;
8
8
  export const UNAUTHORIZED_CODES = [401, 403, 419];
9
9
  export const DEFAULT_ORIGIN = 'https://web.whatsapp.com';
10
10
  export const CALL_VIDEO_PREFIX = 'https://call.whatsapp.com/video/';
11
11
  export const CALL_AUDIO_PREFIX = 'https://call.whatsapp.com/voice/';
12
12
  export const DEF_CALLBACK_PREFIX = 'CB:';
13
+ export const STATUS_EXPIRY_SECONDS = 24 * 60 * 60;
13
14
  export const DEF_TAG_PREFIX = 'TAG:';
14
15
  export const PHONE_CONNECTION_CB = 'CB:Pong';
15
16
  export const WA_ADV_ACCOUNT_SIG_PREFIX = Buffer.from([6, 0]);
@@ -19,6 +20,7 @@ export const WA_ADV_HOSTED_DEVICE_SIG_PREFIX = Buffer.from([6, 6]);
19
20
  export const WA_DEFAULT_EPHEMERAL = 7 * 24 * 60 * 60;
20
21
  export const NOISE_MODE = 'Noise_XX_25519_AESGCM_SHA256\0\0\0\0';
21
22
  export const DICT_VERSION = 3;
23
+ export const PLACEHOLDER_MAX_AGE_SECONDS = 14 * 24 * 60 * 60;
22
24
  export const KEY_BUNDLE_TYPE = Buffer.from([5]);
23
25
  export const NOISE_WA_HEADER = Buffer.from([87, 65, 6, DICT_VERSION]); // last is "DICT_VERSION"
24
26
  /** from: https://stackoverflow.com/questions/3809401/what-is-a-good-regular-expression-to-match-a-url */
@@ -76,6 +78,8 @@ export const MEDIA_PATH_MAP = {
76
78
  document: '/mms/document',
77
79
  audio: '/mms/audio',
78
80
  sticker: '/mms/image',
81
+ 'sticker-pack': '/mms/sticker',
82
+ 'thumbnail-sticker-pack': '/mms/sticker',
79
83
  'thumbnail-link': '/mms/image',
80
84
  'product-catalog-image': '/product/image',
81
85
  'md-app-state': '',
@@ -91,6 +95,8 @@ export const MEDIA_HKDF_KEY_MAPPING = {
91
95
  product: 'Image',
92
96
  ptt: 'Audio',
93
97
  sticker: 'Image',
98
+ 'sticker-pack': 'Sticker Pack',
99
+ 'thumbnail-sticker-pack': 'Sticker Pack',
94
100
  video: 'Video',
95
101
  'thumbnail-document': 'Document Thumbnail',
96
102
  'thumbnail-image': 'Image Thumbnail',
@@ -115,3 +121,16 @@ export const DEFAULT_CACHE_TTLS = {
115
121
  USER_DEVICES: 5 * 60 // 5 minutes
116
122
  };
117
123
  //# sourceMappingURL=index.js.map
124
+
125
+ // Added from Baileys v7.0.0-rc10
126
+ export const HISTORY_SYNC_PAUSED_TIMEOUT_MS = 120000;
127
+
128
+ // Added from Baileys v7.0.0-rc10
129
+ export const TimeMs = {
130
+ Minute: 60 * 1000,
131
+ Hour: 60 * 60 * 1000,
132
+ Day: 24 * 60 * 60 * 1000,
133
+ Week: 7 * 24 * 60 * 60 * 1000
134
+ };
135
+ //# sourceMappingURL=index.js.map
136
+
@@ -166,26 +166,25 @@ export const makeChatsSocket = (config) => {
166
166
  }
167
167
  };
168
168
  /** update the profile picture for yourself or a group */
169
- const updateProfilePicture = async (jid, content) => {
170
- let targetJid
171
-
169
+ const updateProfilePicture = async (jid, content, dimensions) => {
170
+ let targetJid;
172
171
  if (!jid) {
173
- throw new Boom('Illegal no-jid profile update. Please specify either your ID or the ID of the chat you wish to update')
172
+ throw new Boom('Illegal no-jid profile update. Please specify either your ID or the ID of the chat you wish to update');
174
173
  }
175
-
176
174
  if (jidNormalizedUser(jid) !== jidNormalizedUser(authState.creds.me.id)) {
177
- targetJid = jidNormalizedUser(jid) // in case it is someone other than us
175
+ targetJid = jidNormalizedUser(jid); // in case it is someone other than us
176
+ }
177
+ else {
178
+ targetJid = undefined;
178
179
  }
179
-
180
- const { img } = await generateProfilePicture(content)
181
-
180
+ const { img } = await generateProfilePicture(content, dimensions);
182
181
  await query({
183
182
  tag: 'iq',
184
183
  attrs: {
185
- target: targetJid,
186
184
  to: S_WHATSAPP_NET,
187
185
  type: 'set',
188
- xmlns: 'w:profile:picture'
186
+ xmlns: 'w:profile:picture',
187
+ ...(targetJid ? { target: targetJid } : {})
189
188
  },
190
189
  content: [
191
190
  {
@@ -194,8 +193,8 @@ export const makeChatsSocket = (config) => {
194
193
  content: img
195
194
  }
196
195
  ]
197
- })
198
- }
196
+ });
197
+ };
199
198
  /** remove the profile picture for yourself or a group */
200
199
  const removeProfilePicture = async (jid) => {
201
200
  let targetJid;
@@ -2,10 +2,272 @@ import { proto } from '../../WAProto/index.js';
2
2
  import { WAMessageAddressingMode, WAMessageStubType } from '../Types/index.js';
3
3
  import { generateMessageIDV2, unixTimestampSeconds } from '../Utils/index.js';
4
4
  import { getBinaryNodeChild, getBinaryNodeChildren, getBinaryNodeChildString, isLidUser, isPnUser, jidEncode, jidNormalizedUser } from '../WABinary/index.js';
5
+ import { USyncQuery, USyncUser } from '../WAUSync/index.js';
5
6
  import { makeChatsSocket } from './chats.js';
7
+ const pickFirst = (...values) => values.find(v => typeof v === 'string' && v.length > 0);
8
+ const preferPnJid = (primary, pn) => {
9
+ const normalizedPrimary = primary ? jidNormalizedUser(primary) : undefined;
10
+ const normalizedPn = pn ? jidNormalizedUser(pn) : undefined;
11
+ if (isLidUser(normalizedPrimary) && isPnUser(normalizedPn)) return normalizedPn;
12
+ return normalizedPrimary;
13
+ };
14
+ const getParticipantUsername = (attrs = {}) => pickFirst(
15
+ attrs.notify,
16
+ attrs.name,
17
+ attrs.short_name,
18
+ attrs.shortName,
19
+ attrs.pushname,
20
+ attrs.verified_name,
21
+ attrs.verifiedName,
22
+ attrs.vname
23
+ );
24
+ const getJidUser = (jid) => typeof jid === 'string' ? jid.split('@')[0] : undefined;
25
+ const getContactDisplayName = (contact = {}) => pickFirst(
26
+ contact.name,
27
+ contact.notify,
28
+ contact.verifiedName,
29
+ contact.verified_name,
30
+ contact.pushname,
31
+ contact.shortName,
32
+ contact.short_name
33
+ );
34
+ const participantNameFallback = (participant = {}) => getJidUser(participant.phoneNumber || participant.jid || participant.id || participant.lid);
35
+ const buildGroupParticipant = (attrs = {}) => {
36
+ const rawJid = attrs.jid ? jidNormalizedUser(attrs.jid) : undefined;
37
+ const rawLid = attrs.lid ? jidNormalizedUser(attrs.lid) : undefined;
38
+ const rawPn = pickFirst(attrs.phone_number, attrs.phoneNumber, attrs.jid_pn, attrs.participant_pn, attrs.pn);
39
+ const pnJid = rawPn ? jidNormalizedUser(rawPn) : undefined;
40
+ const id = preferPnJid(rawJid, pnJid) || pnJid || rawJid;
41
+ const lid = isLidUser(rawJid) ? rawJid : (isLidUser(rawLid) ? rawLid : undefined);
42
+ const username = getParticipantUsername(attrs);
43
+ const participant = {
44
+ id,
45
+ jid: id,
46
+ phoneNumber: isPnUser(id) ? id : undefined,
47
+ lid,
48
+ username,
49
+ name: username,
50
+ notify: attrs.notify,
51
+ admin: (attrs.type || null)
52
+ };
53
+ const fallback = participantNameFallback(participant);
54
+ return {
55
+ ...participant,
56
+ username: username || fallback,
57
+ name: username || fallback,
58
+ notify: attrs.notify || username || fallback,
59
+ displayName: username || fallback
60
+ };
61
+ };
62
+ const normalizeGroupMetadataJids = (metadata) => ({
63
+ ...metadata,
64
+ owner: preferPnJid(metadata.owner, metadata.ownerPn),
65
+ ownerLid: isLidUser(metadata.owner) ? metadata.owner : undefined,
66
+ subjectOwner: preferPnJid(metadata.subjectOwner, metadata.subjectOwnerPn),
67
+ subjectOwnerLid: isLidUser(metadata.subjectOwner) ? metadata.subjectOwner : undefined,
68
+ descOwner: preferPnJid(metadata.descOwner, metadata.descOwnerPn),
69
+ descOwnerLid: isLidUser(metadata.descOwner) ? metadata.descOwner : undefined,
70
+ participants: Array.isArray(metadata.participants)
71
+ ? metadata.participants.map(p => ({ ...p, id: preferPnJid(p.id, p.phoneNumber) || p.id, jid: preferPnJid(p.jid || p.id, p.phoneNumber) || p.jid || p.id }))
72
+ : []
73
+ });
6
74
  export const makeGroupsSocket = (config) => {
7
75
  const sock = makeChatsSocket(config);
8
- const { authState, ev, query, upsertMessage } = sock;
76
+ const { authState, ev, query, upsertMessage, executeUSyncQuery } = sock;
77
+ const { cachedGroupMetadata } = config;
78
+ // Cache nama kontak dari history/contact/message event.
79
+ // Catatan: metadata grup dari WA sering hanya mengirim jid/lid/phone_number,
80
+ // jadi nama member harus diperkaya dari cache kontak lokal jika tersedia.
81
+ const contactNameCache = new Map();
82
+ const putContactName = (contact = {}) => {
83
+ const ids = [contact.id, contact.jid, contact.phoneNumber, contact.lid]
84
+ .filter(Boolean)
85
+ .map(jid => {
86
+ try { return jidNormalizedUser(jid); } catch { return jid; }
87
+ });
88
+ if (!ids.length) return;
89
+ const displayName = getContactDisplayName(contact);
90
+ const cached = {
91
+ ...contact,
92
+ displayName,
93
+ username: displayName || contact.username,
94
+ name: contact.name || displayName,
95
+ notify: contact.notify || displayName
96
+ };
97
+ for (const id of ids) contactNameCache.set(id, cached);
98
+ };
99
+ const getCachedContactName = (participant = {}) => {
100
+ const ids = [participant.id, participant.jid, participant.phoneNumber, participant.lid].filter(Boolean);
101
+ for (const id of ids) {
102
+ const normalized = (() => { try { return jidNormalizedUser(id); } catch { return id; } })();
103
+ const contact = contactNameCache.get(normalized);
104
+ const name = getContactDisplayName(contact);
105
+ if (name) return name;
106
+ }
107
+ return undefined;
108
+ };
109
+ const enrichGroupMetadataNames = (metadata = {}) => ({
110
+ ...metadata,
111
+ participants: Array.isArray(metadata.participants)
112
+ ? metadata.participants.map(participant => {
113
+ const cachedName = getCachedContactName(participant);
114
+ const displayName = pickFirst(participant.username, participant.name, participant.notify, cachedName, participantNameFallback(participant));
115
+ return {
116
+ ...participant,
117
+ username: displayName,
118
+ name: displayName,
119
+ notify: pickFirst(participant.notify, displayName),
120
+ displayName
121
+ };
122
+ })
123
+ : []
124
+ });
125
+ ev.on('contacts.upsert', contacts => contacts?.forEach?.(putContactName));
126
+ ev.on('contacts.update', contacts => contacts?.forEach?.(putContactName));
127
+ ev.on('messaging-history.set', ({ contacts }) => contacts?.forEach?.(putContactName));
128
+ ev.on('messages.upsert', ({ messages }) => {
129
+ for (const msg of messages || []) {
130
+ const remoteJid = msg?.key?.remoteJid;
131
+ const participant = msg?.key?.participant;
132
+ const id = participant || (remoteJid && !remoteJid.endsWith('@g.us') ? remoteJid : undefined);
133
+ if (id && msg?.pushName) putContactName({ id, notify: msg.pushName });
134
+ }
135
+ });
136
+ // ── Built-in group metadata cache ─────────────────────────────────────────
137
+
138
+ const fetchParticipantUsernames = async (participants = []) => {
139
+ // WA username (@username) beda dengan pushName/kontak.
140
+ // groupMetadata biasanya tidak membawa username, jadi kita query USync username.
141
+ // Jika akun belum punya username atau server tidak mengizinkan, hasilnya kosong.
142
+ const entries = participants
143
+ .map(p => ({ participant: p, jid: p.jid || p.id || p.phoneNumber || p.lid }))
144
+ .filter(({ jid }) => typeof jid === 'string' && jid.length > 0);
145
+ const result = new Map();
146
+ const chunkSize = config.groupUsernameQueryChunkSize || 50;
147
+ for (let i = 0; i < entries.length; i += chunkSize) {
148
+ const chunk = entries.slice(i, i + chunkSize);
149
+ try {
150
+ const usync = new USyncQuery().withContext('interactive').withUsernameProtocol();
151
+ for (const { jid } of chunk) {
152
+ const normalized = (() => { try { return jidNormalizedUser(jid); } catch { return jid; } })();
153
+ usync.withUser(new USyncUser().withId(normalized));
154
+ }
155
+ const res = await executeUSyncQuery(usync);
156
+ for (const item of res?.list || []) {
157
+ const username = typeof item.username === 'string' && item.username.length > 0 ? item.username : undefined;
158
+ if (username) result.set(jidNormalizedUser(item.id), username);
159
+ }
160
+ } catch (err) {
161
+ config.logger?.debug?.({ err }, 'failed to fetch group participant usernames');
162
+ }
163
+ }
164
+ return result;
165
+ };
166
+ const enrichGroupMetadataUsernames = async (metadata = {}) => {
167
+ const normalized = normalizeGroupMetadataJids(metadata);
168
+ const shouldQueryUsername = config.fetchGroupParticipantsUsername !== false;
169
+ const usernameMap = shouldQueryUsername ? await fetchParticipantUsernames(normalized.participants) : new Map();
170
+ const withNames = enrichGroupMetadataNames(normalized);
171
+ return {
172
+ ...withNames,
173
+ participants: withNames.participants.map(participant => {
174
+ const keys = [participant.jid, participant.id, participant.phoneNumber, participant.lid].filter(Boolean);
175
+ let waUsername;
176
+ for (const key of keys) {
177
+ const normalizedKey = (() => { try { return jidNormalizedUser(key); } catch { return key; } })();
178
+ waUsername = usernameMap.get(normalizedKey);
179
+ if (waUsername) break;
180
+ }
181
+ return waUsername
182
+ ? {
183
+ ...participant,
184
+ username: waUsername,
185
+ waUsername,
186
+ displayName: waUsername,
187
+ name: participant.name,
188
+ notify: participant.notify
189
+ }
190
+ : participant;
191
+ })
192
+ };
193
+ };
194
+ const groupMetadataCache = new Map();
195
+ const GROUP_CACHE_TTL = (config.groupCacheTTL || 5) * 60 * 1000; // default 5 menit
196
+ const getCachedGroupMetadata = async (jid) => {
197
+ // 1. Cek user-provided cachedGroupMetadata (dari config makeWASocket)
198
+ if (cachedGroupMetadata) {
199
+ const cached = await cachedGroupMetadata(jid);
200
+ if (cached && Array.isArray(cached.participants)) return cached;
201
+ }
202
+ // 2. Cek internal Map cache
203
+ const entry = groupMetadataCache.get(jid);
204
+ if (entry && Date.now() - entry.ts < GROUP_CACHE_TTL) {
205
+ return entry.data;
206
+ }
207
+ return undefined;
208
+ };
209
+ const setCachedGroupMetadata = (jid, data) => {
210
+ groupMetadataCache.set(jid, { data, ts: Date.now() });
211
+ };
212
+ // Update cache saat groups.update event
213
+ ev.on('groups.update', (updates) => {
214
+ for (const update of updates) {
215
+ const entry = groupMetadataCache.get(update.id);
216
+ if (entry) {
217
+ // Merge update ke cache yang ada
218
+ groupMetadataCache.set(update.id, {
219
+ data: { ...entry.data, ...update },
220
+ ts: entry.ts
221
+ });
222
+ }
223
+ }
224
+ });
225
+ // Update cache saat participant berubah
226
+ // Debounce map to prevent duplicate refresh calls for same group
227
+ const _refreshDebounce = new Map();
228
+ const _refreshGroupMetadata = async (jid) => {
229
+ // Debounce: skip jika refresh sudah dijadwalkan dalam 2 detik terakhir
230
+ if (_refreshDebounce.has(jid)) return;
231
+ _refreshDebounce.set(jid, true);
232
+ setTimeout(() => _refreshDebounce.delete(jid), 2000);
233
+ try {
234
+ const result = await groupQuery(jid, 'get', [{ tag: 'query', attrs: { request: 'interactive' } }]);
235
+ const meta = extractGroupMetadata(result);
236
+ setCachedGroupMetadata(jid, meta);
237
+ // Emit groups.update agar subscriber luar (makeInMemoryStore dll) ikut terupdate
238
+ ev.emit('groups.update', [meta]);
239
+ } catch (e) {
240
+ // Ignore jika gagal (bot mungkin sudah keluar dari grup)
241
+ }
242
+ };
243
+ ev.on('group-participants.update', ({ id, participants, action }) => {
244
+ const entry = groupMetadataCache.get(id);
245
+ if (entry && Array.isArray(entry.data?.participants)) {
246
+ // Fast-path: update cache lokal secara optimistis tanpa tunggu network
247
+ const meta = { ...entry.data, participants: [...entry.data.participants] };
248
+ if (action === 'add') {
249
+ const existing = new Set(meta.participants.map(p => p.id));
250
+ for (const jid of participants) {
251
+ if (!existing.has(jid)) meta.participants.push({ id: jid, admin: null });
252
+ }
253
+ } else if (action === 'remove') {
254
+ meta.participants = meta.participants.filter(p => !participants.includes(p.id));
255
+ } else if (action === 'promote') {
256
+ meta.participants = meta.participants.map(p =>
257
+ participants.includes(p.id) ? { ...p, admin: 'admin' } : p
258
+ );
259
+ } else if (action === 'demote') {
260
+ meta.participants = meta.participants.map(p =>
261
+ participants.includes(p.id) ? { ...p, admin: null } : p
262
+ );
263
+ }
264
+ groupMetadataCache.set(id, { data: meta, ts: entry.ts });
265
+ }
266
+ // Auto-refresh dari network untuk semua aksi participant
267
+ // Ini memastikan data selalu akurat dari server WA
268
+ _refreshGroupMetadata(id);
269
+ });
270
+ // ── End group metadata cache ───────────────────────────────────────────────
9
271
  const groupQuery = async (jid, type, content) => query({
10
272
  tag: 'iq',
11
273
  attrs: {
@@ -16,8 +278,15 @@ export const makeGroupsSocket = (config) => {
16
278
  content
17
279
  });
18
280
  const groupMetadata = async (jid) => {
281
+ // Cek cache dulu sebelum hit network
282
+ const cached = await getCachedGroupMetadata(jid);
283
+ if (cached) return await enrichGroupMetadataUsernames(cached);
284
+ // Fetch dari WA
19
285
  const result = await groupQuery(jid, 'get', [{ tag: 'query', attrs: { request: 'interactive' } }]);
20
- return extractGroupMetadata(result);
286
+ const meta = extractGroupMetadata(result);
287
+ // Simpan ke cache
288
+ setCachedGroupMetadata(jid, meta);
289
+ return await enrichGroupMetadataUsernames(meta);
21
290
  };
22
291
  const groupFetchAllParticipating = async () => {
23
292
  const result = await query({
@@ -48,9 +317,10 @@ export const makeGroupsSocket = (config) => {
48
317
  attrs: {},
49
318
  content: [groupNode]
50
319
  });
51
- data[meta.id] = meta;
320
+ data[meta.id] = enrichGroupMetadataNames(normalizeGroupMetadataJids(meta));
52
321
  }
53
322
  }
323
+ // TODO: properly parse LID / PN DATA
54
324
  sock.ev.emit('groups.update', Object.values(data));
55
325
  return data;
56
326
  };
@@ -80,7 +350,7 @@ export const makeGroupsSocket = (config) => {
80
350
  }))
81
351
  }
82
352
  ]);
83
- return extractGroupMetadata(result);
353
+ return enrichGroupMetadataNames(extractGroupMetadata(result));
84
354
  },
85
355
  groupLeave: async (id) => {
86
356
  await groupQuery('@g.us', 'set', [
@@ -237,7 +507,7 @@ export const makeGroupsSocket = (config) => {
237
507
  participant: key.remoteJid
238
508
  },
239
509
  messageStubType: WAMessageStubType.GROUP_PARTICIPANT_ADD,
240
- messageStubParameters: [authState.creds.me.id],
510
+ messageStubParameters: [JSON.stringify(authState.creds.me)],
241
511
  participant: key.remoteJid,
242
512
  messageTimestamp: unixTimestampSeconds()
243
513
  }, 'notify');
@@ -264,7 +534,47 @@ export const makeGroupsSocket = (config) => {
264
534
  { tag: 'membership_approval_mode', attrs: {}, content: [{ tag: 'group_join', attrs: { state: mode } }] }
265
535
  ]);
266
536
  },
267
- groupFetchAllParticipating
537
+ groupFetchAllParticipating,
538
+ /**
539
+ * Auto detect isAdmin & isBotAdmin dari groupMetadata
540
+ * @param {string} groupJid - JID grup
541
+ * @param {string} senderJid - JID pengirim pesan
542
+ * @returns {Promise<{isAdmin: boolean, isBotAdmin: boolean}>}
543
+ */
544
+ getAdminStatus: async (groupJid, senderJid) => {
545
+ const normalizeJid = (jid) => {
546
+ if (!jid) return null;
547
+ try {
548
+ return jidNormalizedUser(jid).split('@')[0];
549
+ } catch {
550
+ return String(jid).split('@')[0];
551
+ }
552
+ };
553
+ const botJid = sock.authState?.creds?.me?.id;
554
+ const meta = await sock.groupMetadata(groupJid).catch(() => null);
555
+ if (!meta || !Array.isArray(meta.participants)) {
556
+ return { isAdmin: false, isBotAdmin: false };
557
+ }
558
+ const senderNorm = normalizeJid(senderJid);
559
+ const botNorm = normalizeJid(botJid);
560
+ const isAdmin = meta.participants.some(p => {
561
+ const pid = normalizeJid(p.jid || p.id || p.lid);
562
+ return pid === senderNorm && (p.admin === 'admin' || p.admin === 'superadmin');
563
+ });
564
+ // Cek isBotAdmin: via participants
565
+ let isBotAdmin = meta.participants.some(p => {
566
+ const pid = normalizeJid(p.jid || p.id || p.lid);
567
+ return pid === botNorm && (p.admin === 'admin' || p.admin === 'superadmin');
568
+ });
569
+ // Fallback: cek via owner/subjectOwner
570
+ if (!isBotAdmin) {
571
+ const owners = [meta.owner, meta.subjectOwner, meta.ownerPn]
572
+ .filter(Boolean)
573
+ .map(normalizeJid);
574
+ if (owners.includes(botNorm)) isBotAdmin = true;
575
+ }
576
+ return { isAdmin, isBotAdmin };
577
+ }
268
578
  };
269
579
  };
270
580
  export const extractGroupMetadata = (result) => {
@@ -290,8 +600,9 @@ export const extractGroupMetadata = (result) => {
290
600
  notify: group.attrs.notify,
291
601
  addressingMode: group.attrs.addressing_mode === 'lid' ? WAMessageAddressingMode.LID : WAMessageAddressingMode.PN,
292
602
  subject: group.attrs.subject,
293
- subjectOwner: group.attrs.s_o,
294
- subjectOwnerPn: group.attrs.s_o_pn,
603
+ subjectOwner: preferPnJid(group.attrs.s_o, group.attrs.s_o_pn),
604
+ subjectOwnerPn: group.attrs.s_o_pn ? jidNormalizedUser(group.attrs.s_o_pn) : undefined,
605
+ subjectOwnerLid: isLidUser(group.attrs.s_o) ? jidNormalizedUser(group.attrs.s_o) : undefined,
295
606
  subjectTime: +group.attrs.s_t,
296
607
  size: group.attrs.size ? +group.attrs.size : getBinaryNodeChildren(group, 'participant').length,
297
608
  creation: +group.attrs.creation,
@@ -310,17 +621,9 @@ export const extractGroupMetadata = (result) => {
310
621
  isCommunityAnnounce: !!getBinaryNodeChild(group, 'default_sub_group'),
311
622
  joinApprovalMode: !!getBinaryNodeChild(group, 'membership_approval_mode'),
312
623
  memberAddMode,
313
- participants: getBinaryNodeChildren(group, 'participant').map(({ attrs }) => {
314
- // TODO: Store LID MAPPINGS
315
- return {
316
- id: attrs.jid,
317
- phoneNumber: isLidUser(attrs.jid) && isPnUser(attrs.phone_number) ? attrs.phone_number : undefined,
318
- lid: isPnUser(attrs.jid) && isLidUser(attrs.lid) ? attrs.lid : undefined,
319
- admin: (attrs.type || null)
320
- };
321
- }),
624
+ participants: getBinaryNodeChildren(group, 'participant').map(({ attrs }) => buildGroupParticipant(attrs)),
322
625
  ephemeralDuration: eph ? +eph : undefined
323
626
  };
324
- return metadata;
627
+ return normalizeGroupMetadataJids(metadata);
325
628
  };
326
629
  //# sourceMappingURL=groups.js.map