diff --git a/src/renderer/index.ts b/src/renderer/index.ts index 7611793..a15c791 100644 --- a/src/renderer/index.ts +++ b/src/renderer/index.ts @@ -1,365 +1,110 @@ -/// <reference path="./global.d.ts" /> +/// <reference path="../global.d.ts" /> +import { + SettingButton, + SettingItem, + SettingList, + SettingSelect, + SettingSwitch +} from './components'; +import StyleRaw from './style.css?raw'; // 打开设置界面时触发 -async function onSettingWindowCreated (view: Element) { - window.llonebot.log('setting window created') - const isEmpty = (value: any) => value === undefined || value === null || value === '' - const config = await window.llonebot.getConfig() - const httpClass = 'http' - const httpPostClass = 'http-post' - const wsClass = 'ws' - const reverseWSClass = 'reverse-ws' - const llonebotError = await window.llonebot.getError() - window.llonebot.log('获取error' + JSON.stringify(llonebotError)) +async function onSettingWindowCreated(view: Element) { + window.llonebot.log("setting window created"); + const isEmpty = (value: any) => value === undefined || value === null || value === ''; + let config = await window.llonebot.getConfig() - function createHttpHostEleStr (host: string) { - const eleStr = ` - <setting-item data-direction="row" class="hostItem vertical-list-item ${httpPostClass}"> - <h2>HTTP事件上报地址(http)</h2> - <input class="httpHost input-text" type="text" value="${host}" - style="width:60%;padding: 5px" - placeholder="如:http://127.0.0.1:8080/onebot/v11/http"/> - </setting-item> - ` - return eleStr - } + const parser = new DOMParser(); + const doc = parser.parseFromString([ + '<div>', + `<style>${StyleRaw}</style>`, + SettingList([ + SettingItem('启用 HTTP 服务', null, + SettingSwitch('ob11.enableHttp', config.ob11.enableHttp, { 'control-display-id': 'config-ob11-httpPort' }), + ), + SettingItem('HTTP 服务监听端口', null, + '<div class="q-input"><input class="q-input__inner" /></div>', + 'config-ob11-httpPort', config.ob11.enableHttp + ), + SettingItem('启用 HTTP 事件上报', null, + SettingSwitch('ob11.enableHttpPost', config.ob11.enableHttpPost, { 'control-display-id': 'config-ob11-httpPost' }), + ), + SettingItem('HTTP 事件上报地址', null, + '<div></div>', + 'config-ob11-httpPost', config.ob11.enableHttpPost + ), + SettingItem('启用正向 WebSocket 服务', null, + SettingSwitch('ob11.enableWs', config.ob11.enableWs, { 'config-display-id': 'config-ob11-wsPort' }), + ), + SettingItem('正向 WebSocket 服务监听端口', null, + '<div class="q-input"><input class="q-input__inner" /></div>', + 'config-ob11-wsPort', config.ob11.enableWs + ), + SettingItem('启用反向 WebSocket 服务', null, + SettingSwitch('ob11.enableWsReverse', config.ob11.enableWsReverse, { 'config-display-id': 'config-ob11-wsHosts' }), + ), + SettingItem('反向 WebSocket 监听地址', null, + '<div></div>', + 'config-ob11-wsHosts', config.ob11.enableWsReverse + ), + SettingItem( + 'ffmpeg 路径', `${!isEmpty(config.ffmpeg) ? config.ffmpeg : '未指定'}`, + SettingButton('选择', 'config-ffmpeg-select'), + 'config-ffmpeg-path', + ), + SettingItem( + '', null, + SettingButton('保存', 'config-ob11-save', 'primary'), + ) + ]), + SettingList([ + SettingItem( + '消息上报格式类型', + '如客户端无特殊需求推荐保持默认设置,两者的详细差异可参考 <a href="javascript:LiteLoader.api.openExternal(\'https://github.com/botuniverse/onebot-11/tree/master/message#readme\');">OneBot v11 文档</a>', + SettingSelect([ + { text: '消息段', value: 'array' }, + { text: 'CQ码', value: 'string' }, + ], 'ob11.messagePostFormat', config.ob11.messagePostFormat), + ), + SettingItem( + '使用 Base64 编码获取文件', + '开启后,调用 /get_image、/get_record 时,获取不到 url 时添加一个 Base64 字段', + SettingSwitch('enableLocalFile2Url', config.enableLocalFile2Url), + ), + SettingItem( + '调试模式', + '开启后上报信息会添加 raw 字段以附带原始信息', + SettingSwitch('debug', config.debug), + ), + SettingItem( + '上报 Bot 自身发送的消息', + '慎用,可能会导致自己跟自己聊个不停', + SettingSwitch('reportSelfMessage', config.reportSelfMessage), + ), + SettingItem( + '写入日志', + `日志文件目录:${window.LiteLoader.plugins['LLOneBot'].path.data}`, + SettingButton('打开', 'config-open-log-path'), + ), + SettingItem( + '自动删除收到的文件', + '在收到文件后的指定时间内删除该文件', + SettingSwitch('autoDeleteFile', config.autoDeleteFile, { 'config-display-id': 'config-auto-delete-file-second' }), + ), + SettingItem( + '自动删除文件时间', + '单位为秒', + '<div class="q-input"><input class="q-input__inner" /></div>', + 'config-auto-delete-file-second', config.autoDeleteFile + ), + ]), + '</div>', + ].join(''), "text/html"); - function createWsHostEleStr (host: string) { - const eleStr = ` - <setting-item data-direction="row" class="hostItem vertical-list-item ${reverseWSClass}"> - <h2>反向websocket地址:</h2> - <input class="wsHost input-text" type="text" value="${host}" - style="width:60%;padding: 5px" - placeholder="如: ws://127.0.0.1:5410/onebot"/> - </setting-item> - ` - return eleStr - } - - let httpHostsEleStr = '' - for (const host of config.ob11.httpHosts) { - httpHostsEleStr += createHttpHostEleStr(host) - } - - let wsHostsEleStr = '' - for (const host of config.ob11.wsHosts) { - wsHostsEleStr += createWsHostEleStr(host) - } - - const html = ` - <div class="config_view llonebot"> - <setting-section> - <setting-panel id="llonebotError" style="display:${llonebotError.ffmpegError || llonebotError.otherError ? '' : 'none'}"> - <setting-item id="ffmpegError" data-direction="row" - style="diplay:${llonebotError.ffmpegError ? '' : 'none'}" - class="hostItem vertical-list-item"> - <setting-text data-type="secondary" class="err-content">${llonebotError.ffmpegError}</setting-text> - </setting-item> - <setting-item id="otherError" data-direction="row" - style="diplay:${llonebotError.otherError ? '' : 'none'}" - class="hostItem vertical-list-item"> - <setting-text data-type="secondary" class="err-content">${llonebotError.otherError}</setting-text> - </setting-item> - </setting-panel> - <setting-panel> - <setting-list class="wrap"> - <setting-item data-direction="row" class="hostItem vertical-list-item"> - <div> - <div>启用HTTP服务</div> - </div> - <setting-switch id="http" ${config.ob11.enableHttp ? 'is-active' : ''}></setting-switch> - </setting-item> - <setting-item class="vertical-list-item ${httpClass}" data-direction="row" style="display: ${config.ob11.enableHttp ? '' : 'none'}"> - <setting-text>HTTP监听端口</setting-text> - <input id="httpPort" type="number" value="${config.ob11.httpPort}"/> - </setting-item> - <setting-item data-direction="row" class="hostItem vertical-list-item"> - <div> - <div>启用HTTP事件上报</div> - </div> - <setting-switch id="httpPost" ${config.ob11.enableHttpPost ? 'is-active' : ''}></setting-switch> - </setting-item> - <div class="${httpPostClass}" style="display: ${config.ob11.enableHttpPost ? '' : 'none'}"> - <div > - <button id="addHttpHost" class="q-button">添加HTTP POST上报地址</button> - </div> - <div id="httpHostItems"> - ${httpHostsEleStr} - </div> - </div> - <setting-item data-direction="row" class="hostItem vertical-list-item"> - <div> - <div>启用正向Websocket协议</div> - </div> - <setting-switch id="websocket" ${config.ob11.enableWs ? 'is-active' : ''}></setting-switch> - </setting-item> - <setting-item class="vertical-list-item ${wsClass}" data-direction="row" style="display: ${config.ob11.enableWs ? '' : 'none'}"> - <setting-text>正向Websocket监听端口</setting-text> - <input id="wsPort" type="number" value="${config.ob11.wsPort}"/> - </setting-item> - - <setting-item data-direction="row" class="hostItem vertical-list-item"> - <div> - <div>启用反向Websocket协议</div> - </div> - <setting-switch id="websocketReverse" ${config.ob11.enableWsReverse ? 'is-active' : ''}></setting-switch> - </setting-item> - <div class="${reverseWSClass}" style="display: ${config.ob11.enableWsReverse ? '' : 'none'}"> - <div> - <button id="addWsHost" class="q-button">添加反向Websocket地址</button> - </div> - <div id="wsHostItems"> - ${wsHostsEleStr} - </div> - </div> - <setting-item class="vertical-list-item" data-direction="row"> - <setting-text>Access Token</setting-text> - <input id="token" type="text" placeholder="可为空" value="${config.token}"/> - </setting-item> - <setting-item data-direction="row" class="vertical-list-item"> - <setting-item data-direction="row" class="vertical-list-item" style="width: 80%"> - <setting-text>ffmpeg路径</setting-text> - <input id="ffmpegPath" class="input-text" type="text" - style="width:80%;padding: 5px" - value="${config.ffmpeg || ''}"/> - </setting-item> - <button id="selectFFMPEG" class="q-button q-button--small q-button--secondary">选择ffmpeg</button> - </setting-item> - <button id="save" class="q-button">保存</button> - </setting-list> - </setting-panel> - <setting-panel> - - <setting-item data-direction="row" class="vertical-list-item"> - <div> - <setting-text>消息上报数据类型</setting-text> - <setting-text data-type="secondary">如客户端无特殊需求推荐保持默认设置,两者的详细差异可参考 <a href="javascript:LiteLoader.api.openExternal('https://github.com/botuniverse/onebot-11/tree/master/message#readme');">OneBot v11 文档</a></setting-text> - </div> - <setting-select id="messagePostFormat"> - <setting-option data-value="array" ${config.ob11.messagePostFormat !== 'string' ? 'is-selected' : ''}>消息段</setting-option> - <setting-option data-value="string" ${config.ob11.messagePostFormat === 'string' ? 'is-selected' : ''}>CQ码</setting-option> - </setting-select> - </setting-item> - <setting-item data-direction="row" class="vertical-list-item"> - <div> - <div>获取文件使用base64编码</div> - <div class="tips">开启后,调用/get_image、/get_record时,获取不到url时添加一个base64字段</div> - </div> - <setting-switch id="switchFileUrl" ${config.enableLocalFile2Url ? 'is-active' : ''}></setting-switch> - </setting-item> - <setting-item data-direction="row" class="vertical-list-item"> - <div> - <div>debug模式</div> - <div class="tips">开启后上报消息添加raw字段附带原始消息</div> - </div> - <setting-switch id="debug" ${config.debug ? 'is-active' : ''}></setting-switch> - </setting-item> - <setting-item data-direction="row" class="vertical-list-item"> - <div> - <div>上报自身发送消息</div> - <div class="tips"></div> - </div> - <setting-switch id="reportSelfMessage" ${config.reportSelfMessage ? 'is-active' : ''}></setting-switch> - </setting-item> - <setting-item data-direction="row" class="vertical-list-item"> - <div> - <div>日志</div> - <div class="tips">目录:${window.LiteLoader.plugins.LLOneBot.path.data}</div> - </div> - <setting-switch id="log" ${config.log ? 'is-active' : ''}></setting-switch> - </setting-item> - <setting-item data-direction="row" class="vertical-list-item"> - <div> - <div>自动删除收到的文件</div> - <div class="tips"> - 收到文件 - <input id="autoDeleteMin" - min="1" style="width: 50px" - value="${config.autoDeleteFileSecond || 60}" type="number"/>秒后自动删除 - </div> - </div> - <setting-switch id="autoDeleteFile" ${config.autoDeleteFile ? 'is-active' : ''}></setting-switch> - </setting-item> - </setting-panel> - </setting-section> - </div> - <style> - setting-panel { - padding: 10px; - } - .tips { - font-size: 0.75rem; - } - @media (prefers-color-scheme: dark){ - .llonebot input { - color: white; - } - } - </style> - ` - - const parser = new DOMParser() - const doc = parser.parseFromString(html, 'text/html') - - const getError = async () => { - const llonebotError = await window.llonebot.getError() - console.log(llonebotError) - const llonebotErrorEle = document.getElementById('llonebotError') - const ffmpegErrorEle = document.getElementById('ffmpegError') - const otherErrorEle = document.getElementById('otherError') - if (llonebotError.otherError || llonebotError.ffmpegError) { - llonebotErrorEle.style.display = '' - } else { - llonebotErrorEle.style.display = 'none' - } - if (llonebotError.ffmpegError) { - const errContentEle = doc.querySelector('#ffmpegError .err-content') - // const errContent = ffmpegErrorEle.getElementsByClassName("err-content")[0]; - errContentEle.textContent = llonebotError.ffmpegError; - (ffmpegErrorEle).style.display = '' - } else { - ffmpegErrorEle.style.display = '' - } - if (llonebotError.otherError) { - const errContentEle = doc.querySelector('#otherError .err-content') - errContentEle.textContent = llonebotError.otherError - otherErrorEle.style.display = '' - } else { - otherErrorEle.style.display = 'none' - } - } - - function addHostEle (type: string, initValue: string = '') { - let addressEle, hostItemsEle - if (type === 'ws') { - const addressDoc = parser.parseFromString(createWsHostEleStr(initValue), 'text/html') - addressEle = addressDoc.querySelector('setting-item') - hostItemsEle = document.getElementById('wsHostItems') - } else { - const addressDoc = parser.parseFromString(createHttpHostEleStr(initValue), 'text/html') - addressEle = addressDoc.querySelector('setting-item') - hostItemsEle = document.getElementById('httpHostItems') - } - - hostItemsEle.appendChild(addressEle) - } - - doc.getElementById('addHttpHost').addEventListener('click', () => { - addHostEle('http') - }) - doc.getElementById('addWsHost').addEventListener('click', () => { - addHostEle('ws') - }) - doc.getElementById('messagePostFormat').addEventListener('selected', (e: CustomEvent) => { - config.ob11.messagePostFormat = e.detail && !isEmpty(e.detail.value) ? e.detail.value : 'array' - window.llonebot.setConfig(config) - }) - - function switchClick (eleId: string, configKey: string, _config = null) { - if (!_config) { - _config = config - } - doc.getElementById(eleId)?.addEventListener('click', (e) => { - const switchEle = e.target as HTMLInputElement - if (_config[configKey]) { - _config[configKey] = false - switchEle.removeAttribute('is-active') - } else { - _config[configKey] = true - switchEle.setAttribute('is-active', '') - } - // 妈蛋,手动操作DOM越写越麻烦,要不用vue算了 - const keyClassMap = { - enableHttp: httpClass, - enableHttpPost: httpPostClass, - enableWs: wsClass, - enableWsReverse: reverseWSClass - } - for (const e of document.getElementsByClassName(keyClassMap[configKey])) { - (e as HTMLElement).style.display = _config[configKey] ? '' : 'none' - } - - window.llonebot.setConfig(config) - }) - } - - switchClick('http', 'enableHttp', config.ob11) - switchClick('httpPost', 'enableHttpPost', config.ob11) - switchClick('websocket', 'enableWs', config.ob11) - switchClick('websocketReverse', 'enableWsReverse', config.ob11) - switchClick('debug', 'debug') - switchClick('switchFileUrl', 'enableLocalFile2Url') - switchClick('reportSelfMessage', 'reportSelfMessage') - switchClick('log', 'log') - switchClick('autoDeleteFile', 'autoDeleteFile') - - doc.getElementById('save')?.addEventListener('click', - () => { - const httpPortEle: HTMLInputElement = document.getElementById('httpPort') as HTMLInputElement - const httpHostEles: HTMLCollectionOf<HTMLInputElement> = document.getElementsByClassName('httpHost') as HTMLCollectionOf<HTMLInputElement> - const wsPortEle: HTMLInputElement = document.getElementById('wsPort') as HTMLInputElement - const wsHostEles: HTMLCollectionOf<HTMLInputElement> = document.getElementsByClassName('wsHost') as HTMLCollectionOf<HTMLInputElement> - const tokenEle = document.getElementById('token') as HTMLInputElement - const ffmpegPathEle = document.getElementById('ffmpegPath') as HTMLInputElement - - // 获取端口和host - const httpPort = httpPortEle.value - const httpHosts: string[] = [] - - for (const hostEle of httpHostEles) { - const value = hostEle.value.trim() - value && httpHosts.push(value) - } - - const wsPort = wsPortEle.value - const token = tokenEle.value.trim() - const wsHosts: string[] = [] - - for (const hostEle of wsHostEles) { - const value = hostEle.value.trim() - value && wsHosts.push(value) - } - - config.ob11.httpPort = parseInt(httpPort) - config.ob11.httpHosts = httpHosts - config.ob11.wsPort = parseInt(wsPort) - config.ob11.wsHosts = wsHosts - config.token = token - config.ffmpeg = ffmpegPathEle.value.trim() - window.llonebot.setConfig(config) - setTimeout(() => { - getError().then() - }, 1000) - alert('保存成功') - }) - - doc.getElementById('selectFFMPEG')?.addEventListener('click', () => { - window.llonebot.selectFile().then(selectPath => { - if (selectPath) { - config.ffmpeg = (document.getElementById('ffmpegPath') as HTMLInputElement).value = selectPath - // window.llonebot.setConfig(config); - } - }) - }) - - // 自动保存删除文件延时时间 - const autoDeleteMinEle = doc.getElementById('autoDeleteMin') as HTMLInputElement - let st = null - autoDeleteMinEle.addEventListener('change', () => { - if (st) { - clearTimeout(st) - } - st = setTimeout(() => { - console.log('auto delete file minute change') - config.autoDeleteFileSecond = parseInt(autoDeleteMinEle.value) || 1 - window.llonebot.setConfig(config) - }, 1000) - }) - - doc.body.childNodes.forEach(node => { - view.appendChild(node) - }) + doc.body.childNodes.forEach(node => { + view.appendChild(node); + }); } function init () { diff --git a/src/renderer/style.css b/src/renderer/style.css new file mode 100644 index 0000000..421117b --- /dev/null +++ b/src/renderer/style.css @@ -0,0 +1,39 @@ +setting-item[is-hidden], +setting-item[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; +}