mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-01-14 20:30:34 +00:00
Introduced a new eslint.config.js using neostandard and added related devDependencies. Updated codebase for consistent formatting, spacing, and function declarations. Minor refactoring and cleanup across multiple files to improve readability and maintain code style compliance.
241 lines
8.1 KiB
TypeScript
241 lines
8.1 KiB
TypeScript
import { webUiPathWrapper, getInitialWebUiToken } from '@/napcat-webui-backend/index';
|
||
import { Type, Static } from '@sinclair/typebox';
|
||
import Ajv from 'ajv';
|
||
import fs, { constants } from 'node:fs/promises';
|
||
|
||
import { resolve } from 'node:path';
|
||
|
||
import { deepMerge } from '../utils/object';
|
||
import { themeType } from '../types/theme';
|
||
import { getRandomToken } from '../utils/url';
|
||
|
||
// 限制尝试端口的次数,避免死循环
|
||
// 定义配置的类型
|
||
const WebUiConfigSchema = Type.Object({
|
||
host: Type.String({ default: '0.0.0.0' }),
|
||
port: Type.Number({ default: 6099 }),
|
||
token: Type.String({ default: getRandomToken(12) }),
|
||
loginRate: Type.Number({ default: 10 }),
|
||
autoLoginAccount: Type.String({ default: '' }),
|
||
theme: themeType,
|
||
// 是否关闭WebUI
|
||
disableWebUI: Type.Boolean({ default: false }),
|
||
// 是否关闭非局域网访问
|
||
disableNonLANAccess: Type.Boolean({ default: false }),
|
||
});
|
||
|
||
export type WebUiConfigType = Static<typeof WebUiConfigSchema>;
|
||
|
||
// 读取当前目录下名为 webui.json 的配置文件,如果不存在则创建初始化配置文件
|
||
export class WebUiConfigWrapper {
|
||
WebUiConfigData: WebUiConfigType | undefined = undefined;
|
||
|
||
private validateAndApplyDefaults (config: Partial<WebUiConfigType>): WebUiConfigType {
|
||
new Ajv({ coerceTypes: true, useDefaults: true }).compile(WebUiConfigSchema)(config);
|
||
return config as WebUiConfigType;
|
||
}
|
||
|
||
private async ensureConfigFileExists (configPath: string): Promise<void> {
|
||
const configExists = await fs
|
||
.access(configPath, constants.F_OK)
|
||
.then(() => true)
|
||
.catch(() => false);
|
||
if (!configExists) {
|
||
await fs.writeFile(configPath, JSON.stringify(this.validateAndApplyDefaults({}), null, 4));
|
||
}
|
||
}
|
||
|
||
private async readAndValidateConfig (configPath: string): Promise<WebUiConfigType> {
|
||
const fileContent = await fs.readFile(configPath, 'utf-8');
|
||
return this.validateAndApplyDefaults(JSON.parse(fileContent));
|
||
}
|
||
|
||
private async writeConfig (configPath: string, config: WebUiConfigType): Promise<void> {
|
||
const hasWritePermission = await fs
|
||
.access(configPath, constants.W_OK)
|
||
.then(() => true)
|
||
.catch(() => false);
|
||
if (hasWritePermission) {
|
||
await fs.writeFile(configPath, JSON.stringify(config, null, 4));
|
||
} else {
|
||
console.warn(`文件: ${configPath} 没有写入权限, 配置的更改部分可能会在重启后还原.`);
|
||
}
|
||
}
|
||
|
||
async GetWebUIConfig (): Promise<WebUiConfigType> {
|
||
if (this.WebUiConfigData) {
|
||
return this.WebUiConfigData;
|
||
}
|
||
|
||
try {
|
||
const configPath = resolve(webUiPathWrapper.configPath, './webui.json');
|
||
await this.ensureConfigFileExists(configPath);
|
||
const parsedConfig = await this.readAndValidateConfig(configPath);
|
||
// 使用内存中缓存的token进行覆盖,确保强兼容性
|
||
this.WebUiConfigData = {
|
||
...parsedConfig,
|
||
// 首次读取内存中是没有token的,需要进行一层兜底
|
||
token: getInitialWebUiToken() || parsedConfig.token,
|
||
};
|
||
return this.WebUiConfigData;
|
||
} catch (e) {
|
||
console.log('读取配置文件失败', e);
|
||
const defaultConfig = this.validateAndApplyDefaults({});
|
||
this.WebUiConfigData = {
|
||
...defaultConfig,
|
||
token: getInitialWebUiToken() || defaultConfig.token,
|
||
};
|
||
return this.WebUiConfigData;
|
||
}
|
||
}
|
||
|
||
async UpdateWebUIConfig (newConfig: Partial<WebUiConfigType>): Promise<void> {
|
||
const configPath = resolve(webUiPathWrapper.configPath, './webui.json');
|
||
// 使用原始配置进行合并,避免内存token覆盖影响配置更新
|
||
const currentConfig = await this.GetRawWebUIConfig();
|
||
const mergedConfig = deepMerge({ ...currentConfig }, newConfig);
|
||
const updatedConfig = this.validateAndApplyDefaults(mergedConfig);
|
||
await this.writeConfig(configPath, updatedConfig);
|
||
this.WebUiConfigData = updatedConfig;
|
||
}
|
||
|
||
/**
|
||
* 获取配置文件中实际存储的配置(不被内存token覆盖)
|
||
* 主要用于配置更新和特殊场景
|
||
*/
|
||
async GetRawWebUIConfig (): Promise<WebUiConfigType> {
|
||
if (this.WebUiConfigData) {
|
||
return this.WebUiConfigData;
|
||
}
|
||
try {
|
||
const configPath = resolve(webUiPathWrapper.configPath, './webui.json');
|
||
await this.ensureConfigFileExists(configPath);
|
||
const parsedConfig = await this.readAndValidateConfig(configPath);
|
||
this.WebUiConfigData = parsedConfig;
|
||
return this.WebUiConfigData;
|
||
} catch (e) {
|
||
console.log('读取配置文件失败', e);
|
||
return this.validateAndApplyDefaults({});
|
||
}
|
||
}
|
||
|
||
async UpdateToken (oldToken: string, newToken: string): Promise<void> {
|
||
// 使用内存中缓存的token进行验证,确保强兼容性
|
||
const cachedToken = getInitialWebUiToken();
|
||
const tokenToCheck = cachedToken || (await this.GetWebUIConfig()).token;
|
||
|
||
if (tokenToCheck !== oldToken) {
|
||
throw new Error('旧 token 不匹配');
|
||
}
|
||
await this.UpdateWebUIConfig({ token: newToken });
|
||
}
|
||
|
||
// 获取日志文件夹路径
|
||
async GetLogsPath (): Promise<string> {
|
||
return resolve(webUiPathWrapper.logsPath);
|
||
}
|
||
|
||
// 获取日志列表
|
||
async GetLogsList (): Promise<string[]> {
|
||
const logsPath = resolve(webUiPathWrapper.logsPath);
|
||
const logsExist = await fs
|
||
.access(logsPath, constants.F_OK)
|
||
.then(() => true)
|
||
.catch(() => false);
|
||
if (logsExist) {
|
||
return (await fs.readdir(logsPath))
|
||
.filter((file) => file.endsWith('.log'))
|
||
.map((file) => file.replace('.log', ''));
|
||
}
|
||
return [];
|
||
}
|
||
|
||
// 获取指定日志文件内容
|
||
async GetLogContent (filename: string): Promise<string> {
|
||
const logPath = resolve(webUiPathWrapper.logsPath, `${filename}.log`);
|
||
const logExists = await fs
|
||
.access(logPath, constants.R_OK)
|
||
.then(() => true)
|
||
.catch(() => false);
|
||
if (logExists) {
|
||
return await fs.readFile(logPath, 'utf-8');
|
||
}
|
||
return '';
|
||
}
|
||
|
||
// 获取字体文件夹内的字体列表
|
||
async GetFontList (): Promise<string[]> {
|
||
const fontsPath = resolve(webUiPathWrapper.configPath, './fonts');
|
||
const fontsExist = await fs
|
||
.access(fontsPath, constants.F_OK)
|
||
.then(() => true)
|
||
.catch(() => false);
|
||
if (fontsExist) {
|
||
return (await fs.readdir(fontsPath)).filter((file) => file.endsWith('.ttf'));
|
||
}
|
||
return [];
|
||
}
|
||
|
||
// 判断字体是否存在(webui.woff)
|
||
async CheckWebUIFontExist (): Promise<boolean> {
|
||
const fontsPath = resolve(webUiPathWrapper.configPath, './fonts');
|
||
return await fs
|
||
.access(resolve(fontsPath, './webui.woff'), constants.F_OK)
|
||
.then(() => true)
|
||
.catch(() => false);
|
||
}
|
||
|
||
// 获取webui字体文件路径
|
||
GetWebUIFontPath (): string {
|
||
return resolve(webUiPathWrapper.configPath, './fonts/webui.woff');
|
||
}
|
||
|
||
getAutoLoginAccount (): string | undefined {
|
||
return this.WebUiConfigData?.autoLoginAccount;
|
||
}
|
||
|
||
// 获取自动登录账号
|
||
async GetAutoLoginAccount (): Promise<string> {
|
||
return (await this.GetWebUIConfig()).autoLoginAccount;
|
||
}
|
||
|
||
// 更新自动登录账号
|
||
async UpdateAutoLoginAccount (uin: string): Promise<void> {
|
||
await this.UpdateWebUIConfig({ autoLoginAccount: uin });
|
||
}
|
||
|
||
// 获取主题内容
|
||
async GetTheme (): Promise<WebUiConfigType['theme']> {
|
||
const config = await this.GetWebUIConfig();
|
||
|
||
return config.theme;
|
||
}
|
||
|
||
// 更新主题内容
|
||
async UpdateTheme (theme: WebUiConfigType['theme']): Promise<void> {
|
||
await this.UpdateWebUIConfig({ theme });
|
||
}
|
||
|
||
// 获取是否禁用WebUI
|
||
async GetDisableWebUI (): Promise<boolean> {
|
||
const config = await this.GetWebUIConfig();
|
||
return config.disableWebUI;
|
||
}
|
||
|
||
// 更新是否禁用WebUI
|
||
async UpdateDisableWebUI (disable: boolean): Promise<void> {
|
||
await this.UpdateWebUIConfig({ disableWebUI: disable });
|
||
}
|
||
|
||
// 获取是否禁用非局域网访问
|
||
async GetDisableNonLANAccess (): Promise<boolean> {
|
||
const config = await this.GetWebUIConfig();
|
||
return config.disableNonLANAccess;
|
||
}
|
||
|
||
// 更新是否禁用非局域网访问
|
||
async UpdateDisableNonLANAccess (disable: boolean): Promise<void> {
|
||
await this.UpdateWebUIConfig({ disableNonLANAccess: disable });
|
||
}
|
||
}
|