/** * 本插件使用开源项目:https://github.com/GUI-for-Cores/Hotkey-API-Bridge */ const PATH = 'data/third/hotkey-api-bridge' const HOTKEY_FILE = PATH + '/hotkey.json' const BIN_FILE = PATH + '/hotkey-api-bridge.exe' const PID_FILE = PATH + '/pid.txt' /* 触发器 启动APP时 */ const onStartup = async () => { return await Start().catch((err) => { console.log(`[${Plugin.name}]`, '启动进程失败', err) return 2 }) } /* 触发器 关闭APP时 */ const onShutdown = async () => { return await Stop().catch((err) => { console.log(`[${Plugin.name}]`, '停止进程失败', err) return 1 }) } /* 触发器 安装 */ const onInstall = async () => { const envStore = Plugins.useEnvStore() const { os, arch } = envStore.env if (os !== 'windows') { throw '此插件不支持非Windows系统' } const url = `https://github.com/GUI-for-Cores/Hotkey-API-Bridge/releases/download/v1.0.0/Hotkey-API-Bridge-windows-${arch}.exe` const { update, destroy } = Plugins.message.info('正在下载...', 99999) try { await Plugins.Download(url, BIN_FILE, {}, (c, t) => { update('正在下载...' + ((c / t) * 100).toFixed(2) + '%') }) } finally { destroy() } Plugins.message.success('安装成功') return 0 } /* 触发器 卸载 */ const onUninstall = async () => { if (await isServiceRunning()) { throw '请先停止插件' } await Plugins.RemoveFile(PATH) return 0 } const Start = async () => { if (!Plugin.HOTKEY_API_ADDRESS || !Plugin.HOTKEY_API_TOKEN) { throw '请先配置全局热键后端服务' } const { promise, resolve, reject } = Promise.withResolvers() const pid = await Plugins.ExecBackground( BIN_FILE, ['--address', Plugin.HOTKEY_API_ADDRESS], async (out) => { console.log(`[${Plugin.name}]`, out) if (out.includes('Hotkey manager is ready. Add hotkeys via the API.')) { resolve() } }, async () => { console.log(`[${Plugin.name}]`, '插件停止了') reject() }, { Env: { HOTKEY_API_TOKEN: Plugin.HOTKEY_API_TOKEN } } ) await promise await Plugins.WriteFile(PID_FILE, String(pid)) const localHotkeys = await loadLocalHotKeys() const activeItems = localHotkeys.filter((item) => item.enabled).map((item) => ({ hotkey: item.hotkey, request: item.request })) if (activeItems.length != 0) { try { const res = await addHotkeys(activeItems) console.log(`[${Plugin.name}]`, '启动时恢复热键结果', res) } catch (error) { Plugins.message.error(error.message || error) } } return 1 } const Stop = async () => { const pid = await Plugins.ReadFile(PID_FILE).catch(() => '') if (!pid) { throw '插件并未在运行' } await Plugins.KillProcess(Number(pid)) await Plugins.RemoveFile(PID_FILE) return 2 } /* 触发器 手动触发 */ const onRun = async () => { if (!(await isServiceRunning())) { await Start() } // 运行前移除所有热键,避免配置插件时受影响 await removeHotkeys() const { ref, onMounted, onUnmounted } = Vue const localHotkeys = await loadLocalHotKeys() const backup = Plugins.deepClone(localHotkeys) const hotkeyList = ref(localHotkeys) const component = { template: /* html */ `
{{ item.enabled ? '已启用' : '已禁用' }}
{{ item.title || '无备注' }}
`, setup() { const PresetList = [ { title: '显示窗口', request: { method: 'POST', url: `${Plugin.ApiAddress}/v1/gui/window`, headers: { Authorization: `Bearer ${Plugin.ApiSecret}` }, body: JSON.stringify( { mode: 'show' }, null, 2 ) } }, { title: '隐藏窗口', request: { method: 'POST', url: `${Plugin.ApiAddress}/v1/gui/window`, headers: { Authorization: `Bearer ${Plugin.ApiSecret}` }, body: JSON.stringify( { mode: 'hide' }, null, 2 ) } }, { title: '启动核心', request: { method: 'POST', url: `${Plugin.ApiAddress}/v1/cores/start`, headers: { Authorization: `Bearer ${Plugin.ApiSecret}` }, body: '' } }, { title: '停止核心', request: { method: 'POST', url: `${Plugin.ApiAddress}/v1/cores/stop`, headers: { Authorization: `Bearer ${Plugin.ApiSecret}` }, body: '' } }, { title: '重启核心', request: { method: 'POST', url: `${Plugin.ApiAddress}/v1/cores/restart`, headers: { Authorization: `Bearer ${Plugin.ApiSecret}` }, body: '' } }, { title: '直连模式', request: { method: 'POST', url: `${Plugin.ApiAddress}/v1/cores/mode`, headers: { Authorization: `Bearer ${Plugin.ApiSecret}` }, body: JSON.stringify( { mode: 'direct' }, null, 2 ) } }, { title: '规则模式', request: { method: 'POST', url: `${Plugin.ApiAddress}/v1/cores/mode`, headers: { Authorization: `Bearer ${Plugin.ApiSecret}` }, body: JSON.stringify( { mode: 'rule' }, null, 2 ) } }, { title: '全局模式', request: { method: 'POST', url: `${Plugin.ApiAddress}/v1/cores/mode`, headers: { Authorization: `Bearer ${Plugin.ApiSecret}` }, body: JSON.stringify( { mode: 'global' }, null, 2 ) } }, { title: '设置系统代理', request: { method: 'POST', url: `${Plugin.ApiAddress}/v1/gui/systemproxy`, headers: { Authorization: `Bearer ${Plugin.ApiSecret}` }, body: JSON.stringify( { mode: 'set' }, null, 2 ) } }, { title: '清除系统代理', request: { method: 'POST', url: `${Plugin.ApiAddress}/v1/gui/systemproxy`, headers: { Authorization: `Bearer ${Plugin.ApiSecret}` }, body: JSON.stringify( { mode: 'clear' }, null, 2 ) } }, { title: '开启TUN模式', request: { method: 'POST', url: `${Plugin.ApiAddress}/v1/cores/tun`, headers: { Authorization: `Bearer ${Plugin.ApiSecret}` }, body: JSON.stringify( { mode: 'on' }, null, 2 ) } }, { title: '关闭TUN模式', request: { method: 'POST', url: `${Plugin.ApiAddress}/v1/cores/tun`, headers: { Authorization: `Bearer ${Plugin.ApiSecret}` }, body: JSON.stringify( { mode: 'off' }, null, 2 ) } } ] let tmpItem const onKeyDown = async (e) => { const parts = [] if (e.ctrlKey) parts.push('Ctrl') if (e.shiftKey) parts.push('Shift') if (e.altKey) parts.push('Alt') if (['Control', 'Shift', 'Alt', 'Meta'].includes(e.key)) { tmpItem.hotkey = parts.join('+') return } if (/^[a-z]$/.test(e.key)) { parts.push(String(e.key).toUpperCase()) } else { parts.push(String(e.key)) } tmpItem.hotkey = parts.join('+') } const onClick = async () => { tmpItem.entering = false window.removeEventListener('keydown', onKeyDown) window.removeEventListener('click', onClick) } onUnmounted(() => { window.removeEventListener('keydown', onKeyDown) window.removeEventListener('click', onClick) }) return { PresetList, hotkeyList, async add() { hotkeyList.value.push({ id: Plugins.sampleID(), title: '', enabled: true, hidden: false, entering: false, hotkey: '', request: { method: 'GET', url: '', headers: {}, body: '' } }) }, async del(item, index) { hotkeyList.value.splice(index, 1) }, handleAdd(item) { if (!Plugin.ApiAddress || !Plugin.ApiSecret) { Plugins.message.error('请先配置插件ApiAddress/ApiSecret') return } hotkeyList.value.push({ id: Plugins.sampleID(), title: item.title, enabled: true, hidden: true, entering: false, hotkey: '', request: Plugins.deepClone(item.request) }) }, handleEnter(item) { if (tmpItem) { tmpItem.entering = false } tmpItem = item item.entering = true window.addEventListener('keydown', onKeyDown) window.addEventListener('click', onClick) } } } } const modal = Plugins.modal( { title: Plugin.name, minWidth: '80', onCancel: async () => { const activeItems = backup.filter((item) => item.enabled).map((item) => ({ hotkey: item.hotkey, request: item.request })) if (activeItems.length != 0) { try { const res = await addHotkeys(activeItems) console.log(`[${Plugin.name}]`, '恢复热键结果', res) } catch (error) { Plugins.message.error(error.message || error) return } } }, onOk: async () => { if (hotkeyList.value.some((item) => item.enabled && (!item.hotkey || !item.request.url))) { Plugins.message.error('部分已激活项缺少热键或请求URL,请检查') return false } const activeItems = hotkeyList.value.filter((item) => item.enabled).map((item) => ({ hotkey: item.hotkey, request: item.request })) if (activeItems.length != 0) { try { const res = await addHotkeys(activeItems) console.log(`[${Plugin.name}]`, '更新热键结果', res) } catch (error) { Plugins.message.error(error.message || error) return } } await Plugins.WriteFile(HOTKEY_FILE, JSON.stringify(hotkeyList.value, null, 4)) }, beforeClose() { modal.destroy() } }, { default: () => Vue.h(component) } ) modal.open() return 1 } const isServiceRunning = async () => { const pid = await Plugins.ReadFile(PID_FILE).catch(() => '') if (pid) { const name = await Plugins.ProcessInfo(Number(pid)).catch(() => '') return name.startsWith('hotkey-api-bridge') } return false } // 加载本地热键配置 const loadLocalHotKeys = async () => { const res = await Plugins.ReadFile(HOTKEY_FILE).catch(() => '[]') return JSON.parse(res) } // 获取所有已注册的热键 const getHotkeys = async () => { const res = await Plugins.HttpGet(`http://127.0.0.1:32325/hotkeys`, { Authorization: `Bearer ${Plugin.HOTKEY_API_TOKEN}` }) console.log(`[${Plugin.name}]`, 'getHotkeys', res) return res.body } // 移除所有热键 const removeHotkeys = async () => { const res = await Plugins.HttpDelete('http://127.0.0.1:32325/hotkeys', { 'Content-Type': 'application/json', Authorization: `Bearer ${Plugin.HOTKEY_API_TOKEN}` }) console.log(`[${Plugin.name}]`, 'removeHotkeys', res) if (res.status !== 200) { throw res.body } return res.body } // 批量添加热键 const addHotkeys = async (body) => { const res = await Plugins.HttpPost( 'http://127.0.0.1:32325/hotkeys', { 'Content-Type': 'application/json', Authorization: `Bearer ${Plugin.HOTKEY_API_TOKEN}` }, body ) console.log(`[${Plugin.name}]`, 'addHotkeys', res) if (res.status !== 207) { throw res.body } return res.body }