mirror of
https://github.com/JimLiu/baoyu-skills.git
synced 2026-07-25 03:19:48 +08:00
fix(baoyu-post-to-wechat): detect actual image format from buffer magic bytes
CDNs may serve WebP for URLs with .png extension. Detect real format from magic bytes and correct content-type/extension before upload. Also treat .webp as PNG-preferred for transparency handling.
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
|||||||
type WechatUploadAsset,
|
type WechatUploadAsset,
|
||||||
prepareWechatBodyImageUpload,
|
prepareWechatBodyImageUpload,
|
||||||
needsWechatBodyImageProcessing,
|
needsWechatBodyImageProcessing,
|
||||||
|
detectImageFormatFromBuffer,
|
||||||
} from "./wechat-image-processor.ts";
|
} from "./wechat-image-processor.ts";
|
||||||
|
|
||||||
interface AccessTokenResponse {
|
interface AccessTokenResponse {
|
||||||
@@ -138,6 +139,16 @@ async function loadUploadAsset(
|
|||||||
contentType = mimeTypes[fileExt] || "image/jpeg";
|
contentType = mimeTypes[fileExt] || "image/jpeg";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Detect actual format from magic bytes to fix extension/content-type mismatches
|
||||||
|
// (e.g. CDNs serving WebP for URLs with .png extension)
|
||||||
|
const detected = detectImageFormatFromBuffer(fileBuffer);
|
||||||
|
if (detected && detected.contentType !== contentType) {
|
||||||
|
console.error(`[wechat-api] Format mismatch: ${filename} declared as ${contentType}, actual ${detected.contentType}`);
|
||||||
|
contentType = detected.contentType;
|
||||||
|
fileExt = detected.fileExt;
|
||||||
|
filename = `${path.basename(filename, path.extname(filename))}${detected.fileExt}`;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
buffer: fileBuffer,
|
buffer: fileBuffer,
|
||||||
filename,
|
filename,
|
||||||
|
|||||||
@@ -52,6 +52,39 @@ const MIME_TO_EXT: Record<string, string> = {
|
|||||||
const JPEG_QUALITY_STEPS = [82, 74, 66, 58, 50, 42, 34];
|
const JPEG_QUALITY_STEPS = [82, 74, 66, 58, 50, 42, 34];
|
||||||
const MAX_WIDTH_STEPS = [2560, 2048, 1600, 1280, 1024, 800, 640, 480];
|
const MAX_WIDTH_STEPS = [2560, 2048, 1600, 1280, 1024, 800, 640, 480];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect actual image format from buffer magic bytes.
|
||||||
|
* Returns corrected { contentType, fileExt } or null if unknown.
|
||||||
|
*/
|
||||||
|
export function detectImageFormatFromBuffer(buffer: Buffer): { contentType: string; fileExt: string } | null {
|
||||||
|
if (buffer.length < 12) return null;
|
||||||
|
|
||||||
|
// WebP: RIFF....WEBP
|
||||||
|
if (
|
||||||
|
buffer[0] === 0x52 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x46 &&
|
||||||
|
buffer[8] === 0x57 && buffer[9] === 0x45 && buffer[10] === 0x42 && buffer[11] === 0x50
|
||||||
|
) {
|
||||||
|
return { contentType: "image/webp", fileExt: ".webp" };
|
||||||
|
}
|
||||||
|
// PNG: 89 50 4E 47
|
||||||
|
if (buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4e && buffer[3] === 0x47) {
|
||||||
|
return { contentType: "image/png", fileExt: ".png" };
|
||||||
|
}
|
||||||
|
// JPEG: FF D8 FF
|
||||||
|
if (buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff) {
|
||||||
|
return { contentType: "image/jpeg", fileExt: ".jpg" };
|
||||||
|
}
|
||||||
|
// GIF: GIF8
|
||||||
|
if (buffer[0] === 0x47 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x38) {
|
||||||
|
return { contentType: "image/gif", fileExt: ".gif" };
|
||||||
|
}
|
||||||
|
// BMP: BM
|
||||||
|
if (buffer[0] === 0x42 && buffer[1] === 0x4d) {
|
||||||
|
return { contentType: "image/bmp", fileExt: ".bmp" };
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
let webpDecoderReady: Promise<void> | undefined;
|
let webpDecoderReady: Promise<void> | undefined;
|
||||||
|
|
||||||
type JimpImage = Awaited<ReturnType<typeof Jimp.read>>;
|
type JimpImage = Awaited<ReturnType<typeof Jimp.read>>;
|
||||||
@@ -209,7 +242,8 @@ export async function prepareWechatBodyImageUpload(
|
|||||||
|
|
||||||
const image = await loadImageForProcessing(asset);
|
const image = await loadImageForProcessing(asset);
|
||||||
const widths = buildCandidateWidths(image.bitmap.width);
|
const widths = buildCandidateWidths(image.bitmap.width);
|
||||||
const preferPng = imageHasTransparency(image) || ensureFileExt(asset) === ".png";
|
const ext = ensureFileExt(asset);
|
||||||
|
const preferPng = imageHasTransparency(image) || ext === ".png" || ext === ".webp";
|
||||||
const processingNotes = buildProcessingNotes(asset);
|
const processingNotes = buildProcessingNotes(asset);
|
||||||
|
|
||||||
for (const width of widths) {
|
for (const width of widths) {
|
||||||
|
|||||||
Reference in New Issue
Block a user