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.
- package/lib/Pro/newsletter.js +32 -8
- package/lib/Socket/messages-send.js +15 -2
- package/lib/Socket/newsletter.js +10 -10
- package/lib/Types/Mex.js +2 -0
- package/lib/Utils/messages-media.js +4 -1
- package/lib/Utils/messages.js +48 -6
- package/lib/index.js +2 -0
- package/package.json +1 -1
package/lib/Pro/newsletter.js
CHANGED
|
@@ -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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
|
97
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
510
|
+
type: 'text',
|
|
498
511
|
...(additionalAttributes || {})
|
|
499
512
|
},
|
|
500
513
|
content: binaryNodeContent
|
package/lib/Socket/newsletter.js
CHANGED
|
@@ -59,15 +59,8 @@ export const makeNewsletterSocket = (config) => {
|
|
|
59
59
|
})
|
|
60
60
|
);
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
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
|
}
|
package/lib/Utils/messages.js
CHANGED
|
@@ -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
|
-
|
|
236
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|