229 lines
5.4 KiB
JavaScript

const fs = require("fs");
const { initCDP, cleanupCDP } = require("./cdp");
const { searchTarget } = require("./tabs");
const executeScript = async (tab, script, args = {}) => {
const target = await searchTarget(tab);
try {
const { Runtime } = await initCDP(target.id);
const argNames = Object.keys(args);
const argValues = Object.values(args).map((v) => JSON.stringify(v));
const wrappedScript = `
(async function(${argNames.join(", ")}) {
${script}
})(${argValues.join(", ")})
`;
const { result } = await Runtime.evaluate({
expression: wrappedScript,
returnByValue: true,
awaitPromise: true,
});
await cleanupCDP(target.id);
return result.value;
} catch (e) {
console.log(e);
throw new Error("执行脚本失败");
}
};
const clickElement = async (tab, selector) => {
return await executeScript(
tab,
`document.querySelector('${selector}').click()`
);
};
const inputText = async (tab, selector, text) => {
return await executeScript(
tab,
`
const el = document.querySelector('${selector}');
el.value = '${text}';
el.dispatchEvent(new Event('input'));
el.dispatchEvent(new Event('change'));
`
);
};
const submitForm = async (tab, buttonSelector, inputSelectors) => {
return await executeScript(
tab,
`
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
let el = null;
${inputSelectors
.map(
(i) =>
`el = document.querySelector('${i.selector}');
el.value = '${i.value}';
el.dispatchEvent(new Event('input'));
el.dispatchEvent(new Event('change'));`
)
.join("await sleep(200);")}
await sleep(200);
document.querySelector('${buttonSelector}').click();
`
);
};
const getText = async (tab, selector) => {
return await executeScript(
tab,
`const element = document.querySelector('${selector}');
return element?.textContent || element?.innerText || '';`
);
};
const getHtml = async (tab, selector) => {
return await executeScript(
tab,
`const element = document.querySelector('${selector}');
return element?.outerHTML || '';`
);
};
const hideElement = async (tab, selector) => {
return await executeScript(
tab,
`document.querySelector('${selector}').style.display = 'none'`
);
};
const showElement = async (tab, selector) => {
return await executeScript(
tab,
`document.querySelector('${selector}').style.display = ''`
);
};
const scrollTo = async (tab, x, y) => {
return await executeScript(tab, `window.scrollTo(${x}, ${y})`);
};
const scrollToElement = async (tab, selector) => {
return await executeScript(
tab,
`document.querySelector('${selector}').scrollIntoView()`
);
};
const getScrollPosition = async (tab) => {
const result = await executeScript(
tab,
`
return JSON.stringify({
x: window.pageXOffset || document.documentElement.scrollLeft,
y: window.pageYOffset || document.documentElement.scrollTop
});
`
);
return JSON.parse(result);
};
const getPageSize = async (tab) => {
const result = await executeScript(
tab,
`
return JSON.stringify({
width: Math.max(
document.documentElement.scrollWidth,
document.documentElement.clientWidth
),
height: Math.max(
document.documentElement.scrollHeight,
document.documentElement.clientHeight
)
});
`
);
return JSON.parse(result);
};
const waitForElement = async (tab, selector, timeout = 5000) => {
const startTime = Date.now();
while (Date.now() - startTime < timeout) {
const result = await executeScript(
tab,
`!!document.querySelector('${selector}')`
);
if (result) return;
await new Promise((resolve) => setTimeout(resolve, 100));
}
throw new Error(`等待元素 ${selector} 超时`);
};
const injectCSS = async (tab, css) => {
return await executeScript(
tab,
`
const style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
`,
{ css }
);
};
const injectRemoteScript = async (tab, url) => {
return await executeScript(
tab,
`
return await new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = url;
script.type = 'text/javascript';
script.onload = () => resolve(true);
script.onerror = () => reject(new Error('Failed to load script: ' + url));
document.head.appendChild(script);
});
`,
{ url }
);
};
const injectLocalScript = async (tab, filePath) => {
try {
if (!fs.existsSync(filePath)) {
throw new Error(`文件不存在: ${filePath}`);
}
const content = fs.readFileSync(filePath, "utf8");
return await executeScript(
tab,
`
const script = document.createElement('script');
script.type = 'text/javascript';
script.textContent = content;
document.head.appendChild(script);
return true;
`,
{ content }
);
} catch (error) {
throw new Error(`注入本地脚本失败: ${error.message}`);
}
};
module.exports = {
executeScript,
clickElement,
inputText,
submitForm,
getText,
getHtml,
hideElement,
showElement,
scrollTo,
scrollToElement,
getScrollPosition,
getPageSize,
waitForElement,
injectCSS,
injectRemoteScript,
injectLocalScript,
};