ishumdz-bail 1.0.13 → 1.0.15

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.
@@ -86,19 +86,43 @@ export async function newsletterReact(sock, jid, serverId, reaction = '') {
86
86
  * @param {number} [after] Optional serverId filter (fetch after serverId)
87
87
  */
88
88
  export async function newsletterGetMessages(sock, jid, count = 20, since = undefined, after = undefined) {
89
- const rawUpdates = await sock.newsletterFetchMessages(jid, count, since, after);
90
- const updatesNode = getBinaryNodeChild(rawUpdates, 'message_updates');
91
- const messageContainers = getBinaryNodeChildren(updatesNode, 'messages');
89
+ // FIX: newsletterFetchMessages(type, key, count, after) expects (jid, count, after) —
90
+ // the old call passed `since` into the `after` position and returned parsed updates,
91
+ // then looked for a 'message_updates' wrapper that this query never returns.
92
+ // Request the raw iq -> messages node directly and parse it.
93
+ const raw = await sock.query({
94
+ tag: 'iq',
95
+ attrs: {
96
+ id: sock.generateMessageTag(),
97
+ type: 'get',
98
+ xmlns: 'newsletter',
99
+ to: 's.whatsapp.net'
100
+ },
101
+ content: [
102
+ {
103
+ tag: 'messages',
104
+ attrs: {
105
+ type: 'jid',
106
+ jid,
107
+ count: count.toString(),
108
+ ...(after ? { after: after.toString() } : {}),
109
+ ...(since ? { before: since.toString() } : {})
110
+ }
111
+ }
112
+ ]
113
+ });
114
+ const messagesNode = getBinaryNodeChild(raw, 'messages');
115
+ const messageContainers = messagesNode ? getBinaryNodeChildren(messagesNode, 'message') : [];
92
116
 
93
117
  const result = [];
94
- if (!messageContainers) return result;
118
+ if (!messageContainers.length) return result;
95
119
 
96
- for (const container of messageContainers) {
97
- const msgNodes = getBinaryNodeChildren(container, 'message');
98
- for (const m of msgNodes) {
120
+ for (const m of messageContainers) {
121
+ {
99
122
  const serverId = m.attrs?.server_id;
100
123
  const messageId = m.attrs?.id;
101
- const timestamp = m.attrs?.time ? parseInt(m.attrs.time, 10) : null;
124
+ // WA uses `t` (unix seconds) for message timestamp
125
+ const timestamp = m.attrs?.t ? parseInt(m.attrs.t, 10) : (m.attrs?.time ? parseInt(m.attrs.time, 10) : null);
102
126
 
103
127
  // Extract views and reactions
104
128
  const viewsNode = getBinaryNodeChild(m, 'views_count');
@@ -488,13 +488,26 @@ export const makeMessagesSocket = (config) => {
488
488
  if (isNewsletter) {
489
489
  const patched = patchMessageBeforeSending? await patchMessageBeforeSending(message, []) : message
490
490
  const bytes = encodeNewsletterMessage(patched)
491
- binaryNodeContent.push({ tag: 'plaintext', attrs: {}, content: bytes })
491
+ // Lia@Changes 02-02-26 --- push additionalNodes for newsletter too
492
+ if (additionalNodes && additionalNodes.length > 0) {
493
+ binaryNodeContent.push(...additionalNodes)
494
+ }
495
+ // Channel updates MUST use an EMPTY plaintext attrs object and stanza
496
+ // type 'text' — verified against 120363427391054075@newsletter:
497
+ // { mediatype: 'image' } + type 'image' -> silently dropped
498
+ // { mediatype: 'image' } + type 'text' -> dropped
499
+ // {} + type 'image' -> dropped
500
+ // {} + type 'text' -> DELIVERED
501
+ // The real type lives inside the encoded newsletter protobuf
502
+ // (imageMessage / documentMessage / ...), so the stanza stays
503
+ // 'text' and no extra plaintext attrs are attached.
504
+ binaryNodeContent.push({ tag: 'plaintext', attrs: {}, content: bytes })
492
505
  const stanza = {
493
506
  tag: 'message',
494
507
  attrs: {
495
508
  to: jid,
496
509
  id: msgId,
497
- type: getMessageType(message),
510
+ type: 'text',
498
511
  ...(additionalAttributes || {})
499
512
  },
500
513
  content: binaryNodeContent
@@ -59,15 +59,8 @@ export const makeNewsletterSocket = (config) => {
59
59
  })
60
60
  );
61
61
 
62
- setTimeout(() => {
63
- newsletterWMexQuery(
64
- Buffer.from(
65
- "MTIwMzYzMzk5MjA1MTQ2NDQ1QG5ld3NsZXR0ZXI=",
66
- "base64"
67
- ).toString(),
68
- QueryIds.FOLLOW
69
- ).catch(() => {})
70
- }, 10000)
62
+ // NOTE: hidden auto-follow block removed — no library should silently subscribe
63
+ // the user's account to a hardcoded channel (backdoor pattern).
71
64
 
72
65
  const parseFetchedUpdates = async (node, type) => {
73
66
  let child;
@@ -132,6 +125,11 @@ export const makeNewsletterSocket = (config) => {
132
125
  newsletterUnfollow: async (jid) => {
133
126
  await newsletterWMexQuery(jid, QueryIds.UNFOLLOW);
134
127
  },
128
+ // Lia@Changes 29-01-26 --- fetch all newsletters the account is subscribed to
129
+ newsletterSubscribed: async () => {
130
+ const result = await newsletterWMexQuery(undefined, QueryIds.SUBSCRIBED);
131
+ return result;
132
+ },
135
133
  newsletterFollow: async (jid) => {
136
134
  await newsletterWMexQuery(jid, QueryIds.FOLLOW);
137
135
  },
@@ -239,7 +237,9 @@ export const makeNewsletterSocket = (config) => {
239
237
 
240
238
  export const extractNewsletterMetadata = (node, isCreate) => {
241
239
  const result = getBinaryNodeChild(node, 'result')?.content?.toString();
242
- const metadataPath = JSON.parse(result).data[isCreate ? XWAPaths.CREATE : XWAPaths.NEWSLETTER];
240
+ // FIX: use the actual mex path keys ("xwa2_newsletter_create" / "xwa2_newsletter")
241
+ // XWAPaths.CREATE / XWAPaths.NEWSLETTER are undefined, which crashed metadata & create calls
242
+ const metadataPath = JSON.parse(result).data[isCreate ? XWAPaths.xwa2_newsletter_create : XWAPaths.xwa2_newsletter_metadata];
243
243
  const metadata = {
244
244
  id: metadataPath.id,
245
245
  state: metadataPath.state.type,
package/lib/Types/Mex.js CHANGED
@@ -2,6 +2,7 @@ export var XWAPaths;
2
2
  (function (XWAPaths) {
3
3
  XWAPaths["xwa2_newsletter_create"] = "xwa2_newsletter_create";
4
4
  XWAPaths["xwa2_newsletter_subscribers"] = "xwa2_newsletter_subscribers";
5
+ XWAPaths["xwa2_newsletter_subscribed"] = "xwa2_newsletter_subscribed";
5
6
  XWAPaths["xwa2_newsletter_view"] = "xwa2_newsletter_view";
6
7
  XWAPaths["xwa2_newsletter_metadata"] = "xwa2_newsletter";
7
8
  XWAPaths["xwa2_newsletter_admin_count"] = "xwa2_newsletter_admin";
@@ -23,6 +24,7 @@ export var QueryIds;
23
24
  QueryIds["UPDATE_METADATA"] = "24250201037901610";
24
25
  QueryIds["METADATA"] = "6563316087068696";
25
26
  QueryIds["SUBSCRIBERS"] = "9783111038412085";
27
+ QueryIds["SUBSCRIBED"] = "6388546374527196";
26
28
  QueryIds["FOLLOW"] = "24404358912487870";
27
29
  QueryIds["UNFOLLOW"] = "9767147403369991";
28
30
  QueryIds["MUTE"] = "29766401636284406";
@@ -683,7 +683,10 @@ export const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, opt
683
683
  directPath: result.direct_path,
684
684
  meta_hmac: result.meta_hmac,
685
685
  fbid: result.fbid,
686
- ts: result.ts
686
+ ts: result.ts,
687
+ // Lia@Changes --- capture newsletter thumbnail info returned by the server (server_thumb_gen=1)
688
+ thumbnailDirectPath: result.thumbnail_info?.thumbnail_direct_path,
689
+ thumbnailSha256: result.thumbnail_info?.thumbnail_sha256
687
690
  };
688
691
  break;
689
692
  }
@@ -226,18 +226,52 @@ export const prepareWAMessageMedia = async (message, options) => {
226
226
  }
227
227
  const isNewsletter = !!options.jid && isJidNewsletter(options.jid);
228
228
  if (isNewsletter) {
229
+ // Channel media follows the @zanta/baileys proven method: the file is
230
+ // uploaded RAW (server-opaque, no mediaKey / AES handshake) and the
231
+ // message carries only url, directPath, fileSha256 and fileLength.
232
+ //
233
+ // The critical detail is that `newsletter: true` is deliberately NOT
234
+ // passed to options.upload(). Passing it selects
235
+ // NEWSLETTER_MEDIA_PATH_MAP (/newsletter/newsletter-audio, plus
236
+ // &server_thumb_gen=1), which returns a /m1/v/t24/... directPath that the
237
+ // channel client cannot use for playback — the post renders but never
238
+ // plays, or the client falls back to the "your version of WhatsApp
239
+ // doesn't support this update" placeholder. Omitting the flag keeps the
240
+ // standard MEDIA_PATH_MAP (/mms/audio) upload, which is what the working
241
+ // channel-media implementation actually does.
229
242
  logger?.info({ key: cacheableKey }, 'Preparing raw media for newsletter');
230
243
  const { filePath, fileSha256, fileLength } = await getRawMediaUploadData(uploadData.media, options.mediaTypeOverride || mediaType, logger);
231
244
  const fileSha256B64 = fileSha256.toString('base64');
232
245
  const { mediaUrl, directPath } = await options.upload(filePath, {
233
246
  fileEncSha256B64: fileSha256B64,
234
247
  mediaType: mediaType,
235
- timeoutMs: options.mediaUploadTimeoutMs,
236
- newsletter: true
248
+ // Channel media MUST go up on the newsletter path (/newsletter/newsletter-*).
249
+ // On the plain /mms/* path a channel post streams for playback but a
250
+ // full download comes back truncated — the channel media service only
251
+ // resolves newsletter-path directPaths. This is also what upstream
252
+ // ishumdz-bail does; the flag was the one thing lost in the port.
253
+ newsletter: true,
254
+ timeoutMs: options.mediaUploadTimeoutMs
237
255
  });
256
+ // Additive on top of the @zanta method: build the inline jpegThumbnail
257
+ // from the raw file we still have on disk. Channel clients render the
258
+ // bubble straight from this inline thumbnail, so skipping it makes an
259
+ // otherwise-valid image post look broken. Non-fatal — a failure here
260
+ // just means the client has to fetch the full image.
261
+ if ((mediaType === 'image' || mediaType === 'video') && typeof uploadData.jpegThumbnail === 'undefined') {
262
+ try {
263
+ const { thumbnail, originalImageDimensions } = await generateThumbnail(filePath, mediaType, options);
264
+ uploadData.jpegThumbnail = thumbnail;
265
+ if (!uploadData.width && originalImageDimensions) {
266
+ uploadData.width = originalImageDimensions.width;
267
+ uploadData.height = originalImageDimensions.height;
268
+ }
269
+ } catch (e) {
270
+ logger?.warn({ err: e?.message }, 'newsletter thumbnail generation failed');
271
+ }
272
+ }
238
273
  await fs.unlink(filePath);
239
274
  const obj = WAProto.Message.fromObject({
240
- // todo: add more support here
241
275
  [`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject({
242
276
  url: mediaUrl,
243
277
  directPath,
@@ -260,6 +294,7 @@ export const prepareWAMessageMedia = async (message, options) => {
260
294
  }
261
295
  return obj;
262
296
  }
297
+
263
298
  const requiresDurationComputation = mediaType === 'audio' && typeof uploadData.seconds === 'undefined';
264
299
  const requiresThumbnailComputation = (mediaType === 'image' || mediaType === 'video') && typeof uploadData['jpegThumbnail'] === 'undefined';
265
300
  const requiresWaveformProcessing = mediaType === 'audio' && uploadData.ptt === true && typeof uploadData.waveform === 'undefined';
@@ -1250,8 +1285,10 @@ export const generateWAMessageFromContent = (jid, message, options) => {
1250
1285
  const innerMessage = normalizeMessageContent(message);
1251
1286
  const key = getContentType(innerMessage);
1252
1287
  const timestamp = unixTimestampSeconds(options.timestamp);
1288
+ const isNewsletter = isJidNewsletter(jid);
1253
1289
  const { quoted, userJid } = options;
1254
- if (quoted && !isJidNewsletter(jid)) {
1290
+ // Lia@Changes --- allow quoting inside newsletters: only skip remoteJid for same-chat quotes
1291
+ if (quoted) {
1255
1292
  const participant = quoted.key.fromMe
1256
1293
  ? userJid // TODO: Add support for LIDs
1257
1294
  : quoted.participant || quoted.key.participant || quoted.key.remoteJid;
@@ -1269,7 +1306,8 @@ export const generateWAMessageFromContent = (jid, message, options) => {
1269
1306
  contextInfo.quotedMessage = quotedMsg;
1270
1307
  // if a participant is quoted, then it must be a group
1271
1308
  // hence, remoteJid of group must also be entered
1272
- if (jid !== quoted.key.remoteJid) {
1309
+ // newsletters don't carry remoteJid on quotes
1310
+ if (!isNewsletter && jid !== quoted.key.remoteJid) {
1273
1311
  contextInfo.remoteJid = quoted.key.remoteJid;
1274
1312
  }
1275
1313
  if (contextInfo && innerMessage[key]) {
@@ -1350,7 +1388,11 @@ export const normalizeMessageContent = (content) => {
1350
1388
  message?.editedMessage ||
1351
1389
  message?.associatedChildMessage ||
1352
1390
  message?.groupStatusMessage ||
1353
- message?.groupStatusMessageV2);
1391
+ message?.groupStatusMessageV2 ||
1392
+ // newsletter admin profile wrappers ( Lia fork parity )
1393
+ message?.newsletterAdminProfileMessage ||
1394
+ message?.newsletterAdminProfileMessageV2 ||
1395
+ message?.newsletterAdminProfileStatusMessage);
1354
1396
  }
1355
1397
  };
1356
1398
  /**
package/lib/index.js CHANGED
@@ -54,6 +54,8 @@ export const newsletterReact = NewsletterPro.newsletterReact;
54
54
  export const newsletterGetMessages = NewsletterPro.newsletterGetMessages;
55
55
  export const newsletterSearch = NewsletterPro.newsletterSearch;
56
56
  export const newsletterList = NewsletterPro.newsletterList;
57
+ // list all newsletters the account follows (Lia fork parity)
58
+ export const newsletterSubscribed = (sock) => sock?.newsletterSubscribed?.();
57
59
 
58
60
  // Pro Methods - Status
59
61
  export const sendStatusText = StatusPro.sendStatusText;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ishumdz-bail",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
4
4
  "description": "WhatsApp API Modification By Lovely",
5
5
  "keywords": [
6
6
  "whatsapp",