utools.getAppVersion() < '2.6.1' && alert('请升级 uTools 至最新版本'); // ----------------------------------------------------------------- const fs = require('fs'); const os = require('os'); const child_process = require("child_process") const iconv = require('iconv-lite') const electron = require('electron') const path = require("path") const axios = require('axios'); const http = require('http'); const url = require('url') const kill = require('tree-kill') const crypto = require("crypto"); require('ses') const md5 = (input) => { return crypto.createHash("md5").update(input, "utf8").digest("hex"); }; window._ = require("lodash") window.getuToolsLite = require("./lib/utoolsLite") // window.yuQueClient = axios.create({ // baseURL: 'https://www.yuque.com/api/v2/', // headers: { // 'Content-Type': 'application/json', // // 只读权限 // 'X-Auth-Token': 'WNrd0Z4kfCZLFrGLVAaas93DZ7sbG6PirKq7VxBL' // } // }); window.getSharedQcById = async (id) => { const url = "https://qc.qaz.ink/home/quick/script/getScript"; const timeStamp = parseInt(new Date().getTime() / 1000); const { data } = await axios.get(url, { params: { id, }, headers: { "verify-encrypt": md5("quickcommand666" + timeStamp), "verify-time": timeStamp, }, }); return JSON.stringify(data.data) }; // 检测进程是否存在 let isProcessExits = pid => { try { return process.kill(pid, 0) } catch (e) { return false } } window.isAppVersion4 = () => utools.getAppVersion() >= "4.0.0"; // 多开检测 window.multiProcessDetection = () => { let pids = JSON.parse(localStorage.getItem('processes')) || []; if (pids.length) pids = pids.filter(x => isProcessExits(x)); pids.push(process.pid) localStorage.setItem('processes', JSON.stringify(pids)) if (pids.length > 1) return true; return false; } // axios.defaults.adapter = require('axios/lib/adapters/http') if (!utools.isWindows()) process.env.PATH = `/usr/local/bin:/usr/local/sbin:${process.env.PATH}`; if (utools.isMacOS()) process.env.PATH = `/opt/homebrew/bin:/opt/homebrew/sbin:${process.env.PATH}`; const shortCodes = [ open = path => { utools.shellOpenItem(path) }, locate = path => { utools.shellShowItemInFolder(path); }, visit = url => { utools.shellOpenExternal(url); }, system = cmd => { child_process.exec(cmd); }, message = msg => { utools.showNotification(msg) }, keyTap = (key, ...modifier) => utools.simulateKeyboardTap(key, ...modifier), copyTo = text => { electron.clipboard.writeText(text) }, send = text => { copyTo(text); quickcommand.simulatePaste(); } ] const ctlKey = utools.isMacOs() ? 'command' : 'control' window.quickcommand = { // 模拟复制操作 simulateCopy: function() { utools.simulateKeyboardTap('c', ctlKey); }, // 模拟粘贴操作 simulatePaste: function() { utools.simulateKeyboardTap('v', ctlKey); }, // setTimout 不能在 vm2 中使用,同时在 electron 中有 bug sleep: function(ms) { var start = new Date().getTime() try { // node 16.13.1 child_process.execSync(getSleepCodeByShell(ms), { timeout: ms, windowsHide: true }) } catch (ex) {} var end = new Date().getTime() return (end - start) }, // 重写 setTimeout setTimeout: function(callback, ms) { var start = new Date().getTime() child_process.exec(getSleepCodeByShell(ms), { timeout: ms }, (err, stdout, stderr) => { var end = new Date().getTime() callback(end - start) }) }, // 关闭进程 kill: function(pid, signal = 'SIGTERM', cb) { kill(pid, signal, cb) }, // dom 解析 htmlParse: function(html) { return new DOMParser().parseFromString(html, 'text/html') }, // 下载文件 downloadFile: function(url, file = {}) { return new Promise((reslove, reject) => { if (file instanceof Object) file = utools.showSaveDialog(JSON.parse(JSON.stringify(file))) axios({ method: 'get', url: url, responseType: 'arraybuffer' }).then(res => { var filebuffer = Buffer.from(res.data) fs.writeFile(file, filebuffer, err => { if (err) reject(err) else reslove(filebuffer) }) }).catch(err => { reject(err) }) }) }, // 上传文件 uploadFile: function(url, file = {}, name = 'file', formData = {}) { return new Promise((reslove, reject) => { var objfile if (file instanceof File) { objfile = file } else { if (file instanceof Object) file = utools.showOpenDialog(JSON.parse(JSON.stringify(file)))[0] if (!fs.existsSync(file)) return reject('文件不存在') var arraybuffer = fs.readFileSync(file).buffer var objfile = new File([arraybuffer], path.basename(file)) } var form = new FormData(); form.append(name, objfile) var keys = Object.keys(formData) if (keys.length) keys.forEach(k => form.append(k, formData[k])) axios.post(url, form, { headers: { 'accept': 'application/json', 'Content-Type': `multipart/form-data; boundary=${formData._boundary}`, } }).then(res => { reslove(res) }).catch(err => { reject(err) }) }) }, // 载入在线资源 loadRemoteScript: async function(url) { if (!/^((ht|f)tps?):\/\/([\w\-]+(\.[\w\-]+)*\/)*[\w\-]+(\.[\w\-]+)*\/?(\?([\w\-\.,@?^=%&:\/~\+#]*)+)?/.test(url)) throw 'url 不合法' let local = getQuickcommandTempFile('js') await this.downloadFile(url, local) let source = require(local) fs.unlinkSync(local) return source }, // 唤醒 uTools wakeUtools: function() { let uToolsPath = utools.isMacOs() ? process.execPath.replace(/\/Frameworks\/.*/, "/MacOS/uTools") : process.execPath child_process.exec(uToolsPath, () => {}) }, readClipboard: function() { return electron.clipboard.readText() }, writeClipboard: function(text) { electron.clipboard.writeText(text) } } if (process.platform === 'win32') { // 运行vbs脚本 quickcommand.runVbs = function (script) { return new Promise((reslove, reject) => { var tempfile = getQuickcommandTempFile("vbs", "TempVBSScript"); fs.writeFile(tempfile, iconv.encode(script, "gbk"), (err) => { child_process.exec( `cscript.exe /nologo "${tempfile}"`, { encoding: "buffer", }, (err, stdout, stderr) => { if (err) reject(iconv.decode(stderr, "gbk")); else reslove(iconv.decode(stdout, "gbk")); } ); }); }); }; // 运行powershell脚本 quickcommand.runPowerShell = function (script) { return new Promise((reslove, reject) => { let base64str = Buffer.from(script, "utf16le").toString("base64"); child_process.exec( `powershell.exe -e "${base64str}"`, { encoding: "buffer", }, (err, stdout, stderr) => { if (err) reject(iconv.decode(stderr, "gbk")); else reslove(iconv.decode(stdout, "gbk")); } ); }); }; } if (process.platform === 'darwin') { // 运行AppleScript脚本 quickcommand.runAppleScript = function (script) { return new Promise((reslove, reject) => { child_process.execFile( 'osascript', ['-e', script], (err, stdout, stderr) => { if (err) reject(stderr); else reslove(stdout); } ); }); }; } // python -c window.runPythonCommand = py => { try { let result = child_process.execFileSync("python", ["-c", py], { windowsHide: true, encoding: 'buffer' }) return iconv.decode(result, utools.isWindows() ? 'gbk' : 'utf8').trim() } catch (e) { alert(e) return "" } } // 在终端中执行 if (process.platform !== 'linux') quickcommand.runInTerminal = function(cmdline, dir) { let command = getCommandToLaunchTerminal(cmdline, dir) child_process.exec(command) } let getCommandToLaunchTerminal = (cmdline, dir) => { let cd, command; if (utools.isWindows()) { let appPath = path.join(utools.getPath('home'), '/AppData/Local/Microsoft/WindowsApps/'); // 直接 existsSync wt.exe 无效 if (fs.existsSync(appPath) && fs.readdirSync(appPath).includes('wt.exe')) { cmdline = cmdline.replace(/"/g, `\\"`); cd = dir ? `-d "${dir.replace(/\\/g, '/')}"` : ''; command = `${appPath}wt.exe ${cd} cmd /k "${cmdline}"`; } else { cmdline = cmdline.replace(/"/g, `^"`); cd = dir ? `cd /d "${dir.replace(/\\/g, '/')}" &&` : ''; command = `${cd} start "" cmd /k "${cmdline}"`; } } else if (utools.isMacOs()) { cmdline = cmdline.replace(/"/g, `\\"`); cd = dir ? `cd ${dir.replace(/ /g, '\\\\ ')} &&` : ''; command = fs.existsSync('/Applications/iTerm.app') ? `osascript -e 'tell application "iTerm" if application "iTerm" is running then create window with default profile end if tell current session of first window to write text "clear && ${cd} ${cmdline}" activate end tell'` : `osascript -e 'tell application "Terminal" if application "Terminal" is running then do script "clear && ${cd} ${cmdline}" else do script "clear && ${cd} ${cmdline}" in window 1 end if activate end tell'`; } return command; } window.pluginInfo = () => { return JSON.parse(fs.readFileSync(path.join(__dirname, 'plugin.json'))) } let getSleepCodeByShell = ms => { var cmd, tempFilePath if (utools.isWindows()) { tempFilePath = getQuickcommandTempFile('vbs', 'SleepVBSScript') cmd = `echo set ws=CreateObject("Wscript.Shell") > ${tempFilePath} && echo Wscript.sleep ${ms} >> ${tempFilePath} && cscript /nologo ${tempFilePath}` } else { cmd = `sleep ${ms / 1000}` } return cmd } window.htmlEncode = (value) => { let dom = quickcommand.htmlParse().querySelector('body') dom.innerText = value return dom.innerHTML } window.removeHtmlTags = value => { return quickcommand.htmlParse(value).querySelector('body').innerText } window.hexEncode = text => Buffer.from(text, 'utf8').toString('hex') window.hexDecode = text => Buffer.from(text, 'hex').toString('utf8') window.base64Decode = text => Buffer.from(text, 'base64').toString('utf8') window.processPlatform = process.platform window.joinPath = path.join window.getUtoolsPlugins = () => { let root = utools.isMacOs() ? path.join(os.homedir(), 'Library/Application Support/uTools/plugins/') : (utools.isWindows() ? path.join(os.homedir(), 'AppData/Roaming/uTools/plugins') : path.join(os.homedir(), '.config/uTools/plugins')) let plugins = {}; let files = fs.readdirSync(root); let deleted = path.join(root, "deleted"); let deletedList = fs.existsSync(deleted) ? fs.readFileSync(path.join(root, "deleted"), "utf8").split("|") : []; files.forEach((file) => { if (/[a-zA-Z0-9\-]+\.asar$/.test(file) && !deletedList.includes(file)) { let pluginInfo = JSON.parse( fs.readFileSync(path.join(root, file, "plugin.json")) ); pluginInfo.logoPath = path.join(root, file, pluginInfo.logo); let keyWordFeatures = []; pluginInfo.features.forEach((f) => { f.cmds.some((c) => { c.length && (keyWordFeatures.push(c)); return true; }); }); if (!_.isEmpty(keyWordFeatures)) { pluginInfo["keyWordFeatures"] = keyWordFeatures plugins[pluginInfo.pluginName] = pluginInfo } } }); return plugins; } window.getQuickcommandTempFile = (ext, name, dir = 'quickcommandTempDir') => { if (!name) name = new Date().getTime() + (Math.random() * 10 ** 6).toFixed() let tempDir = path.join(os.tmpdir(), dir) if (!fs.existsSync(tempDir)) fs.mkdirSync(tempDir) return path.join(tempDir, `${name}.${ext}`) } window.delTempFile = (...args) => { let tmpPath = path.join(os.tmpdir(), ...args) if (fs.existsSync(tmpPath)) fs.unlinkSync(tmpPath) } window.getBase64Ico = filepath => { let sourceImage, ext = path.extname(filepath).slice(1) if (['png', 'jpg', 'jpeg', 'bmp', 'ico', 'gif', 'svg'].includes(ext)) { if (ext == 'svg') ext = 'svg+xml' sourceImage = `data:image/${ext};base64,` + fs.readFileSync(filepath, 'base64') if (ext == 'png') return sourceImage } else { sourceImage = utools.getFileIcon(filepath) return sourceImage } return sourceImage } window.getFileInfo = options => { var file if (options.type == 'file') { file = options.argvs } else if (options.type == 'dialog') { var dialog = utools.showOpenDialog(options.argvs); if (!dialog) return false file = dialog[0] } else { return false } var information = { name: path.basename(file), ext: path.extname(file), path: file } if (options.readfile) { var codec = (information.ext == '.bat' || information == '.ps1') ? 'gbk' : 'utf8' information.data = iconv.decode(fs.readFileSync(file), codec) } return information } window.getCurrentFolderPathFix = () => { let pwd = utools.getCurrentFolderPath() let pwdFix = pwd ? pwd : path.join(utools.getPath('home'), 'desktop') return pwdFix.replace(/\\/g, '\\\\') } window.saveFile = (content, file) => { if (file instanceof Object) file = utools.showSaveDialog(file) if (!file) return false try { fs.writeFileSync(file, content) return true } catch (error) { return false } } window.getSelectFile = hwnd => { if (utools.isWindows()) { var cmd = `powershell.exe -NoProfile "(New-Object -COM 'Shell.Application').Windows() | Where-Object { $_.HWND -eq ${hwnd} } | Select-Object -Expand Document | select @{ n='SelectItems'; e={$_.SelectedItems()} } | select -Expand SelectItems | select -Expand Path "`; let result = child_process.execSync(cmd, { encoding: "buffer", windowsHide: true }) return iconv.decode(result, 'GBK').trim().replace(/\\/g, '/'); } else { var cmd = `osascript -e 'tell application "Finder" to set selectedItems to selection as alias list if selectedItems is {} then return set parentPath to do shell script "dirname " & quoted form of POSIX path of (item 1 of selectedItems) set pathData to "" repeat with theItem in selectedItems set pathData to pathData & POSIX path of theItem & linefeed end repeat ' ` let result = child_process.execSync(cmd, { encoding: "utf8", windowsHide: true }) return result ? result.trim() : "" } } let runUbrowser = path => { utools.ubrowser.goto(path) .css(` .ant-modal-content, .ant-modal-mask, [class*='index-module_contentWrapper'], [class*='index-module_reward'], [class*='ReaderLayout-module_asideWrapper'], [class*='CornerBubble-module_cornerBubble'], [class*='DocReader-module_comment'], #header, #footer { display: none }`) .run({ width: 980, height: 750 }); } const docsRepoUrl = 'https://www.yuque.com/fofolee/qcdocs3' window.showUb = { help: function(path = '') { runUbrowser(docsRepoUrl + '/bg31vl' + path) }, docs: function(path = '') { runUbrowser(docsRepoUrl + '/pt589p' + path) }, changelog: function(path = '') { runUbrowser(docsRepoUrl + '/ucnd2o' + path) }, } 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 getSandboxFuns = () => { var sandbox = { fetch: fetch.bind(window), utools: getuToolsLite(), electron, axios, Audio, _, AbortController, AbortSignal, Buffer, require, // 兼容老版本 fs, path, os, child_process, } shortCodes.forEach(f => { sandbox[f.name] = f }) return sandbox } // 简化报错信息 let liteErr = e => { if (!e) return return e.error ? e.error.stack.replace(/([ ] +at.+)|(.+\.js:\d+)/g, '').trim() : e.message } // vm 模块将无法在渲染进程中使用,改用 ses 来执行代码 window.evalCodeInSandbox = (code, addVars = {}) => { let sandboxWithAD = Object.assign(addVars, getSandboxFuns()) sandboxWithAD.quickcommand = _.cloneDeep(quickcommand) try { return new Compartment(sandboxWithAD).evaluate(code); } catch (error) { throw liteErr(error) } } let isWatchingError = false window.runCodeInSandbox = (code, callback, addVars = {}) => { let sandbox = getSandboxFuns() sandbox.console = { log: (...stdout) => { console.log("Result:", stdout); callback(stdout, null) }, error: (...stderr) => { callback(null, stderr) } } let sandboxWithAD = Object.assign(addVars, sandbox) sandboxWithAD.quickcommand = _.cloneDeep(quickcommand) if (addVars.enterData) { sandboxWithAD.quickcommand.enterData = addVars.enterData sandboxWithAD.quickcommand.payload = addVars.enterData.payload } try { new Compartment(sandboxWithAD).evaluate(code) } catch (e) { console.log('Error: ', e) callback(null, liteErr(e)) } // 自动捕捉错误 let cbUnhandledError = e => { removeAllListener() console.log('UnhandledError: ', e) callback(null, liteErr(e)) } 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, realTime=true) => { let { bin, argv, ext, charset, scptarg, envPath, alias } = option let script = getQuickcommandTempFile(ext, 'quickcommandTempScript'); // 批处理和 powershell 默认编码为 GBK, 解决批处理的换行问题 if (charset.scriptCode) cmd = iconv.encode(cmd.replace(/\n/g, '\r\n'), charset.scriptCode); fs.writeFileSync(script, cmd); // var argvs = [script] // if (argv) { // argvs = argv.split(' ') // argvs.push(script); // } let child, cmdline; if (bin.slice(-7) == 'csc.exe') { cmdline = `${bin} ${argv} /out:"${script.slice(0, -2) + 'exe'}" "${script}" && "${script.slice(0, -2) + 'exe'}" ${scptarg}` } else if (bin == 'gcc') { var suffix = utools.isWindows() ? '.exe' : '' cmdline = `${bin} ${argv} "${script.slice(0, -2)}" "${script}" && "${script.slice(0, -2) + suffix}" ${scptarg}` } else if (utools.isWindows() && bin == 'bash') { cmdline = `${bin} ${argv} "${script.replace(/\\/g, '/').replace(/C:/i, '/mnt/c')}" ${scptarg}` } else { cmdline = `${bin} ${argv} "${script}" ${scptarg}` } let processEnv = _.cloneDeep(process.env); if (envPath) processEnv.PATH = envPath; if (alias) cmdline = alias + '\n' + cmdline; // 在终端中输出 if (terminal) cmdline = getCommandToLaunchTerminal(cmdline) child = child_process.spawn(cmdline, { encoding: 'buffer', shell: true, env: processEnv }); let chunks = [], err_chunks = []; console.log('Running: ' + cmdline); child.stdout.on('data', chunk => { if (charset.outputCode) chunk = iconv.decode(chunk, charset.outputCode); realTime ? callback(chunk.toString(), null) : chunks.push(chunk); }); child.stderr.on("data", (err_chunk) => { if (charset.outputCode) err_chunk = iconv.decode(err_chunk, charset.outputCode); realTime ? callback(null, err_chunk.toString()) : err_chunks.push(err_chunk); }); if (!realTime) { child.on('close', code => { let stdout = chunks.join(""); let stderr = err_chunks.join(""); callback(stdout, stderr) }) } return child } const dbStorage = utools.dbStorage; let httpServer; window.quickcommandHttpServer = () => { let run = (port = 33442) => { let httpResponse = (res, code, result) => { // 只收受一次 console.log,接收后就关闭连接 if (res.finished) return res.writeHead(code, { 'Content-Type': 'text/html' }); if (result) res.write(result); res.end(); } let runUserCode = (res, userVars) => { let cmd = dbStorage.getItem('cfg_serverCode'); // 不需要返回输出的提前关闭连接 if (!cmd.includes('console.log')) httpResponse(res, 200); window.runCodeInSandbox(cmd, (stdout, stderr) => { // 错误返回 500 if (stderr) return httpResponse(res, 500, stderr.join(" ")); return httpResponse(res, 200, stdout.join(" ")); }, userVars) } httpServer = http.createServer(); httpServer.on('request', (req, res) => { if (req.method === 'GET') { let parsedParams = _.cloneDeep(url.parse(req.url, true).query); runUserCode(res, 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, parsedParams); }) } else { httpResponse(res, 405); } }) httpServer.listen(port, 'localhost'); httpServer.on('error', err => { utools.showNotification('快捷命令服务:', err) }) } let stop = () => { if (!httpServer) return httpServer.close() } return { run, stop } }