import { CheckVersion, Config } from '../common/types' import { SettingButton, SettingItem, SettingList, SettingSwitch, SettingSelect, SettingInput } from './components' import { version } from '../version' // @ts-expect-error: Unreachable code error import StyleRaw from './style.css?raw' type HostsType = 'httpHosts' | 'wsHosts' function isEmpty(value: unknown) { return value === undefined || value === null || value === '' } async function onSettingWindowCreated(view: Element) { const config = await window.llonebot.getConfig() const ob11Config = { ...config.ob11 } const setConfig = (key: string, value: unknown) => { const configKey = key.split('.') if (key.startsWith('ob11')) { if (configKey.length === 2) Object.assign(ob11Config, { [configKey[1]]: value }) else Object.assign(ob11Config, { [key]: value }) } else { if (configKey.length === 2) { Object.assign(config[configKey[0] as keyof Config[keyof Config]], { [configKey[1]]: value }) } else { Object.assign(config, { [key]: value }) } } } const parser = new DOMParser() const doc = parser.parseFromString( [ '
', ``, `
`, SettingList([ SettingItem( '正在检查 LLOneBot 更新', null, SettingButton('请稍候', 'llonebot-update-button', 'secondary'), ), ]), SettingList([ SettingItem( '是否启用 LLOneBot,重启 QQ 后生效', null, SettingSwitch('enableLLOB', config.enableLLOB, { 'control-display-id': 'config-enableLLOB' }), ) ]), SettingList([ SettingItem( '是否启用 Satori 协议', '重启 QQ 后生效', SettingSwitch('satori.enable', config.satori.enable), ), SettingItem( '服务端口', null, SettingInput('satori.port', 'port', config.satori.port, config.satori.port), ), SettingItem( '服务令牌', null, SettingInput('satori.token', 'text', config.satori.token, '未设置', 'width:170px;'), ), SettingItem('', null, SettingButton('保存', 'config-ob11-save', 'primary')), ]), SettingList([ SettingItem( '是否启用 OneBot 协议', '重启 QQ 后生效', SettingSwitch('ob11.enable', config.ob11.enable), ), SettingItem( '启用 HTTP 服务', null, SettingSwitch('ob11.enableHttp', config.ob11.enableHttp, { 'control-display-id': 'config-ob11-httpPort' }), ), SettingItem( 'HTTP 服务监听端口', null, SettingInput('ob11.httpPort', 'port', config.ob11.httpPort, config.ob11.httpPort), '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( 'HTTP、正向 WebSocket 服务仅监听 127.0.0.1', '而不是 0.0.0.0', SettingSwitch('ob11.listenLocalhost', config.ob11.listenLocalhost), ), SettingItem( '上报 Bot 自身发送的消息', '上报 event 为 message_sent', SettingSwitch('ob11.reportSelfMessage', config.ob11.reportSelfMessage), ), SettingItem( '使用 Base64 编码获取文件', '调用 /get_image、/get_record、/get_file 时,没有 url 时添加 Base64 字段', SettingSwitch('enableLocalFile2Url', config.enableLocalFile2Url), ), SettingItem('', null, SettingButton('保存', 'config-ob11-save-2', 'primary')), ]), SettingList([ SettingItem( 'FFmpeg 路径,发送语音、视频需要', `可点此下载, 路径: ${!isEmpty(config.ffmpeg) ? config.ffmpeg : '未指定' }, 需保证 FFprobe 和 FFmpeg 在一起`, SettingButton('选择 FFmpeg', 'config-ffmpeg-select'), ), SettingItem( '音乐卡片签名 URL 地址', null, `
`, 'config-musicSignUrl', ), 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'), ), SettingItem( '消息内容缓存时长', '单位为秒,可用于获取撤回的消息', `
`, ), SettingItem('', null, SettingButton('保存', 'config-ob11-save-3', 'primary')), ]), SettingList([ SettingItem('GitHub 仓库', `https://github.com/LLOneBot/LLOneBot`, SettingButton('点个星星', 'open-github')), SettingItem('LLOneBot 文档', `https://llonebot.github.io/`, 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.LiteLoader.api.openExternal('https://github.com/LLOneBot/LLOneBot') }) doc.querySelector('#open-telegram')?.addEventListener('click', () => { window.LiteLoader.api.openExternal('https://t.me/+nLZEnpne-pQ1OWFl') }) doc.querySelector('#open-qq-group')?.addEventListener('click', () => { window.LiteLoader.api.openExternal('https://qm.qq.com/q/bDnHRG38aI') }) doc.querySelector('#open-docs')?.addEventListener('click', () => { window.LiteLoader.api.openExternal('https://llonebot.github.io/') }) // 生成反向地址列表 const buildHostListItem = (type: HostsType, host: string, index: number, 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 buildHostList = (hosts: string[], type: HostsType, inputAttr = {}) => { const result: HTMLElement[] = [] hosts.forEach((host, index) => { result.push(buildHostListItem(type, host, index, inputAttr)) }) return result } const addReverseHost = (type: HostsType, doc: Document = document, inputAttr = {}) => { const hostContainerDom = doc.body.querySelector(`#config-ob11-${type}-list`) hostContainerDom?.appendChild(buildHostListItem(type, '', ob11Config[type].length, inputAttr)) ob11Config[type].push('') } const initReverseHost = (type: HostsType, doc: Document = document) => { const hostContainerDom = doc.body.querySelector(`#config-ob11-${type}-list`)! const nodes = [...hostContainerDom.childNodes] nodes.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', () => { window.llonebot.selectFile().then((path) => { if (!isEmpty(path)) { setConfig('ffmpeg', path) document.querySelector('#config-ffmpeg-path-text')!.innerHTML = path } }) }) doc.querySelector('#config-open-log-path')?.addEventListener('click', () => { window.LiteLoader.api.openPath(window.LiteLoader.plugins['LLOneBot'].path.data) }) // 开关 doc.querySelectorAll('setting-switch[data-config-key]').forEach(element => { const dom = element as HTMLElement 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(element => { const dom = element as HTMLInputElement dom.addEventListener('input', () => { const Type = dom.getAttribute('type') const configKey = dom.dataset.configKey const configValue = Type === 'number' ? (parseInt(dom.value) >= 1 ? parseInt(dom.value) : 1) : dom.value setConfig(configKey!, configValue) }) }) // 下拉框 doc?.querySelectorAll('ob-setting-select[data-config-key]').forEach(element => { const dom = element as HTMLElement dom?.addEventListener('selected', e => { const { detail } = e as CustomEvent const configKey = dom.dataset.configKey const configValue = 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.querySelector('#config-ob11-save-2')?.addEventListener('click', () => { config.ob11 = ob11Config window.llonebot.setConfig(false, config) showError().then() alert('保存成功') }) doc.querySelector('#config-ob11-save-3')?.addEventListener('click', () => { config.ob11 = ob11Config window.llonebot.setConfig(false, config) showError().then() alert('保存成功') }) doc.body.childNodes.forEach((node) => { view.appendChild(node) }) // 更新逻辑 async function checkVersionFunc(info: CheckVersion) { const titleDom = view.querySelector('#llonebot-update-title')! const buttonDom = view.querySelector('#llonebot-update-button')! if (info.version === '') { titleDom.innerHTML = `当前版本为 v${version},检查更新失败` buttonDom.innerHTML = '点击重试' buttonDom.addEventListener('click', async () => { window.llonebot.checkVersion().then(checkVersionFunc) }, { once: true }) } else if (!info.result) { titleDom.innerHTML = '当前已是最新版本 v' + version buttonDom.innerHTML = '无需更新' } else { titleDom.innerHTML = `当前版本为 v${version},最新版本为 v${info.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', () => { 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') { globalThis.navigation?.addEventListener('navigatesuccess', init, { once: true }) } else { init() }*/ export { onSettingWindowCreated }