style: 异步实现

This commit is contained in:
手瓜一十雪
2024-12-02 11:44:37 +08:00
parent 3b5d2c8f6f
commit 45b1f369ac
8 changed files with 42 additions and 76 deletions

View File

@@ -1,7 +1,7 @@
import winston, { format, transports } from 'winston'; import winston, { format, transports } from 'winston';
import { truncateString } from '@/common/helper'; import { truncateString } from '@/common/helper';
import path from 'node:path'; import path from 'node:path';
import fs from 'node:fs'; import fs from 'node:fs/promises';
import { NTMsgAtType, ChatType, ElementType, MessageElement, RawMessage, SelfInfo } from '@/core'; import { NTMsgAtType, ChatType, ElementType, MessageElement, RawMessage, SelfInfo } from '@/core';
import EventEmitter from 'node:events'; import EventEmitter from 'node:events';
export enum LogLevel { export enum LogLevel {
@@ -97,26 +97,20 @@ export class LogWrapper {
cleanOldLogs(logDir: string) { cleanOldLogs(logDir: string) {
const oneWeekAgo = Date.now() - 7 * 24 * 60 * 60 * 1000; const oneWeekAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;
fs.readdir(logDir, (err, files) => { fs.readdir(logDir).then((files) => {
if (err) {
this.logger.error('Failed to read log directory', err);
return;
}
files.forEach((file) => { files.forEach((file) => {
const filePath = path.join(logDir, file); const filePath = path.join(logDir, file);
this.deleteOldLogFile(filePath, oneWeekAgo); this.deleteOldLogFile(filePath, oneWeekAgo);
}); });
}).catch((err) => {
this.logger.error('Failed to read log directory', err);
}); });
} }
private deleteOldLogFile(filePath: string, oneWeekAgo: number) { private deleteOldLogFile(filePath: string, oneWeekAgo: number) {
fs.stat(filePath, (err, stats) => { fs.stat(filePath).then((stats) => {
if (err) {
this.logger.error('Failed to get file stats', err);
return;
}
if (stats.mtime.getTime() < oneWeekAgo) { if (stats.mtime.getTime() < oneWeekAgo) {
fs.unlink(filePath, (err) => { fs.unlink(filePath).catch((err) => {
if (err) { if (err) {
if (err.code === 'ENOENT') { if (err.code === 'ENOENT') {
this.logger.warn(`File already deleted: ${filePath}`); this.logger.warn(`File already deleted: ${filePath}`);
@@ -128,6 +122,8 @@ export class LogWrapper {
} }
}); });
} }
}).catch((err) => {
this.logger.error('Failed to get file stats', err);
}); });
} }
@@ -316,9 +312,8 @@ function textElementToText(textElement: any): string {
function replyElementToText(replyElement: any, msg: RawMessage, recursiveLevel: number): string { function replyElementToText(replyElement: any, msg: RawMessage, recursiveLevel: number): string {
const recordMsgOrNull = msg.records.find((record) => replyElement.sourceMsgIdInRecords === record.msgId); const recordMsgOrNull = msg.records.find((record) => replyElement.sourceMsgIdInRecords === record.msgId);
return `[回复消息 ${ return `[回复消息 ${recordMsgOrNull && recordMsgOrNull.peerUin != '284840486' && recordMsgOrNull.peerUin != '1094950020'
recordMsgOrNull && recordMsgOrNull.peerUin != '284840486' && recordMsgOrNull.peerUin != '1094950020' ? rawMessageToText(recordMsgOrNull, recursiveLevel + 1)
? rawMessageToText(recordMsgOrNull, recursiveLevel + 1) : `未找到消息记录 (MsgId = ${replyElement.sourceMsgIdInRecords})`
: `未找到消息记录 (MsgId = ${replyElement.sourceMsgIdInRecords})` }]`;
}]`;
} }

View File

@@ -1,6 +1,5 @@
import https from 'node:https'; import https from 'node:https';
import http from 'node:http'; import http from 'node:http';
import { readFileSync } from 'node:fs';
export class RequestUtil { export class RequestUtil {
// 适用于获取服务器下发cookies时获取仅GET // 适用于获取服务器下发cookies时获取仅GET
@@ -112,24 +111,4 @@ export class RequestUtil {
static async HttpGetText(url: string, method: string = 'GET', data?: any, headers: { [key: string]: string } = {}) { static async HttpGetText(url: string, method: string = 'GET', data?: any, headers: { [key: string]: string } = {}) {
return this.HttpGetJson<string>(url, method, data, headers, false, false); return this.HttpGetJson<string>(url, method, data, headers, false, false);
} }
static async createFormData(boundary: string, filePath: string): Promise<Buffer> {
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'),
]);
}
} }

View File

@@ -1,6 +1,6 @@
import { OneBotAction } from '@/onebot/action/OneBotAction'; import { OneBotAction } from '@/onebot/action/OneBotAction';
import { ActionName } from '@/onebot/action/router'; import { ActionName } from '@/onebot/action/router';
import * as fs from 'node:fs'; import fs from 'node:fs/promises';
import { checkFileExist, uri2local } from '@/common/file'; import { checkFileExist, uri2local } from '@/common/file';
import { Static, Type } from '@sinclair/typebox'; import { Static, Type } from '@sinclair/typebox';
@@ -21,9 +21,7 @@ export default class SetAvatar extends OneBotAction<Payload, null> {
if (path) { if (path) {
await checkFileExist(path, 5000);// 避免崩溃 await checkFileExist(path, 5000);// 避免崩溃
const ret = await this.core.apis.UserApi.setQQAvatar(path); const ret = await this.core.apis.UserApi.setQQAvatar(path);
fs.unlink(path, () => { fs.unlink(path).catch(() => { });
});
if (!ret) { if (!ret) {
throw new Error(`头像${payload.file}设置失败,api无返回`); throw new Error(`头像${payload.file}设置失败,api无返回`);
} }
@@ -34,7 +32,7 @@ export default class SetAvatar extends OneBotAction<Payload, null> {
throw new Error(`头像${payload.file}设置失败,未知的错误,${ret.result}:${ret.errMsg}`); throw new Error(`头像${payload.file}设置失败,未知的错误,${ret.result}:${ret.errMsg}`);
} }
} else { } else {
fs.unlink(path, () => { }); fs.unlink(path).catch(() => { });
throw new Error(`头像${payload.file}设置失败,无法获取头像,文件可能不存在`); throw new Error(`头像${payload.file}设置失败,无法获取头像,文件可能不存在`);
} }
return null; return null;

View File

@@ -1,7 +1,7 @@
import { checkFileExist, uri2local } from '@/common/file'; import { checkFileExist, uri2local } from '@/common/file';
import { OneBotAction } from '@/onebot/action/OneBotAction'; import { OneBotAction } from '@/onebot/action/OneBotAction';
import { ActionName } from '@/onebot/action/router'; import { ActionName } from '@/onebot/action/router';
import { unlink } from 'node:fs'; import { unlink } from 'node:fs/promises';
import { Static, Type } from '@sinclair/typebox'; import { Static, Type } from '@sinclair/typebox';
const SchemaData = Type.Object({ const SchemaData = Type.Object({
@@ -41,8 +41,7 @@ export class SendGroupNotice extends OneBotAction<Payload, null> {
throw new Error(`群公告${payload.image}设置失败,图片上传失败`); throw new Error(`群公告${payload.image}设置失败,图片上传失败`);
} }
unlink(path, () => { unlink(path).catch(() => { });
});
UploadImage = ImageUploadResult.picInfo; UploadImage = ImageUploadResult.picInfo;
} }

View File

@@ -1,9 +1,8 @@
import { OneBotAction } from '@/onebot/action/OneBotAction'; import { OneBotAction } from '@/onebot/action/OneBotAction';
import { ActionName, BaseCheckResult } from '@/onebot/action/router'; import { ActionName } from '@/onebot/action/router';
import * as fs from 'node:fs';
import { checkFileExistV2, uri2local } from '@/common/file'; import { checkFileExistV2, uri2local } from '@/common/file';
import { Static, Type } from '@sinclair/typebox'; import { Static, Type } from '@sinclair/typebox';
import fs from 'node:fs/promises';
const SchemaData = Type.Object({ const SchemaData = Type.Object({
file: Type.String(), file: Type.String(),
group_id: Type.Union([Type.Number(), Type.String()]) group_id: Type.Union([Type.Number(), Type.String()])
@@ -14,7 +13,7 @@ type Payload = Static<typeof SchemaData>;
export default class SetGroupPortrait extends OneBotAction<Payload, any> { export default class SetGroupPortrait extends OneBotAction<Payload, any> {
actionName = ActionName.SetGroupPortrait; actionName = ActionName.SetGroupPortrait;
payloadSchema = SchemaData; payloadSchema = SchemaData;
async _handle(payload: Payload): Promise<any> { async _handle(payload: Payload): Promise<any> {
const { path, success } = (await uri2local(this.core.NapCatTempPath, payload.file)); const { path, success } = (await uri2local(this.core.NapCatTempPath, payload.file));
if (!success) { if (!success) {
@@ -23,7 +22,7 @@ export default class SetGroupPortrait extends OneBotAction<Payload, any> {
if (path) { if (path) {
await checkFileExistV2(path, 5000); // 文件不存在QQ会崩溃需要提前判断 await checkFileExistV2(path, 5000); // 文件不存在QQ会崩溃需要提前判断
const ret = await this.core.apis.GroupApi.setGroupAvatar(payload.group_id.toString(), path); const ret = await this.core.apis.GroupApi.setGroupAvatar(payload.group_id.toString(), path);
fs.unlink(path, () => { }); fs.unlink(path).catch(() => { });
if (!ret) { if (!ret) {
throw new Error(`头像${payload.file}设置失败,api无返回`); throw new Error(`头像${payload.file}设置失败,api无返回`);
} }
@@ -34,7 +33,7 @@ export default class SetGroupPortrait extends OneBotAction<Payload, any> {
} }
return ret; return ret;
} else { } else {
fs.unlink(path, () => { }); fs.unlink(path).catch(() => { });
throw new Error(`头像${payload.file}设置失败,无法获取头像,文件可能不存在`); throw new Error(`头像${payload.file}设置失败,无法获取头像,文件可能不存在`);
} }
} }

View File

@@ -15,7 +15,6 @@ import {
RawMessage, RawMessage,
SendMessageElement, SendMessageElement,
SendTextElement, SendTextElement,
BaseEmojiType,
FaceType, FaceType,
GrayTipElement, GrayTipElement,
} from '@/core'; } from '@/core';
@@ -26,12 +25,9 @@ import { EventType } from '@/onebot/event/OneBotEvent';
import { encodeCQCode } from '@/onebot/helper/cqcode'; import { encodeCQCode } from '@/onebot/helper/cqcode';
import { uri2local } from '@/common/file'; import { uri2local } from '@/common/file';
import { RequestUtil } from '@/common/request'; import { RequestUtil } from '@/common/request';
import fs from 'node:fs'; import fsPromise, { constants } from 'node:fs/promises';
import fsPromise from 'node:fs/promises';
import { OB11FriendAddNoticeEvent } from '@/onebot/event/notice/OB11FriendAddNoticeEvent'; import { OB11FriendAddNoticeEvent } from '@/onebot/event/notice/OB11FriendAddNoticeEvent';
// import { decodeSysMessage } from '@/core/packet/proto/old/ProfileLike';
import { ForwardMsgBuilder } from "@/common/forward-msg-builder"; import { ForwardMsgBuilder } from "@/common/forward-msg-builder";
import { decodeSysMessage } from "@/core/helper/adaptDecoder";
import { GroupChange, PushMsgBody } from "@/core/packet/transformer/proto"; import { GroupChange, PushMsgBody } from "@/core/packet/transformer/proto";
import { NapProtoMsg } from '@napneko/nap-proto-core'; import { NapProtoMsg } from '@napneko/nap-proto-core';
import { OB11GroupIncreaseEvent } from '../event/notice/OB11GroupIncreaseEvent'; import { OB11GroupIncreaseEvent } from '../event/notice/OB11GroupIncreaseEvent';
@@ -887,16 +883,16 @@ export class OneBotMsgApi {
try { try {
for (const fileElement of sendElements) { for (const fileElement of sendElements) {
if (fileElement.elementType === ElementType.PTT) { if (fileElement.elementType === ElementType.PTT) {
totalSize += fs.statSync(fileElement.pttElement.filePath).size; totalSize += (await fsPromise.stat(fileElement.pttElement.filePath)).size;
} }
if (fileElement.elementType === ElementType.FILE) { if (fileElement.elementType === ElementType.FILE) {
totalSize += fs.statSync(fileElement.fileElement.filePath).size; totalSize += (await fsPromise.stat(fileElement.fileElement.filePath)).size;
} }
if (fileElement.elementType === ElementType.VIDEO) { if (fileElement.elementType === ElementType.VIDEO) {
totalSize += fs.statSync(fileElement.videoElement.filePath).size; totalSize += (await fsPromise.stat(fileElement.videoElement.filePath)).size;
} }
if (fileElement.elementType === ElementType.PIC) { if (fileElement.elementType === ElementType.PIC) {
totalSize += fs.statSync(fileElement.picElement.sourcePath).size; totalSize += (await fsPromise.stat(fileElement.picElement.sourcePath)).size;
} }
} }
//且 PredictTime ((totalSize / 1024 / 512) * 1000)不等于Nan //且 PredictTime ((totalSize / 1024 / 512) * 1000)不等于Nan
@@ -916,9 +912,9 @@ export class OneBotMsgApi {
}, returnMsg.msgId); }, returnMsg.msgId);
setTimeout(() => { setTimeout(() => {
deleteAfterSentFiles.forEach(file => { deleteAfterSentFiles.forEach(async file => {
try { try {
if (fs.existsSync(file)) { if (await fsPromise.access(file, constants.W_OK).then(() => true).catch(() => false)) {
fsPromise.unlink(file).then().catch(e => this.core.context.logger.logError('发送消息删除文件失败', e)); fsPromise.unlink(file).then().catch(e => this.core.context.logger.logError('发送消息删除文件失败', e));
} }
} catch (error) { } catch (error) {

View File

@@ -9,13 +9,13 @@ export const LogHandler: RequestHandler = async (req, res) => {
if (filename.includes('..')) { if (filename.includes('..')) {
return sendError(res, 'ID不合法'); return sendError(res, 'ID不合法');
} }
const logContent = WebUiConfigWrapper.GetLogContent(filename); const logContent = await WebUiConfigWrapper.GetLogContent(filename);
return sendSuccess(res, logContent); return sendSuccess(res, logContent);
}; };
// 日志列表 // 日志列表
export const LogListHandler: RequestHandler = async (_, res) => { export const LogListHandler: RequestHandler = async (_, res) => {
const logList = WebUiConfigWrapper.GetLogsList(); const logList = await WebUiConfigWrapper.GetLogsList();
return sendSuccess(res, logList); return sendSuccess(res, logList);
}; };
// 实时日志SSE // 实时日志SSE

View File

@@ -1,5 +1,5 @@
import { webUiPathWrapper } from '@/webui'; import { webUiPathWrapper } from '@/webui';
import { existsSync, readFileSync, writeFileSync, readdirSync } from 'node:fs'; import fs, { constants } from 'node:fs/promises';
import * as net from 'node:net'; import * as net from 'node:net';
import { resolve } from 'node:path'; import { resolve } from 'node:path';
@@ -90,18 +90,18 @@ export class WebUiConfigWrapper {
try { try {
const configPath = resolve(webUiPathWrapper.configPath, './webui.json'); const configPath = resolve(webUiPathWrapper.configPath, './webui.json');
if (!existsSync(configPath)) { if (!await fs.access(configPath, constants.R_OK | constants.W_OK).then(() => true).catch(() => false)) {
writeFileSync(configPath, JSON.stringify(defaultconfig, null, 4)); await fs.writeFile(configPath, JSON.stringify(defaultconfig, null, 4));
} }
const fileContent = readFileSync(configPath, 'utf-8'); const fileContent = await fs.readFile(configPath, 'utf-8');
// 更新配置字段后新增字段可能会缺失,同步一下 // 更新配置字段后新增字段可能会缺失,同步一下
const parsedConfig = this.applyDefaults(JSON.parse(fileContent) as Partial<WebUiConfigType>, defaultconfig); const parsedConfig = this.applyDefaults(JSON.parse(fileContent) as Partial<WebUiConfigType>, defaultconfig);
if (!parsedConfig.prefix.startsWith('/')) parsedConfig.prefix = '/' + parsedConfig.prefix; if (!parsedConfig.prefix.startsWith('/')) parsedConfig.prefix = '/' + parsedConfig.prefix;
if (parsedConfig.prefix.endsWith('/')) parsedConfig.prefix = parsedConfig.prefix.slice(0, -1); if (parsedConfig.prefix.endsWith('/')) parsedConfig.prefix = parsedConfig.prefix.slice(0, -1);
// 配置已经被操作过了,还是回写一下吧,不然新配置不会出现在配置文件里 // 配置已经被操作过了,还是回写一下吧,不然新配置不会出现在配置文件里
writeFileSync(configPath, JSON.stringify(parsedConfig, null, 4)); await fs.writeFile(configPath, JSON.stringify(parsedConfig, null, 4));
// 不希望回写的配置放后面 // 不希望回写的配置放后面
// 查询主机地址是否可用 // 查询主机地址是否可用
@@ -137,19 +137,19 @@ export class WebUiConfigWrapper {
return resolve(webUiPathWrapper.logsPath); return resolve(webUiPathWrapper.logsPath);
} }
// 获取日志列表 // 获取日志列表
public static GetLogsList(): string[] { public static async GetLogsList(): Promise<string[]> {
if (existsSync(webUiPathWrapper.logsPath)) { if (await fs.access(webUiPathWrapper.logsPath, constants.F_OK).then(() => true).catch(() => false)) {
return readdirSync(webUiPathWrapper.logsPath) return (await fs.readdir(webUiPathWrapper.logsPath))
.filter((file) => file.endsWith('.log')) .filter((file) => file.endsWith('.log'))
.map((file) => file.replace('.log', '')); .map((file) => file.replace('.log', ''));
} }
return []; return [];
} }
// 获取指定日志文件内容 // 获取指定日志文件内容
public static GetLogContent(filename: string): string { public static async GetLogContent(filename: string): Promise<string> {
const logPath = resolve(webUiPathWrapper.logsPath, `${filename}.log`); const logPath = resolve(webUiPathWrapper.logsPath, `${filename}.log`);
if (existsSync(logPath)) { if (await fs.access(logPath, constants.R_OK).then(() => true).catch(() => false)) {
return readFileSync(logPath, 'utf-8'); return await fs.readFile(logPath, 'utf-8');
} }
return ''; return '';
} }