diff --git a/plugin/lib/quickcomposer.js b/plugin/lib/quickcomposer.js index b79aaa1..2052497 100644 --- a/plugin/lib/quickcomposer.js +++ b/plugin/lib/quickcomposer.js @@ -11,6 +11,7 @@ const quickcomposer = { windows: require("./quickcomposer/windows"), macos: require("./quickcomposer/macos"), status: require("./quickcomposer/status"), + browser: require("./quickcomposer/browser"), }; module.exports = quickcomposer; diff --git a/plugin/lib/quickcomposer/windows/browser.js b/plugin/lib/quickcomposer/windows/browser.js new file mode 100644 index 0000000..f919be1 --- /dev/null +++ b/plugin/lib/quickcomposer/windows/browser.js @@ -0,0 +1,296 @@ +const { runCsharpFeature } = require("../../csharp"); + +const addressBarName = "地址和搜索栏"; +const addressBarXpath = + "/Pane[3]/Pane/Pane[1]/Pane[2]/Pane[1]/ToolBar/Pane/Group/Edit"; + +const runBrowserAutomation = async (params) => { + const { value, browser = "msedge" } = params; + const baseArgs = ["-method", "process", "-window", browser]; + + if (value) { + baseArgs.push("-type", "setvalue", "-value", value, "-sendenter"); + } else { + baseArgs.push("-type", "getvalue"); + } + + try { + return await runCsharpFeature("automation", [ + ...baseArgs, + "-name", + addressBarName, + ]); + } catch (error) { + console.log(error); + try { + return await runCsharpFeature("automation", [ + ...baseArgs, + "-xpath", + addressBarXpath, + ]); + } catch (error) { + console.log(error); + throw "获取浏览器地址栏失败"; + } + } +}; + +const setUrl = async (browser = "msedge", url) => { + return await runBrowserAutomation({ value: url, browser }); +}; + +const getUrl = async (browser = "msedge") => { + return await runBrowserAutomation({ browser }); +}; + +const executeScript = async (browser = "msedge", script, args = {}) => { + // 构建参数列表 + const argNames = Object.keys(args); + const argValues = Object.values(args).map((v) => JSON.stringify(v)); + + const wrapperScript = ` + (function(${argNames.join(", ")}) { + ${script} + })(${argValues.join(", ")}) + `; + return await runBrowserAutomation({ + value: `javascript:${encodeURIComponent(wrapperScript)}`, + browser, + }); +}; + +// 点击元素 +const clickElement = async (browser = "msedge", selector) => { + const script = ` + const element = document.querySelector('${selector}'); + if (element) { + element.click(); + return '##execute_script_success##'; + } else { + return '##execute_script_failed##'; + } + `; + return await executeScript(browser, script); +}; + +// 输入文本 +const inputText = async (browser = "msedge", selector, text) => { + const script = ` + const element = document.querySelector('${selector}'); + if (element) { + element.value = '${text}'; + element.dispatchEvent(new Event('input')); + element.dispatchEvent(new Event('change')); + return '##execute_script_success##'; + } else { + return '##execute_script_failed##'; + } + `; + return await executeScript(browser, script); +}; + +// 获取文本 +const getText = async (browser = "msedge", selector) => { + const script = ` + const element = document.querySelector('${selector}'); + return element ? element.textContent : ''; + `; + return await executeScript(browser, script); +}; + +// 获取HTML +const getHtml = async (browser = "msedge", selector) => { + const script = ` + const element = document.querySelector('${selector}'); + return element ? element.innerHTML : ''; + `; + return await executeScript(browser, script); +}; + +// 隐藏元素 +const hideElement = async (browser = "msedge", selector) => { + const script = ` + const element = document.querySelector('${selector}'); + if (element) { + element.style.display = 'none'; + return '##execute_script_success##'; + } else { + return '##execute_script_failed##'; + } + `; + return await executeScript(browser, script); +}; + +// 显示元素 +const showElement = async (browser = "msedge", selector) => { + const script = ` + const element = document.querySelector('${selector}'); + if (element) { + element.style.display = ''; + return '##execute_script_success##'; + } else { + return '##execute_script_failed##'; + } + `; + return await executeScript(browser, script); +}; + +// 注入CSS +const injectCSS = async (browser = "msedge", css) => { + const script = ` + const style = document.createElement('style'); + style.textContent = \`${css}\`; + document.head.appendChild(style); + return '##execute_script_success##'; + `; + return await executeScript(browser, script); +}; + +// 设置Cookie +const setCookie = async (browser = "msedge", cookies, options = {}) => { + const cookieStatements = cookies + .map((cookie) => { + let cookieString = `${cookie.name}=${cookie.value}`; + if (options.expires) { + const expiresDate = new Date( + Date.now() + options.expires * 60 * 60 * 1000 + ); + cookieString += `;expires=${expiresDate.toUTCString()}`; + } + if (options.path) cookieString += `;path=${options.path}`; + if (options.domain) cookieString += `;domain=${options.domain}`; + if (options.secure) cookieString += ";secure"; + return `document.cookie = ${JSON.stringify(cookieString)};`; + }) + .join("\n"); + + const script = ` + ${cookieStatements} + return '##execute_script_success##'; + `; + + return await this.executeScript(browser, script); +}; + +// 获取Cookie +const getCookie = async (browser = "msedge", name) => { + const script = ` + const value = document.cookie + .split('; ') + .find(row => row.startsWith('${name}=')) + ?.split('=')[1]; + return value || ''; + `; + return await executeScript(browser, script); +}; + +// 删除Cookie +const deleteCookie = async ( + browser = "msedge", + name, + path = "/", + domain = "" +) => { + const script = ` + document.cookie = '${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT' + + (${domain ? `'; domain=${domain}'` : "''"}) + + (${path ? `'; path=${path}'` : "''"}); + return '##execute_script_success##'; + `; + return await executeScript(browser, script); +}; + +// 滚动到指定位置 +const scrollTo = async (browser = "msedge", x, y) => { + const script = ` + window.scrollTo(${x}, ${y}); + return '##execute_script_success##'; + `; + return await executeScript(browser, script); +}; + +// 滚动到元素位置 +const scrollToElement = async (browser = "msedge", selector) => { + const script = ` + const element = document.querySelector('${selector}'); + if (element) { + element.scrollIntoView({ behavior: 'smooth', block: 'center' }); + return '##execute_script_success##'; + } else { + return '##execute_script_failed##'; + } + `; + return await executeScript(browser, script); +}; + +// 获取滚动位置 +const getScrollPosition = async (browser = "msedge") => { + const script = ` + return JSON.stringify({ + x: window.pageXOffset || document.documentElement.scrollLeft, + y: window.pageYOffset || document.documentElement.scrollTop + }); + `; + return await executeScript(browser, script); +}; + +// 获取页面尺寸 +const getPageSize = async (browser = "msedge") => { + const script = ` + return JSON.stringify({ + width: Math.max( + document.documentElement.scrollWidth, + document.documentElement.clientWidth + ), + height: Math.max( + document.documentElement.scrollHeight, + document.documentElement.clientHeight + ) + }); + `; + return await executeScript(browser, script); +}; + +// 等待元素出现 +const waitForElement = async (browser = "msedge", selector, timeout = 5000) => { + const startTime = Date.now(); + + // 检查元素是否存在的函数 + const checkScript = ` + const element = document.querySelector('${selector}'); + return element ? '##execute_script_success##' : '##execute_script_failed##'; + `; + + // 轮询检查元素 + while (Date.now() - startTime < timeout) { + const result = await executeScript(browser, checkScript); + if (result === true) { + return true; + } + // 等待100ms再次检查 + await new Promise((resolve) => setTimeout(resolve, 100)); + } + + return false; +}; + +module.exports = { + getUrl, + setUrl, + executeScript, + clickElement, + inputText, + getText, + getHtml, + hideElement, + showElement, + injectCSS, + setCookie, + getCookie, + deleteCookie, + scrollTo, + scrollToElement, + getScrollPosition, + getPageSize, + waitForElement, +}; diff --git a/plugin/lib/quickcomposer/windows/index.js b/plugin/lib/quickcomposer/windows/index.js index ebd0a72..2efecb4 100644 --- a/plugin/lib/quickcomposer/windows/index.js +++ b/plugin/lib/quickcomposer/windows/index.js @@ -7,6 +7,7 @@ const service = require("./service"); const software = require("./software"); const utils = require("./utils"); const automation = require("./automation"); +const browser = require("./browser"); module.exports = { window, @@ -18,4 +19,5 @@ module.exports = { software, utils, automation, + browser, }; diff --git a/src/js/composer/commands/windowsCommands.js b/src/js/composer/commands/windowsCommands.js index 04b90e5..a8f30a2 100644 --- a/src/js/composer/commands/windowsCommands.js +++ b/src/js/composer/commands/windowsCommands.js @@ -1,5 +1,5 @@ import { newVarInputVal } from "js/composer/varInputValManager.js"; - +import { browserCommands } from "js/composer/commands/browserCommands.js"; const sendKeys = [ // 特殊按键 { value: "{ENTER}", label: "回车键 (Enter)" }, @@ -109,7 +109,7 @@ const registryPaths = [ const searchWindowConfig = [ { label: "窗口查找方式", - component: "q-select", + component: "QSelect", icon: "search", width: 3, options: [ @@ -160,7 +160,7 @@ const searchElementConfig = [ ...windowHandleConfig, { label: "元素查找方式", - component: "q-select", + component: "QSelect", icon: "search", width: 4, options: [ @@ -804,6 +804,49 @@ export const windowsCommands = { }, ], }, + // 浏览器 + { + value: "quickcomposer.windows.browser.getUrl", + label: "浏览器控制", + icon: "language", + isAsync: true, + config: [ + { + component: "ButtonGroup", + width: 12, + options: [ + { label: "Microsoft Edge", value: "msedge" }, + { label: "Google Chrome", value: "chrome" }, + ], + defaultValue: "msedge", + }, + ], + subCommands: browserCommands.commands + .find((command) => command.label === "浏览器操作") + .subCommands.map((command) => ({ + ...command, + value: command.value.replace( + "quickcomposer.browser.", + "quickcomposer.windows.browser." + ), + })) + // 无法获得输出,过滤相关命令 + .filter( + (command) => + ![ + "getTabs", + "activateTab", + "getText", + "getHtml", + "getCookie", + "getScrollPosition", + "getPageSize", + "waitForElement", + ] + .map((func) => `quickcomposer.windows.browser.${func}`) + .includes(command.value) + ), + }, // 监控 { value: "quickcomposer.windows.monitor.watchClipboard", @@ -1019,7 +1062,7 @@ export const windowsCommands = { }, { label: "值类型", - component: "q-select", + component: "QSelect", icon: "category", width: 4, options: [