diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a23fc28..0226340b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,5 +8,6 @@ QQ Version: Windows 9.9.9-23361 / Linux 3.2.7-23361 ## 新增与调整 * 再一次对获取Cookies与获取群成员优化,分别添加30/60/120分钟缓存 -* +* 新增 WebUi 支持远程配置设置 详细参考官方教程 + 新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api) diff --git a/package.json b/package.json index eecc6251..e107c502 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "build:prod": "vite build --mode production", "build": "npm run build:dev", "build:core": "cd ./src/core && npm run build && cd ../.. && node ./script/copy-core.cjs", + "build:webui": "cd ./src/webui && vite build", "watch": "npm run watch:dev", "debug-win": "powershell dist/napcat.ps1", "lint": "eslint --fix src/**/*.{js,ts}", diff --git a/src/index.ts b/src/index.ts index fa3272f6..3ed44336 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,15 +8,16 @@ import { checkVersion } from '@/common/utils/version'; import { log, logDebug, logError, LogLevel, setLogLevel } from '@/common/utils/log'; import { NapCatOnebot11 } from '@/onebot11/main'; import { hookApi } from '@/core/external/hook'; - +import { InitWebUi } from './webui/index'; program .option('-q, --qq ', 'QQ号') .parse(process.argv); +InitWebUi(); const cmdOptions = program.opts(); // console.log(process.argv); -for(let k=0; k<30; k++) { +for (let k = 0; k < 30; k++) { new Promise((r, j) => { hookApi.getRKey(); }).then(); @@ -61,8 +62,8 @@ const quickLoginQQ = cmdOptions.qq; // }); if (quickLoginQQ) { log('正在快速登录 ', quickLoginQQ); - napCatCore.quickLogin(quickLoginQQ).then(res=>{ - if (res.loginErrorInfo.errMsg){ + napCatCore.quickLogin(quickLoginQQ).then(res => { + if (res.loginErrorInfo.errMsg) { logError('快速登录错误:', res.loginErrorInfo.errMsg); } }).catch((e) => { diff --git a/src/webui/index.ts b/src/webui/index.ts new file mode 100644 index 00000000..cf907f74 --- /dev/null +++ b/src/webui/index.ts @@ -0,0 +1,12 @@ +import express from 'express'; +import { resolve } from 'node:path'; +const app = express() +export async function InitWebUi() { + app.use(express.json()) + app.use('/webui', express.static(resolve(__dirname, './static'))) + // 启动WebUi + app.listen(6099, async () => { + console.log(`WebUi is running at IP:6099`) + }) + +} diff --git a/src/webui/src/NapCat.ts b/src/webui/src/NapCat.ts new file mode 100644 index 00000000..cfaefaf1 --- /dev/null +++ b/src/webui/src/NapCat.ts @@ -0,0 +1,289 @@ +import { SettingList } from "./components/SettingList"; +import { SettingItem } from "./components/SettingItem"; +import { SettingButton } from "./components/SettingButton"; +import { SettingSwitch } from "./components/SettingSwitch"; +import { SettingSelect } from "./components/SettingSelect"; +import { WebUiApi } from "./components/WebApi" +async function onSettingWindowCreated(view: Element) { + const isEmpty = (value: any) => value === undefined || value === undefined || value === ''; + let ob11Config = await WebUiApi.getOB11Config(); + const setOB11Config = (key: string, value: any) => { + } + + const parser = new DOMParser(); + const doc = parser.parseFromString( + [ + '
', + ` +
+
`, + SettingList([ + SettingItem( + '正在检查 Napcat 更新', + undefined, + SettingButton('请稍候', 'napcat-update-button', 'secondary'), + ), + ]), + SettingList([ + SettingItem( + '启用 HTTP 服务', + undefined, + SettingSwitch('ob11.enableHttp', ob11Config.enableHttp, { 'control-display-id': 'config-ob11-httpPort' }), + ), + SettingItem( + 'HTTP 服务监听端口', + undefined, + `
`, + 'config-ob11-httpPort', + ob11Config.enableHttp, + ), + SettingItem( + '启用 HTTP 心跳', + undefined, + SettingSwitch('ob11.enableHttpHeart', ob11Config.enableHttpHeart, { + 'control-display-id': 'config-ob11-enableHttpHeart', + }), + ), + SettingItem( + '启用 HTTP 事件上报', + undefined, + SettingSwitch('ob11.enableHttpPost', ob11Config.enableHttpPost, { + 'control-display-id': 'config-ob11-httpHosts', + }), + ), + `
+ +
+ HTTP 事件上报密钥 +
+
+ +
+
+ +
+ HTTP 事件上报地址 +
+ 添加 +
+
+
`, + SettingItem( + '启用正向 WebSocket 服务', + undefined, + SettingSwitch('ob11.enableWs', ob11Config.enableWs, { 'control-display-id': 'config-ob11-wsPort' }), + ), + SettingItem( + '正向 WebSocket 服务监听端口', + undefined, + `
`, + 'config-ob11-wsPort', + ob11Config.enableWs, + ), + SettingItem( + '启用反向 WebSocket 服务', + undefined, + SettingSwitch('ob11.enableWsReverse', ob11Config.enableWsReverse, { + 'control-display-id': 'config-ob11-wsHosts', + }), + ), + `
+ +
+ 反向 WebSocket 监听地址 +
+ 添加 +
+
+
`, + SettingItem( + ' WebSocket 服务心跳间隔', + '控制每隔多久发送一个心跳包,单位为毫秒', + `
`, + ), + SettingItem( + 'Access token', + undefined, + `
`, + ), + SettingItem( + '新消息上报格式', + '如客户端无特殊需求推荐保持默认设置,两者的详细差异可参考 OneBot v11 文档', + SettingSelect( + [ + { text: '消息段', value: 'array' }, + { text: 'CQ码', value: 'string' }, + ], + 'ob11.messagePostFormat', + ob11Config.messagePostFormat, + ), + ), + SettingItem( + '音乐卡片签名地址', + undefined, + `
`, + 'config-musicSignUrl', + ), + SettingItem('', undefined, SettingButton('保存', 'config-ob11-save', 'primary')), + ]), + SettingList([ + SettingItem( + '上报 Bot 自身发送的消息', + '上报 event 为 message_sent', + SettingSwitch('reportSelfMessage', ob11Config.reportSelfMessage), + ) + ]), + SettingList([ + SettingItem('GitHub 仓库', `https://github.com/`, SettingButton('点个星星', 'open-github')), + SettingItem('NapCat 文档', `https://`, SettingButton('看看文档', 'open-docs')), + SettingItem('Telegram 群', `https://t.me/+nLZEnpne-pQ1OWFl`, SettingButton('进去逛逛', 'open-telegram')), + SettingItem('QQ 群', `545402644`, SettingButton('我要进去', 'open-qq-group')), + ]), + '
', + ].join(''), + 'text/html', + ) + + // 外链按钮 + doc.querySelector('#open-github')?.addEventListener('click', () => { + window.open("https://github.com/", '_blank'); + }) + doc.querySelector('#open-telegram')?.addEventListener('click', () => { + window.open('https://t.me/+nLZEnpne-pQ1OWFl') + }) + doc.querySelector('#open-qq-group')?.addEventListener('click', () => { + window.open('https://qm.qq.com/q/bDnHRG38aI') + }) + doc.querySelector('#open-docs')?.addEventListener('click', () => { + window.open('https://github.io/') + }) + // 生成反向地址列表 + const buildHostListItem = (type: string, host: string, index: number, inputAttrs: any = {}) => { + const dom = { + container: document.createElement('setting-item'), + input: document.createElement('input'), + inputContainer: document.createElement('div'), + deleteBtn: document.createElement('setting-button'), + } + dom.container.classList.add('setting-host-list-item') + dom.container.dataset.direction = 'row' + Object.assign(dom.input, inputAttrs) + dom.input.classList.add('q-input__inner') + dom.input.type = 'url' + dom.input.value = host + dom.input.addEventListener('input', () => { + ob11Config[type][index] = dom.input.value + }) + + dom.inputContainer.classList.add('q-input') + dom.inputContainer.appendChild(dom.input) + + dom.deleteBtn.innerHTML = '删除' + dom.deleteBtn.dataset.type = 'secondary' + dom.deleteBtn.addEventListener('click', () => { + ob11Config[type].splice(index, 1) + initReverseHost(type) + }) + + dom.container.appendChild(dom.inputContainer) + dom.container.appendChild(dom.deleteBtn) + + return dom.container + } + const buildHostList = (hosts: string[], type: string, inputAttr: any = {}) => { + const result: HTMLElement[] = [] + + hosts.forEach((host, index) => { + result.push(buildHostListItem(type, host, index, inputAttr)) + }) + + return result + } + const addReverseHost = (type: string, doc: Document = document, inputAttr: any = {}) => { + const hostContainerDom = doc.body.querySelector(`#config-ob11-${type}-list`); + hostContainerDom?.appendChild(buildHostListItem(type, '', ob11Config[type].length, inputAttr)); + ob11Config[type].push(''); + } + const initReverseHost = (type: string, doc: Document = document) => { + const hostContainerDom = doc.body?.querySelector(`#config-ob11-${type}-list`); + /*[...hostContainerDom.childNodes].forEach((dom) => dom.remove()); + buildHostList(ob11Config[type], type).forEach((dom) => { + hostContainerDom?.appendChild(dom); + })*/ + } + initReverseHost('httpHosts', doc); + initReverseHost('wsHosts', doc); + + doc + .querySelector('#config-ob11-httpHosts-add') + ?.addEventListener('click', () => + addReverseHost('httpHosts', document, { placeholder: '如:http://127.0.0.1:5140/onebot' }), + ) + doc + .querySelector('#config-ob11-wsHosts-add') + ?.addEventListener('click', () => + addReverseHost('wsHosts', document, { placeholder: '如:ws://127.0.0.1:5140/onebot' }), + ) + + doc.querySelector('#config-ffmpeg-select')?.addEventListener('click', () => { + //选择ffmpeg + }) + + doc.querySelector('#config-open-log-path')?.addEventListener('click', () => { + //打开日志 + }) + + // 开关 + doc.querySelectorAll('setting-switch[data-config-key]').forEach((dom: Element) => { + dom.addEventListener('click', () => { + const active = dom.getAttribute('is-active') === undefined + //@ts-ignore 等待修复 + setOB11Config(dom.dataset.configKey, active) + if (active) dom.setAttribute('is-active', '') + else dom.removeAttribute('is-active') + //@ts-ignore 等待修复 + if (!isEmpty(dom.dataset.controlDisplayId)) { + //@ts-ignore 等待修复 + const displayDom = document.querySelector(`#${dom.dataset.controlDisplayId}`) + if (active) displayDom?.removeAttribute('is-hidden') + else displayDom?.setAttribute('is-hidden', '') + } + }) + }) + + // 输入框 + doc + .querySelectorAll('setting-item .q-input input.q-input__inner[data-config-key]') + .forEach((dom: Element) => { + dom.addEventListener('input', () => { + const Type = dom.getAttribute('type') + //@ts-ignore 等待修复 + const configKey = dom.dataset.configKey + const configValue = Type === 'number' ? (parseInt((dom as HTMLInputElement).value) >= 1 ? parseInt((dom as HTMLInputElement).value) : 1) : (dom as HTMLInputElement).value + + setOB11Config(configKey, configValue) + }) + }) + + // 下拉框 + doc.querySelectorAll('ob-setting-select[data-config-key]').forEach((dom: Element) => { + //@ts-ignore 等待修复 + dom?.addEventListener('selected', (e: CustomEvent) => { + //@ts-ignore 等待修复 + const configKey = dom.dataset.configKey + const configValue = e.detail.value + setOB11Config(configKey, configValue); + }) + }) + + // 保存按钮 + doc.querySelector('#config-ob11-save')?.addEventListener('click', () => { + WebUiApi.setOB11Config(ob11Config); + alert('保存成功'); + }) + doc.body.childNodes.forEach((node) => { + view.appendChild(node) + }) +} +export { onSettingWindowCreated }; \ No newline at end of file diff --git a/static/components/SettingButton.ts b/src/webui/src/components/SettingButton.ts similarity index 100% rename from static/components/SettingButton.ts rename to src/webui/src/components/SettingButton.ts diff --git a/static/components/SettingItem.ts b/src/webui/src/components/SettingItem.ts similarity index 100% rename from static/components/SettingItem.ts rename to src/webui/src/components/SettingItem.ts diff --git a/static/components/SettingList.ts b/src/webui/src/components/SettingList.ts similarity index 100% rename from static/components/SettingList.ts rename to src/webui/src/components/SettingList.ts diff --git a/static/components/SettingOption.ts b/src/webui/src/components/SettingOption.ts similarity index 100% rename from static/components/SettingOption.ts rename to src/webui/src/components/SettingOption.ts diff --git a/static/components/SettingSelect.ts b/src/webui/src/components/SettingSelect.ts similarity index 90% rename from static/components/SettingSelect.ts rename to src/webui/src/components/SettingSelect.ts index e5627517..0831d8be 100644 --- a/static/components/SettingSelect.ts +++ b/src/webui/src/components/SettingSelect.ts @@ -1,4 +1,4 @@ -import { SettingOption } from './option' +import { SettingOption } from './SettingOption' interface MouseEventExtend extends MouseEvent { target: HTMLElement @@ -30,15 +30,15 @@ window.customElements.define( super() this.attachShadow({ mode: 'open' }) - this.shadowRoot.append(SelectTemplate.content.cloneNode(true)) + this.shadowRoot?.append(SelectTemplate.content.cloneNode(true)); - this._button = this.shadowRoot.querySelector('div[part="button"]') - this._text = this.shadowRoot.querySelector('input[part="current-text"]') - this._context = this.shadowRoot.querySelector('ul[part="option-list"]') + this._button = this.shadowRoot.querySelector('div[part="button"]'); + this._text = this.shadowRoot.querySelector('input[part="current-text"]'); + this._context = this.shadowRoot.querySelector('ul[part="option-list"]'); const buttonClick = () => { - const isHidden = this._context.classList.toggle('hidden') - window[`${isHidden ? 'remove' : 'add'}EventListener`]('pointerdown', windowPointerDown) + const isHidden = this._context.classList.toggle('hidden'); + window[`${isHidden ? 'remove' : 'add'}EventListener`]('pointerdown', windowPointerDown); } const windowPointerDown = ({ target }) => { @@ -55,7 +55,7 @@ window.customElements.define( this.querySelectorAll('setting-option[is-selected]').forEach((dom) => dom.toggleAttribute('is-selected')) target.toggleAttribute('is-selected') - this._text.value = target.textContent + this._text.value = target.textContent as string; this.dispatchEvent( new CustomEvent('selected', { bubbles: true, @@ -68,7 +68,7 @@ window.customElements.define( ) }) - this._text.value = this.querySelector('setting-option[is-selected]').textContent + this._text.value = this.querySelector('setting-option[is-selected]')?.textContent as string; } }, ) diff --git a/static/components/SettingSwitch.ts b/src/webui/src/components/SettingSwitch.ts similarity index 100% rename from static/components/SettingSwitch.ts rename to src/webui/src/components/SettingSwitch.ts diff --git a/src/webui/src/components/WebApi.ts b/src/webui/src/components/WebApi.ts new file mode 100644 index 00000000..d5ed7ab6 --- /dev/null +++ b/src/webui/src/components/WebApi.ts @@ -0,0 +1,53 @@ +export interface OB11Config { + httpHost: "", + httpPort: number; + httpPostUrls: string[]; + httpSecret: "", + wsHost: "", + wsPort: number; + wsReverseUrls: string[]; + enableHttp: boolean; + enableHttpHeart: boolean; + enableHttpPost: boolean; + enableWs: boolean; + enableWsReverse: boolean; + messagePostFormat: 'array' | 'string'; + reportSelfMessage: boolean; + enableLocalFile2Url: boolean; + debug: boolean; + heartInterval: number; + token: "", + musicSignUrl: "", +} + +class WebUiApiWrapper { + token: string = ""; + public async setOB11Config(config: OB11Config) { + + } + public async getOB11Config(): Promise { + // 返回示例配置 + return { + httpHost: "", + httpPort: 3000, + httpPostUrls: [], + httpSecret: "", + wsHost: "", + wsPort: 3000, + wsReverseUrls: [], + enableHttp: false, + enableHttpHeart: false, + enableHttpPost: false, + enableWs: false, + enableWsReverse: false, + messagePostFormat: 'array', + reportSelfMessage: false, + enableLocalFile2Url: false, + debug: false, + heartInterval: 60000, + token: "", + musicSignUrl: "", + }; + } +} +export const WebUiApi = new WebUiApiWrapper(); \ No newline at end of file diff --git a/src/webui/vite.config.ts b/src/webui/vite.config.ts new file mode 100644 index 00000000..bfdea4d2 --- /dev/null +++ b/src/webui/vite.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'vite' + +export default defineConfig({ + build:{ + target: 'esnext', + minify: false, + lib: { + entry: 'src/NapCat.ts', + formats: ['es'], + fileName: () => 'renderer.js', + } + } +}); \ No newline at end of file diff --git a/static/assets/NapCat.css b/static/assets/NapCat.css index e69de29b..f82825c6 100644 --- a/static/assets/NapCat.css +++ b/static/assets/NapCat.css @@ -0,0 +1,180 @@ +setting-item[is-hidden], +setting-item[is-hidden] + setting-divider { + display: none !important; +} + +.config-host-list { + width: 100%; + padding-left: 16px; + box-sizing: border-box; +} +.config-host-list[is-hidden], +.config-host-list[is-hidden] + setting-divider { + display: none !important; +} + +setting-item .q-input { + height: 24px; + width: 100px; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; + box-sizing: border-box; + position: relative; + background: var(--bg_bottom_light); + border: 1px solid var(--border_dark); +} + +setting-item .q-input .q-input__inner { + border-top-left-radius: 4px; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; + box-sizing: border-box; + color: var(--text_primary); + font-family: inherit; + font-size: 12px; + height: 24px; + line-height: 24px; + width: 100%; + border: 1px solid transparent; + padding: 0px 8px; +} + +setting-item .q-input input[type='number'].q-input__inner::-webkit-outer-spin-button, +setting-item .q-input input[type='number'].q-input__inner::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} + +.config-host-list setting-item.setting-host-list-item .q-input { + width: 260px; +} + +setting-item a { + color: var(--text-link); +} +setting-item a:hover { + color: var(--hover-link); +} +setting-item a:active, +setting-item a:visited { + color: var(--text-link); +} + +ob-setting-select { + width: 100px; +} + +ob-setting-select, +ob-setting-select::part(parent), +ob-setting-select::part(button) { + display: block; + position: relative; + height: 24px; + font-size: 12px; + line-height: 24px; + box-sizing: border-box; +} + +ob-setting-select::part(button) { + display: flex; + padding: 0px 8px; + background-color: transparent; + border-radius: 4px; + border: 1px solid var(--border_dark); + z-index: 5; + cursor: default; + align-items: center; + flex-direction: row; + flex-wrap: nowrap; +} + +ob-setting-select::part(current-text) { + display: block; + margin-right: 8px; + padding: 0px; + background: none; + background-color: transparent; + font-size: 12px; + color: var(--text_primary); + text-overflow: ellipsis; + border-radius: 0px; + border: none; + outline: none; + overflow: hidden; + appearance: none; + box-sizing: border-box; + cursor: default; + flex: 1; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; + -webkit-pointer-events: none; + -moz-pointer-events: none; + -ms-pointer-events: none; + -o-pointer-events: none; + pointer-events: none; +} + +ob-setting-select::part(button-arrow) { + position: relative; + display: block; + width: 16px; + height: 16px; + color: var(--icon_primary); +} + +ob-setting-select::part(option-list) { + display: flex; + position: absolute; + top: 100%; + padding: 4px; + margin: 5px 0px; + width: 100%; + max-height: var(--q-contextmenu-max-height); + background-color: var(--blur_middle_standard); + background-clip: padding-box; + backdrop-filter: blur(8px); + font-size: 12px; + box-shadow: var(--shadow_bg_middle_secondary); + border: 1px solid var(--border_secondary); + border-radius: 4px; + box-sizing: border-box; + app-region: no-drag; + overflow-x: hidden; + overflow-y: auto; + list-style: none; + z-index: 999; + flex-direction: column; + align-items: stretch; + flex-wrap: nowrap; + justify-content: flex-start; + gap: 4px; +} + +#napcat-error { + display: none; +} + +#napcat-error setting-panel { + background: rgba(255, 0, 0, 0.5); + color: white; +} + +#napcat-error setting-panel pre { + margin: 0; + padding: 16px; + box-sizing: border-box; +} + +#napcat-error setting-panel pre code { + font-family: 'FiraCode Nerd Font', 'Fira Code', 'Cascadia Code', Consolas, 'Courier New', monospace; +} + +#napcat-error.show { + display: block; +} \ No newline at end of file diff --git a/static/assets/renderer.js b/static/assets/renderer.js new file mode 100644 index 00000000..aa2162c2 --- /dev/null +++ b/static/assets/renderer.js @@ -0,0 +1,374 @@ +const SettingList = (items, title, isCollapsible = false, direction = "column") => { + return ` + + + ${items.join("")} + + + `; +}; + +const SettingItem = (title, subtitle, action, id, visible = true) => { + return ` +
+ ${title} + ${subtitle ? `${subtitle}` : ""} +
+ ${action ? `
${action}
` : ""} +
`; +}; + +const SettingButton = (text, id, type = "secondary") => { + return `${text}`; +}; + +const SettingSwitch = (configKey, isActive = false, extraData) => { + return ` `data-${key}="${extraData[key]}"`) : ""} + > + `; +}; + +const SettingOption = (text, value, isSelected = false) => { + return `${text}`; +}; + +const SelectTemplate = document.createElement("template"); +SelectTemplate.innerHTML = ` +
+
+ + + + +
+ +
`; +window.customElements.define( + "ob-setting-select", + class extends HTMLElement { + _button; + _text; + _context; + constructor() { + super(); + this.attachShadow({ mode: "open" }); + this.shadowRoot?.append(SelectTemplate.content.cloneNode(true)); + this._button = this.shadowRoot.querySelector('div[part="button"]'); + this._text = this.shadowRoot.querySelector('input[part="current-text"]'); + this._context = this.shadowRoot.querySelector('ul[part="option-list"]'); + const buttonClick = () => { + const isHidden = this._context.classList.toggle("hidden"); + window[`${isHidden ? "remove" : "add"}EventListener`]("pointerdown", windowPointerDown); + }; + const windowPointerDown = ({ target }) => { + if (!this.contains(target)) + buttonClick(); + }; + this._button.addEventListener("click", buttonClick); + this._context.addEventListener("click", ({ target }) => { + if (target.tagName !== "SETTING-OPTION") + return; + buttonClick(); + if (target.hasAttribute("is-selected")) + return; + this.querySelectorAll("setting-option[is-selected]").forEach((dom) => dom.toggleAttribute("is-selected")); + target.toggleAttribute("is-selected"); + this._text.value = target.textContent; + this.dispatchEvent( + new CustomEvent("selected", { + bubbles: true, + composed: true, + detail: { + name: target.textContent, + value: target.dataset.value + } + }) + ); + }); + this._text.value = this.querySelector("setting-option[is-selected]")?.textContent; + } + } +); +const SettingSelect = (items, configKey, configValue) => { + return ` + ${items.map((e, i) => { + return SettingOption(e.text, e.value, configKey && configValue ? configValue === e.value : i === 0); + }).join("")} +`; +}; + +class WebUiApiWrapper { + token = ""; + async setOB11Config(config) { + } + async getOB11Config() { + return { + httpHost: "", + httpPort: 3e3, + httpPostUrls: [], + httpSecret: "", + wsHost: "", + wsPort: 3e3, + wsReverseUrls: [], + enableHttp: false, + enableHttpHeart: false, + enableHttpPost: false, + enableWs: false, + enableWsReverse: false, + messagePostFormat: "array", + reportSelfMessage: false, + enableLocalFile2Url: false, + debug: false, + heartInterval: 6e4, + token: "", + musicSignUrl: "" + }; + } +} +const WebUiApi = new WebUiApiWrapper(); + +async function onSettingWindowCreated(view) { + const isEmpty = (value) => value === void 0 || value === void 0 || value === ""; + let ob11Config = await WebUiApi.getOB11Config(); + const setOB11Config = (key, value) => { + }; + const parser = new DOMParser(); + const doc = parser.parseFromString( + [ + "
", + ` +
+
`, + SettingList([ + SettingItem( + '正在检查 Napcat 更新', + void 0, + SettingButton("请稍候", "napcat-update-button", "secondary") + ) + ]), + SettingList([ + SettingItem( + "启用 HTTP 服务", + void 0, + SettingSwitch("ob11.enableHttp", ob11Config.enableHttp, { "control-display-id": "config-ob11-httpPort" }) + ), + SettingItem( + "HTTP 服务监听端口", + void 0, + `
`, + "config-ob11-httpPort", + ob11Config.enableHttp + ), + SettingItem( + "启用 HTTP 心跳", + void 0, + SettingSwitch("ob11.enableHttpHeart", ob11Config.enableHttpHeart, { + "control-display-id": "config-ob11-enableHttpHeart" + }) + ), + SettingItem( + "启用 HTTP 事件上报", + void 0, + SettingSwitch("ob11.enableHttpPost", ob11Config.enableHttpPost, { + "control-display-id": "config-ob11-httpHosts" + }) + ), + `
+ +
+ HTTP 事件上报密钥 +
+
+ +
+
+ +
+ HTTP 事件上报地址 +
+ 添加 +
+
+
`, + SettingItem( + "启用正向 WebSocket 服务", + void 0, + SettingSwitch("ob11.enableWs", ob11Config.enableWs, { "control-display-id": "config-ob11-wsPort" }) + ), + SettingItem( + "正向 WebSocket 服务监听端口", + void 0, + `
`, + "config-ob11-wsPort", + ob11Config.enableWs + ), + SettingItem( + "启用反向 WebSocket 服务", + void 0, + SettingSwitch("ob11.enableWsReverse", ob11Config.enableWsReverse, { + "control-display-id": "config-ob11-wsHosts" + }) + ), + `
+ +
+ 反向 WebSocket 监听地址 +
+ 添加 +
+
+
`, + SettingItem( + " WebSocket 服务心跳间隔", + "控制每隔多久发送一个心跳包,单位为毫秒", + `
` + ), + SettingItem( + "Access token", + void 0, + `
` + ), + SettingItem( + "新消息上报格式", + `如客户端无特殊需求推荐保持默认设置,两者的详细差异可参考 OneBot v11 文档`, + SettingSelect( + [ + { text: "消息段", value: "array" }, + { text: "CQ码", value: "string" } + ], + "ob11.messagePostFormat", + ob11Config.messagePostFormat + ) + ), + SettingItem( + "音乐卡片签名地址", + void 0, + `
`, + "config-musicSignUrl" + ), + SettingItem("", void 0, SettingButton("保存", "config-ob11-save", "primary")) + ]), + SettingList([ + SettingItem( + "上报 Bot 自身发送的消息", + "上报 event 为 message_sent", + SettingSwitch("reportSelfMessage", ob11Config.reportSelfMessage) + ) + ]), + SettingList([ + SettingItem("GitHub 仓库", `https://github.com/`, SettingButton("点个星星", "open-github")), + SettingItem("NapCat 文档", `https://`, SettingButton("看看文档", "open-docs")), + SettingItem("Telegram 群", `https://t.me/+nLZEnpne-pQ1OWFl`, SettingButton("进去逛逛", "open-telegram")), + SettingItem("QQ 群", `545402644`, SettingButton("我要进去", "open-qq-group")) + ]), + "
" + ].join(""), + "text/html" + ); + doc.querySelector("#open-github")?.addEventListener("click", () => { + window.open("https://github.com/", "_blank"); + }); + doc.querySelector("#open-telegram")?.addEventListener("click", () => { + window.open("https://t.me/+nLZEnpne-pQ1OWFl"); + }); + doc.querySelector("#open-qq-group")?.addEventListener("click", () => { + window.open("https://qm.qq.com/q/bDnHRG38aI"); + }); + doc.querySelector("#open-docs")?.addEventListener("click", () => { + window.open("https://github.io/"); + }); + const buildHostListItem = (type, host, index, inputAttrs = {}) => { + const dom = { + container: document.createElement("setting-item"), + input: document.createElement("input"), + inputContainer: document.createElement("div"), + deleteBtn: document.createElement("setting-button") + }; + dom.container.classList.add("setting-host-list-item"); + dom.container.dataset.direction = "row"; + Object.assign(dom.input, inputAttrs); + dom.input.classList.add("q-input__inner"); + dom.input.type = "url"; + dom.input.value = host; + dom.input.addEventListener("input", () => { + ob11Config[type][index] = dom.input.value; + }); + dom.inputContainer.classList.add("q-input"); + dom.inputContainer.appendChild(dom.input); + dom.deleteBtn.innerHTML = "删除"; + dom.deleteBtn.dataset.type = "secondary"; + dom.deleteBtn.addEventListener("click", () => { + ob11Config[type].splice(index, 1); + initReverseHost(type); + }); + dom.container.appendChild(dom.inputContainer); + dom.container.appendChild(dom.deleteBtn); + return dom.container; + }; + const addReverseHost = (type, doc2 = document, inputAttr = {}) => { + const hostContainerDom = doc2.body.querySelector(`#config-ob11-${type}-list`); + hostContainerDom?.appendChild(buildHostListItem(type, "", ob11Config[type].length, inputAttr)); + ob11Config[type].push(""); + }; + const initReverseHost = (type, doc2 = document) => { + doc2.body?.querySelector(`#config-ob11-${type}-list`); + }; + initReverseHost("httpHosts", doc); + initReverseHost("wsHosts", doc); + doc.querySelector("#config-ob11-httpHosts-add")?.addEventListener( + "click", + () => addReverseHost("httpHosts", document, { placeholder: "如:http://127.0.0.1:5140/onebot" }) + ); + doc.querySelector("#config-ob11-wsHosts-add")?.addEventListener( + "click", + () => addReverseHost("wsHosts", document, { placeholder: "如:ws://127.0.0.1:5140/onebot" }) + ); + doc.querySelector("#config-ffmpeg-select")?.addEventListener("click", () => { + }); + doc.querySelector("#config-open-log-path")?.addEventListener("click", () => { + }); + doc.querySelectorAll("setting-switch[data-config-key]").forEach((dom) => { + dom.addEventListener("click", () => { + const active = dom.getAttribute("is-active") === void 0; + setOB11Config(dom.dataset.configKey); + if (active) + dom.setAttribute("is-active", ""); + else + dom.removeAttribute("is-active"); + if (!isEmpty(dom.dataset.controlDisplayId)) { + const displayDom = document.querySelector(`#${dom.dataset.controlDisplayId}`); + if (active) + displayDom?.removeAttribute("is-hidden"); + else + displayDom?.setAttribute("is-hidden", ""); + } + }); + }); + doc.querySelectorAll("setting-item .q-input input.q-input__inner[data-config-key]").forEach((dom) => { + dom.addEventListener("input", () => { + const Type = dom.getAttribute("type"); + dom.dataset.configKey; + Type === "number" ? parseInt(dom.value) >= 1 ? parseInt(dom.value) : 1 : dom.value; + }); + }); + doc.querySelectorAll("ob-setting-select[data-config-key]").forEach((dom) => { + dom?.addEventListener("selected", (e) => { + dom.dataset.configKey; + e.detail.value; + }); + }); + doc.querySelector("#config-ob11-save")?.addEventListener("click", () => { + WebUiApi.setOB11Config(ob11Config); + alert("保存成功"); + }); + doc.body.childNodes.forEach((node) => { + view.appendChild(node); + }); +} + +export { onSettingWindowCreated }; diff --git a/static/assets/style.css b/static/assets/style.css new file mode 100644 index 00000000..c52bec45 --- /dev/null +++ b/static/assets/style.css @@ -0,0 +1,201 @@ +body, html { + background: var(--bg_bottom_standard); + color: var(--text_primary); + font-family: "Color Emoji", system-ui, "PingFang SC", PingFangSC-Regular, "Microsoft YaHei", "Hiragino Sans GB", "Heiti SC", "WenQuanYi Micro Hei", Arial, Helvetica, sans-serif, "Apple Braille", "Color Emoji Fix"; + min-height: 100vh; + scroll-behavior: smooth; + width: 100%; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; + + --z_index_popover: 999; + --nt_mix_tokens: nt_brand_standard_2_overlay_hover_brand_2_mix,nt_brand_standard_2_overlay_pressed_brand_2_mix,nt_feedback_error_2_overlay_hover_brand_2_mix,nt_feedback_error_2_overlay_pressed_brand_2_mix,nt_icon_white_2_overlay_hover_2_mix,nt_icon_white_2_overlay_pressed_2_mix,nt_bubble_host_2_overlay_pressed_brand_2_mix,nt_bg_white_2_overlay_pressed_brand_2_mix,nt_bg_white_2_overlay_hover_2_mix,nt_bg_white_2_overlay_pressed_2_mix,nt_fg_white_2_overlay_hover_2_mix,nt_fg_white_2_overlay_pressed_2_mix,nt_icon_red_2_overlay_hover_2_mix,nt_icon_red_2_overlay_pressed_2_mix,nt_fg_grey_standard_2_overlay_hover_2_mix,nt_fg_grey_standard_2_overlay_pressed_2_mix,nt_bubble_guest_2_overlay_pressed_2_mix,nt_icon_primary_2_20_2_alpha,nt_bg_grey_standard_2_95_2_alpha,nt_tag_red_2_20_2_alpha,nt_tag_red_2_25_2_alpha,nt_tag_blue_2_20_2_alpha,nt_tag_blue_2_25_2_alpha,nt_tag_blue_2_10_2_alpha,nt_tag_purple_2_20_2_alpha,nt_brand_standard_2_20_2_alpha,nt_tag_sage_green_2_20_2_alpha,nt_feedback_error_2_20_2_alpha,nt_text_white_2_60_2_alpha,nt_bg_white_2_70_2_alpha,nt_bg_white_2_90_2_alpha,nt_bg_white_2_97_2_alpha,nt_bg_white_2_40_2_alpha,nt_bg_white_2_30_2_alpha,nt_text_white_2_80_2_alpha,nt_brand_standard_2_50_2_alpha,nt_bg_nav_secondary_2_60_2_alpha,nt_bg_nav_2_60_2_alpha,nt_feedback_error_2_10_2_alpha,nt_brand_standard_2_10_2_alpha,nt_on_brand_primary_2_40_2_alpha,nt_text_primary_2_72_2_alpha,nt_text_white_2_72_2_alpha; + } + + a, address, article, aside, b, blockquote, body, div, em, fieldset, footer, form, h1, h2, h3, h4, h5, h6, header, html, i, iframe, img, label, legend, li, main, nav, ol, p, s, section, span, table, tbody, td, tfoot, th, thead, tr, ul { + box-sizing: border-box; + font-size: 100%; + font-style: inherit; + font-weight: inherit; + border: 0px; + margin: 0px; + padding: 0px; + } + + #app { + position: relative; + display: block; + padding: 20px; + min-height: 100vh; + background: transparent; + } + + .fake-bar { + position: fixed; + display: none; + top: 0; + left: 0; + } + + /* ======== Input ======== */ + .q-input { + align-items: center; + border-radius: 4px; + box-sizing: border-box; + color: var(--text_secondary); + display: inline-flex; + position: relative; + width: 100%; + border: 1px solid transparent; + } + + .q-input input, .q-input textarea { + appearance: none; + background-color: transparent; + box-sizing: border-box; + color: var(--text_primary); + flex-grow: 1; + flex-shrink: 1; + flex-basis: 0%; + font-size: 14px; + height: fit-content; + outline-color: initial; + outline-style: none; + outline-width: initial; + resize: none; + width: 100%; + border: none; + } + + /* ======== Switch ======== */ + .q-switch { + background-color: var(--fill_standard_primary); + border-radius: 14px; + box-sizing: border-box; + display: inline-flex; + position: relative; + transition-behavior: normal; + transition-duration: 0.2s; + transition-timing-function: cubic-bezier(0.38, 0, 0.24, 1); + transition-delay: 0s; + transition-property: all; + width: 28px; + padding: 3px; + } + + .q-switch__handle { + border-radius: 5px; + box-shadow: rgba(0, 0, 0, 0.09) 0px 2px 4px; + box-sizing: border-box; + display: inline-block; + height: 10px; + position: relative; + transition-behavior: normal; + transition-duration: 0.2s; + transition-timing-function: cubic-bezier(0.38, 0, 0.24, 1); + transition-delay: 0s; + transition-property: all; + width: 10px; + z-index: 2; + background: var(--icon_white); + } + + .q-switch:not(.is-disabled):hover { + background: var(--fill_standard_secondary); + } + + .q-switch:not(.is-disabled):active { + background: var(--nt_bg_white_2_overlay_pressed_brand_2_mix); + } + .q-switch:not(.is-disabled):active .q-switch__handle { + width: 12px; + } + + .q-switch.is-active { + background-color: var(--brand_standard); + } + + .q-switch.is-active .q-switch__handle { + transform: translateX(12px); + } + + .q-switch.is-active:not(.is-disabled):hover { + background: var(--nt_brand_standard_2_overlay_hover_brand_2_mix); + } + + .q-switch.is-active:not(.is-disabled):active { + background: var(--nt_brand_standard_2_overlay_pressed_brand_2_mix); + } + .q-switch.is-active:not(.is-disabled):active .q-switch__handle { + transform: translateX(10px); + } + + /* ======== Button ======== */ + .q-button { + align-items: center; + background-color: var(--brand_standard); + border-top-left-radius: 4px; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; + display: inline-flex; + font-size: 14px; + justify-content: center; + line-height: 18px; + outline-color: initial; + outline-style: none; + outline-width: initial; + position: relative; + vertical-align: text-bottom; + border: 1px solid var(--fg_grey_standard); + padding: 5px 11px; + } + + .q-button--small { + font-size: 12px; + line-height: 14px; + min-width: 62px; + padding: 4px 7px; + } + + .q-button--primary { + background-color: var(--brand_standard); + border-color: var(--brand_standard); + color: var(--on_brand_primary); + } + + .q-button--secondary { + background-color: transparent; + border-color: var(--fg_grey_standard); + color: var(--text_primary); + } + + .q-button:not([disabled]):hover { + background-color: var(--overlay_hover); + } + + .q-button:not([disabled]):active { + background-color: var(--overlay_pressed); + } + + .q-button--primary:hover { + background-color: var(--nt_brand_standard_2_overlay_hover_brand_2_mix); + border-color: var(--nt_brand_standard_2_overlay_hover_brand_2_mix); + } + + .q-button--primary:active { + background-color: var(--nt_brand_standard_2_overlay_pressed_brand_2_mix); + border-color: var(--nt_brand_standard_2_overlay_pressed_brand_2_mix); + } + + .q-button[disabled] { + opacity: 0.3; + cursor: not-allowed; + } + + .q-button--secondary[disabled] { + background-color: transparent; + } \ No newline at end of file diff --git a/static/components/NapCat.ts b/static/components/NapCat.ts deleted file mode 100644 index ff2bfc55..00000000 --- a/static/components/NapCat.ts +++ /dev/null @@ -1,420 +0,0 @@ -import { SettingList } from "./SettingList"; -import { SettingItem } from "./SettingItem"; -import { SettingButton } from "./SettingButton"; -import { SettingSwitch } from "./SettingSwitch"; -import { SettingSelect } from "./SettingSelect"; - -// @ts-ignore 引入Css 能不能优雅点 神经这样写 -import StyleRaw from "../assets/NapCat.css"; -function aprilFoolsEgg(node: Element) { - async function onSettingWindowCreated(view: Element) { - const isEmpty = (value: any) => value === undefined || value === null || value === '' - //@ts-ignore 等待替换为异步Http获取 带上Token - let config = await window.llonebot.getConfig(); - let ob11Config = { ...config.ob11 }; - const setConfig = (key: string, value: any) => { - const configKey = key.split('.'); - if (key.indexOf('ob11') === 0) { - if (configKey.length === 2) ob11Config[configKey[1]] = value; - else ob11Config[key] = value; - } else { - if (configKey.length === 2) { config[configKey[0]][configKey[1]] = value } - else { config[key] = value } - - if (!['heartInterval', 'token', 'ffmpeg'].includes(key)) { - //@ts-ignore 等待替换为异步Http设置 带上Token - window.llonebot.setConfig(false, config) - } - } - } - - const parser = new DOMParser() - const doc = parser.parseFromString( - [ - '
', - ``, - ` -
-
`, - SettingList([ - SettingItem( - '正在检查 LLOneBot 更新', - null, - SettingButton('请稍候', 'llonebot-update-button', 'secondary'), - ), - ]), - SettingList([ - SettingItem( - '启用 HTTP 服务', - null, - SettingSwitch('ob11.enableHttp', config.ob11.enableHttp, { 'control-display-id': 'config-ob11-httpPort' }), - ), - SettingItem( - 'HTTP 服务监听端口', - null, - `
`, - 'config-ob11-httpPort', - config.ob11.enableHttp, - ), - SettingItem( - '启用 HTTP 心跳', - null, - SettingSwitch('ob11.enableHttpHeart', config.ob11.enableHttpHeart, { - 'control-display-id': 'config-ob11-enableHttpHeart', - }), - ), - SettingItem( - '启用 HTTP 事件上报', - null, - SettingSwitch('ob11.enableHttpPost', config.ob11.enableHttpPost, { - 'control-display-id': 'config-ob11-httpHosts', - }), - ), - `
- -
- HTTP 事件上报密钥 -
-
- -
-
- -
- HTTP 事件上报地址 -
- 添加 -
-
-
`, - SettingItem( - '启用正向 WebSocket 服务', - null, - SettingSwitch('ob11.enableWs', config.ob11.enableWs, { 'control-display-id': 'config-ob11-wsPort' }), - ), - SettingItem( - '正向 WebSocket 服务监听端口', - null, - `
`, - 'config-ob11-wsPort', - config.ob11.enableWs, - ), - SettingItem( - '启用反向 WebSocket 服务', - null, - SettingSwitch('ob11.enableWsReverse', config.ob11.enableWsReverse, { - 'control-display-id': 'config-ob11-wsHosts', - }), - ), - `
- -
- 反向 WebSocket 监听地址 -
- 添加 -
-
-
`, - SettingItem( - ' WebSocket 服务心跳间隔', - '控制每隔多久发送一个心跳包,单位为毫秒', - `
`, - ), - SettingItem( - 'Access token', - null, - `
`, - ), - SettingItem( - '新消息上报格式', - '如客户端无特殊需求推荐保持默认设置,两者的详细差异可参考 OneBot v11 文档', - SettingSelect( - [ - { text: '消息段', value: 'array' }, - { text: 'CQ码', value: 'string' }, - ], - 'ob11.messagePostFormat', - config.ob11.messagePostFormat, - ), - ), - SettingItem( - 'ffmpeg 路径,发送语音、视频需要,同时保证ffprobe和ffmpeg在一起', - ` 下载地址 , 路径:${!isEmpty(config.ffmpeg) ? config.ffmpeg : '未指定' - }`, - SettingButton('选择ffmpeg', 'config-ffmpeg-select'), - ), - SettingItem( - '音乐卡片签名地址', - null, - `
`, - 'config-musicSignUrl', - ), - SettingItem('', null, SettingButton('保存', 'config-ob11-save', 'primary')), - ]), - SettingList([ - SettingItem( - '戳一戳消息, 暂时只支持Windows版的LLOneBot', - `重启QQ后生效,如果导致QQ崩溃请勿开启此项, 群戳一戳只能收到群号`, - SettingSwitch('enablePoke', config.enablePoke), - ), - SettingItem( - '使用 Base64 编码获取文件', - '调用 /get_image、/get_record、/get_file 时,没有 url 时添加 Base64 字段', - SettingSwitch('enableLocalFile2Url', config.enableLocalFile2Url), - ), - SettingItem('调试模式', '开启后上报信息会添加 raw 字段以附带原始信息', SettingSwitch('debug', config.debug)), - SettingItem( - '上报 Bot 自身发送的消息', - '上报 event 为 message_sent', - SettingSwitch('reportSelfMessage', config.reportSelfMessage), - ), - SettingItem( - '自动删除收到的文件', - '在收到文件后的指定时间内删除该文件', - SettingSwitch('autoDeleteFile', config.autoDeleteFile, { - 'control-display-id': 'config-auto-delete-file-second', - }), - ), - SettingItem( - '自动删除文件时间', - '单位为秒', - `
`, - 'config-auto-delete-file-second', - config.autoDeleteFile, - ), - SettingItem('写入日志', `将日志文件写入插件的数据文件夹`, SettingSwitch('log', config.log)), - SettingItem( - '日志文件目录', - `${window.LiteLoader.plugins['LLOneBot'].path.data}/logs`, - SettingButton('打开', 'config-open-log-path'), - ), - ]), - SettingList([ - SettingItem('GitHub 仓库', `https://github.com/`, SettingButton('点个星星', 'open-github')), - SettingItem('NapCat 文档', `https://`, SettingButton('看看文档', 'open-docs')), - SettingItem('Telegram 群', `https://t.me/+nLZEnpne-pQ1OWFl`, SettingButton('进去逛逛', 'open-telegram')), - SettingItem('QQ 群', `545402644`, SettingButton('我要进去', 'open-qq-group')), - ]), - '
', - ].join(''), - 'text/html', - ) - - const showError = async () => { - await new Promise((res) => setTimeout(() => res(true), 1000)) - - const errDom = document.querySelector('#llonebot-error') || doc.querySelector('#llonebot-error') - const errCodeDom = errDom.querySelector('code') - const errMsg = await window.llonebot.getError() - - if (!errMsg) { - errDom.classList.remove('show') - } else { - errDom.classList.add('show') - } - errCodeDom.innerHTML = errMsg - } - showError().then() - - // 外链按钮 - doc.querySelector('#open-github')?.addEventListener('click', () => { - window.open("https://github.com/", '_blank'); - }) - doc.querySelector('#open-telegram')?.addEventListener('click', () => { - window.open('https://t.me/+nLZEnpne-pQ1OWFl') - }) - doc.querySelector('#open-qq-group')?.addEventListener('click', () => { - window.open('https://qm.qq.com/q/bDnHRG38aI') - }) - doc.querySelector('#open-docs')?.addEventListener('click', () => { - window.open('https://github.io/') - }) - // 生成反向地址列表 - const buildHostListItem = (type: string, host: string, index: number, inputAttrs: any = {}) => { - const dom = { - container: document.createElement('setting-item'), - input: document.createElement('input'), - inputContainer: document.createElement('div'), - deleteBtn: document.createElement('setting-button'), - } - dom.container.classList.add('setting-host-list-item') - dom.container.dataset.direction = 'row' - Object.assign(dom.input, inputAttrs) - dom.input.classList.add('q-input__inner') - dom.input.type = 'url' - dom.input.value = host - dom.input.addEventListener('input', () => { - ob11Config[type][index] = dom.input.value - }) - - dom.inputContainer.classList.add('q-input') - dom.inputContainer.appendChild(dom.input) - - dom.deleteBtn.innerHTML = '删除' - dom.deleteBtn.dataset.type = 'secondary' - dom.deleteBtn.addEventListener('click', () => { - ob11Config[type].splice(index, 1) - initReverseHost(type) - }) - - dom.container.appendChild(dom.inputContainer) - dom.container.appendChild(dom.deleteBtn) - - return dom.container - } - const buildHostList = (hosts: string[], type: string, inputAttr: any = {}) => { - const result: HTMLElement[] = [] - - hosts.forEach((host, index) => { - result.push(buildHostListItem(type, host, index, inputAttr)) - }) - - return result - } - const addReverseHost = (type: string, doc: Document = document, inputAttr: any = {}) => { - const hostContainerDom = doc.body.querySelector(`#config-ob11-${type}-list`); - hostContainerDom?.appendChild(buildHostListItem(type, '', ob11Config[type].length, inputAttr)); - ob11Config[type].push(''); - } - const initReverseHost = (type: string, doc: Document = document) => { - const hostContainerDom = doc.body.querySelector(`#config-ob11-${type}-list`) - ;[...hostContainerDom.childNodes].forEach((dom) => dom.remove()) - buildHostList(ob11Config[type], type).forEach((dom) => { - hostContainerDom?.appendChild(dom); - }) - } - initReverseHost('httpHosts', doc); - initReverseHost('wsHosts', doc); - - doc - .querySelector('#config-ob11-httpHosts-add') - ?.addEventListener('click', () => - addReverseHost('httpHosts', document, { placeholder: '如:http://127.0.0.1:5140/onebot' }), - ) - doc - .querySelector('#config-ob11-wsHosts-add') - ?.addEventListener('click', () => - addReverseHost('wsHosts', document, { placeholder: '如:ws://127.0.0.1:5140/onebot' }), - ) - - doc.querySelector('#config-ffmpeg-select')?.addEventListener('click', () => { - //选择ffmpeg - }) - - doc.querySelector('#config-open-log-path')?.addEventListener('click', () => { - //打开日志 - }) - - // 开关 - doc.querySelectorAll('setting-switch[data-config-key]').forEach((dom: Element) => { - dom.addEventListener('click', () => { - const active = dom.getAttribute('is-active') === null - - setConfig(dom.dataset.configKey, active) - - if (active) dom.setAttribute('is-active', '') - else dom.removeAttribute('is-active') - - if (!isEmpty(dom.dataset.controlDisplayId)) { - const displayDom = document.querySelector(`#${dom.dataset.controlDisplayId}`) - if (active) displayDom?.removeAttribute('is-hidden') - else displayDom?.setAttribute('is-hidden', '') - } - }) - }) - - // 输入框 - doc - .querySelectorAll('setting-item .q-input input.q-input__inner[data-config-key]') - .forEach((dom: Element) => { - dom.addEventListener('input', () => { - const Type = dom.getAttribute('type') - const configKey = dom.dataset.configKey - const configValue = Type === 'number' ? (parseInt((dom as HTMLInputElement).value) >= 1 ? parseInt((dom as HTMLInputElement).value) : 1) : (dom as HTMLInputElement).value - - setConfig(configKey, configValue) - }) - }) - - // 下拉框 - doc.querySelectorAll('ob-setting-select[data-config-key]').forEach((dom: HTMLElement) => { - dom.addEventListener('selected', (e: CustomEvent) => { - const configKey = dom.dataset.configKey - const configValue = e.detail.value - - setConfig(configKey, configValue) - }) - }) - - // 保存按钮 - doc.querySelector('#config-ob11-save')?.addEventListener('click', () => { - config.ob11 = ob11Config - - window.llonebot.setConfig(false, config) - // window.location.reload(); - showError().then() - alert('保存成功') - }) - - doc.body.childNodes.forEach((node) => { - view.appendChild(node) - }) - // 更新逻辑 - async function checkVersionFunc(ResultVersion: CheckVersion) { - const titleDom = view.querySelector('#llonebot-update-title')! - const buttonDom = view.querySelector('#llonebot-update-button')! - - if (ResultVersion.version === '') { - titleDom.innerHTML = '检查更新失败' - buttonDom.innerHTML = '点击重试' - - buttonDom.addEventListener('click', async () => { - window.llonebot.checkVersion().then(checkVersionFunc) - }) - - return - } - if (!ResultVersion.result) { - titleDom.innerHTML = '当前已是最新版本 v' + ResultVersion.version - buttonDom.innerHTML = '无需更新' - } else { - titleDom.innerHTML = '已检测到最新版本 v' + ResultVersion.version - buttonDom.innerHTML = '点击更新' - buttonDom.dataset.type = 'primary' - - const update = async () => { - buttonDom.innerHTML = '正在更新中...' - const result = await window.llonebot.updateLLOneBot() - if (result) { - buttonDom.innerHTML = '更新完成,请重启' - } else { - buttonDom.innerHTML = '更新失败,前往仓库下载' - } - - buttonDom.removeEventListener('click', update) - } - buttonDom.addEventListener('click', update) - } - } - window.llonebot.checkVersion().then(checkVersionFunc) - window.addEventListener('beforeunload', (event) => { - if (JSON.stringify(ob11Config) === JSON.stringify(config.ob11)) return - config.ob11 = ob11Config - window.llonebot.setConfig(true, config) - }) - } - - function init() { - const hash = location.hash - if (hash === '#/blank') { - } - } - - if (location.hash === '#/blank') { - ; (window as any).navigation.addEventListener('navigatesuccess', init, { once: true }) - } else { - init() - } - - export { onSettingWindowCreated } \ No newline at end of file diff --git a/static/index.html b/static/index.html index b31008ec..45ffe9bf 100644 --- a/static/index.html +++ b/static/index.html @@ -1,21 +1,35 @@ - + + - - - - + + + + + + + - - Plugin iframe + + + + NapCat-WebUi + - + + \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts index be4294b3..d75f68b1 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -43,6 +43,7 @@ const baseConfigPlugin: PluginOption[] = [ targets: [ // ...external.map(genCpModule), { src: './src/napcat.json', dest: 'dist/config/' }, + { src: './static/', dest: 'dist/static/', flatten: false }, { src: './src/onebot11/onebot11.json', dest: 'dist/config/' }, { src: './package.json', dest: 'dist' }, { src: './README.md', dest: 'dist' },