mirror of
https://github.com/fofolee/uTools-quickcommand.git
synced 2025-07-19 01:09:49 +08:00
重构浏览器自动化功能:执行脚本,获取地址等操作,需指定标签页,新增标签页截图功能
This commit is contained in:
parent
26360e9643
commit
e942259c2e
@ -6,10 +6,7 @@ const http = require("http");
|
|||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
|
|
||||||
let client = null;
|
let clients = new Map(); // 存储每个标签页的CDP客户端
|
||||||
let Page = null;
|
|
||||||
let Runtime = null;
|
|
||||||
let DOM = null;
|
|
||||||
|
|
||||||
const getBrowserPath = (browser = "msedge") => {
|
const getBrowserPath = (browser = "msedge") => {
|
||||||
const platform = os.platform();
|
const platform = os.platform();
|
||||||
@ -120,7 +117,6 @@ const launchBrowser = async (options) => {
|
|||||||
throw new Error("未找到浏览器,或未指定浏览器路径");
|
throw new Error("未找到浏览器,或未指定浏览器路径");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查找可用端口
|
|
||||||
const port = await findAvailablePort(9222);
|
const port = await findAvailablePort(9222);
|
||||||
|
|
||||||
const args = [
|
const args = [
|
||||||
@ -140,7 +136,6 @@ const launchBrowser = async (options) => {
|
|||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
|
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
// 如果使用独立用户数据目录,则需要先杀死已有的浏览器进程
|
|
||||||
if (!useSingleUserDataDir) {
|
if (!useSingleUserDataDir) {
|
||||||
try {
|
try {
|
||||||
await killRunningBrowser(browserType);
|
await killRunningBrowser(browserType);
|
||||||
@ -151,9 +146,7 @@ const launchBrowser = async (options) => {
|
|||||||
}
|
}
|
||||||
const child = exec(
|
const child = exec(
|
||||||
`"${browserPath}" ${args.join(" ")}`,
|
`"${browserPath}" ${args.join(" ")}`,
|
||||||
{
|
{ windowsHide: true },
|
||||||
windowsHide: true,
|
|
||||||
},
|
|
||||||
async (error) => {
|
async (error) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
reject(error);
|
reject(error);
|
||||||
@ -162,13 +155,9 @@ const launchBrowser = async (options) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// 等待端口可用
|
|
||||||
waitForPort(port).then((success) => {
|
waitForPort(port).then((success) => {
|
||||||
if (success) {
|
if (success) {
|
||||||
resolve({
|
resolve({ pid: child.pid, port });
|
||||||
pid: child.pid,
|
|
||||||
port,
|
|
||||||
}); // 返回使用的端口号
|
|
||||||
} else {
|
} else {
|
||||||
reject(new Error("浏览器启动超时,请检查是否有权限问题或防火墙限制"));
|
reject(new Error("浏览器启动超时,请检查是否有权限问题或防火墙限制"));
|
||||||
}
|
}
|
||||||
@ -192,78 +181,247 @@ const killRunningBrowser = (browserType = "msedge") => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const initCDP = async (port) => {
|
const initCDP = async (targetId) => {
|
||||||
if (!client) {
|
if (!clients.has(targetId)) {
|
||||||
try {
|
try {
|
||||||
client = await CDP({ port });
|
const client = await CDP({ target: targetId });
|
||||||
({ Page, Runtime, DOM } = client);
|
const { Page, Runtime, Target, Network, Emulation } = client;
|
||||||
await Promise.all([Page.enable(), Runtime.enable(), DOM.enable()]);
|
await Promise.all([Page.enable(), Runtime.enable()]);
|
||||||
|
clients.set(targetId, {
|
||||||
|
client,
|
||||||
|
Page,
|
||||||
|
Runtime,
|
||||||
|
Target,
|
||||||
|
Network,
|
||||||
|
Emulation,
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
throw new Error(`请先通过浏览器控制中的"启动浏览器"打开浏览器`);
|
throw new Error(`请先通过浏览器控制中的"启动浏览器"打开浏览器`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { Page, Runtime, DOM };
|
return clients.get(targetId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getUrl = async () => {
|
const cleanupCDP = async (targetId) => {
|
||||||
const { Page } = await initCDP();
|
const client = clients.get(targetId);
|
||||||
const { frameTree } = await Page.getFrameTree();
|
if (client) {
|
||||||
return frameTree.frame.url;
|
await client.client.close();
|
||||||
};
|
clients.delete(targetId);
|
||||||
|
|
||||||
const setUrl = async (url) => {
|
|
||||||
const { Page } = await initCDP();
|
|
||||||
await Page.navigate({ url });
|
|
||||||
await Page.loadEventFired();
|
|
||||||
};
|
|
||||||
|
|
||||||
const executeScript = async (script, args = {}) => {
|
|
||||||
const { Runtime } = await initCDP();
|
|
||||||
// 构建参数列表
|
|
||||||
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,
|
|
||||||
});
|
|
||||||
return result.value;
|
|
||||||
};
|
|
||||||
|
|
||||||
const setCookie = async (cookies, options = {}) => {
|
|
||||||
const { Network } = await initCDP();
|
|
||||||
for (const cookie of cookies) {
|
|
||||||
await Network.setCookie({
|
|
||||||
name: cookie.name,
|
|
||||||
value: cookie.value,
|
|
||||||
domain: options.domain || undefined,
|
|
||||||
path: options.path || "/",
|
|
||||||
secure: options.secure || false,
|
|
||||||
expires: options.expires
|
|
||||||
? Math.floor(Date.now() / 1000) + options.expires * 3600
|
|
||||||
: undefined,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCookie = async (name) => {
|
// 获取所有标签页
|
||||||
const { Network } = await initCDP();
|
const getTabs = async () => {
|
||||||
const cookies = await Network.getCookies();
|
const targets = await CDP.List();
|
||||||
|
return targets
|
||||||
|
.filter((target) => target.type === "page")
|
||||||
|
.map((target) => ({
|
||||||
|
url: target.url,
|
||||||
|
title: target.title,
|
||||||
|
id: target.id,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取当前活动标签页
|
||||||
|
const getCurrentTab = async () => {
|
||||||
|
const targets = await CDP.List();
|
||||||
|
// 一般排第一个的就是活动标签页
|
||||||
|
const currentTarget = targets.find((target) => target.type === "page");
|
||||||
|
|
||||||
|
if (!currentTarget) {
|
||||||
|
throw new Error("未找到当前活动标签页");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: currentTarget.url,
|
||||||
|
title: currentTarget.title,
|
||||||
|
id: currentTarget.id,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// 搜索标签页
|
||||||
|
const searchTarget = async (tab) => {
|
||||||
|
if (!tab || !tab.by || !tab.searchValue || tab.by === "active") {
|
||||||
|
const currentTab = await getCurrentTab();
|
||||||
|
return currentTab;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targets = await CDP.List();
|
||||||
|
const target = targets.find((target) =>
|
||||||
|
target[tab.by].includes(tab.searchValue)
|
||||||
|
);
|
||||||
|
if (!target) {
|
||||||
|
throw new Error(`未找到目标标签页: ${tab.by} = ${tab.searchValue}`);
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 激活指定标签页
|
||||||
|
const activateTab = async (tab) => {
|
||||||
|
const target = await searchTarget(tab);
|
||||||
|
await CDP.Activate({ id: target.id });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 创建新标签页
|
||||||
|
const createNewTab = async (url = "about:blank") => {
|
||||||
|
const currentTab = await getCurrentTab();
|
||||||
|
const { Target } = await initCDP(currentTab.id);
|
||||||
|
const { targetId } = await Target.createTarget({ url });
|
||||||
|
const { targetInfo } = await Target.getTargetInfo({ targetId });
|
||||||
|
await cleanupCDP(currentTab.id);
|
||||||
|
return {
|
||||||
|
url: targetInfo.url,
|
||||||
|
title: targetInfo.title,
|
||||||
|
id: targetId,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// 关闭标签页
|
||||||
|
const closeTab = async (tab) => {
|
||||||
|
const target = await searchTarget(tab);
|
||||||
|
await cleanupCDP(target.id);
|
||||||
|
await CDP.Close({ id: target.id });
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUrl = async (tab) => {
|
||||||
|
const target = await searchTarget(tab);
|
||||||
|
const { Page } = await initCDP(target.id);
|
||||||
|
const { frameTree } = await Page.getFrameTree();
|
||||||
|
await cleanupCDP(target.id);
|
||||||
|
return frameTree.frame.url;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setUrl = async (tab, url) => {
|
||||||
|
const target = await searchTarget(tab);
|
||||||
|
const { Page } = await initCDP(target.id);
|
||||||
|
await Page.navigate({ url });
|
||||||
|
await Page.loadEventFired();
|
||||||
|
await cleanupCDP(target.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 = `
|
||||||
|
(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 setCookie = async (tab, cookies, options = {}) => {
|
||||||
|
const target = await searchTarget(tab);
|
||||||
|
const { Network, Page } = await initCDP(target.id);
|
||||||
|
try {
|
||||||
|
// 直接从Page获取URL,避免创建新连接
|
||||||
|
const { frameTree } = await Page.getFrameTree();
|
||||||
|
const url = frameTree.frame.url;
|
||||||
|
|
||||||
|
for (const cookie of cookies) {
|
||||||
|
await Network.setCookie({
|
||||||
|
name: cookie.name,
|
||||||
|
value: cookie.value,
|
||||||
|
domain: options.domain || url.split("/")[2],
|
||||||
|
path: options.path || "/",
|
||||||
|
secure: options.secure || false,
|
||||||
|
expires: options.expires
|
||||||
|
? Math.floor(Date.now() / 1000) + options.expires * 3600
|
||||||
|
: undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
await cleanupCDP(target.id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCookie = async (tab, name) => {
|
||||||
|
const target = await searchTarget(tab);
|
||||||
|
const { Network } = await initCDP(target.id);
|
||||||
|
const { cookies } = await Network.getCookies();
|
||||||
|
await cleanupCDP(target.id);
|
||||||
|
if (!name) return cookies;
|
||||||
return cookies.find((cookie) => cookie.name === name);
|
return cookies.find((cookie) => cookie.name === name);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 捕获标签页截图
|
||||||
|
const captureScreenshot = async (tab, options = {}) => {
|
||||||
|
const target = await searchTarget(tab);
|
||||||
|
const { format = "png", quality = 100, fullPage = false, savePath } = options;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { Page, Emulation } = await initCDP(target.id);
|
||||||
|
|
||||||
|
if (fullPage) {
|
||||||
|
const metrics = await Page.getLayoutMetrics();
|
||||||
|
const width = Math.max(
|
||||||
|
metrics.contentSize.width,
|
||||||
|
metrics.layoutViewport.clientWidth,
|
||||||
|
metrics.visualViewport.clientWidth
|
||||||
|
);
|
||||||
|
const height = Math.max(
|
||||||
|
metrics.contentSize.height,
|
||||||
|
metrics.layoutViewport.clientHeight,
|
||||||
|
metrics.visualViewport.clientHeight
|
||||||
|
);
|
||||||
|
await Emulation.setDeviceMetricsOverride({
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
deviceScaleFactor: 1,
|
||||||
|
mobile: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await Page.captureScreenshot({
|
||||||
|
format,
|
||||||
|
quality: format === "jpeg" ? quality : undefined,
|
||||||
|
fromSurface: true,
|
||||||
|
captureBeyondViewport: fullPage,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (fullPage) {
|
||||||
|
await Emulation.clearDeviceMetricsOverride();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (savePath) {
|
||||||
|
fs.writeFileSync(savePath, data, "base64");
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
} finally {
|
||||||
|
await cleanupCDP(target.id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
launchBrowser,
|
launchBrowser,
|
||||||
|
killRunningBrowser,
|
||||||
|
getTabs,
|
||||||
|
getCurrentTab,
|
||||||
|
activateTab,
|
||||||
|
createNewTab,
|
||||||
|
closeTab,
|
||||||
getUrl,
|
getUrl,
|
||||||
setUrl,
|
setUrl,
|
||||||
executeScript,
|
executeScript,
|
||||||
setCookie,
|
setCookie,
|
||||||
getCookie,
|
getCookie,
|
||||||
|
captureScreenshot,
|
||||||
};
|
};
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
const { executeScript } = require("./browser");
|
const { executeScript } = require("./browser");
|
||||||
|
|
||||||
const clickElement = async (selector) => {
|
const clickElement = async (tab, selector) => {
|
||||||
return await executeScript(`document.querySelector('${selector}').click()`);
|
return await executeScript(
|
||||||
|
tab,
|
||||||
|
`document.querySelector('${selector}').click()`
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const inputText = async (selector, text) => {
|
const inputText = async (tab, selector, text) => {
|
||||||
return await executeScript(
|
return await executeScript(
|
||||||
|
tab,
|
||||||
`
|
`
|
||||||
const el = document.querySelector('${selector}');
|
const el = document.querySelector('${selector}');
|
||||||
el.value = '${text}';
|
el.value = '${text}';
|
||||||
@ -15,52 +19,62 @@ const inputText = async (selector, text) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getText = async (selector) => {
|
const getText = async (tab, selector) => {
|
||||||
return await executeScript(
|
return await executeScript(
|
||||||
|
tab,
|
||||||
`document.querySelector('${selector}')?.textContent || ''`
|
`document.querySelector('${selector}')?.textContent || ''`
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getHtml = async (selector) => {
|
const getHtml = async (tab, selector) => {
|
||||||
return await executeScript(
|
return await executeScript(
|
||||||
|
tab,
|
||||||
`const element = document.querySelector('${selector}');
|
`const element = document.querySelector('${selector}');
|
||||||
return element ? element.innerHTML : '';`
|
return element ? element.innerHTML : '';`
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const hideElement = async (selector) => {
|
const hideElement = async (tab, selector) => {
|
||||||
return await executeScript(
|
return await executeScript(
|
||||||
|
tab,
|
||||||
`document.querySelector('${selector}').style.display = 'none'`
|
`document.querySelector('${selector}').style.display = 'none'`
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const showElement = async (selector) => {
|
const showElement = async (tab, selector) => {
|
||||||
return await executeScript(
|
return await executeScript(
|
||||||
|
tab,
|
||||||
`document.querySelector('${selector}').style.display = ''`
|
`document.querySelector('${selector}').style.display = ''`
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const scrollTo = async (x, y) => {
|
const scrollTo = async (tab, x, y) => {
|
||||||
return await executeScript(`window.scrollTo(${x}, ${y})`);
|
return await executeScript(tab, `window.scrollTo(${x}, ${y})`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const scrollToElement = async (selector) => {
|
const scrollToElement = async (tab, selector) => {
|
||||||
return await executeScript(
|
return await executeScript(
|
||||||
|
tab,
|
||||||
`document.querySelector('${selector}').scrollIntoView()`
|
`document.querySelector('${selector}').scrollIntoView()`
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getScrollPosition = async () => {
|
const getScrollPosition = async (tab) => {
|
||||||
return await executeScript(`
|
return await executeScript(
|
||||||
|
tab,
|
||||||
|
`
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
x: window.pageXOffset || document.documentElement.scrollLeft,
|
x: window.pageXOffset || document.documentElement.scrollLeft,
|
||||||
y: window.pageYOffset || document.documentElement.scrollTop
|
y: window.pageYOffset || document.documentElement.scrollTop
|
||||||
});
|
});
|
||||||
`);
|
`
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPageSize = async () => {
|
const getPageSize = async (tab) => {
|
||||||
return await executeScript(`
|
return await executeScript(
|
||||||
|
tab,
|
||||||
|
`
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
width: Math.max(
|
width: Math.max(
|
||||||
document.documentElement.scrollWidth,
|
document.documentElement.scrollWidth,
|
||||||
@ -71,13 +85,15 @@ const getPageSize = async () => {
|
|||||||
document.documentElement.clientHeight
|
document.documentElement.clientHeight
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
`);
|
`
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const waitForElement = async (selector, timeout = 5000) => {
|
const waitForElement = async (tab, selector, timeout = 5000) => {
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
while (Date.now() - startTime < timeout) {
|
while (Date.now() - startTime < timeout) {
|
||||||
const result = await executeScript(
|
const result = await executeScript(
|
||||||
|
tab,
|
||||||
`!!document.querySelector('${selector}')`
|
`!!document.querySelector('${selector}')`
|
||||||
);
|
);
|
||||||
if (result) return;
|
if (result) return;
|
||||||
@ -86,8 +102,9 @@ const waitForElement = async (selector, timeout = 5000) => {
|
|||||||
throw new Error(`等待元素 ${selector} 超时`);
|
throw new Error(`等待元素 ${selector} 超时`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const injectCSS = async (css) => {
|
const injectCSS = async (tab, css) => {
|
||||||
return await executeScript(
|
return await executeScript(
|
||||||
|
tab,
|
||||||
`
|
`
|
||||||
const style = document.createElement('style');
|
const style = document.createElement('style');
|
||||||
style.textContent = \`${css}\`;
|
style.textContent = \`${css}\`;
|
@ -116,8 +116,10 @@ const getOptimalSelector = () => {
|
|||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSelector = async () => {
|
const getSelector = async (tab) => {
|
||||||
return await executeScript(`
|
return await executeScript(
|
||||||
|
tab,
|
||||||
|
`
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
// 创建高亮元素
|
// 创建高亮元素
|
||||||
const highlight = document.createElement('div');
|
const highlight = document.createElement('div');
|
||||||
@ -172,7 +174,8 @@ const getSelector = async () => {
|
|||||||
document.addEventListener('mousemove', handleMouseMove);
|
document.addEventListener('mousemove', handleMouseMove);
|
||||||
document.addEventListener('click', handleClick, true);
|
document.addEventListener('click', handleClick, true);
|
||||||
});
|
});
|
||||||
`);
|
`
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
const browser = require("./browser");
|
const browser = require("./browser");
|
||||||
const tabs = require("./tabs");
|
|
||||||
const getSelector = require("./getSelector");
|
const getSelector = require("./getSelector");
|
||||||
const script = require("./script");
|
const execScript = require("./execScript");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
...browser,
|
...browser,
|
||||||
...getSelector,
|
...getSelector,
|
||||||
...tabs,
|
...execScript,
|
||||||
...script,
|
|
||||||
};
|
};
|
||||||
|
@ -1,76 +0,0 @@
|
|||||||
const CDP = require("chrome-remote-interface");
|
|
||||||
const { executeScript } = require("./browser");
|
|
||||||
|
|
||||||
let client = null;
|
|
||||||
let Page = null;
|
|
||||||
let Runtime = null;
|
|
||||||
let Target = null;
|
|
||||||
|
|
||||||
const initCDP = async (port) => {
|
|
||||||
if (!client) {
|
|
||||||
try {
|
|
||||||
client = await CDP({ port });
|
|
||||||
({ Page, Runtime, Target } = client);
|
|
||||||
await Promise.all([Page.enable(), Runtime.enable()]);
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
throw new Error(`请先通过浏览器控制中的"启动浏览器"打开浏览器`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { Page, Runtime, Target };
|
|
||||||
};
|
|
||||||
|
|
||||||
// 获取所有标签页
|
|
||||||
const getTabs = async () => {
|
|
||||||
const targets = await CDP.List();
|
|
||||||
return targets
|
|
||||||
.filter((target) => target.type === "page")
|
|
||||||
.map((target) => ({
|
|
||||||
url: target.url,
|
|
||||||
title: target.title,
|
|
||||||
id: target.id,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const searchTarget = async (searchProperty, searchValue) => {
|
|
||||||
const targets = await CDP.List();
|
|
||||||
const target = targets.find((target) =>
|
|
||||||
target[searchProperty].includes(searchValue)
|
|
||||||
);
|
|
||||||
if (!target) {
|
|
||||||
throw new Error(`未找到目标: ${searchProperty} = ${searchValue}`);
|
|
||||||
}
|
|
||||||
return target;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 激活指定标签页
|
|
||||||
const activateTab = async (searchProperty, searchValue) => {
|
|
||||||
const target = await searchTarget(searchProperty, searchValue);
|
|
||||||
await CDP.Activate({ id: target.id });
|
|
||||||
};
|
|
||||||
|
|
||||||
// 创建新标签页
|
|
||||||
const createNewTab = async (url = "about:blank") => {
|
|
||||||
const { Target } = await initCDP();
|
|
||||||
const { targetId } = await Target.createTarget({ url });
|
|
||||||
const { targetInfo } = await Target.getTargetInfo({ targetId });
|
|
||||||
return {
|
|
||||||
url: targetInfo.url,
|
|
||||||
title: targetInfo.title,
|
|
||||||
id: targetId,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// 关闭标签页
|
|
||||||
const closeTab = async (searchProperty, searchValue) => {
|
|
||||||
const target = await searchTarget(searchProperty, searchValue);
|
|
||||||
await CDP.Close({ id: target.id });
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
getTabs,
|
|
||||||
activateTab,
|
|
||||||
createNewTab,
|
|
||||||
closeTab,
|
|
||||||
};
|
|
@ -1,5 +1,32 @@
|
|||||||
import { newVarInputVal } from "js/composer/varInputValManager";
|
import { newVarInputVal } from "js/composer/varInputValManager";
|
||||||
|
|
||||||
|
const tabConfig = {
|
||||||
|
component: "OptionEditor",
|
||||||
|
width: 12,
|
||||||
|
options: {
|
||||||
|
by: {
|
||||||
|
component: "QSelect",
|
||||||
|
label: "标签",
|
||||||
|
width: 3,
|
||||||
|
options: [
|
||||||
|
{ label: "当前标签页", value: "active" },
|
||||||
|
{ label: "通过URL", value: "url" },
|
||||||
|
{ label: "通过标题", value: "title" },
|
||||||
|
{ label: "通过ID", value: "id" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
searchValue: {
|
||||||
|
component: "VariableInput",
|
||||||
|
icon: "tab",
|
||||||
|
width: 9,
|
||||||
|
placeholder: "当前标签页留空,其他支持模糊匹配",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultValue: {
|
||||||
|
by: "active",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const browserCommands = {
|
export const browserCommands = {
|
||||||
label: "浏览器控制",
|
label: "浏览器控制",
|
||||||
icon: "web",
|
icon: "web",
|
||||||
@ -79,6 +106,7 @@ export const browserCommands = {
|
|||||||
label: "获取/设置网址",
|
label: "获取/设置网址",
|
||||||
icon: "link",
|
icon: "link",
|
||||||
isAsync: true,
|
isAsync: true,
|
||||||
|
config: [tabConfig],
|
||||||
subCommands: [
|
subCommands: [
|
||||||
{
|
{
|
||||||
value: "quickcomposer.browser.getUrl",
|
value: "quickcomposer.browser.getUrl",
|
||||||
@ -103,7 +131,7 @@ export const browserCommands = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "quickcomposer.browser.getTabs",
|
value: "quickcomposer.browser.getTabs",
|
||||||
label: "获取/切换标签",
|
label: "标签操作",
|
||||||
icon: "tab",
|
icon: "tab",
|
||||||
isAsync: true,
|
isAsync: true,
|
||||||
subCommands: [
|
subCommands: [
|
||||||
@ -116,26 +144,12 @@ export const browserCommands = {
|
|||||||
value: "quickcomposer.browser.activateTab",
|
value: "quickcomposer.browser.activateTab",
|
||||||
label: "切换标签",
|
label: "切换标签",
|
||||||
icon: "tab_unselected",
|
icon: "tab_unselected",
|
||||||
config: [
|
config: [tabConfig],
|
||||||
{
|
},
|
||||||
component: "QSelect",
|
{
|
||||||
icon: "tab",
|
value: "quickcomposer.browser.getCurrentTab",
|
||||||
width: 3,
|
label: "获取当前标签页",
|
||||||
options: [
|
icon: "tab",
|
||||||
{ label: "通过URL", value: "url" },
|
|
||||||
{ label: "通过标题", value: "title" },
|
|
||||||
{ label: "通过ID", value: "id" },
|
|
||||||
],
|
|
||||||
defaultValue: "url",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "URL/标题/ID",
|
|
||||||
component: "VariableInput",
|
|
||||||
icon: "tab",
|
|
||||||
width: 9,
|
|
||||||
placeholder: "支持模糊匹配",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "quickcomposer.browser.createNewTab",
|
value: "quickcomposer.browser.createNewTab",
|
||||||
@ -155,26 +169,69 @@ export const browserCommands = {
|
|||||||
value: "quickcomposer.browser.closeTab",
|
value: "quickcomposer.browser.closeTab",
|
||||||
label: "关闭标签页",
|
label: "关闭标签页",
|
||||||
icon: "tab",
|
icon: "tab",
|
||||||
config: [
|
config: [tabConfig],
|
||||||
{
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "quickcomposer.browser.captureScreenshot",
|
||||||
|
label: "捕获截图",
|
||||||
|
icon: "screenshot",
|
||||||
|
config: [
|
||||||
|
tabConfig,
|
||||||
|
{
|
||||||
|
label: "选项",
|
||||||
|
component: "OptionEditor",
|
||||||
|
icon: "settings",
|
||||||
|
width: 12,
|
||||||
|
options: {
|
||||||
|
format: {
|
||||||
|
label: "格式",
|
||||||
component: "QSelect",
|
component: "QSelect",
|
||||||
icon: "tab",
|
icon: "format",
|
||||||
width: 3,
|
width: 4,
|
||||||
options: [
|
options: [
|
||||||
{ label: "通过URL", value: "url" },
|
{ label: "PNG", value: "png" },
|
||||||
{ label: "通过标题", value: "title" },
|
{ label: "JPEG", value: "jpeg" },
|
||||||
{ label: "通过ID", value: "id" },
|
{ label: "WebP", value: "webp" },
|
||||||
],
|
],
|
||||||
defaultValue: "url",
|
|
||||||
},
|
},
|
||||||
{
|
quality: {
|
||||||
label: "搜索条件",
|
label: "质量",
|
||||||
|
component: "NumberInput",
|
||||||
|
icon: "quality",
|
||||||
|
width: 4,
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
},
|
||||||
|
fullPage: {
|
||||||
|
label: "全屏截图",
|
||||||
|
component: "CheckButton",
|
||||||
|
icon: "fullscreen",
|
||||||
|
width: 4,
|
||||||
|
},
|
||||||
|
savePath: {
|
||||||
|
label: "保存路径",
|
||||||
component: "VariableInput",
|
component: "VariableInput",
|
||||||
icon: "tab",
|
icon: "folder",
|
||||||
width: 9,
|
placeholder: "留空则不保存",
|
||||||
placeholder: "支持模糊匹配",
|
width: 12,
|
||||||
|
options: {
|
||||||
|
dialog: {
|
||||||
|
type: "save",
|
||||||
|
options: {
|
||||||
|
title: "保存截图",
|
||||||
|
properties: ["saveFile"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
},
|
||||||
|
defaultValue: {
|
||||||
|
format: "png",
|
||||||
|
quality: 100,
|
||||||
|
fullPage: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -184,6 +241,7 @@ export const browserCommands = {
|
|||||||
icon: "code",
|
icon: "code",
|
||||||
isAsync: true,
|
isAsync: true,
|
||||||
config: [
|
config: [
|
||||||
|
tabConfig,
|
||||||
{
|
{
|
||||||
label: "脚本内容",
|
label: "脚本内容",
|
||||||
component: "CodeEditor",
|
component: "CodeEditor",
|
||||||
@ -204,6 +262,7 @@ export const browserCommands = {
|
|||||||
label: "Cookie操作",
|
label: "Cookie操作",
|
||||||
icon: "cookie",
|
icon: "cookie",
|
||||||
isAsync: true,
|
isAsync: true,
|
||||||
|
config: [tabConfig],
|
||||||
subCommands: [
|
subCommands: [
|
||||||
{
|
{
|
||||||
value: "quickcomposer.browser.setCookie",
|
value: "quickcomposer.browser.setCookie",
|
||||||
@ -282,7 +341,7 @@ export const browserCommands = {
|
|||||||
component: "VariableInput",
|
component: "VariableInput",
|
||||||
icon: "label",
|
icon: "label",
|
||||||
width: 12,
|
width: 12,
|
||||||
placeholder: "输入Cookie名称",
|
placeholder: "输入Cookie名称,留空则获取所有",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -294,6 +353,7 @@ export const browserCommands = {
|
|||||||
icon: "style",
|
icon: "style",
|
||||||
isAsync: true,
|
isAsync: true,
|
||||||
config: [
|
config: [
|
||||||
|
tabConfig,
|
||||||
{
|
{
|
||||||
label: "CSS内容",
|
label: "CSS内容",
|
||||||
component: "CodeEditor",
|
component: "CodeEditor",
|
||||||
@ -303,19 +363,13 @@ export const browserCommands = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
value: "quickcomposer.browser.getSelector",
|
|
||||||
label: "手动选择元素",
|
|
||||||
icon: "mouse",
|
|
||||||
isAsync: true,
|
|
||||||
config: [],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
value: "quickcomposer.browser.clickElement",
|
value: "quickcomposer.browser.clickElement",
|
||||||
label: "元素操作",
|
label: "元素操作",
|
||||||
icon: "web",
|
icon: "web",
|
||||||
isAsync: true,
|
isAsync: true,
|
||||||
config: [
|
config: [
|
||||||
|
tabConfig,
|
||||||
{
|
{
|
||||||
label: "选择器",
|
label: "选择器",
|
||||||
component: "VariableInput",
|
component: "VariableInput",
|
||||||
@ -395,6 +449,7 @@ export const browserCommands = {
|
|||||||
label: "滚动及页面尺寸",
|
label: "滚动及页面尺寸",
|
||||||
icon: "open_in_full",
|
icon: "open_in_full",
|
||||||
isAsync: true,
|
isAsync: true,
|
||||||
|
config: [tabConfig],
|
||||||
subCommands: [
|
subCommands: [
|
||||||
{
|
{
|
||||||
value: "quickcomposer.browser.scrollTo",
|
value: "quickcomposer.browser.scrollTo",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user