diff --git a/plugin/preload.js b/plugin/preload.js index 90c0fbf..d219977 100644 --- a/plugin/preload.js +++ b/plugin/preload.js @@ -9,6 +9,8 @@ const { } = require('./lib/vm2') const path = require("path") const axios = require('axios'); +const http = require('http'); +const url = require('url') window._ = require("lodash") window.yuQueClient = axios.create({ @@ -266,154 +268,6 @@ let getSleepCodeByShell = ms => { return cmd } -// 屏蔽危险函数 -window.getuToolsLite = () => { - var utoolsLite = Object.assign({}, utools) - if (utools.isDev()) return utoolsLite - // 数据库相关接口 - delete utoolsLite.db - delete utoolsLite.dbStorage - delete utoolsLite.removeFeature - delete utoolsLite.setFeature - delete utoolsLite.onDbPull - // 支付相关接口 - delete utoolsLite.fetchUserServerTemporaryToken - delete utoolsLite.getUserServerTemporaryToken - delete utoolsLite.openPayment - delete utoolsLite.fetchUserPayments - return utoolsLite -} - -let getSandboxFuns = () => { - var sandbox = { - utools: getuToolsLite(), - quickcommand: quickcommand, - electron: electron, - axios: axios, - Audio: Audio, - fetch: fetch - } - shortCodes.forEach(f => { - sandbox[f.name] = f - }) - return sandbox -} - -let createNodeVM = () => { - var sandbox = getSandboxFuns() - const vm = new NodeVM({ - require: { - external: true, - builtin: ["*"], - }, - console: 'redirect', - env: process.env, - sandbox: sandbox, - }); - return vm -} - -let stringifyAll = item => { - var cache = []; - var string = JSON.stringify(item, (key, value) => { - if (typeof value === 'object' && value !== null) { - if (cache.indexOf(value) !== -1) return - cache.push(value); - } - return value; - }, '\t') - if (string != "{}") return string - else return item.toString() -} - -let parseItem = item => { - if (typeof item == "object") { - if (Buffer.isBuffer(item)) { - var bufferString = `[Buffer ${item.slice(0, 50).toString('hex').match(/\w{1,2}/g).join(" ")}` - if (item.length > 50) bufferString += `... ${(item.length / 1000).toFixed(2)}kb` - return bufferString + ']' - } else if (item instanceof ArrayBuffer) { - return `ArrayBuffer(${item.byteLength})` - } else if (item instanceof Blob) { - return `Blob {size: ${item.size}, type: "${item.type}"}` - } else { - try { - return stringifyAll(item) - } catch (error) {} - } - } else if (typeof item == "undefined") { - return "undefined" - } - return item.toString() -} - -window.convertFilePathToUtoolsPayload = files => files.map(file => { - let isFile = fs.statSync(file).isFile() - return { - isFile: isFile, - isDirectory: !isFile, - name: path.basename(file), - path: file - } -}) - -let parseStdout = stdout => stdout.map(x => parseItem(x)).join("\n") - -window.VmEval = (cmd, sandbox = {}) => new VM({ - sandbox: sandbox -}).run(cmd) - -// The vm module of Node.js is deprecated in the renderer process and will be removed -window.runCodeInVm = (cmd, cb) => { - const vm = createNodeVM() - //重定向 console - vm.on('console.log', (...stdout) => { - console.log(stdout); - cb(parseStdout(stdout), null) - }); - - vm.on('console.error', stderr => { - cb(null, stderr.toString()) - }); - - let liteErr = e => { - if (!e) return - return e.stack.replace(/([ ] +at.+)|(.+\.js:\d+)/g, '').trim() - } - - // 错误处理 - try { - vm.run(cmd, path.join(__dirname, 'preload.js')); - } catch (e) { - console.log('Error: ', e) - cb(null, liteErr(e)) - } - - let cbUnhandledError = e => { - removeAllListener() - console.log('UnhandledError: ', e) - cb(null, liteErr(e.error)) - } - - let cbUnhandledRejection = e => { - removeAllListener() - console.log('UnhandledRejection: ', e) - cb(null, liteErr(e.reason)) - } - - let removeAllListener = () => { - window.removeEventListener('error', cbUnhandledError) - window.removeEventListener('unhandledrejection', cbUnhandledRejection) - delete window.isWatchingError - } - - if (!window.isWatchingError) { - window.addEventListener('error', cbUnhandledError) - window.addEventListener('unhandledrejection', cbUnhandledRejection) - window.isWatchingError = true - } -} - window.htmlEncode = (value) => { return String(value).replace(/&/g, "&").replace(/>/g, ">").replace(/ { return pwdFix.replace(/\\/g, '\\\\') } -window.getMatchedFilesFix = payload => { - let MatchedFiles = payload - let Matched = cmd.match(/\{\{MatchedFiles(\[\d+\]){0,1}(\.\w{1,11}){0,1}\}\}/g) - Matched && Matched.forEach(m => { - repl = eval(m.slice(2, -2)) - typeof repl == 'object' ? (repl = JSON.stringify(repl)) : (repl = repl.replace('\\', '\\\\')) - cmd = cmd.replace(m, repl.replace('$', '$$$')) - }) -} - window.saveFile = (content, file) => { if (file instanceof Object) file = utools.showSaveDialog(file) if (!file) return false @@ -559,6 +403,156 @@ window.getSelectFile = hwnd => { window.clipboardReadText = () => electron.clipboard.readText() +window.convertFilePathToUtoolsPayload = files => files.map(file => { + let isFile = fs.statSync(file).isFile() + return { + isFile: isFile, + isDirectory: !isFile, + name: path.basename(file), + path: file + } +}) + +let stringifyAll = item => { + var cache = []; + var string = JSON.stringify(item, (key, value) => { + if (typeof value === 'object' && value !== null) { + if (cache.indexOf(value) !== -1) return + cache.push(value); + } + return value; + }, '\t') + if (string != "{}") return string + else return item.toString() +} + +let parseItem = item => { + if (typeof item == "object") { + if (Buffer.isBuffer(item)) { + var bufferString = `[Buffer ${item.slice(0, 50).toString('hex').match(/\w{1,2}/g).join(" ")}` + if (item.length > 50) bufferString += `... ${(item.length / 1000).toFixed(2)}kb` + return bufferString + ']' + } else if (item instanceof ArrayBuffer) { + return `ArrayBuffer(${item.byteLength})` + } else if (item instanceof Blob) { + return `Blob {size: ${item.size}, type: "${item.type}"}` + } else { + try { + return stringifyAll(item) + } catch (error) {} + } + } else if (typeof item == "undefined") { + return "undefined" + } + return item.toString() +} + +let parseStdout = stdout => stdout.map(x => parseItem(x)).join("\n") + +// 屏蔽危险函数 +window.getuToolsLite = () => { + var utoolsLite = Object.assign({}, utools) + if (utools.isDev()) return utoolsLite + // 数据库相关接口 + delete utoolsLite.db + delete utoolsLite.dbStorage + delete utoolsLite.removeFeature + delete utoolsLite.setFeature + delete utoolsLite.onDbPull + // 支付相关接口 + delete utoolsLite.fetchUserServerTemporaryToken + delete utoolsLite.getUserServerTemporaryToken + delete utoolsLite.openPayment + delete utoolsLite.fetchUserPayments + return utoolsLite +} + +let getSandboxFuns = () => { + var sandbox = { + utools: getuToolsLite(), + quickcommand: quickcommand, + electron: electron, + axios: axios, + Audio: Audio, + fetch: fetch + } + shortCodes.forEach(f => { + sandbox[f.name] = f + }) + return sandbox +} + +let createNodeVM = (userVars) => { + var sandbox = getSandboxFuns() + Object.assign(userVars, sandbox) + const vm = new NodeVM({ + require: { + external: true, + builtin: ["*"], + }, + console: 'redirect', + env: process.env, + sandbox: userVars, + }); + return vm +} + +window.VmEval = (cmd, sandbox = {}) => new VM({ + sandbox: sandbox +}).run(cmd) + +let isWatchingError = false +// The vm module of Node.js is deprecated in the renderer process and will be removed +window.runCodeInVm = (cmd, callback, userVars = {}) => { + const vm = createNodeVM(userVars) + //重定向 console + vm.on('console.log', (...stdout) => { + console.log(stdout); + callback(parseStdout(stdout), null) + }); + + vm.on('console.error', stderr => { + callback(null, stderr.toString()) + }); + + let liteErr = e => { + if (!e) return + return e.stack.replace(/([ ] +at.+)|(.+\.js:\d+)/g, '').trim() + } + + // 错误处理 + try { + vm.run(cmd, path.join(__dirname, 'preload.js')); + } catch (e) { + console.log('Error: ', e) + callback(null, liteErr(e)) + } + + let cbUnhandledError = e => { + removeAllListener() + console.log('UnhandledError: ', e) + callback(null, liteErr(e.error)) + } + + let cbUnhandledRejection = e => { + removeAllListener() + console.log('UnhandledRejection: ', e) + callback(null, liteErr(e.reason)) + } + + let removeAllListener = () => { + window.removeEventListener('error', cbUnhandledError) + window.removeEventListener('unhandledrejection', cbUnhandledRejection) + isWatchingError = false + } + + if (!isWatchingError) { + window.addEventListener('error', cbUnhandledError) + window.addEventListener('unhandledrejection', cbUnhandledRejection) + isWatchingError = true + } +} + window.runCodeFile = (cmd, option, terminal, callback) => { var bin = option.bin, argv = option.argv, @@ -609,4 +603,61 @@ window.runCodeFile = (cmd, option, terminal, callback) => { // let stderr = err_chunks.join(""); // callback(stdout, stderr) // }) +} + +let httpServer +window.quickcommandHttpServer = () => { + let run = (cmd = '', port = 33442) => { + let httpResponse = (res, code, result) => { + // 因为无法判断 vm2 是否执行完毕,故只收受一次 console.log,接收后就关闭连接 + if (res.finished) return + res.writeHead(code, { + 'Content-Type': 'text/html' + }); + if (result) res.write(result); + res.end(); + } + let runUserCode = (res, cmd, userVars) => { + // 不需要返回输出的提前关闭连接 + if (!cmd.includes('console.log')) httpResponse(res, 200) + window.runCodeInVm(cmd, (stdout, stderr) => { + // 错误返回 500 + if (stderr) return httpResponse(res, 500, stderr) + return httpResponse(res, 200, stdout) + }, userVars) + } + httpServer = http.createServer() + httpServer.on('request', (req, res) => { + if (req.method === 'GET') { + let parsedParams = _.cloneDeep(url.parse(req.url, true).query) + runUserCode(res, cmd, parsedParams) + } else if (req.method === 'POST') { + let data = [] + req.on('data', (chunk) => { + data.push(chunk) + }) + req.on('end', () => { + let parsedParams + let params = data.join("").toString() + // 先尝试作为 json 解析 + try { + parsedParams = JSON.parse(params) + } catch (error) { + parsedParams = _.cloneDeep(url.parse('?' + params, true).query) + } + runUserCode(res, cmd, parsedParams) + }) + } else { + httpResponse(res, 405) + } + }) + httpServer.listen(port, 'localhost'); + } + let stop = () => { + httpServer.close() + } + return { + run, + stop + } } \ No newline at end of file diff --git a/src/App.vue b/src/App.vue index 9d5d317..42ad3c4 100644 --- a/src/App.vue +++ b/src/App.vue @@ -33,6 +33,16 @@ export default defineComponent({ }); return; } + // 如果配置了后台服务则开启监听 + if (this.$profile.quickFeatures.apiServer.serverStatus) { + window + .quickcommandHttpServer() + .run( + this.$profile.quickFeatures.apiServer.cmd, + this.$profile.quickFeatures.apiServer.port + ); + console.log("Server Start..."); + } // 默认主题色 this.setCssVar("primary", this.$profile.primaryColor); // 进入插件 diff --git a/src/assets/feature/api.png b/src/assets/feature/api.png new file mode 100644 index 0000000..c423397 Binary files /dev/null and b/src/assets/feature/api.png differ diff --git a/src/assets/feature/crontab.png b/src/assets/feature/crontab.png new file mode 100644 index 0000000..5d0a5af Binary files /dev/null and b/src/assets/feature/crontab.png differ diff --git a/src/boot/global.js b/src/boot/global.js index 1aada40..66fdd95 100644 --- a/src/boot/global.js +++ b/src/boot/global.js @@ -10,7 +10,7 @@ import Cron from "croner" let userProfile = UTOOLS.getDB( UTOOLS.DBPRE.CFG + "preferences" ); -Object.assign(defaultProfile, userProfile) +_.merge(defaultProfile, _.cloneDeep(userProfile)) // "async" is optional; // more info on params: https://v2.quasar.dev/quasar-cli/boot-files diff --git a/src/components/CommandEditor.vue b/src/components/CommandEditor.vue index 73f1afa..7f0a83b 100644 --- a/src/components/CommandEditor.vue +++ b/src/components/CommandEditor.vue @@ -143,6 +143,7 @@ + + 实用功能 + + + + + + + + + 启用后,选中文件可以通过超级面板快速将文件收藏到「{{ + quickFeatures.favFile.tag + }}」标签 + + + + + + + + + + 启用后,在浏览器界面可以通过超级面板快速将网址收藏到「{{ + quickFeatures.favUrl.tag + }}」标签 + + + + + + + + + + 启用后,在主输入框输入「插件别名」可以快速设置插件别名
+ 并将所有设置的别名保存至「{{ + quickFeatures.pluNickName.tag + }}」标签 +
+
+
+ + + + + + + 启用后,在主输入框输入「计划任务」可以配置计划任务,定制执行指定或新建的快捷命令
+ 如果是直接新建,则新建的任务会保存在「{{ + quickFeatures.crontab.tag + }}」标签
+ 注意此功能并没有使用系统自带的计划任务,需要配置插件跟随 + utools 启动和保留后台
+ 本功能比系统自带的更为强大,因为你可以在计划任务中任意使用 + utools 或 quickcommand 的 api +
+
+
+ + + + + + + 启用后,在主输入框输入「快捷命令服务」可以进入配置一个后台服务
+ 通过本地监听{{ + quickFeatures.apiServer.port + }}端口的形式,接收用户传送过来的参数,然后根据参数执行不同的操作 +
+ 本功能的意义在于,将 utools + 的接口暴露出来,可以通过命令行等外部途径
+ 直接启用 ubrowser 或者直接redirect 到相应的插件
+ 需要配置插件跟随 utools 启动和保留后台 +
+
+
+
+
+ @@ -77,108 +247,6 @@ - - - - - - 实用功能 - - - - - - - - - 启用后,选中文件可以通过超级面板快速将文件收藏到「{{ - quickFeatures.favFile.tag - }}」标签 - - - - - - - - - - 启用后,在浏览器界面可以通过超级面板快速将网址收藏到「{{ - quickFeatures.favUrl.tag - }}」标签 - - - - - - - - - - 启用后,在主输入框输入「插件别名」可以快速设置插件别名
- 并将所有设置的别名保存至「{{ - quickFeatures.pluNickName.tag - }}」标签 -
-
-
-
-
-
diff --git a/src/components/MonacoEditor.vue b/src/components/MonacoEditor.vue index acd951f..0faa9bb 100644 --- a/src/components/MonacoEditor.vue +++ b/src/components/MonacoEditor.vue @@ -1,7 +1,7 @@ + + diff --git a/src/components/quickFeatures/CrontabCmd.vue b/src/components/quickFeatures/CrontabCmd.vue new file mode 100644 index 0000000..f461aa2 --- /dev/null +++ b/src/components/quickFeatures/CrontabCmd.vue @@ -0,0 +1,55 @@ + + + + + diff --git a/src/components/quickFeatures/FavFile.vue b/src/components/quickFeatures/FavFile.vue new file mode 100644 index 0000000..d5d6178 --- /dev/null +++ b/src/components/quickFeatures/FavFile.vue @@ -0,0 +1,40 @@ + diff --git a/src/components/quickFeatures/FavUrl.vue b/src/components/quickFeatures/FavUrl.vue new file mode 100644 index 0000000..0b8f513 --- /dev/null +++ b/src/components/quickFeatures/FavUrl.vue @@ -0,0 +1,76 @@ + diff --git a/src/components/quickFeatures/PluginNickName.vue b/src/components/quickFeatures/PluginNickName.vue new file mode 100644 index 0000000..c223aaf --- /dev/null +++ b/src/components/quickFeatures/PluginNickName.vue @@ -0,0 +1,144 @@ + + + diff --git a/src/js/options/defaultProfile.js b/src/js/options/defaultProfile.js index b1147c3..656e84d 100644 --- a/src/js/options/defaultProfile.js +++ b/src/js/options/defaultProfile.js @@ -17,5 +17,15 @@ export default { enable: false, tag: "别名" }, + crontab: { + enable: false, + tag: "任务" + }, + apiServer: { + enable: false, + port: 33442, + cmd: "", + serverStatus: false + } } } \ No newline at end of file diff --git a/src/js/options/quickFeatures.js b/src/js/options/quickFeatures.js index aa3142a..375ce9e 100644 --- a/src/js/options/quickFeatures.js +++ b/src/js/options/quickFeatures.js @@ -42,7 +42,21 @@ const quickFeatures = { cmds: ["插件别名"], icon: require("../../assets/feature/plugin.png"), platform: ["win32", "darwin", "linux"], + }, + crontab: { + code: "feature_crontab", + explain: "为快捷命令添加计划任务", + cmds: ["计划任务", "crontab"], + icon: require("../../assets/feature/crontab.png"), + platform: ["win32", "darwin", "linux"], + }, + apiServer: { + code: "feature_apiServer", + explain: "配置快捷命令后台服务", + cmds: ["快捷命令服务配置", "quickcommandServer"], + icon: require("../../assets/feature/api.png"), + platform: ["win32", "darwin", "linux"], } } -export default quickFeatures +export default quickFeatures \ No newline at end of file diff --git a/src/pages/quickFeaturesPage.vue b/src/pages/quickFeaturesPage.vue index 646db64..0c29b01 100644 --- a/src/pages/quickFeaturesPage.vue +++ b/src/pages/quickFeaturesPage.vue @@ -1,244 +1,29 @@