Compare commits

...

5 Commits

Author SHA1 Message Date
Renato Alcara 9181d01339 fix: mediaUrl fallback to directPath for newsletter uploads
Newsletter endpoints return only direct_path (no url field).
Use direct_path as fallback so mediaUrl is never silently undefined.
Also update WAMediaUploadFunction return type to reflect that
mediaUrl may be undefined when only direct_path is present.

Resolves Copilot review comments on PR #311.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 11:59:06 -03:00
Renato Alcara 60798b77ff fix: use newsletter-specific upload paths for channel media
Port of WhiskeySockets/Baileys#2434 by @alesdi.

Newsletter media uploads were using regular /mms/* paths instead of
newsletter-specific paths, causing broken/invisible media in channels
(ACK error 479) and incorrect directPath values.

Changes:
- Add NEWSLETTER_MEDIA_PATH_MAP constant with /newsletter/* paths
- Pass newsletter:true to upload function for newsletter media
- Use newsletter paths + server_thumb_gen=1 query param in upload URL
- Return thumbnailDirectPath/thumbnailSha256 from upload response
- Omit url field for newsletter messages (directPath only)
- Add mediatype attribute to plaintext node for newsletter stanzas
- Extend WAMediaUploadFunction type with newsletter option + thumbnail fields
2026-03-20 11:37:13 -03:00
Renato Alcara d2db4cec97 chore: update WhatsApp Web version to v2.3000.1035595667 (#308)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-20 06:31:30 -03:00
github-actions[bot] be44c5fdb0 chore: update proto/version to v2.3000.1035577069 (#307)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-03-20 01:30:09 -03:00
Renato Alcara f63f0a766f fix: align Bad MAC retry receipt with WA Desktop behavior
fix: align Bad MAC retry receipt with WA Desktop behavior
2026-03-19 23:48:44 -03:00
9 changed files with 85 additions and 26 deletions
+2 -2
View File
@@ -1,7 +1,7 @@
syntax = "proto3";
package proto;
/// WhatsApp Version: 2.3000.1035484955
/// WhatsApp Version: 2.3000.1035577069
message ADVDeviceIdentity {
optional uint32 rawId = 1;
@@ -2149,7 +2149,6 @@ message LimitSharing {
CHAT_SETTING = 1;
BIZ_SUPPORTS_FB_HOSTING = 2;
UNKNOWN_GROUP = 3;
DEPRECATION = 4;
}
}
@@ -4893,6 +4892,7 @@ message SyncActionValue {
repeated SyncActionValue.BroadcastListParticipant participants = 2;
optional string listName = 3;
repeated string labelIds = 4;
optional string audienceExpression = 5;
}
message CallLogAction {
+3 -2
View File
@@ -5356,8 +5356,7 @@ export namespace proto {
UNKNOWN = 0,
CHAT_SETTING = 1,
BIZ_SUPPORTS_FB_HOSTING = 2,
UNKNOWN_GROUP = 3,
DEPRECATION = 4
UNKNOWN_GROUP = 3
}
}
@@ -12483,6 +12482,7 @@ export namespace proto {
participants?: (proto.SyncActionValue.IBroadcastListParticipant[]|null);
listName?: (string|null);
labelIds?: (string[]|null);
audienceExpression?: (string|null);
}
class BusinessBroadcastListAction implements IBusinessBroadcastListAction {
@@ -12491,6 +12491,7 @@ export namespace proto {
public participants: proto.SyncActionValue.IBroadcastListParticipant[];
public listName?: (string|null);
public labelIds: string[];
public audienceExpression?: (string|null);
public static create(properties?: proto.SyncActionValue.IBusinessBroadcastListAction): proto.SyncActionValue.BusinessBroadcastListAction;
public static encode(m: proto.SyncActionValue.IBusinessBroadcastListAction, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.SyncActionValue.BusinessBroadcastListAction;
+21 -9
View File
@@ -26692,10 +26692,6 @@ export const proto = $root.proto = (() => {
case 3:
m.limitSharingTrigger = 3;
break;
case "DEPRECATION":
case 4:
m.limitSharingTrigger = 4;
break;
}
if (d.limitSharingInitiatedByMe != null) {
m.limitSharingInitiatedByMe = Boolean(d.limitSharingInitiatedByMe);
@@ -36778,10 +36774,6 @@ export const proto = $root.proto = (() => {
case 3:
m.trigger = 3;
break;
case "DEPRECATION":
case 4:
m.trigger = 4;
break;
}
if (d.limitSharingSettingTimestamp != null) {
if ($util.Long)
@@ -36846,7 +36838,6 @@ export const proto = $root.proto = (() => {
values[valuesById[1] = "CHAT_SETTING"] = 1;
values[valuesById[2] = "BIZ_SUPPORTS_FB_HOSTING"] = 2;
values[valuesById[3] = "UNKNOWN_GROUP"] = 3;
values[valuesById[4] = "DEPRECATION"] = 4;
return values;
})();
@@ -87581,6 +87572,7 @@ export const proto = $root.proto = (() => {
BusinessBroadcastListAction.prototype.participants = $util.emptyArray;
BusinessBroadcastListAction.prototype.listName = null;
BusinessBroadcastListAction.prototype.labelIds = $util.emptyArray;
BusinessBroadcastListAction.prototype.audienceExpression = null;
let $oneOfFields;
@@ -87596,6 +87588,12 @@ export const proto = $root.proto = (() => {
set: $util.oneOfSetter($oneOfFields)
});
// Virtual OneOf for proto3 optional field
Object.defineProperty(BusinessBroadcastListAction.prototype, "_audienceExpression", {
get: $util.oneOfGetter($oneOfFields = ["audienceExpression"]),
set: $util.oneOfSetter($oneOfFields)
});
BusinessBroadcastListAction.create = function create(properties) {
return new BusinessBroadcastListAction(properties);
};
@@ -87615,6 +87613,8 @@ export const proto = $root.proto = (() => {
for (var i = 0; i < m.labelIds.length; ++i)
w.uint32(34).string(m.labelIds[i]);
}
if (m.audienceExpression != null && Object.hasOwnProperty.call(m, "audienceExpression"))
w.uint32(42).string(m.audienceExpression);
return w;
};
@@ -87647,6 +87647,10 @@ export const proto = $root.proto = (() => {
m.labelIds.push(r.string());
break;
}
case 5: {
m.audienceExpression = r.string();
break;
}
default:
r.skipType(t & 7);
break;
@@ -87683,6 +87687,9 @@ export const proto = $root.proto = (() => {
m.labelIds[i] = String(d.labelIds[i]);
}
}
if (d.audienceExpression != null) {
m.audienceExpression = String(d.audienceExpression);
}
return m;
};
@@ -87716,6 +87723,11 @@ export const proto = $root.proto = (() => {
d.labelIds[j] = m.labelIds[j];
}
}
if (m.audienceExpression != null && m.hasOwnProperty("audienceExpression")) {
d.audienceExpression = m.audienceExpression;
if (o.oneofs)
d._audienceExpression = "audienceExpression";
}
return d;
};
+1 -1
View File
@@ -1 +1 @@
{"version":[2,3000,1035504971]}
{"version":[2,3000,1035595667]}
+9
View File
@@ -157,6 +157,15 @@ export const MEDIA_PATH_MAP: { [T in MediaType]?: string } = {
'thumbnail-sticker-pack': '/mms/thumbnail-sticker-pack'
}
export const NEWSLETTER_MEDIA_PATH_MAP: { [T in MediaType]?: string } = {
image: '/newsletter/newsletter-image',
video: '/newsletter/newsletter-video',
document: '/newsletter/newsletter-document',
audio: '/newsletter/newsletter-audio',
sticker: '/newsletter/newsletter-image',
'thumbnail-link': '/newsletter/newsletter-image'
}
export const MEDIA_HKDF_KEY_MAPPING = {
audio: 'Audio',
document: 'Document',
+1 -1
View File
@@ -1051,7 +1051,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
const bytes = encodeNewsletterMessage(patched as proto.IMessage)
binaryNodeContent.push({
tag: 'plaintext',
attrs: {},
attrs: mediaType ? { mediatype: mediaType } : {},
content: bytes
})
const stanza: BinaryNode = {
+10 -2
View File
@@ -990,8 +990,16 @@ export type MessageGenerationOptionsFromContent = MiscMessageGenerationOptions &
export type WAMediaUploadFunction = (
encFilePath: string,
opts: { fileEncSha256B64: string; mediaType: MediaType; timeoutMs?: number }
) => Promise<{ mediaUrl: string; directPath: string; meta_hmac?: string; ts?: number; fbid?: number }>
opts: { fileEncSha256B64: string; mediaType: MediaType; timeoutMs?: number; newsletter?: boolean }
) => Promise<{
mediaUrl: string | undefined
directPath: string
meta_hmac?: string
ts?: number
fbid?: number
thumbnailDirectPath?: string
thumbnailSha256?: string
}>
export type MediaGenerationOptions = {
logger?: ILogger
+32 -6
View File
@@ -10,7 +10,13 @@ import { join } from 'path'
import { Readable, Transform } from 'stream'
import { URL } from 'url'
import { proto } from '../../WAProto/index.js'
import { DEFAULT_ORIGIN, MEDIA_HKDF_KEY_MAPPING, MEDIA_PATH_MAP, type MediaType } from '../Defaults'
import {
DEFAULT_ORIGIN,
MEDIA_HKDF_KEY_MAPPING,
MEDIA_PATH_MAP,
type MediaType,
NEWSLETTER_MEDIA_PATH_MAP
} from '../Defaults'
import type {
BaileysEventMap,
DownloadableMessage,
@@ -681,6 +687,10 @@ type MediaUploadResult = {
meta_hmac?: string
ts?: number
fbid?: number
thumbnail_info?: {
thumbnail_sha256?: string
thumbnail_direct_path?: string
}
}
export type UploadParams = {
@@ -825,11 +835,21 @@ export const getWAUploadToServer = (
{ customUploadHosts, fetchAgent, logger, options }: SocketConfig,
refreshMediaConn: (force: boolean) => Promise<MediaConnInfo>
): WAMediaUploadFunction => {
return async (filePath, { mediaType, fileEncSha256B64, timeoutMs }) => {
return async (filePath, { mediaType, fileEncSha256B64, timeoutMs, newsletter }) => {
// send a query JSON to obtain the url & auth token to upload our media
let uploadInfo = await refreshMediaConn(false)
let urls: { mediaUrl: string; directPath: string; meta_hmac?: string; ts?: number; fbid?: number } | undefined
let urls:
| {
mediaUrl: string
directPath: string
meta_hmac?: string
ts?: number
fbid?: number
thumbnailDirectPath?: string
thumbnailSha256?: string
}
| undefined
const hosts = [...customUploadHosts, ...uploadInfo.hosts]
fileEncSha256B64 = encodeBase64EncodedStringForUpload(fileEncSha256B64)
@@ -851,7 +871,11 @@ export const getWAUploadToServer = (
logger.debug(`uploading to "${hostname}"`)
const auth = encodeURIComponent(uploadInfo.auth)
const url = `https://${hostname}${MEDIA_PATH_MAP[mediaType]}/${fileEncSha256B64}?auth=${auth}&token=${fileEncSha256B64}`
const mediaPath = (newsletter ? NEWSLETTER_MEDIA_PATH_MAP[mediaType] : undefined) || MEDIA_PATH_MAP[mediaType]
let url = `https://${hostname}${mediaPath}/${fileEncSha256B64}?auth=${auth}&token=${fileEncSha256B64}`
if (newsletter) {
url += '&server_thumb_gen=1'
}
let result: MediaUploadResult | undefined
try {
@@ -868,11 +892,13 @@ export const getWAUploadToServer = (
if (result?.url || result?.direct_path) {
urls = {
mediaUrl: result.url!,
mediaUrl: result.url || result.direct_path!,
directPath: result.direct_path!,
meta_hmac: result.meta_hmac,
fbid: result.fbid,
ts: result.ts
ts: result.ts,
thumbnailDirectPath: result.thumbnail_info?.thumbnail_direct_path,
thumbnailSha256: result.thumbnail_info?.thumbnail_sha256
}
break
} else {
+6 -3
View File
@@ -196,10 +196,11 @@ export const prepareWAMessageMedia = async (
)
const fileSha256B64 = fileSha256.toString('base64')
const { mediaUrl, directPath } = await options.upload(filePath, {
const { directPath, thumbnailDirectPath, thumbnailSha256 } = await options.upload(filePath, {
fileEncSha256B64: fileSha256B64,
mediaType: mediaType,
timeoutMs: options.mediaUploadTimeoutMs
timeoutMs: options.mediaUploadTimeoutMs,
newsletter: true
})
await fs.unlink(filePath)
@@ -207,10 +208,12 @@ export const prepareWAMessageMedia = async (
const obj = WAProto.Message.fromObject({
// todo: add more support here
[`${mediaType}Message`]: (MessageTypeProto as any)[mediaType].fromObject({
url: mediaUrl,
// url intentionally omitted — newsletters use directPath only
directPath,
fileSha256,
fileLength,
thumbnailDirectPath,
thumbnailSha256: thumbnailSha256 ? Buffer.from(thumbnailSha256, 'base64') : undefined,
...uploadData,
media: undefined
})