diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 2833eb43..aea63431 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,68 +1,64 @@ module.exports = { - 'env': { - 'browser': true, - 'es2021': true, - 'node': true - }, - 'ignorePatterns': ['src/core/', 'src/core.lib/','src/proto/'], - 'extends': [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended' - ], - 'overrides': [ - { - 'env': { + 'env': { + 'browser': true, + 'es2021': true, 'node': true - }, - 'files': [ - '.eslintrc.{js,cjs}' - ], - 'parserOptions': { - 'sourceType': 'script' - } - } - ], - 'parser': '@typescript-eslint/parser', - 'parserOptions': { - 'ecmaVersion': 'latest', - 'sourceType': 'module' - }, - 'plugins': [ - '@typescript-eslint', - 'import' - ], - 'settings': { - 'import/parsers': { - '@typescript-eslint/parser': ['.ts'] }, - 'import/resolver': { - 'typescript': { - 'alwaysTryTypes': true - } + 'ignorePatterns': ['src/core/', 'src/core.lib/','src/proto/'], + 'extends': [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended' + ], + 'overrides': [ + { + 'env': { + 'node': true + }, + 'files': [ + '.eslintrc.{js,cjs}' + ], + 'parserOptions': { + 'sourceType': 'script' + } + } + ], + 'parser': '@typescript-eslint/parser', + 'parserOptions': { + 'ecmaVersion': 'latest', + 'sourceType': 'module' + }, + 'plugins': [ + '@typescript-eslint', + 'import' + ], + 'settings': { + 'import/parsers': { + '@typescript-eslint/parser': ['.ts'] + }, + 'import/resolver': { + 'typescript': { + 'alwaysTryTypes': true + } + } + }, + 'rules': { + 'indent': [ + 'error', + 4 + ], + 'linebreak-style': [ + 'error', + 'unix' + ], + 'semi': [ + 'error', + 'always' + ], + 'no-unused-vars': 'off', + 'no-async-promise-executor': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/no-var-requires': 'off', + 'object-curly-spacing': ['error', 'always'], } - }, - 'rules': { - 'indent': [ - 'error', - 4 - ], - 'linebreak-style': [ - 'error', - 'unix' - ], - 'quotes': [ - 'error', - 'single' - ], - 'semi': [ - 'error', - 'always' - ], - 'no-unused-vars': 'off', - 'no-async-promise-executor': 'off', - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-unused-vars': 'off', - '@typescript-eslint/no-var-requires': 'off', - 'object-curly-spacing': ['error', 'always'], - } }; diff --git a/src/common/framework/event.ts b/src/common/framework/event.ts index a35a0ebf..46b2398b 100644 --- a/src/common/framework/event.ts +++ b/src/common/framework/event.ts @@ -58,21 +58,21 @@ export class NTEventChannel extends EventEmitter { } async createEventWithListener any, ListenerType extends (...args: any) => any> - ( - eventName: string, - listenerName: string, - waitTimes = 1, - timeout: number = 3000, - checker: (...args: Parameters) => boolean, - ...eventArg: Parameters - ) { + ( + eventName: string, + listenerName: string, + waitTimes = 1, + timeout: number = 3000, + checker: (...args: Parameters) => boolean, + ...eventArg: Parameters + ) { return new Promise<[EventRet: Awaited>, ...Parameters]>(async (resolve, reject) => { const ListenerNameList = listenerName.split('/'); const ListenerMainName = ListenerNameList[0]; //const ListenerSubName = ListenerNameList[1]; this.getOrInitListener(ListenerMainName); let complete = 0; - let retData: Parameters | undefined = undefined; + const retData: Parameters | undefined = undefined; let retEvent: any = {}; const databack = () => { if (complete == 0) { @@ -82,7 +82,7 @@ export class NTEventChannel extends EventEmitter { } }; const Timeouter = setTimeout(databack, timeout); - let callback = (...args: Parameters) => { + const callback = (...args: Parameters) => { if (checker(...args)) { complete++; if (complete >= waitTimes) { @@ -109,9 +109,9 @@ export class NTEventChannel extends EventEmitter { //getNodeIKernelGroupListener,GroupService //console.log('2', eventName); const services = (this.wrapperSession as unknown as eventType)[serviceName](); - let event = services[eventName] + const event = services[eventName] //重新绑定this - .bind(services); + .bind(services); if (event) { return event as T; } diff --git a/src/common/utils/QQBasicInfo.ts b/src/common/utils/QQBasicInfo.ts index 2b406ec2..c9270514 100644 --- a/src/common/utils/QQBasicInfo.ts +++ b/src/common/utils/QQBasicInfo.ts @@ -29,7 +29,7 @@ export class QQBasicInfoWrapper { ? JSON.parse(fs.readFileSync(this.QQVersionConfigPath!).toString()) : getDefaultQQVersionConfigInfo(); this.QQPackageInfo = JSON.parse(fs.readFileSync(this.QQPackageInfoPath).toString()); - let { appid: IQQVersionAppid, qua: IQQVersionQua } = this.getAppidV2(); + const { appid: IQQVersionAppid, qua: IQQVersionQua } = this.getAppidV2(); this.QQVersionAppid = IQQVersionAppid; this.QQVersionQua = IQQVersionQua; } @@ -40,13 +40,13 @@ export class QQBasicInfoWrapper { } getFullQQVesion() { - let version = this.isQuickUpdate ? this.QQVersionConfig?.curVersion : this.QQPackageInfo?.version; + const version = this.isQuickUpdate ? this.QQVersionConfig?.curVersion : this.QQPackageInfo?.version; if(!version) throw new Error("QQ版本获取失败"); return version; } requireMinNTQQBuild(buildStr: string) { - let currentBuild = parseInt(this.getQQBuildStr() || "0"); + const currentBuild = parseInt(this.getQQBuildStr() || "0"); if (currentBuild == 0) throw new Error("QQBuildStr获取失败"); return currentBuild >= parseInt(buildStr); } @@ -59,7 +59,7 @@ export class QQBasicInfoWrapper { getAppidV2(): { appid: string; qua: string } { const appidTbale = AppidTable as unknown as QQAppidTableType; try { - let fullVersion = this.getFullQQVesion(); + const fullVersion = this.getFullQQVesion(); if (!fullVersion) throw new Error("QQ版本获取失败"); const data = appidTbale[fullVersion]; if (data) { diff --git a/src/common/utils/log.ts b/src/common/utils/log.ts index 56f998e8..f2b55e11 100644 --- a/src/common/utils/log.ts +++ b/src/common/utils/log.ts @@ -10,126 +10,126 @@ export enum LogLevel { FATAL = 'fatal', } function getFormattedTimestamp() { - const now = new Date(); - const year = now.getFullYear(); - const month = (now.getMonth() + 1).toString().padStart(2, '0'); - const day = now.getDate().toString().padStart(2, '0'); - const hours = now.getHours().toString().padStart(2, '0'); - const minutes = now.getMinutes().toString().padStart(2, '0'); - const seconds = now.getSeconds().toString().padStart(2, '0'); - const milliseconds = now.getMilliseconds().toString().padStart(3, '0'); - return `${year}-${month}-${day}_${hours}-${minutes}-${seconds}.${milliseconds}`; + const now = new Date(); + const year = now.getFullYear(); + const month = (now.getMonth() + 1).toString().padStart(2, '0'); + const day = now.getDate().toString().padStart(2, '0'); + const hours = now.getHours().toString().padStart(2, '0'); + const minutes = now.getMinutes().toString().padStart(2, '0'); + const seconds = now.getSeconds().toString().padStart(2, '0'); + const milliseconds = now.getMilliseconds().toString().padStart(3, '0'); + return `${year}-${month}-${day}_${hours}-${minutes}-${seconds}.${milliseconds}`; } export class LogWrapper { - fileLogEnabled = true; - consoleLogEnabled = true; - logConfig: Configuration; - loggerConsole: log4js.Logger; - loggerFile: log4js.Logger; - loggerDefault: log4js.Logger; - // eslint-disable-next-line no-control-regex - colorEscape = /\x1B[@-_][0-?]*[ -/]*[@-~]/g; - constructor(logDir: string) { + fileLogEnabled = true; + consoleLogEnabled = true; + logConfig: Configuration; + loggerConsole: log4js.Logger; + loggerFile: log4js.Logger; + loggerDefault: log4js.Logger; + // eslint-disable-next-line no-control-regex + colorEscape = /\x1B[@-_][0-?]*[ -/]*[@-~]/g; + constructor(logDir: string) { // logDir = path.join(path.resolve(__dirname), 'logs'); - const filename = `${getFormattedTimestamp()}.log`; - const logPath = path.join(logDir, filename); - this.logConfig = { - appenders: { - FileAppender: { // 输出到文件的appender - type: 'file', - filename: logPath, // 指定日志文件的位置和文件名 - maxLogSize: 10485760, // 日志文件的最大大小(单位:字节),这里设置为10MB - layout: { - type: 'pattern', - pattern: '%d{yyyy-MM-dd hh:mm:ss} [%p] %X{userInfo} | %m' - } - }, - ConsoleAppender: { // 输出到控制台的appender - type: 'console', - layout: { - type: 'pattern', - pattern: `%d{yyyy-MM-dd hh:mm:ss} [%[%p%]] ${chalk.magenta('%X{userInfo}')} | %m` - } + const filename = `${getFormattedTimestamp()}.log`; + const logPath = path.join(logDir, filename); + this.logConfig = { + appenders: { + FileAppender: { // 输出到文件的appender + type: 'file', + filename: logPath, // 指定日志文件的位置和文件名 + maxLogSize: 10485760, // 日志文件的最大大小(单位:字节),这里设置为10MB + layout: { + type: 'pattern', + pattern: '%d{yyyy-MM-dd hh:mm:ss} [%p] %X{userInfo} | %m' + } + }, + ConsoleAppender: { // 输出到控制台的appender + type: 'console', + layout: { + type: 'pattern', + pattern: `%d{yyyy-MM-dd hh:mm:ss} [%[%p%]] ${chalk.magenta('%X{userInfo}')} | %m` + } + } + }, + categories: { + default: { appenders: ['FileAppender', 'ConsoleAppender'], level: 'debug' }, // 默认情况下同时输出到文件和控制台 + file: { appenders: ['FileAppender'], level: 'debug' }, + console: { appenders: ['ConsoleAppender'], level: 'debug' } + } + }; + log4js.configure(this.logConfig); + this.loggerConsole = log4js.getLogger('console'); + this.loggerFile = log4js.getLogger('file'); + this.loggerDefault = log4js.getLogger('default'); + this.setLogSelfInfo({ nick: '', uin: '', uid: '' }); + } + setLogLevel(fileLogLevel: LogLevel, consoleLogLevel: LogLevel) { + this.logConfig.categories.file.level = fileLogLevel; + this.logConfig.categories.console.level = consoleLogLevel; + log4js.configure(this.logConfig); + } + + setLogSelfInfo(selfInfo: { nick: string, uin: string, uid: string }) { + const userInfo = `${selfInfo.nick}(${selfInfo.uin})`; + this.loggerConsole.addContext('userInfo', userInfo); + this.loggerFile.addContext('userInfo', userInfo); + this.loggerDefault.addContext('userInfo', userInfo); + } + + + enableFileLog(enable: boolean) { + this.fileLogEnabled = enable; + } + enableConsoleLog(enable: boolean) { + this.consoleLogEnabled = enable; + } + + formatMsg(msg: any[]) { + let logMsg = ''; + for (const msgItem of msg) { + if (msgItem instanceof Error) { // 判断是否是错误 + logMsg += msgItem.stack + ' '; + continue; + } else if (typeof msgItem === 'object') { // 判断是否是对象 + const obj = JSON.parse(JSON.stringify(msgItem, null, 2)); + logMsg += JSON.stringify(truncateString(obj)) + ' '; + continue; + } + logMsg += msgItem + ' '; } - }, - categories: { - default: { appenders: ['FileAppender', 'ConsoleAppender'], level: 'debug' }, // 默认情况下同时输出到文件和控制台 - file: { appenders: ['FileAppender'], level: 'debug' }, - console: { appenders: ['ConsoleAppender'], level: 'debug' } - } - }; - log4js.configure(this.logConfig); - this.loggerConsole = log4js.getLogger('console'); - this.loggerFile = log4js.getLogger('file'); - this.loggerDefault = log4js.getLogger('default'); - this.setLogSelfInfo({ nick: '', uin: '', uid: '' }); - } - setLogLevel(fileLogLevel: LogLevel, consoleLogLevel: LogLevel) { - this.logConfig.categories.file.level = fileLogLevel; - this.logConfig.categories.console.level = consoleLogLevel; - log4js.configure(this.logConfig); - } - - setLogSelfInfo(selfInfo: { nick: string, uin: string, uid: string }) { - const userInfo = `${selfInfo.nick}(${selfInfo.uin})`; - this.loggerConsole.addContext('userInfo', userInfo); - this.loggerFile.addContext('userInfo', userInfo); - this.loggerDefault.addContext('userInfo', userInfo); - } - - - enableFileLog(enable: boolean) { - this.fileLogEnabled = enable; - } - enableConsoleLog(enable: boolean) { - this.consoleLogEnabled = enable; - } - - formatMsg(msg: any[]) { - let logMsg = ''; - for (const msgItem of msg) { - if (msgItem instanceof Error) { // 判断是否是错误 - logMsg += msgItem.stack + ' '; - continue; - } else if (typeof msgItem === 'object') { // 判断是否是对象 - const obj = JSON.parse(JSON.stringify(msgItem, null, 2)); - logMsg += JSON.stringify(truncateString(obj)) + ' '; - continue; - } - logMsg += msgItem + ' '; + return logMsg; } - return logMsg; - } - _log(level: LogLevel, ...args: any[]) { - if (this.consoleLogEnabled) { - this.loggerConsole[level](this.formatMsg(args)); + _log(level: LogLevel, ...args: any[]) { + if (this.consoleLogEnabled) { + this.loggerConsole[level](this.formatMsg(args)); + } + if (this.fileLogEnabled) { + this.loggerFile[level](this.formatMsg(args).replace(this.colorEscape, '')); + } } - if (this.fileLogEnabled) { - this.loggerFile[level](this.formatMsg(args).replace(this.colorEscape, '')); - } - } - log(...args: any[]) { + log(...args: any[]) { // info 等级 - this._log(LogLevel.INFO, ...args); - } + this._log(LogLevel.INFO, ...args); + } - logDebug(...args: any[]) { - this._log(LogLevel.DEBUG, ...args); - } + logDebug(...args: any[]) { + this._log(LogLevel.DEBUG, ...args); + } - logError(...args: any[]) { - this._log(LogLevel.ERROR, ...args); - } + logError(...args: any[]) { + this._log(LogLevel.ERROR, ...args); + } - logWarn(...args: any[]) { - this._log(LogLevel.WARN, ...args); - } + logWarn(...args: any[]) { + this._log(LogLevel.WARN, ...args); + } - logFatal(...args: any[]) { - this._log(LogLevel.FATAL, ...args); - } + logFatal(...args: any[]) { + this._log(LogLevel.FATAL, ...args); + } } diff --git a/src/common/utils/proxy-handler.ts b/src/common/utils/proxy-handler.ts index 330cb1dd..98f5e08a 100644 --- a/src/common/utils/proxy-handler.ts +++ b/src/common/utils/proxy-handler.ts @@ -17,5 +17,5 @@ export function proxyHandlerOf(logger: LogWrapper) { } export function proxiedListenerOf(listener: T, logger: LogWrapper) { - return new Proxy(listener, proxyHandlerOf(logger)) + return new Proxy(listener, proxyHandlerOf(logger)); } \ No newline at end of file diff --git a/src/common/utils/request.ts b/src/common/utils/request.ts index 8bd51e7f..0ca401b6 100644 --- a/src/common/utils/request.ts +++ b/src/common/utils/request.ts @@ -2,190 +2,190 @@ import https from 'node:https'; import http from 'node:http'; import { readFileSync } from 'node:fs'; export class RequestUtil { - // 适用于获取服务器下发cookies时获取,仅GET - static async HttpsGetCookies(url: string): Promise<{ [key: string]: string }> { - const client = url.startsWith('https') ? https : http; - return new Promise((resolve, reject) => { - const req = client.get(url, (res) => { - let cookies: { [key: string]: string } = {}; - const handleRedirect = (res: http.IncomingMessage) => { - //console.log(res.headers.location); - if (res.statusCode === 301 || res.statusCode === 302) { - if (res.headers.location) { - const redirectUrl = new URL(res.headers.location, url); - RequestUtil.HttpsGetCookies(redirectUrl.href).then((redirectCookies) => { - // 合并重定向过程中的cookies - cookies = { ...cookies, ...redirectCookies }; - resolve(cookies); - }).catch((err) => { - reject(err); - }); - } else { - resolve(cookies); - } - } else { - resolve(cookies); - } - }; - res.on('data', () => { }); // Necessary to consume the stream - res.on('end', () => { - handleRedirect(res); + // 适用于获取服务器下发cookies时获取,仅GET + static async HttpsGetCookies(url: string): Promise<{ [key: string]: string }> { + const client = url.startsWith('https') ? https : http; + return new Promise((resolve, reject) => { + const req = client.get(url, (res) => { + let cookies: { [key: string]: string } = {}; + const handleRedirect = (res: http.IncomingMessage) => { + //console.log(res.headers.location); + if (res.statusCode === 301 || res.statusCode === 302) { + if (res.headers.location) { + const redirectUrl = new URL(res.headers.location, url); + RequestUtil.HttpsGetCookies(redirectUrl.href).then((redirectCookies) => { + // 合并重定向过程中的cookies + cookies = { ...cookies, ...redirectCookies }; + resolve(cookies); + }).catch((err) => { + reject(err); + }); + } else { + resolve(cookies); + } + } else { + resolve(cookies); + } + }; + res.on('data', () => { }); // Necessary to consume the stream + res.on('end', () => { + handleRedirect(res); + }); + if (res.headers['set-cookie']) { + //console.log(res.headers['set-cookie']); + res.headers['set-cookie'].forEach((cookie) => { + const parts = cookie.split(';')[0].split('='); + const key = parts[0]; + const value = parts[1]; + if (key && value && key.length > 0 && value.length > 0) { + cookies[key] = value; + } + }); + } + }); + req.on('error', (error: any) => { + reject(error); + }); }); - if (res.headers['set-cookie']) { - //console.log(res.headers['set-cookie']); - res.headers['set-cookie'].forEach((cookie) => { - const parts = cookie.split(';')[0].split('='); - const key = parts[0]; - const value = parts[1]; - if (key && value && key.length > 0 && value.length > 0) { - cookies[key] = value; - } - }); - } - }); - req.on('error', (error: any) => { - reject(error); - }); - }); - } - - - - // 请求和回复都是JSON data传原始内容 自动编码json - static async HttpGetJson(url: string, method: string = 'GET', data?: any, headers: { [key: string]: string } = {}, isJsonRet: boolean = true, isArgJson: boolean = true): Promise { - const option = new URL(url); - const protocol = url.startsWith('https://') ? https : http; - const options = { - hostname: option.hostname, - port: option.port, - path: option.href, - method: method, - headers: headers - }; - // headers: { - // 'Content-Type': 'application/json', - // 'Content-Length': Buffer.byteLength(postData), - // }, - return new Promise((resolve, reject) => { - const req = protocol.request(options, (res: any) => { - let responseBody = ''; - res.on('data', (chunk: string | Buffer) => { - responseBody += chunk.toString(); - }); - - res.on('end', () => { - try { - if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) { - if (isJsonRet) { - const responseJson = JSON.parse(responseBody); - resolve(responseJson as T); - } else { - resolve(responseBody as T); - } - } else { - reject(new Error(`Unexpected status code: ${res.statusCode}`)); - } - } catch (parseError) { - reject(parseError); - } - }); - }); - - req.on('error', (error: any) => { - reject(error); - }); - if (method === 'POST' || method === 'PUT' || method === 'PATCH') { - if (isArgJson) { - req.write(JSON.stringify(data)); - } else { - req.write(data); - } - - } - req.end(); - }); - } - - // 请求返回都是原始内容 - static async HttpGetText(url: string, method: string = 'GET', data?: any, headers: { [key: string]: string } = {}) { - return this.HttpGetJson(url, method, data, headers, false, false); - } - - static async createFormData(boundary: string, filePath: string): Promise { - let type = 'image/png'; - if (filePath.endsWith('.jpg')) { - type = 'image/jpeg'; } - const formDataParts = [ - `------${boundary}\r\n`, - `Content-Disposition: form-data; name="share_image"; filename="${filePath}"\r\n`, - 'Content-Type: ' + type + '\r\n\r\n' - ]; - const fileContent = readFileSync(filePath); - const footer = `\r\n------${boundary}--`; - return Buffer.concat([ - Buffer.from(formDataParts.join(''), 'utf8'), - fileContent, - Buffer.from(footer, 'utf8') - ]); - } - static async uploadImageForOpenPlatform(filePath: string,cookies:string): Promise { - return new Promise(async (resolve, reject) => { + + // 请求和回复都是JSON data传原始内容 自动编码json + static async HttpGetJson(url: string, method: string = 'GET', data?: any, headers: { [key: string]: string } = {}, isJsonRet: boolean = true, isArgJson: boolean = true): Promise { + const option = new URL(url); + const protocol = url.startsWith('https://') ? https : http; + const options = { + hostname: option.hostname, + port: option.port, + path: option.href, + method: method, + headers: headers + }; + // headers: { + // 'Content-Type': 'application/json', + // 'Content-Length': Buffer.byteLength(postData), + // }, + return new Promise((resolve, reject) => { + const req = protocol.request(options, (res: any) => { + let responseBody = ''; + res.on('data', (chunk: string | Buffer) => { + responseBody += chunk.toString(); + }); + + res.on('end', () => { + try { + if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) { + if (isJsonRet) { + const responseJson = JSON.parse(responseBody); + resolve(responseJson as T); + } else { + resolve(responseBody as T); + } + } else { + reject(new Error(`Unexpected status code: ${res.statusCode}`)); + } + } catch (parseError) { + reject(parseError); + } + }); + }); + + req.on('error', (error: any) => { + reject(error); + }); + if (method === 'POST' || method === 'PUT' || method === 'PATCH') { + if (isArgJson) { + req.write(JSON.stringify(data)); + } else { + req.write(data); + } + + } + req.end(); + }); + } + + // 请求返回都是原始内容 + static async HttpGetText(url: string, method: string = 'GET', data?: any, headers: { [key: string]: string } = {}) { + return this.HttpGetJson(url, method, data, headers, false, false); + } + + static async createFormData(boundary: string, filePath: string): Promise { + let type = 'image/png'; + if (filePath.endsWith('.jpg')) { + type = 'image/jpeg'; + } + const formDataParts = [ + `------${boundary}\r\n`, + `Content-Disposition: form-data; name="share_image"; filename="${filePath}"\r\n`, + 'Content-Type: ' + type + '\r\n\r\n' + ]; + + const fileContent = readFileSync(filePath); + const footer = `\r\n------${boundary}--`; + return Buffer.concat([ + Buffer.from(formDataParts.join(''), 'utf8'), + fileContent, + Buffer.from(footer, 'utf8') + ]); + } + + static async uploadImageForOpenPlatform(filePath: string,cookies:string): Promise { + return new Promise(async (resolve, reject) => { type retType = { retcode: number, result?: { url: string } }; try { - const options = { - hostname: 'cgi.connect.qq.com', - port: 443, - path: '/qqconnectopen/upload_share_image', - method: 'POST', - headers: { - 'Referer': 'https://cgi.connect.qq.com', - 'Cookie': cookies, - 'Accept': '*/*', - 'Connection': 'keep-alive', - 'Content-Type': 'multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW' - } - }; - const req = https.request(options, async (res) => { - let responseBody = ''; - - res.on('data', (chunk: string | Buffer) => { - responseBody += chunk.toString(); - }); - - res.on('end', () => { - try { - if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) { - const responseJson = JSON.parse(responseBody) as retType; - resolve(responseJson.result!.url!); - } else { - reject(new Error(`Unexpected status code: ${res.statusCode}`)); + const options = { + hostname: 'cgi.connect.qq.com', + port: 443, + path: '/qqconnectopen/upload_share_image', + method: 'POST', + headers: { + 'Referer': 'https://cgi.connect.qq.com', + 'Cookie': cookies, + 'Accept': '*/*', + 'Connection': 'keep-alive', + 'Content-Type': 'multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW' } - } catch (parseError) { - reject(parseError); - } + }; + const req = https.request(options, async (res) => { + let responseBody = ''; + + res.on('data', (chunk: string | Buffer) => { + responseBody += chunk.toString(); + }); + + res.on('end', () => { + try { + if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) { + const responseJson = JSON.parse(responseBody) as retType; + resolve(responseJson.result!.url!); + } else { + reject(new Error(`Unexpected status code: ${res.statusCode}`)); + } + } catch (parseError) { + reject(parseError); + } + + }); }); - }); + req.on('error', (error) => { + reject(error); + console.error('Error during upload:', error); + }); - req.on('error', (error) => { - reject(error); - console.error('Error during upload:', error); - }); - - const body = await RequestUtil.createFormData('WebKitFormBoundary7MA4YWxkTrZu0gW', filePath); - // req.setHeader('Content-Length', Buffer.byteLength(body)); - // console.log(`Prepared data size: ${Buffer.byteLength(body)} bytes`); - req.write(body); - req.end(); - return; + const body = await RequestUtil.createFormData('WebKitFormBoundary7MA4YWxkTrZu0gW', filePath); + // req.setHeader('Content-Length', Buffer.byteLength(body)); + // console.log(`Prepared data size: ${Buffer.byteLength(body)} bytes`); + req.write(body); + req.end(); + return; } catch (error) { - reject(error); + reject(error); } return undefined; - }); - } + }); + } } diff --git a/src/common/utils/system.ts b/src/common/utils/system.ts index 305e35f6..91520d4a 100644 --- a/src/common/utils/system.ts +++ b/src/common/utils/system.ts @@ -9,58 +9,58 @@ let osName: string; let machineId: Promise; try { - osName = os.hostname(); + osName = os.hostname(); } catch (e) { - osName = 'NapCat'; // + crypto.randomUUID().substring(0, 4); + osName = 'NapCat'; // + crypto.randomUUID().substring(0, 4); } const invalidMacAddresses = new Set([ - '00:00:00:00:00:00', - 'ff:ff:ff:ff:ff:ff', - 'ac:de:48:00:11:22' + '00:00:00:00:00:00', + 'ff:ff:ff:ff:ff:ff', + 'ac:de:48:00:11:22' ]); function validateMacAddress(candidate: string): boolean { - // eslint-disable-next-line no-useless-escape - const tempCandidate = candidate.replace(/\-/g, ':').toLowerCase(); - return !invalidMacAddresses.has(tempCandidate); + // eslint-disable-next-line no-useless-escape + const tempCandidate = candidate.replace(/\-/g, ':').toLowerCase(); + return !invalidMacAddresses.has(tempCandidate); } export async function getMachineId(): Promise { - if (!machineId) { - machineId = (async () => { - const id = await getMacMachineId(); - return id || randomUUID(); // fallback, generate a UUID - })(); - } + if (!machineId) { + machineId = (async () => { + const id = await getMacMachineId(); + return id || randomUUID(); // fallback, generate a UUID + })(); + } - return machineId; + return machineId; } export function getMac(): string { - const ifaces = networkInterfaces(); - for (const name in ifaces) { - const networkInterface = ifaces[name]; - if (networkInterface) { - for (const { mac } of networkInterface) { - if (validateMacAddress(mac)) { - return mac; + const ifaces = networkInterfaces(); + for (const name in ifaces) { + const networkInterface = ifaces[name]; + if (networkInterface) { + for (const { mac } of networkInterface) { + if (validateMacAddress(mac)) { + return mac; + } + } } - } } - } - throw new Error('Unable to retrieve mac address (unexpected format)'); + throw new Error('Unable to retrieve mac address (unexpected format)'); } async function getMacMachineId(): Promise { - try { - const crypto = await import('crypto'); - const macAddress = getMac(); - return crypto.createHash('sha256').update(macAddress, 'utf8').digest('hex'); - } catch (err) { - return undefined; - } + try { + const crypto = await import('crypto'); + const macAddress = getMac(); + return crypto.createHash('sha256').update(macAddress, 'utf8').digest('hex'); + } catch (err) { + return undefined; + } } const homeDir = os.homedir(); diff --git a/src/liteloader/napcat.cjs b/src/liteloader/napcat.cjs index e0d441a1..a649af70 100644 --- a/src/liteloader/napcat.cjs +++ b/src/liteloader/napcat.cjs @@ -4,7 +4,7 @@ const CurrentPath = path.dirname(__filename); let Process = require('process'); let os = require('os'); -Process.dlopenOrig = Process.dlopen +Process.dlopenOrig = Process.dlopen; let proxyHandler = { get(target, prop, receiver) { @@ -22,22 +22,22 @@ let WrapperNodeApi = undefined;//NativeNpdeApi let WrapperLoginService = undefined; Process.dlopen = function (module, filename, flags = os.constants.dlopen.RTLD_LAZY) { - let dlopenRet = this.dlopenOrig(module, filename, flags) + let dlopenRet = this.dlopenOrig(module, filename, flags); for (let export_name in module.exports) { module.exports[export_name] = new Proxy(module.exports[export_name], { construct: (target, args, _newTarget) => { - let ret = new target(...args) - if (export_name === 'NodeIQQNTWrapperSession') WrapperSession = ret - if (export_name === 'NodeIKernelLoginService') WrapperLoginService = ret - return ret + let ret = new target(...args); + if (export_name === 'NodeIQQNTWrapperSession') WrapperSession = ret; + if (export_name === 'NodeIKernelLoginService') WrapperLoginService = ret; + return ret; }, - }) + }); } if (filename.toLowerCase().indexOf('wrapper.node') != -1) { WrapperNodeApi = module.exports; } return dlopenRet; -} +}; function getWrapperSession() { return WrapperSession; } diff --git a/src/liteloader/napcat.ts b/src/liteloader/napcat.ts index 0400bd69..92ebd840 100644 --- a/src/liteloader/napcat.ts +++ b/src/liteloader/napcat.ts @@ -16,13 +16,13 @@ import { sleep } from "@/common/utils/helper"; export async function NCoreInitLiteLoader(session: NodeIQQNTWrapperSession, loginService: NodeIKernelLoginService) { //在进入本层前是否登录未进行判断 console.log("NapCat LiteLoader App Loading..."); - let pathWrapper = new NapCatPathWrapper(); - let logger = new LogWrapper(pathWrapper.logsPath); - let basicInfoWrapper = new QQBasicInfoWrapper({ logger }); - let wrapper = loadQQWrapper(basicInfoWrapper.getFullQQVesion()); + const pathWrapper = new NapCatPathWrapper(); + const logger = new LogWrapper(pathWrapper.logsPath); + const basicInfoWrapper = new QQBasicInfoWrapper({ logger }); + const wrapper = loadQQWrapper(basicInfoWrapper.getFullQQVesion()); //直到登录成功后,执行下一步 - let selfInfo = await new Promise((resolve) => { - let loginListener = new LoginListener(); + const selfInfo = await new Promise((resolve) => { + const loginListener = new LoginListener(); loginListener.onQRCodeLoginSucceed = async (loginResult) => resolve({ uid: loginResult.uid, uin: loginResult.uin, @@ -35,7 +35,7 @@ export async function NCoreInitLiteLoader(session: NodeIQQNTWrapperSession, logi // 过早进入会导致addKernelMsgListener等Listener添加失败 await sleep(2500); // 初始化 NapCatLiteLoader - let loaderObject = new NapCatLiteLoader(wrapper, session, logger, loginService, selfInfo, basicInfoWrapper); + const loaderObject = new NapCatLiteLoader(wrapper, session, logger, loginService, selfInfo, basicInfoWrapper); //启动WebUi diff --git a/src/webui/index.ts b/src/webui/index.ts index ed65bd81..c05fddcd 100644 --- a/src/webui/index.ts +++ b/src/webui/index.ts @@ -20,26 +20,26 @@ const __dirname = dirname(__filename); * @returns {Promise} 无返回值。 */ export async function InitWebUi() { - const config = await WebUiConfig.GetWebUIConfig(); - if (config.port == 0) { - log('[NapCat] [WebUi] Current WebUi is not run.'); - return; - } - app.use(express.json()); - // 初始服务 - // WebUI只在config.prefix所示路径上提供服务,可配合Nginx挂载到子目录中 - app.all(config.prefix + '/', (_req, res) => { - res.json({ - msg: 'NapCat WebAPI is now running!', + const config = await WebUiConfig.GetWebUIConfig(); + if (config.port == 0) { + log('[NapCat] [WebUi] Current WebUi is not run.'); + return; + } + app.use(express.json()); + // 初始服务 + // WebUI只在config.prefix所示路径上提供服务,可配合Nginx挂载到子目录中 + app.all(config.prefix + '/', (_req, res) => { + res.json({ + msg: 'NapCat WebAPI is now running!', + }); + }); + // 配置静态文件服务,提供./static目录下的文件服务,访问路径为/webui + app.use(config.prefix + '/webui', express.static(resolve(__dirname, './static'))); + //挂载API接口 + app.use(config.prefix + '/api', ALLRouter); + app.listen(config.port, config.host, async () => { + log(`[NapCat] [WebUi] Current WebUi is running at http://${config.host}:${config.port}${config.prefix}`); + log(`[NapCat] [WebUi] Login URL is http://${config.host}:${config.port}${config.prefix}/webui`); + log(`[NapCat] [WebUi] Login Token is ${config.token}`); }); - }); - // 配置静态文件服务,提供./static目录下的文件服务,访问路径为/webui - app.use(config.prefix + '/webui', express.static(resolve(__dirname, './static'))); - //挂载API接口 - app.use(config.prefix + '/api', ALLRouter); - app.listen(config.port, config.host, async () => { - log(`[NapCat] [WebUi] Current WebUi is running at http://${config.host}:${config.port}${config.prefix}`); - log(`[NapCat] [WebUi] Login URL is http://${config.host}:${config.port}${config.prefix}/webui`); - log(`[NapCat] [WebUi] Login Token is ${config.token}`); - }); } diff --git a/src/webui/src/api/Auth.ts b/src/webui/src/api/Auth.ts index 7f8e9d8c..519d47c0 100644 --- a/src/webui/src/api/Auth.ts +++ b/src/webui/src/api/Auth.ts @@ -4,65 +4,65 @@ import { WebUiConfig } from '../helper/config'; import { WebUiDataRuntime } from '../helper/Data'; const isEmpty = (data: any) => data === undefined || data === null || data === ''; export const LoginHandler: RequestHandler = async (req, res) => { - const WebUiConfigData = await WebUiConfig.GetWebUIConfig(); - const { token } = req.body; - if (isEmpty(token)) { - res.json({ - code: -1, - message: 'token is empty' - }); - return; - } - if (!await WebUiDataRuntime.checkLoginRate(WebUiConfigData.loginRate)) { - res.json({ - code: -1, - message: 'login rate limit' - }); - return; - } - //验证config.token是否等于token - if (WebUiConfigData.token !== token) { - res.json({ - code: -1, - message: 'token is invalid' - }); - return; - } - const signCredential = Buffer.from(JSON.stringify(await AuthHelper.signCredential(WebUiConfigData.token))).toString('base64'); - res.json({ - code: 0, - message: 'success', - data: { - 'Credential': signCredential + const WebUiConfigData = await WebUiConfig.GetWebUIConfig(); + const { token } = req.body; + if (isEmpty(token)) { + res.json({ + code: -1, + message: 'token is empty' + }); + return; + } + if (!await WebUiDataRuntime.checkLoginRate(WebUiConfigData.loginRate)) { + res.json({ + code: -1, + message: 'login rate limit' + }); + return; } - }); - return; + //验证config.token是否等于token + if (WebUiConfigData.token !== token) { + res.json({ + code: -1, + message: 'token is invalid' + }); + return; + } + const signCredential = Buffer.from(JSON.stringify(await AuthHelper.signCredential(WebUiConfigData.token))).toString('base64'); + res.json({ + code: 0, + message: 'success', + data: { + 'Credential': signCredential + } + }); + return; }; export const LogoutHandler: RequestHandler = (req, res) => { - // 这玩意无状态销毁个灯 得想想办法 - res.json({ - code: 0, - message: 'success' - }); - return; -}; -export const checkHandler: RequestHandler = async (req, res) => { - const WebUiConfigData = await WebUiConfig.GetWebUIConfig(); - const authorization = req.headers.authorization; - try { - const CredentialBase64:string = authorization?.split(' ')[1] as string; - const Credential = JSON.parse(Buffer.from(CredentialBase64, 'base64').toString()); - await AuthHelper.validateCredentialWithinOneHour(WebUiConfigData.token,Credential); + // 这玩意无状态销毁个灯 得想想办法 res.json({ - code: 0, - message: 'success' + code: 0, + message: 'success' }); return; - } catch (e) { - res.json({ - code: -1, - message: 'failed' - }); - } - return; +}; +export const checkHandler: RequestHandler = async (req, res) => { + const WebUiConfigData = await WebUiConfig.GetWebUIConfig(); + const authorization = req.headers.authorization; + try { + const CredentialBase64:string = authorization?.split(' ')[1] as string; + const Credential = JSON.parse(Buffer.from(CredentialBase64, 'base64').toString()); + await AuthHelper.validateCredentialWithinOneHour(WebUiConfigData.token,Credential); + res.json({ + code: 0, + message: 'success' + }); + return; + } catch (e) { + res.json({ + code: -1, + message: 'failed' + }); + } + return; }; diff --git a/src/webui/src/api/LogConsole.ts b/src/webui/src/api/LogConsole.ts index 68eec759..8d45e23a 100644 --- a/src/webui/src/api/LogConsole.ts +++ b/src/webui/src/api/LogConsole.ts @@ -9,44 +9,44 @@ import { fileURLToPath } from 'node:url'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); export const GetLogFileListHandler: RequestHandler = async (req, res) => { - try { - const LogsPath = resolve(__dirname, './logs/'); - const LogFiles = await readdir(LogsPath); - res.json({ - code: 0, - data: LogFiles - }); - } catch (error) { - res.json({ code: -1, msg: 'Failed to retrieve log file list.' }); - } + try { + const LogsPath = resolve(__dirname, './logs/'); + const LogFiles = await readdir(LogsPath); + res.json({ + code: 0, + data: LogFiles + }); + } catch (error) { + res.json({ code: -1, msg: 'Failed to retrieve log file list.' }); + } }; export const GetLogFileHandler: RequestHandler = async (req, res) => { - const LogsPath = resolve(__dirname, './logs/'); - const LogFile = req.query.file as string; + const LogsPath = resolve(__dirname, './logs/'); + const LogFile = req.query.file as string; - // if (!isValidFileName(LogFile)) { - // res.json({ code: -1, msg: 'LogFile is not safe' }); - // return; - // } + // if (!isValidFileName(LogFile)) { + // res.json({ code: -1, msg: 'LogFile is not safe' }); + // return; + // } - const filePath = `${LogsPath}/${LogFile}`; - if (!existsSync(filePath)) { - res.status(404).json({ code: -1, msg: 'LogFile does not exist' }); - return; - } - - try { - const fileStats = await stat(filePath); - if (!fileStats.isFile()) { - res.json({ code: -1, msg: 'LogFile must be a file' }); - return; + const filePath = `${LogsPath}/${LogFile}`; + if (!existsSync(filePath)) { + res.status(404).json({ code: -1, msg: 'LogFile does not exist' }); + return; } - res.sendFile(filePath); - } catch (error) { - res.json({ code: -1, msg: 'Failed to send log file.' }); - } + try { + const fileStats = await stat(filePath); + if (!fileStats.isFile()) { + res.json({ code: -1, msg: 'LogFile must be a file' }); + return; + } + + res.sendFile(filePath); + } catch (error) { + res.json({ code: -1, msg: 'Failed to send log file.' }); + } }; // export function isValidFileName(fileName: string): boolean { // const invalidChars = /[\.\:\*\?\"\<\>\|\/\\]/; diff --git a/src/webui/src/api/OB11Config.ts b/src/webui/src/api/OB11Config.ts index 1a3ffd50..f9e07ee3 100644 --- a/src/webui/src/api/OB11Config.ts +++ b/src/webui/src/api/OB11Config.ts @@ -11,87 +11,87 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const isEmpty = (data: any) => - data === undefined || data === null || data === ''; + data === undefined || data === null || data === ''; export const OB11GetConfigHandler: RequestHandler = async (req, res) => { - const isLogin = await WebUiDataRuntime.getQQLoginStatus(); - if (!isLogin) { + const isLogin = await WebUiDataRuntime.getQQLoginStatus(); + if (!isLogin) { + res.send({ + code: -1, + message: 'Not Login', + }); + return; + } + const uin = await WebUiDataRuntime.getQQLoginUin(); + const configFilePath = resolve(__dirname, `./config/onebot11_${uin}.json`); + //console.log(configFilePath); + let data: OB11Config; + try { + data = JSON.parse( + existsSync(configFilePath) + ? readFileSync(configFilePath).toString() + : readFileSync(resolve(__dirname, './config/onebot11.json')).toString() + ); + } catch (e) { + data = {} as OB11Config; + res.send({ + code: -1, + message: 'Config Get Error', + }); + return; + } res.send({ - code: -1, - message: 'Not Login', + code: 0, + message: 'success', + data: data, }); return; - } - const uin = await WebUiDataRuntime.getQQLoginUin(); - const configFilePath = resolve(__dirname, `./config/onebot11_${uin}.json`); - //console.log(configFilePath); - let data: OB11Config; - try { - data = JSON.parse( - existsSync(configFilePath) - ? readFileSync(configFilePath).toString() - : readFileSync(resolve(__dirname, './config/onebot11.json')).toString() - ); - } catch (e) { - data = {} as OB11Config; - res.send({ - code: -1, - message: 'Config Get Error', - }); - return; - } - res.send({ - code: 0, - message: 'success', - data: data, - }); - return; }; export const OB11SetConfigHandler: RequestHandler = async (req, res) => { - const isLogin = await WebUiDataRuntime.getQQLoginStatus(); - if (!isLogin) { - res.send({ - code: -1, - message: 'Not Login', - }); - return; - } - if (isEmpty(req.body.config)) { - res.send({ - code: -1, - message: 'config is empty', - }); - return; - } - let SetResult; - try { - await WebUiDataRuntime.setOB11Config(JSON.parse(req.body.config)); - SetResult = true; - } catch (e) { - SetResult = false; - } + const isLogin = await WebUiDataRuntime.getQQLoginStatus(); + if (!isLogin) { + res.send({ + code: -1, + message: 'Not Login', + }); + return; + } + if (isEmpty(req.body.config)) { + res.send({ + code: -1, + message: 'config is empty', + }); + return; + } + let SetResult; + try { + await WebUiDataRuntime.setOB11Config(JSON.parse(req.body.config)); + SetResult = true; + } catch (e) { + SetResult = false; + } - // let configFilePath = resolve(__dirname, `./config/onebot11_${await WebUiDataRuntime.getQQLoginUin()}.json`); - // try { - // JSON.parse(req.body.config) - // readFileSync(configFilePath); - // } - // catch (e) { - // //console.log(e); - // configFilePath = resolve(__dirname, `./config/onebot11.json`); - // } - // //console.log(configFilePath,JSON.parse(req.body.config)); - // writeFileSync(configFilePath, JSON.stringify(JSON.parse(req.body.config), null, 4)); - if (SetResult) { - res.send({ - code: 0, - message: 'success', - }); - } else { - res.send({ - code: -1, - message: 'Config Set Error', - }); - } + // let configFilePath = resolve(__dirname, `./config/onebot11_${await WebUiDataRuntime.getQQLoginUin()}.json`); + // try { + // JSON.parse(req.body.config) + // readFileSync(configFilePath); + // } + // catch (e) { + // //console.log(e); + // configFilePath = resolve(__dirname, `./config/onebot11.json`); + // } + // //console.log(configFilePath,JSON.parse(req.body.config)); + // writeFileSync(configFilePath, JSON.stringify(JSON.parse(req.body.config), null, 4)); + if (SetResult) { + res.send({ + code: 0, + message: 'success', + }); + } else { + res.send({ + code: -1, + message: 'Config Set Error', + }); + } - return; + return; }; diff --git a/src/webui/src/api/QQLogin.ts b/src/webui/src/api/QQLogin.ts index 84355f62..0ea96df0 100644 --- a/src/webui/src/api/QQLogin.ts +++ b/src/webui/src/api/QQLogin.ts @@ -3,75 +3,75 @@ import { WebUiDataRuntime } from '../helper/Data'; import { sleep } from '@/common/utils/helper'; const isEmpty = (data: any) => data === undefined || data === null || data === ''; export const QQGetQRcodeHandler: RequestHandler = async (req, res) => { - if (await WebUiDataRuntime.getQQLoginStatus()) { - res.send({ - code: -1, - message: 'QQ Is Logined' - }); - return; - } - const qrcodeUrl = await WebUiDataRuntime.getQQLoginQrcodeURL(); - if (isEmpty(qrcodeUrl)) { - res.send({ - code: -1, - message: 'QRCode Get Error' - }); - return; - } - res.send({ - code: 0, - message: 'success', - data: { - qrcode: qrcodeUrl + if (await WebUiDataRuntime.getQQLoginStatus()) { + res.send({ + code: -1, + message: 'QQ Is Logined' + }); + return; } - }); - return; + const qrcodeUrl = await WebUiDataRuntime.getQQLoginQrcodeURL(); + if (isEmpty(qrcodeUrl)) { + res.send({ + code: -1, + message: 'QRCode Get Error' + }); + return; + } + res.send({ + code: 0, + message: 'success', + data: { + qrcode: qrcodeUrl + } + }); + return; }; export const QQCheckLoginStatusHandler: RequestHandler = async (req, res) => { - res.send({ - code: 0, - message: 'success', - data: { - isLogin: await WebUiDataRuntime.getQQLoginStatus() - } - }); + res.send({ + code: 0, + message: 'success', + data: { + isLogin: await WebUiDataRuntime.getQQLoginStatus() + } + }); }; export const QQSetQuickLoginHandler: RequestHandler = async (req, res) => { - const { uin } = req.body; - const isLogin = await WebUiDataRuntime.getQQLoginStatus(); - if (isLogin) { + const { uin } = req.body; + const isLogin = await WebUiDataRuntime.getQQLoginStatus(); + if (isLogin) { + res.send({ + code: -1, + message: 'QQ Is Logined' + }); + return; + } + if (isEmpty(uin)) { + res.send({ + code: -1, + message: 'uin is empty' + }); + return; + } + const { result, message } = await WebUiDataRuntime.getQQQuickLogin(uin); + if (!result) { + res.send({ + code: -1, + message: message + }); + return; + } + //本来应该验证 但是http不宜这么搞 建议前端验证 + //isLogin = await WebUiDataRuntime.getQQLoginStatus(); res.send({ - code: -1, - message: 'QQ Is Logined' + code: 0, + message: 'success' }); - return; - } - if (isEmpty(uin)) { - res.send({ - code: -1, - message: 'uin is empty' - }); - return; - } - const { result, message } = await WebUiDataRuntime.getQQQuickLogin(uin); - if (!result) { - res.send({ - code: -1, - message: message - }); - return; - } - //本来应该验证 但是http不宜这么搞 建议前端验证 - //isLogin = await WebUiDataRuntime.getQQLoginStatus(); - res.send({ - code: 0, - message: 'success' - }); }; export const QQGetQuickLoginListHandler: RequestHandler = async (req, res) => { - const quickLoginList = await WebUiDataRuntime.getQQQuickLoginList(); - res.send({ - code: 0, - data: quickLoginList - }); + const quickLoginList = await WebUiDataRuntime.getQQQuickLoginList(); + res.send({ + code: 0, + data: quickLoginList + }); }; \ No newline at end of file diff --git a/src/webui/src/helper/Data.ts b/src/webui/src/helper/Data.ts index 8be898c5..e944ac4d 100644 --- a/src/webui/src/helper/Data.ts +++ b/src/webui/src/helper/Data.ts @@ -13,71 +13,71 @@ interface LoginRuntimeType { } } const LoginRuntime: LoginRuntimeType = { - LoginCurrentTime: Date.now(), - LoginCurrentRate: 0, - QQLoginStatus: false, //已实现 但太傻了 得去那边注册个回调刷新 - QQQRCodeURL: '', - QQLoginUin: '', - NapCatHelper: { - SetOb11ConfigCall: async (ob11: OB11Config) => { return; }, - CoreQuickLoginCall: async (uin: string) => { return { result: false, message: '' }; }, - QQLoginList: [] - } + LoginCurrentTime: Date.now(), + LoginCurrentRate: 0, + QQLoginStatus: false, //已实现 但太傻了 得去那边注册个回调刷新 + QQQRCodeURL: '', + QQLoginUin: '', + NapCatHelper: { + SetOb11ConfigCall: async (ob11: OB11Config) => { return; }, + CoreQuickLoginCall: async (uin: string) => { return { result: false, message: '' }; }, + QQLoginList: [] + } }; export const WebUiDataRuntime = { - checkLoginRate: async function (RateLimit: number): Promise { - LoginRuntime.LoginCurrentRate++; - //console.log(RateLimit, LoginRuntime.LoginCurrentRate, Date.now() - LoginRuntime.LoginCurrentTime); - if (Date.now() - LoginRuntime.LoginCurrentTime > 1000 * 60) { - LoginRuntime.LoginCurrentRate = 0;//超出时间重置限速 - LoginRuntime.LoginCurrentTime = Date.now(); - return true; + checkLoginRate: async function (RateLimit: number): Promise { + LoginRuntime.LoginCurrentRate++; + //console.log(RateLimit, LoginRuntime.LoginCurrentRate, Date.now() - LoginRuntime.LoginCurrentTime); + if (Date.now() - LoginRuntime.LoginCurrentTime > 1000 * 60) { + LoginRuntime.LoginCurrentRate = 0;//超出时间重置限速 + LoginRuntime.LoginCurrentTime = Date.now(); + return true; + } + if (LoginRuntime.LoginCurrentRate <= RateLimit) { + return true; + } + return false; } - if (LoginRuntime.LoginCurrentRate <= RateLimit) { - return true; + , + getQQLoginStatus: async function (): Promise { + return LoginRuntime.QQLoginStatus; + } + , + setQQLoginStatus: async function (status: boolean): Promise { + LoginRuntime.QQLoginStatus = status; + } + , + setQQLoginQrcodeURL: async function (url: string): Promise { + LoginRuntime.QQQRCodeURL = url; + } + , + getQQLoginQrcodeURL: async function (): Promise { + return LoginRuntime.QQQRCodeURL; + } + , + setQQLoginUin: async function (uin: string): Promise { + LoginRuntime.QQLoginUin = uin; + } + , + getQQLoginUin: async function (): Promise { + return LoginRuntime.QQLoginUin; + }, + getQQQuickLoginList: async function (): Promise { + return LoginRuntime.NapCatHelper.QQLoginList; + }, + setQQQuickLoginList: async function (list: string[]): Promise { + LoginRuntime.NapCatHelper.QQLoginList = list; + }, + setQQQuickLoginCall(func: (uin: string) => Promise<{ result: boolean, message: string }>): void { + LoginRuntime.NapCatHelper.CoreQuickLoginCall = func; + }, + getQQQuickLogin: async function (uin: string): Promise<{ result: boolean, message: string }> { + return await LoginRuntime.NapCatHelper.CoreQuickLoginCall(uin); + }, + setOB11ConfigCall: async function (func: (ob11: OB11Config) => Promise): Promise { + LoginRuntime.NapCatHelper.SetOb11ConfigCall = func; + }, + setOB11Config: async function (ob11: OB11Config): Promise { + await LoginRuntime.NapCatHelper.SetOb11ConfigCall(ob11); } - return false; - } - , - getQQLoginStatus: async function (): Promise { - return LoginRuntime.QQLoginStatus; - } - , - setQQLoginStatus: async function (status: boolean): Promise { - LoginRuntime.QQLoginStatus = status; - } - , - setQQLoginQrcodeURL: async function (url: string): Promise { - LoginRuntime.QQQRCodeURL = url; - } - , - getQQLoginQrcodeURL: async function (): Promise { - return LoginRuntime.QQQRCodeURL; - } - , - setQQLoginUin: async function (uin: string): Promise { - LoginRuntime.QQLoginUin = uin; - } - , - getQQLoginUin: async function (): Promise { - return LoginRuntime.QQLoginUin; - }, - getQQQuickLoginList: async function (): Promise { - return LoginRuntime.NapCatHelper.QQLoginList; - }, - setQQQuickLoginList: async function (list: string[]): Promise { - LoginRuntime.NapCatHelper.QQLoginList = list; - }, - setQQQuickLoginCall(func: (uin: string) => Promise<{ result: boolean, message: string }>): void { - LoginRuntime.NapCatHelper.CoreQuickLoginCall = func; - }, - getQQQuickLogin: async function (uin: string): Promise<{ result: boolean, message: string }> { - return await LoginRuntime.NapCatHelper.CoreQuickLoginCall(uin); - }, - setOB11ConfigCall: async function (func: (ob11: OB11Config) => Promise): Promise { - LoginRuntime.NapCatHelper.SetOb11ConfigCall = func; - }, - setOB11Config: async function (ob11: OB11Config): Promise { - await LoginRuntime.NapCatHelper.SetOb11ConfigCall(ob11); - } }; \ No newline at end of file diff --git a/src/webui/src/helper/SignToken.ts b/src/webui/src/helper/SignToken.ts index 8803a9e1..0cce59f9 100644 --- a/src/webui/src/helper/SignToken.ts +++ b/src/webui/src/helper/SignToken.ts @@ -11,58 +11,58 @@ interface WebUiCredentialJson { } export class AuthHelper { - private static secretKey = Math.random().toString(36).slice(2); + private static secretKey = Math.random().toString(36).slice(2); - /** + /** * 签名凭证方法。 * @param token 待签名的凭证字符串。 * @returns 签名后的凭证对象。 */ - public static async signCredential(token: string): Promise { - const innerJson: WebUiCredentialInnerJson = { - CreatedTime: Date.now(), - TokenEncoded: token, - }; - const jsonString = JSON.stringify(innerJson); - const hmac = crypto.createHmac('sha256', AuthHelper.secretKey) - .update(jsonString, 'utf8') - .digest('hex'); - return { Data: innerJson, Hmac: hmac }; - } + public static async signCredential(token: string): Promise { + const innerJson: WebUiCredentialInnerJson = { + CreatedTime: Date.now(), + TokenEncoded: token, + }; + const jsonString = JSON.stringify(innerJson); + const hmac = crypto.createHmac('sha256', AuthHelper.secretKey) + .update(jsonString, 'utf8') + .digest('hex'); + return { Data: innerJson, Hmac: hmac }; + } - /** + /** * 检查凭证是否被篡改的方法。 * @param credentialJson 凭证的JSON对象。 * @returns 布尔值,表示凭证是否有效。 */ - public static async checkCredential(credentialJson: WebUiCredentialJson): Promise { - try { - const jsonString = JSON.stringify(credentialJson.Data); - const calculatedHmac = crypto.createHmac('sha256', AuthHelper.secretKey) - .update(jsonString, 'utf8') - .digest('hex'); - return calculatedHmac === credentialJson.Hmac; - } catch (error) { - return false; + public static async checkCredential(credentialJson: WebUiCredentialJson): Promise { + try { + const jsonString = JSON.stringify(credentialJson.Data); + const calculatedHmac = crypto.createHmac('sha256', AuthHelper.secretKey) + .update(jsonString, 'utf8') + .digest('hex'); + return calculatedHmac === credentialJson.Hmac; + } catch (error) { + return false; + } } - } - /** + /** * 验证凭证在1小时内有效且token与原始token相同。 * @param token 待验证的原始token。 * @param credentialJson 已签名的凭证JSON对象。 * @returns 布尔值,表示凭证是否有效且token匹配。 */ - public static async validateCredentialWithinOneHour(token: string, credentialJson: WebUiCredentialJson): Promise { - const isValid = await AuthHelper.checkCredential(credentialJson); - if (!isValid) { - return false; + public static async validateCredentialWithinOneHour(token: string, credentialJson: WebUiCredentialJson): Promise { + const isValid = await AuthHelper.checkCredential(credentialJson); + if (!isValid) { + return false; + } + + const currentTime = Date.now() / 1000; + const createdTime = credentialJson.Data.CreatedTime; + const timeDifference = currentTime - createdTime; + + return timeDifference <= 3600 && credentialJson.Data.TokenEncoded === token; } - - const currentTime = Date.now() / 1000; - const createdTime = credentialJson.Data.CreatedTime; - const timeDifference = currentTime - createdTime; - - return timeDifference <= 3600 && credentialJson.Data.TokenEncoded === token; - } } \ No newline at end of file diff --git a/src/webui/src/helper/config.ts b/src/webui/src/helper/config.ts index 44b77c75..652e286d 100644 --- a/src/webui/src/helper/config.ts +++ b/src/webui/src/helper/config.ts @@ -13,60 +13,60 @@ const __dirname = dirname(__filename); const MAX_PORT_TRY = 100; async function tryUseHost(host: string): Promise { - return new Promise(async (resolve, reject) => { - try { - const server = net.createServer(); - server.on('listening', () => { - server.close(); - resolve(host); - }); + return new Promise(async (resolve, reject) => { + try { + const server = net.createServer(); + server.on('listening', () => { + server.close(); + resolve(host); + }); - server.on('error', (err: any) => { - if (err.code === 'EADDRNOTAVAIL') { - reject('主机地址验证失败,可能为非本机地址'); - } else { - reject(`遇到错误: ${err.code}`); + server.on('error', (err: any) => { + if (err.code === 'EADDRNOTAVAIL') { + reject('主机地址验证失败,可能为非本机地址'); + } else { + reject(`遇到错误: ${err.code}`); + } + }); + + // 尝试监听 让系统随机分配一个端口 + server.listen(0, host); + } catch (error) { + // 这里捕获到的错误应该是启动服务器时的同步错误 + reject(`服务器启动时发生错误: ${error}`); } - }); - - // 尝试监听 让系统随机分配一个端口 - server.listen(0, host); - } catch (error) { - // 这里捕获到的错误应该是启动服务器时的同步错误 - reject(`服务器启动时发生错误: ${error}`); - } - }); + }); } async function tryUsePort(port: number, host: string, tryCount: number = 0): Promise { - return new Promise(async (resolve, reject) => { - try { - const server = net.createServer(); - server.on('listening', () => { - server.close(); - resolve(port); - }); + return new Promise(async (resolve, reject) => { + try { + const server = net.createServer(); + server.on('listening', () => { + server.close(); + resolve(port); + }); - server.on('error', (err: any) => { - if (err.code === 'EADDRINUSE') { - if (tryCount < MAX_PORT_TRY) { - // 使用循环代替递归 - resolve(tryUsePort(port + 1, host, tryCount + 1)); - } else { - reject(`端口尝试失败,达到最大尝试次数: ${MAX_PORT_TRY}`); - } - } else { - reject(`遇到错误: ${err.code}`); + server.on('error', (err: any) => { + if (err.code === 'EADDRINUSE') { + if (tryCount < MAX_PORT_TRY) { + // 使用循环代替递归 + resolve(tryUsePort(port + 1, host, tryCount + 1)); + } else { + reject(`端口尝试失败,达到最大尝试次数: ${MAX_PORT_TRY}`); + } + } else { + reject(`遇到错误: ${err.code}`); + } + }); + + // 尝试监听端口 + server.listen(port, host); + } catch (error) { + // 这里捕获到的错误应该是启动服务器时的同步错误 + reject(`服务器启动时发生错误: ${error}`); } - }); - - // 尝试监听端口 - server.listen(port, host); - } catch (error) { - // 这里捕获到的错误应该是启动服务器时的同步错误 - reject(`服务器启动时发生错误: ${error}`); - } - }); + }); } export interface WebUiConfigType { @@ -78,65 +78,65 @@ export interface WebUiConfigType { } // 读取当前目录下名为 webui.json 的配置文件,如果不存在则创建初始化配置文件 class WebUiConfigWrapper { - WebUiConfigData: WebUiConfigType | undefined = undefined; - private applyDefaults(obj: Partial, defaults: T): T { - return { ...defaults, ...obj }; - } - async GetWebUIConfig(): Promise { - if (this.WebUiConfigData) { - return this.WebUiConfigData; + WebUiConfigData: WebUiConfigType | undefined = undefined; + private applyDefaults(obj: Partial, defaults: T): T { + return { ...defaults, ...obj }; } - const defaultconfig: WebUiConfigType = { - host: '0.0.0.0', - port: 6099, - prefix: '', - token: '', // 默认先填空,空密码无法登录 - loginRate: 3 - }; - try { - defaultconfig.token = Math.random().toString(36).slice(2); //生成随机密码 - } catch (e) { - logError('随机密码生成失败', e); - } - try { - const configPath = resolve(__dirname, './config/webui.json'); - - if (!existsSync(configPath)) { - writeFileSync(configPath, JSON.stringify(defaultconfig, null, 4)); - } - - const fileContent = readFileSync(configPath, 'utf-8'); - // 更新配置字段后新增字段可能会缺失,同步一下 - const parsedConfig = this.applyDefaults(JSON.parse(fileContent) as Partial, defaultconfig); - - if (!parsedConfig.prefix.startsWith('/')) parsedConfig.prefix = '/' + parsedConfig.prefix; - if (parsedConfig.prefix.endsWith('/')) parsedConfig.prefix = parsedConfig.prefix.slice(0, -1); - // 配置已经被操作过了,还是回写一下吧,不然新配置不会出现在配置文件里 - writeFileSync(configPath, JSON.stringify(parsedConfig, null, 4)); - // 不希望回写的配置放后面 - - // 查询主机地址是否可用 - const [host_err, host] = await tryUseHost(parsedConfig.host).then(data => [null, data as string]).catch(err => [err, null]); - if (host_err) { - logError('host不可用', host_err); - parsedConfig.port = 0; // 设置为0,禁用WebUI - } else { - parsedConfig.host = host; - // 修正端口占用情况 - const [port_err, port] = await tryUsePort(parsedConfig.port, parsedConfig.host).then(data => [null, data as number]).catch(err => [err, null]); - if (port_err) { - logError('port不可用', port_err); - parsedConfig.port = 0; // 设置为0,禁用WebUI - } else { - parsedConfig.port = port; + async GetWebUIConfig(): Promise { + if (this.WebUiConfigData) { + return this.WebUiConfigData; } - } - this.WebUiConfigData = parsedConfig; - return this.WebUiConfigData; - } catch (e) { - logError('读取配置文件失败', e); + const defaultconfig: WebUiConfigType = { + host: '0.0.0.0', + port: 6099, + prefix: '', + token: '', // 默认先填空,空密码无法登录 + loginRate: 3 + }; + try { + defaultconfig.token = Math.random().toString(36).slice(2); //生成随机密码 + } catch (e) { + logError('随机密码生成失败', e); + } + try { + const configPath = resolve(__dirname, './config/webui.json'); + + if (!existsSync(configPath)) { + writeFileSync(configPath, JSON.stringify(defaultconfig, null, 4)); + } + + const fileContent = readFileSync(configPath, 'utf-8'); + // 更新配置字段后新增字段可能会缺失,同步一下 + const parsedConfig = this.applyDefaults(JSON.parse(fileContent) as Partial, defaultconfig); + + if (!parsedConfig.prefix.startsWith('/')) parsedConfig.prefix = '/' + parsedConfig.prefix; + if (parsedConfig.prefix.endsWith('/')) parsedConfig.prefix = parsedConfig.prefix.slice(0, -1); + // 配置已经被操作过了,还是回写一下吧,不然新配置不会出现在配置文件里 + writeFileSync(configPath, JSON.stringify(parsedConfig, null, 4)); + // 不希望回写的配置放后面 + + // 查询主机地址是否可用 + const [host_err, host] = await tryUseHost(parsedConfig.host).then(data => [null, data as string]).catch(err => [err, null]); + if (host_err) { + logError('host不可用', host_err); + parsedConfig.port = 0; // 设置为0,禁用WebUI + } else { + parsedConfig.host = host; + // 修正端口占用情况 + const [port_err, port] = await tryUsePort(parsedConfig.port, parsedConfig.host).then(data => [null, data as number]).catch(err => [err, null]); + if (port_err) { + logError('port不可用', port_err); + parsedConfig.port = 0; // 设置为0,禁用WebUI + } else { + parsedConfig.port = port; + } + } + this.WebUiConfigData = parsedConfig; + return this.WebUiConfigData; + } catch (e) { + logError('读取配置文件失败', e); + } + return defaultconfig; // 理论上这行代码到不了,到了只能返回默认配置了 } - return defaultconfig; // 理论上这行代码到不了,到了只能返回默认配置了 - } } export const WebUiConfig = new WebUiConfigWrapper(); diff --git a/src/webui/src/router/index.ts b/src/webui/src/router/index.ts index 6efd6e76..56c206c9 100644 --- a/src/webui/src/router/index.ts +++ b/src/webui/src/router/index.ts @@ -7,57 +7,57 @@ import { OB11ConfigRouter } from './OB11Config'; import { WebUiConfig } from '../helper/config'; const router = Router(); export async function AuthApi(req: Request, res: Response, next: NextFunction) { - //判断当前url是否为/login 如果是跳过鉴权 - if (req.url == '/auth/login') { - next(); - return; - } - if (req.headers?.authorization) { - const authorization = req.headers.authorization.split(' '); - if (authorization.length < 2) { - res.json({ - code: -1, - msg: 'Unauthorized', - }); - return; + //判断当前url是否为/login 如果是跳过鉴权 + if (req.url == '/auth/login') { + next(); + return; } - const token = authorization[1]; - let Credential: any; - try { - Credential = JSON.parse(Buffer.from(token, 'base64').toString('utf-8')); - } catch (e) { - res.json({ - code: -1, - msg: 'Unauthorized', - }); - return; - } - const config = await WebUiConfig.GetWebUIConfig(); - const credentialJson = await AuthHelper.validateCredentialWithinOneHour(config.token, Credential); - if (credentialJson) { - //通过验证 - next(); - return; + if (req.headers?.authorization) { + const authorization = req.headers.authorization.split(' '); + if (authorization.length < 2) { + res.json({ + code: -1, + msg: 'Unauthorized', + }); + return; + } + const token = authorization[1]; + let Credential: any; + try { + Credential = JSON.parse(Buffer.from(token, 'base64').toString('utf-8')); + } catch (e) { + res.json({ + code: -1, + msg: 'Unauthorized', + }); + return; + } + const config = await WebUiConfig.GetWebUIConfig(); + const credentialJson = await AuthHelper.validateCredentialWithinOneHour(config.token, Credential); + if (credentialJson) { + //通过验证 + next(); + return; + } + res.json({ + code: -1, + msg: 'Unauthorized', + }); + return; } + res.json({ - code: -1, - msg: 'Unauthorized', + code: -1, + msg: 'Server Error', }); return; - } - - res.json({ - code: -1, - msg: 'Server Error', - }); - return; } router.use(AuthApi); router.all('/test', (req, res) => { - res.json({ - code: 0, - msg: 'ok', - }); + res.json({ + code: 0, + msg: 'ok', + }); }); router.use('/auth', AuthRouter); router.use('/QQLogin', QQLoginRouter); diff --git a/src/webui/ui/NapCat.ts b/src/webui/ui/NapCat.ts index 2f3f8108..f6544d0e 100644 --- a/src/webui/ui/NapCat.ts +++ b/src/webui/ui/NapCat.ts @@ -5,64 +5,64 @@ import { SettingSwitch } from './components/SettingSwitch'; import { SettingSelect } from './components/SettingSelect'; import { OB11Config, OB11ConfigWrapper } from './components/WebUiApiOB11Config'; async function onSettingWindowCreated(view: Element) { - const isEmpty = (value: any) => value === undefined || value === undefined || value === ''; - await OB11ConfigWrapper.Init(localStorage.getItem('auth') as string); - const ob11Config: OB11Config = await OB11ConfigWrapper.GetOB11Config(); - const setOB11Config = (key: string, value: any) => { - const configKey = key.split('.'); - if (configKey.length === 2) { - ob11Config[configKey[1]] = value; - } else if (configKey.length === 3) { - ob11Config[configKey[1]][configKey[2]] = value; - } + const isEmpty = (value: any) => value === undefined || value === undefined || value === ''; + await OB11ConfigWrapper.Init(localStorage.getItem('auth') as string); + const ob11Config: OB11Config = await OB11ConfigWrapper.GetOB11Config(); + const setOB11Config = (key: string, value: any) => { + const configKey = key.split('.'); + if (configKey.length === 2) { + ob11Config[configKey[1]] = value; + } else if (configKey.length === 3) { + ob11Config[configKey[1]][configKey[2]] = value; + } // OB11ConfigWrapper.SetOB11Config(ob11Config); // 只有当点保存时才下发配置,而不是在修改值后立即下发 - }; + }; - const parser = new DOMParser(); - const doc = parser.parseFromString( - [ - '
', - ` + const parser = new DOMParser(); + const doc = parser.parseFromString( + [ + '
', + `
`, - SettingList([ - SettingItem( - 'Napcat', - undefined, - SettingButton('V1.8.3', 'napcat-update-button', 'secondary') - ), - ]), - SettingList([ - SettingItem( - '启用 HTTP 服务', - undefined, - SettingSwitch('ob11.http.enable', ob11Config.http.enable, { - 'control-display-id': 'config-ob11-http-port', - }) - ), - SettingItem( - 'HTTP 服务监听端口', - undefined, - `
`, - 'config-ob11-http-port', - ob11Config.http.enable - ), - SettingItem( - '启用 HTTP 心跳', - undefined, - SettingSwitch('ob11.http.enableHeart', ob11Config.http.enableHeart, { - 'control-display-id': 'config-ob11-HTTP.enableHeart', - }) - ), - SettingItem( - '启用 HTTP 事件上报', - undefined, - SettingSwitch('ob11.http.enablePost', ob11Config.http.enablePost, { - 'control-display-id': 'config-ob11-http-postUrls', - }) - ), - `
+ SettingList([ + SettingItem( + 'Napcat', + undefined, + SettingButton('V1.8.3', 'napcat-update-button', 'secondary') + ), + ]), + SettingList([ + SettingItem( + '启用 HTTP 服务', + undefined, + SettingSwitch('ob11.http.enable', ob11Config.http.enable, { + 'control-display-id': 'config-ob11-http-port', + }) + ), + SettingItem( + 'HTTP 服务监听端口', + undefined, + `
`, + 'config-ob11-http-port', + ob11Config.http.enable + ), + SettingItem( + '启用 HTTP 心跳', + undefined, + SettingSwitch('ob11.http.enableHeart', ob11Config.http.enableHeart, { + 'control-display-id': 'config-ob11-HTTP.enableHeart', + }) + ), + SettingItem( + '启用 HTTP 事件上报', + undefined, + SettingSwitch('ob11.http.enablePost', ob11Config.http.enablePost, { + 'control-display-id': 'config-ob11-http-postUrls', + }) + ), + `
HTTP 事件上报密钥 @@ -80,28 +80,28 @@ async function onSettingWindowCreated(view: Element) {
`, - SettingItem( - '启用正向 WebSocket 服务', - undefined, - SettingSwitch('ob11.ws.enable', ob11Config.ws.enable, { - 'control-display-id': 'config-ob11-ws-port', - }) - ), - SettingItem( - '正向 WebSocket 服务监听端口', - undefined, - `
`, - 'config-ob11-ws-port', - ob11Config.ws.enable - ), - SettingItem( - '启用反向 WebSocket 服务', - undefined, - SettingSwitch('ob11.reverseWs.enable', ob11Config.reverseWs.enable, { - 'control-display-id': 'config-ob11-reverseWs-urls', - }) - ), - `
+ SettingItem( + '启用正向 WebSocket 服务', + undefined, + SettingSwitch('ob11.ws.enable', ob11Config.ws.enable, { + 'control-display-id': 'config-ob11-ws-port', + }) + ), + SettingItem( + '正向 WebSocket 服务监听端口', + undefined, + `
`, + 'config-ob11-ws-port', + ob11Config.ws.enable + ), + SettingItem( + '启用反向 WebSocket 服务', + undefined, + SettingSwitch('ob11.reverseWs.enable', ob11Config.reverseWs.enable, { + 'control-display-id': 'config-ob11-reverseWs-urls', + }) + ), + `
反向 WebSocket 监听地址 @@ -110,42 +110,42 @@ async function onSettingWindowCreated(view: Element) {
`, - SettingItem( - ' WebSocket 服务心跳间隔', - '控制每隔多久发送一个心跳包,单位为毫秒', - `
` - ), - SettingItem( - 'Access token', - undefined, - `
` - ), - SettingItem( - '新消息上报格式', - '如客户端无特殊需求推荐保持默认设置,两者的详细差异可参考 OneBot v11 文档', - SettingSelect( - [ - { text: '消息段', value: 'array' }, - { text: 'CQ码', value: 'string' }, - ], - 'ob11.messagePostFormat', - ob11Config.messagePostFormat - ) - ), - SettingItem( - '音乐卡片签名地址', - undefined, - `
`, - 'ob11.musicSignUrl' - ), - SettingItem( - '启用本地进群时间与发言时间记录', - undefined, - SettingSwitch('ob11.GroupLocalTime.Record', ob11Config.GroupLocalTime.Record, { - 'control-display-id': 'config-ob11-GroupLocalTime-RecordList', - }) - ), - `
+ SettingItem( + ' WebSocket 服务心跳间隔', + '控制每隔多久发送一个心跳包,单位为毫秒', + `
` + ), + SettingItem( + 'Access token', + undefined, + `
` + ), + SettingItem( + '新消息上报格式', + '如客户端无特殊需求推荐保持默认设置,两者的详细差异可参考 OneBot v11 文档', + SettingSelect( + [ + { text: '消息段', value: 'array' }, + { text: 'CQ码', value: 'string' }, + ], + 'ob11.messagePostFormat', + ob11Config.messagePostFormat + ) + ), + SettingItem( + '音乐卡片签名地址', + undefined, + `
`, + 'ob11.musicSignUrl' + ), + SettingItem( + '启用本地进群时间与发言时间记录', + undefined, + SettingSwitch('ob11.GroupLocalTime.Record', ob11Config.GroupLocalTime.Record, { + 'control-display-id': 'config-ob11-GroupLocalTime-RecordList', + }) + ), + `
群列表 @@ -154,239 +154,239 @@ async function onSettingWindowCreated(view: Element) {
`, - SettingItem( - '', - undefined, - SettingButton('保存', 'config-ob11-save', 'primary') - ), - ]), - SettingList([ - SettingItem( - '上报 Bot 自身发送的消息', - '上报 event 为 message_sent', - SettingSwitch('ob11.reportSelfMessage', ob11Config.reportSelfMessage) - ), - ]), - SettingList([ - SettingItem( - 'GitHub 仓库', - 'https://github.com/NapNeko/NapCatQQ', - SettingButton('点个星星', 'open-github') - ), - SettingItem('NapCat 文档', '', SettingButton('看看文档', 'open-docs')), - SettingItem( - 'Telegram 群', - 'https://t.me/+nLZEnpne-pQ1OWFl', - SettingButton('进去逛逛', 'open-telegram') - ), - SettingItem( - 'QQ 群', - '545402644', - SettingButton('我要进去', 'open-qq-group') - ), - ]), - '
', - ].join(''), - 'text/html' - ); + SettingItem( + '', + undefined, + SettingButton('保存', 'config-ob11-save', 'primary') + ), + ]), + SettingList([ + SettingItem( + '上报 Bot 自身发送的消息', + '上报 event 为 message_sent', + SettingSwitch('ob11.reportSelfMessage', ob11Config.reportSelfMessage) + ), + ]), + SettingList([ + SettingItem( + 'GitHub 仓库', + 'https://github.com/NapNeko/NapCatQQ', + SettingButton('点个星星', 'open-github') + ), + SettingItem('NapCat 文档', '', 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/NapNeko/NapCatQQ', '_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://napneko.github.io/', '_blank'); - }); - // 生成反向地址列表 - 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.split('-')[0]][type.split('-')[1]][index] = + // 外链按钮 + doc.querySelector('#open-github')?.addEventListener('click', () => { + window.open('https://github.com/NapNeko/NapCatQQ', '_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://napneko.github.io/', '_blank'); + }); + // 生成反向地址列表 + 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.split('-')[0]][type.split('-')[1]][index] = dom.input.value; - }); + }); - dom.inputContainer.classList.add('q-input'); - dom.inputContainer.appendChild(dom.input); + 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.split('-')[0]][type.split('-')[1]].splice(index, 1); - initReverseHost(type); - }); + dom.deleteBtn.innerHTML = '删除'; + dom.deleteBtn.dataset.type = 'secondary'; + dom.deleteBtn.addEventListener('click', () => { + ob11Config[type.split('-')[0]][type.split('-')[1]].splice(index, 1); + initReverseHost(type); + }); - dom.container.appendChild(dom.inputContainer); - dom.container.appendChild(dom.deleteBtn); + dom.container.appendChild(dom.inputContainer); + dom.container.appendChild(dom.deleteBtn); - return dom.container; - }; - const buildHostList = ( - hosts: string[], - type: string, - inputAttr: any = {} - ) => { - const result: HTMLElement[] = []; + 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)); - }); + hosts?.forEach((host, index) => { + result.push(buildHostListItem(type, host, index, inputAttr)); + }); - return result; - }; - const addReverseHost = ( - type: string, - doc: Document = document, - inputAttr: any = {} - ) => { - type = type.replace(/\./g, '-');//替换操作 - const hostContainerDom = doc.body.querySelector( - `#config-ob11-${type}-list` - ); - hostContainerDom?.appendChild( - buildHostListItem( - type, - '', - ob11Config[type.split('-')[0]][type.split('-')[1]].length, - inputAttr - ) - ); - ob11Config[type.split('-')[0]][type.split('-')[1]].push(''); - }; - const initReverseHost = (type: string, doc: Document = document) => { - type = type.replace(/\./g, '-');//替换操作 - const hostContainerDom = doc.body?.querySelector( - `#config-ob11-${type}-list` - ); - if (hostContainerDom) { - [...hostContainerDom.childNodes].forEach((dom) => dom.remove()); - buildHostList( - ob11Config[type.split('-')[0]][type.split('-')[1]], - type - ).forEach((dom) => { - hostContainerDom?.appendChild(dom); - }); - } - }; - - initReverseHost('http.postUrls', doc); - initReverseHost('reverseWs.urls', doc); - initReverseHost('GroupLocalTime.RecordList', doc); - - doc - .querySelector('#config-ob11-http-postUrls-add') - ?.addEventListener('click', () => - addReverseHost('http.postUrls', document, { - placeholder: '如:http://127.0.0.1:5140/onebot', - }) - ); - - doc - .querySelector('#config-ob11-reverseWs-urls-add') - ?.addEventListener('click', () => - addReverseHost('reverseWs.urls', document, { - placeholder: '如:ws://127.0.0.1:5140/onebot', - }) - ); - doc - .querySelector('#config-ob11-GroupLocalTime-RecordList-add') - ?.addEventListener('click', () => - addReverseHost('GroupLocalTime.RecordList', document, { - placeholder: '此处填写群号 -1为全部', - }) - ); - 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-expect-error 等待修复 - setOB11Config(dom.dataset.configKey, active); - if (active) dom.setAttribute('is-active', ''); - else dom.removeAttribute('is-active'); - //@ts-expect-error 等待修复 - if (!isEmpty(dom.dataset.controlDisplayId)) { - const displayDom = document.querySelector( - //@ts-expect-error 等待修复 - `#${dom.dataset.controlDisplayId}` - ); - if (active) displayDom?.removeAttribute('is-hidden'); - else displayDom?.setAttribute('is-hidden', ''); + return result; + }; + const addReverseHost = ( + type: string, + doc: Document = document, + inputAttr: any = {} + ) => { + type = type.replace(/\./g, '-');//替换操作 + const hostContainerDom = doc.body.querySelector( + `#config-ob11-${type}-list` + ); + hostContainerDom?.appendChild( + buildHostListItem( + type, + '', + ob11Config[type.split('-')[0]][type.split('-')[1]].length, + inputAttr + ) + ); + ob11Config[type.split('-')[0]][type.split('-')[1]].push(''); + }; + const initReverseHost = (type: string, doc: Document = document) => { + type = type.replace(/\./g, '-');//替换操作 + const hostContainerDom = doc.body?.querySelector( + `#config-ob11-${type}-list` + ); + if (hostContainerDom) { + [...hostContainerDom.childNodes].forEach((dom) => dom.remove()); + buildHostList( + ob11Config[type.split('-')[0]][type.split('-')[1]], + type + ).forEach((dom) => { + hostContainerDom?.appendChild(dom); + }); } - }); + }; + + initReverseHost('http.postUrls', doc); + initReverseHost('reverseWs.urls', doc); + initReverseHost('GroupLocalTime.RecordList', doc); + + doc + .querySelector('#config-ob11-http-postUrls-add') + ?.addEventListener('click', () => + addReverseHost('http.postUrls', document, { + placeholder: '如:http://127.0.0.1:5140/onebot', + }) + ); + + doc + .querySelector('#config-ob11-reverseWs-urls-add') + ?.addEventListener('click', () => + addReverseHost('reverseWs.urls', document, { + placeholder: '如:ws://127.0.0.1:5140/onebot', + }) + ); + doc + .querySelector('#config-ob11-GroupLocalTime-RecordList-add') + ?.addEventListener('click', () => + addReverseHost('GroupLocalTime.RecordList', document, { + placeholder: '此处填写群号 -1为全部', + }) + ); + doc.querySelector('#config-ffmpeg-select')?.addEventListener('click', () => { + //选择ffmpeg }); - // 输入框 - 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-expect-error等待修复 - const configKey = dom.dataset.configKey; - const configValue = + 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-expect-error 等待修复 + setOB11Config(dom.dataset.configKey, active); + if (active) dom.setAttribute('is-active', ''); + else dom.removeAttribute('is-active'); + //@ts-expect-error 等待修复 + if (!isEmpty(dom.dataset.controlDisplayId)) { + const displayDom = document.querySelector( + //@ts-expect-error 等待修复 + `#${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-expect-error等待修复 + 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; + ? parseInt((dom as HTMLInputElement).value) >= 1 + ? parseInt((dom as HTMLInputElement).value) + : 1 + : (dom as HTMLInputElement).value; - setOB11Config(configKey, configValue); - }); + setOB11Config(configKey, configValue); + }); + }); + + // 下拉框 + doc + .querySelectorAll('ob-setting-select[data-config-key]') + .forEach((dom: Element) => { + //@ts-expect-error等待修复 + dom?.addEventListener('selected', (e: CustomEvent) => { + //@ts-expect-error等待修复 + const configKey = dom.dataset.configKey; + const configValue = e.detail.value; + setOB11Config(configKey, configValue); + }); + }); + + // 保存按钮 + doc.querySelector('#config-ob11-save')?.addEventListener('click', () => { + OB11ConfigWrapper.SetOB11Config(ob11Config); + alert('保存成功'); }); - - // 下拉框 - doc - .querySelectorAll('ob-setting-select[data-config-key]') - .forEach((dom: Element) => { - //@ts-expect-error等待修复 - dom?.addEventListener('selected', (e: CustomEvent) => { - //@ts-expect-error等待修复 - const configKey = dom.dataset.configKey; - const configValue = e.detail.value; - setOB11Config(configKey, configValue); - }); + doc.body.childNodes.forEach((node) => { + view.appendChild(node); }); - - // 保存按钮 - doc.querySelector('#config-ob11-save')?.addEventListener('click', () => { - OB11ConfigWrapper.SetOB11Config(ob11Config); - alert('保存成功'); - }); - doc.body.childNodes.forEach((node) => { - view.appendChild(node); - }); } export { onSettingWindowCreated }; diff --git a/src/webui/ui/components/SettingButton.ts b/src/webui/ui/components/SettingButton.ts index 35f6bc87..02132f3c 100644 --- a/src/webui/ui/components/SettingButton.ts +++ b/src/webui/ui/components/SettingButton.ts @@ -1,3 +1,3 @@ export const SettingButton = (text: string, id?: string, type: string = 'secondary') => { - return `${text}`; + return `${text}`; }; \ No newline at end of file diff --git a/src/webui/ui/components/SettingItem.ts b/src/webui/ui/components/SettingItem.ts index 40c573ae..11c15426 100644 --- a/src/webui/ui/components/SettingItem.ts +++ b/src/webui/ui/components/SettingItem.ts @@ -1,11 +1,11 @@ export const SettingItem = ( - title: string, - subtitle?: string, - action?: string, - id?: string, - visible: boolean = true, + title: string, + subtitle?: string, + action?: string, + id?: string, + visible: boolean = true, ) => { - return ` + return `
${title} ${subtitle ? `${subtitle}` : ''} diff --git a/src/webui/ui/components/SettingList.ts b/src/webui/ui/components/SettingList.ts index 2357773a..8db03dc2 100644 --- a/src/webui/ui/components/SettingList.ts +++ b/src/webui/ui/components/SettingList.ts @@ -1,10 +1,10 @@ export const SettingList = ( - items: string[], - title?: string, - isCollapsible: boolean = false, - direction: string = 'column', + items: string[], + title?: string, + isCollapsible: boolean = false, + direction: string = 'column', ) => { - return ` + return ` ${items.join('')} diff --git a/src/webui/ui/components/SettingOption.ts b/src/webui/ui/components/SettingOption.ts index ccb0654b..ce44e2e4 100644 --- a/src/webui/ui/components/SettingOption.ts +++ b/src/webui/ui/components/SettingOption.ts @@ -1,3 +1,3 @@ export const SettingOption = (text: string, value?: string, isSelected: boolean = false) => { - return `${text}`; + return `${text}`; }; \ No newline at end of file diff --git a/src/webui/ui/components/SettingSelect.ts b/src/webui/ui/components/SettingSelect.ts index 7764cd4a..161186b0 100644 --- a/src/webui/ui/components/SettingSelect.ts +++ b/src/webui/ui/components/SettingSelect.ts @@ -20,65 +20,65 @@ SelectTemplate.innerHTML = `