fix(baoyu-post-to-wechat): improve image upload with file chooser interception

This commit is contained in:
Jim Liu 宝玉
2026-03-08 14:07:55 -05:00
parent 8e58855047
commit f19ed00cb2
@@ -551,37 +551,126 @@ export async function postToWeChat(options: WeChatBrowserOptions): Promise<void>
const absolutePaths = images.map(p => path.isAbsolute(p) ? p : path.resolve(process.cwd(), p));
console.log(`[wechat-browser] Images: ${absolutePaths.join(', ')}`);
const { root } = await cdp.send<{ root: { nodeId: number } }>('DOM.getDocument', {}, { sessionId });
// --- PRIMARY approach: intercept file chooser dialog ---
let uploadSuccess = false;
try {
console.log('[wechat-browser] [primary] Enabling file chooser interception...');
await cdp.send('Page.setInterceptFileChooserDialog', { enabled: true }, { sessionId });
// Try primary selector, then fallback to any multi-file image input
let { nodeId } = await cdp.send<{ nodeId: number }>('DOM.querySelector', {
nodeId: root.nodeId,
selector: '.js_upload_btn_container input[type=file]',
}, { sessionId });
// Set up listener for file chooser opened event BEFORE clicking
const fileChooserPromise = new Promise<{ backendNodeId: number; mode: string }>((resolve, reject) => {
const timeout = setTimeout(() => reject(new Error('File chooser dialog not opened within 10s')), 10_000);
cdp!.on('Page.fileChooserOpened', (params: unknown) => {
clearTimeout(timeout);
const p = params as { backendNodeId: number; mode: string };
console.log(`[wechat-browser] [primary] File chooser opened: backendNodeId=${p.backendNodeId}, mode=${p.mode}`);
resolve(p);
});
});
if (!nodeId) {
console.log('[wechat-browser] Primary file input not found, trying fallback selector...');
const fallback = await cdp.send<{ nodeId: number }>('DOM.querySelector', {
nodeId: root.nodeId,
selector: 'input[type=file][multiple][accept*="image"]',
// Trigger file chooser by calling .click() on the file input with userGesture
const fileInputSelectors = [
'.js_upload_btn_container input[type=file]',
'input[type=file][multiple][accept*="image"]',
'input[type=file][accept*="image"]',
'input[type=file][multiple]',
'input[type=file]',
];
console.log('[wechat-browser] [primary] Clicking file input via JS .click() with userGesture...');
const clickResult = await cdp.send<{ result: { value: string } }>('Runtime.evaluate', {
expression: `
(function() {
const selectors = ${JSON.stringify(fileInputSelectors)};
for (const sel of selectors) {
const el = document.querySelector(sel);
if (el) {
el.click();
return JSON.stringify({ clicked: sel });
}
}
const debug = [];
document.querySelectorAll('input[type=file]').forEach((inp, i) => {
debug.push({ i, accept: inp.accept, multiple: inp.multiple, parentClass: inp.parentElement?.className?.slice(0, 60) });
});
return JSON.stringify({ error: 'no file input found', fileInputs: debug });
})()
`,
returnByValue: true,
userGesture: true,
}, { sessionId });
nodeId = fallback.nodeId;
console.log(`[wechat-browser] [primary] Click result: ${clickResult.result.value}`);
const clickStatus = JSON.parse(clickResult.result.value);
if (clickStatus.error) {
throw new Error(`File input not found: ${clickStatus.error}`);
}
// Wait for the file chooser event
console.log('[wechat-browser] [primary] Waiting for file chooser dialog...');
const chooser = await fileChooserPromise;
console.log(`[wechat-browser] [primary] Setting files via backendNodeId=${chooser.backendNodeId}...`);
await cdp.send('DOM.setFileInputFiles', {
files: absolutePaths,
backendNodeId: chooser.backendNodeId,
}, { sessionId });
console.log('[wechat-browser] [primary] Files set successfully via file chooser interception');
uploadSuccess = true;
} catch (primaryErr) {
console.log(`[wechat-browser] [primary] File chooser approach failed: ${primaryErr instanceof Error ? primaryErr.message : String(primaryErr)}`);
// Disable interception before falling back
try { await cdp.send('Page.setInterceptFileChooserDialog', { enabled: false }, { sessionId }); } catch {}
}
if (!nodeId) throw new Error('File input not found');
// --- FALLBACK approach: direct DOM.setFileInputFiles on nodeId ---
if (!uploadSuccess) {
console.log('[wechat-browser] [fallback] Trying direct DOM.setFileInputFiles...');
const { root } = await cdp.send<{ root: { nodeId: number } }>('DOM.getDocument', {}, { sessionId });
await cdp.send('DOM.setFileInputFiles', {
nodeId,
files: absolutePaths,
}, { sessionId });
const fileInputSelectors = [
'.js_upload_btn_container input[type=file]',
'input[type=file][multiple][accept*="image"]',
'input[type=file][accept*="image"]',
'input[type=file][multiple]',
'input[type=file]',
];
// Dispatch change event to trigger the upload
await cdp.send('Runtime.evaluate', {
expression: `
const fileInput = document.querySelector('.js_upload_btn_container input[type=file]') || document.querySelector('input[type=file][multiple][accept*="image"]');
if (fileInput) fileInput.dispatchEvent(new Event('change', { bubbles: true }));
`,
}, { sessionId });
let nodeId = 0;
for (const sel of fileInputSelectors) {
const result = await cdp.send<{ nodeId: number }>('DOM.querySelector', { nodeId: root.nodeId, selector: sel }, { sessionId });
if (result.nodeId) {
console.log(`[wechat-browser] [fallback] Found file input with selector: ${sel}`);
nodeId = result.nodeId;
break;
}
}
if (!nodeId) throw new Error('File input not found with any selector');
await cdp.send('DOM.setFileInputFiles', { nodeId, files: absolutePaths }, { sessionId });
console.log('[wechat-browser] [fallback] Files set via nodeId');
// Dispatch change event
await cdp.send('Runtime.evaluate', {
expression: `
(function() {
const selectors = ${JSON.stringify(fileInputSelectors)};
for (const sel of selectors) {
const el = document.querySelector(sel);
if (el) {
el.dispatchEvent(new Event('change', { bubbles: true }));
el.dispatchEvent(new Event('input', { bubbles: true }));
return 'dispatched on ' + sel;
}
}
return 'no input found for event dispatch';
})()
`,
returnByValue: true,
}, { sessionId });
console.log('[wechat-browser] [fallback] Change event dispatched');
}
// Wait for images to upload
console.log('[wechat-browser] Waiting for images to upload...');
@@ -589,11 +678,16 @@ export async function postToWeChat(options: WeChatBrowserOptions): Promise<void>
for (let i = 0; i < 30; i++) {
await sleep(2000);
const uploadCheck = await cdp.send<{ result: { value: string } }>('Runtime.evaluate', {
expression: `JSON.stringify({ uploaded: document.querySelectorAll('.weui-desktop-upload__thumb, .pic_item, [class*=upload_thumb]').length })`,
expression: `
JSON.stringify({
uploaded: document.querySelectorAll('.weui-desktop-upload__thumb, .pic_item, [class*=upload_thumb], [class*="pic_item"], [class*="upload__thumb"]').length,
loading: document.querySelectorAll('[class*="upload_loading"], [class*="uploading"], .weui-desktop-upload__loading').length
})
`,
returnByValue: true,
}, { sessionId });
const status = JSON.parse(uploadCheck.result.value);
console.log(`[wechat-browser] Upload progress: ${status.uploaded}/${targetCount}`);
console.log(`[wechat-browser] Upload progress: ${status.uploaded}/${targetCount} (loading: ${status.loading})`);
if (status.uploaded >= targetCount) break;
}