Compare commits

...

13 Commits

Author SHA1 Message Date
手瓜一十雪
2260fe32a1 Merge pull request #514 from NapNeko/refactor-log4js--winston
refactor: log4js to winston
2024-11-13 12:29:35 +08:00
手瓜一十雪
2c398a6832 fix: error 2024-11-13 12:29:17 +08:00
手瓜一十雪
3e1f566699 feat: 异常处理与log4js替换 2024-11-13 12:26:23 +08:00
手瓜一十雪
4f89f184b8 refactor: package json 2024-11-13 12:19:18 +08:00
Mlikiowa
787685c937 release: v3.6.13 2024-11-12 10:45:11 +00:00
手瓜一十雪
ed9cd2fe38 fix: #513 2024-11-12 18:37:44 +08:00
Mlikiowa
e287906a9d release: v3.6.12 2024-11-11 13:03:29 +00:00
手瓜一十雪
8bae789020 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-11-11 21:00:27 +08:00
手瓜一十雪
ce57b7b725 fix: #509 2024-11-11 21:00:12 +08:00
pk5ls20
1d9872195d fix: offset name 2024-11-11 20:57:30 +08:00
pk5ls20
98d1f8e29f feat: add some macOS offset 2024-11-11 20:54:57 +08:00
手瓜一十雪
221b3fb730 fix 2024-11-11 20:35:13 +08:00
Mlikiowa
90a834495a release: v3.6.11 2024-11-11 12:17:25 +00:00
14 changed files with 137 additions and 70 deletions

View File

@@ -30,6 +30,8 @@ NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现
[Cloudflare.Pages](https://napneko.pages.dev/) [Cloudflare.Pages](https://napneko.pages.dev/)
[Server.China](https://napneko.com/)
[Server.Other](https://napcat.cyou/) [Server.Other](https://napcat.cyou/)
[Github.IO](https://napneko.github.io/) [Github.IO](https://napneko.github.io/)

View File

@@ -4,7 +4,7 @@
"name": "NapCatQQ", "name": "NapCatQQ",
"slug": "NapCat.Framework", "slug": "NapCat.Framework",
"description": "高性能的 OneBot 11 协议实现", "description": "高性能的 OneBot 11 协议实现",
"version": "3.6.10", "version": "3.6.13",
"icon": "./logo.png", "icon": "./logo.png",
"authors": [ "authors": [
{ {

View File

@@ -2,7 +2,7 @@
"name": "napcat", "name": "napcat",
"private": true, "private": true,
"type": "module", "type": "module",
"version": "3.6.10", "version": "3.6.13",
"scripts": { "scripts": {
"build:framework": "vite build --mode framework", "build:framework": "vite build --mode framework",
"build:shell": "vite build --mode shell", "build:shell": "vite build --mode shell",
@@ -45,12 +45,12 @@
"typescript-eslint": "^8.13.0", "typescript-eslint": "^8.13.0",
"vite": "^5.2.6", "vite": "^5.2.6",
"vite-plugin-cp": "^4.0.8", "vite-plugin-cp": "^4.0.8",
"vite-tsconfig-paths": "^5.1.0" "vite-tsconfig-paths": "^5.1.0",
"winston": "^3.17.0"
}, },
"dependencies": { "dependencies": {
"express": "^5.0.0", "express": "^5.0.0",
"fluent-ffmpeg": "^2.1.2", "fluent-ffmpeg": "^2.1.2",
"log4js": "^6.9.1",
"qrcode-terminal": "^0.12.0", "qrcode-terminal": "^0.12.0",
"silk-wasm": "^3.6.1", "silk-wasm": "^3.6.1",
"ws": "^8.18.0" "ws": "^8.18.0"

View File

@@ -1,4 +1,4 @@
import log4js, { Configuration } from 'log4js'; 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 chalk from 'chalk'; import chalk from 'chalk';
@@ -27,76 +27,78 @@ function getFormattedTimestamp() {
export class LogWrapper { export class LogWrapper {
fileLogEnabled = true; fileLogEnabled = true;
consoleLogEnabled = true; consoleLogEnabled = true;
logConfig: Configuration; logger: winston.Logger;
loggerConsole: log4js.Logger;
loggerFile: log4js.Logger;
loggerDefault: log4js.Logger;
// eslint-disable-next-line no-control-regex
colorEscape = /\x1B[@-_][0-?]*[ -/]*[@-~]/g;
constructor(logDir: string) { constructor(logDir: string) {
const filename = `${getFormattedTimestamp()}.log`; const filename = `${getFormattedTimestamp()}.log`;
const logPath = path.join(logDir, filename); const logPath = path.join(logDir, filename);
this.logConfig = {
appenders: { this.logger = winston.createLogger({
FileAppender: { // 输出到文件的appender level: 'debug',
type: 'file', format: format.combine(
filename: logPath, // 指定日志文件的位置和文件名 format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
maxLogSize: 10485760, // 日志文件的最大大小单位字节这里设置为10MB format.printf(({ timestamp, level, message, ...meta }) => {
layout: { const userInfo = meta.userInfo ? `${meta.userInfo} | ` : '';
type: 'pattern', return `${timestamp} [${level}] ${userInfo}${message}`;
pattern: '%d{yyyy-MM-dd hh:mm:ss} [%p] %X{userInfo} | %m', })
}, ),
}, transports: [
ConsoleAppender: { // 输出到控制台的appender new transports.File({ filename: logPath, level: 'debug' }),
type: 'console', new transports.Console({
layout: { format: format.combine(
type: 'pattern', format.colorize(),
pattern: `%d{yyyy-MM-dd hh:mm:ss} [%[%p%]] ${chalk.magenta('%X{userInfo}')} | %m`, format.printf(({ timestamp, level, message, ...meta }) => {
}, const userInfo = meta.userInfo ? `${chalk.magenta(meta.userInfo)} | ` : '';
}, return `${timestamp} [${level}] ${userInfo}${message}`;
}, })
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: '' }); this.setLogSelfInfo({ nick: '', uin: '', uid: '' });
} }
setFileAndConsoleLogLevel(fileLogLevel: LogLevel, consoleLogLevel: LogLevel) { setFileAndConsoleLogLevel(fileLogLevel: LogLevel, consoleLogLevel: LogLevel) {
this.logConfig.categories.file.level = fileLogLevel; this.logger.transports.forEach((transport) => {
this.logConfig.categories.console.level = consoleLogLevel; if (transport instanceof transports.File) {
log4js.configure(this.logConfig); transport.level = fileLogLevel;
} else if (transport instanceof transports.Console) {
transport.level = consoleLogLevel;
}
});
} }
setLogSelfInfo(selfInfo: { nick: string, uin: string, uid: string }) { setLogSelfInfo(selfInfo: { nick: string, uin: string, uid: string }) {
const userInfo = `${selfInfo.nick}(${selfInfo.uin})`; const userInfo = `${selfInfo.nick}(${selfInfo.uin})`;
this.loggerConsole.addContext('userInfo', userInfo); this.logger.defaultMeta = { userInfo };
this.loggerFile.addContext('userInfo', userInfo);
this.loggerDefault.addContext('userInfo', userInfo);
} }
setFileLogEnabled(isEnabled: boolean) { setFileLogEnabled(isEnabled: boolean) {
this.fileLogEnabled = isEnabled; this.fileLogEnabled = isEnabled;
this.logger.transports.forEach((transport) => {
if (transport instanceof transports.File) {
transport.silent = !isEnabled;
}
});
} }
setConsoleLogEnabled(isEnabled: boolean) { setConsoleLogEnabled(isEnabled: boolean) {
this.consoleLogEnabled = isEnabled; this.consoleLogEnabled = isEnabled;
this.logger.transports.forEach((transport) => {
if (transport instanceof transports.Console) {
transport.silent = !isEnabled;
}
});
} }
formatMsg(msg: any[]) { formatMsg(msg: any[]) {
let logMsg = ''; let logMsg = '';
for (const msgItem of msg) { for (const msgItem of msg) {
if (msgItem instanceof Error) { // 判断是否是错误 if (msgItem instanceof Error) {
logMsg += msgItem.stack + ' '; logMsg += msgItem.stack + ' ';
continue; continue;
} else if (typeof msgItem === 'object') { // 判断是否是对象 } else if (typeof msgItem === 'object') {
const obj = JSON.parse(JSON.stringify(msgItem, null, 2)); const obj = JSON.parse(JSON.stringify(msgItem, null, 2));
logMsg += JSON.stringify(truncateString(obj)) + ' '; logMsg += JSON.stringify(truncateString(obj)) + ' ';
continue; continue;
@@ -106,18 +108,18 @@ export class LogWrapper {
return logMsg; return logMsg;
} }
_log(level: LogLevel, ...args: any[]) { _log(level: LogLevel, ...args: any[]) {
if (this.consoleLogEnabled) { const message = this.formatMsg(args);
this.loggerConsole[level](this.formatMsg(args)); if (this.consoleLogEnabled && this.fileLogEnabled) {
} this.logger.log(level, message);
if (this.fileLogEnabled) { } else if (this.consoleLogEnabled) {
this.loggerFile[level](this.formatMsg(args).replace(this.colorEscape, '')); this.logger.log(level, message);
} else if (this.fileLogEnabled) {
this.logger.log(level, message.replace(/\x1B[@-_][0-?]*[ -/]*[@-~]/g, ''));
} }
} }
log(...args: any[]) { log(...args: any[]) {
// info 等级
this._log(LogLevel.INFO, ...args); this._log(LogLevel.INFO, ...args);
} }
@@ -140,7 +142,6 @@ export class LogWrapper {
logMessage(msg: RawMessage, selfInfo: SelfInfo) { logMessage(msg: RawMessage, selfInfo: SelfInfo) {
const isSelfSent = msg.senderUin === selfInfo.uin; const isSelfSent = msg.senderUin === selfInfo.uin;
// Intercept grey tip
if (msg.elements[0]?.elementType === ElementType.GreyTip) { if (msg.elements[0]?.elementType === ElementType.GreyTip) {
return; return;
} }
@@ -167,12 +168,10 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
} }
} else if (msg.chatType == ChatType.KCHATTYPEDATALINE) { } else if (msg.chatType == ChatType.KCHATTYPEDATALINE) {
tokens.push('移动设备'); tokens.push('移动设备');
} else /* temp */ { } else {
tokens.push(`临时消息 (${msg.peerUin})`); tokens.push(`临时消息 (${msg.peerUin})`);
} }
// message content
function msgElementToText(element: MessageElement) { function msgElementToText(element: MessageElement) {
if (element.textElement) { if (element.textElement) {
if (element.textElement.atType === AtType.notAt) { if (element.textElement.atType === AtType.notAt) {
@@ -190,7 +189,7 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
record => element.replyElement!.sourceMsgIdInRecords === record.msgId, record => element.replyElement!.sourceMsgIdInRecords === record.msgId,
); );
return `[回复消息 ${recordMsgOrNull && return `[回复消息 ${recordMsgOrNull &&
recordMsgOrNull.peerUin != '284840486' && recordMsgOrNull.peerUin != '1094950020'// 非转发消息; 否则定位不到 recordMsgOrNull.peerUin != '284840486' && recordMsgOrNull.peerUin != '1094950020'
? ?
rawMessageToText(recordMsgOrNull, recursiveLevel + 1) : rawMessageToText(recordMsgOrNull, recursiveLevel + 1) :
`未找到消息记录 (MsgId = ${element.replyElement.sourceMsgIdInRecords})` `未找到消息记录 (MsgId = ${element.replyElement.sourceMsgIdInRecords})`

View File

@@ -1 +1 @@
export const napCatVersion = '3.6.10'; export const napCatVersion = '3.6.13';

View File

@@ -7,6 +7,14 @@
"recv": "37A9004", "recv": "37A9004",
"send": "37A4BD0" "send": "37A4BD0"
}, },
"6.9.56-28418-x64": {
"send": "4471360",
"recv": "4473BCC"
},
"6.9.56-28418-arm64": {
"send": "3FBDBF8",
"recv": "3FC0410"
},
"9.9.15-28498-x64": { "9.9.15-28498-x64": {
"recv": "37A9004", "recv": "37A9004",
"send": "37A4BD0" "send": "37A4BD0"
@@ -35,6 +43,10 @@
"send": "6E91318", "send": "6E91318",
"recv": "6E94B50" "recv": "6E94B50"
}, },
"6.9.58-28971-x64": {
"send": "449ACA0",
"recv": "449D50C"
},
"6.9.58-28971-arm64": { "6.9.58-28971-arm64": {
"send": "3FE0DB0", "send": "3FE0DB0",
"recv": "3FE35C8" "recv": "3FE35C8"
@@ -62,5 +74,13 @@
"3.2.13-29456-arm64": { "3.2.13-29456-arm64": {
"send": "6ECA130", "send": "6ECA130",
"recv": "6ECD968" "recv": "6ECD968"
},
"6.9.59-29456-x64": {
"send": "44C57A0",
"recv": "44C800C"
},
"6.9.59-29456-arm64": {
"send": "4005FE8",
"recv": "4008800"
} }
} }

View File

@@ -1,4 +1,4 @@
import { ChatType, RawMessage } from '@/core/entities'; import { ChatType, KickedOffLineInfo, RawMessage } from '@/core/entities';
import { CommonFileInfo } from '@/core'; import { CommonFileInfo } from '@/core';
export interface OnRichMediaDownloadCompleteParams { export interface OnRichMediaDownloadCompleteParams {
@@ -212,7 +212,7 @@ export class NodeIKernelMsgListener {
} }
onKickedOffLine(kickedInfo: unknown) { onKickedOffLine(kickedInfo: KickedOffLineInfo) {
} }

View File

@@ -23,6 +23,14 @@ export async function NCoreInitFramework(
) { ) {
//在进入本层前是否登录未进行判断 //在进入本层前是否登录未进行判断
console.log('NapCat Framework App Loading...'); console.log('NapCat Framework App Loading...');
process.on('uncaughtException', (err) => {
console.log('[NapCat] [Error] Unhandled Exception:', err.message);
});
process.on('unhandledRejection', (reason, promise) => {
console.log('[NapCat] [Error] unhandledRejection:', reason);
});
const pathWrapper = new NapCatPathWrapper(); const pathWrapper = new NapCatPathWrapper();
const logger = new LogWrapper(pathWrapper.logsPath); const logger = new LogWrapper(pathWrapper.logsPath);
const basicInfoWrapper = new QQBasicInfoWrapper({ logger }); const basicInfoWrapper = new QQBasicInfoWrapper({ logger });

View File

@@ -2,12 +2,14 @@ import { ChatType, Peer } from '@/core/entities';
import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import BaseAction from '../BaseAction'; import BaseAction from '../BaseAction';
import { ActionName } from '../types'; import { ActionName } from '../types';
import { MessageUnique } from '@/common/message-unique';
const SchemaData = { const SchemaData = {
type: 'object', type: 'object',
properties: { properties: {
user_id: { type: ['number', 'string'] }, user_id: { type: ['number', 'string'] },
group_id: { type: ['number', 'string'] }, group_id: { type: ['number', 'string'] },
message_id: { type: ['number', 'string'] },
}, },
} as const satisfies JSONSchema; } as const satisfies JSONSchema;
@@ -15,6 +17,16 @@ type PlayloadType = FromSchema<typeof SchemaData>;
class MarkMsgAsRead extends BaseAction<PlayloadType, null> { class MarkMsgAsRead extends BaseAction<PlayloadType, null> {
async getPeer(payload: PlayloadType): Promise<Peer> { async getPeer(payload: PlayloadType): Promise<Peer> {
if (payload.message_id) {
let s_peer = MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id)?.Peer;
if (s_peer) {
return s_peer;
}
let l_peer = MessageUnique.getPeerByMsgId(payload.message_id.toString())?.Peer;
if (l_peer) {
return l_peer;
}
}
if (payload.user_id) { if (payload.user_id) {
const peerUid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString()); const peerUid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
if (!peerUid) { if (!peerUid) {

View File

@@ -0,0 +1,16 @@
import { OB11BaseNoticeEvent } from './OB11BaseNoticeEvent';
import { NapCatCore } from '@/core';
export class BotOfflineEvent extends OB11BaseNoticeEvent {
notice_type = 'bot_offline';
user_id: number;
tag: string = 'BotOfflineEvent';
message: string = 'BotOfflineEvent';
public constructor(core: NapCatCore, tag: string, message: string) {
super(core);
this.user_id = +core.selfInfo.uin;
this.tag = tag;
this.message = message;
}
}

View File

@@ -47,6 +47,7 @@ import { LRUCache } from '@/common/lru-cache';
import { NodeIKernelRecentContactListener } from '@/core/listeners/NodeIKernelRecentContactListener'; import { NodeIKernelRecentContactListener } from '@/core/listeners/NodeIKernelRecentContactListener';
import { Native } from '@/native'; import { Native } from '@/native';
import { decodeMessage, decodeRecallGroup } from '@/core/packet/proto/old/Message'; import { decodeMessage, decodeRecallGroup } from '@/core/packet/proto/old/Message';
import { BotOfflineEvent } from './event/notice/BotOfflineEvent';
//OneBot实现类 //OneBot实现类
export class NapCatOneBot11Adapter { export class NapCatOneBot11Adapter {
@@ -343,7 +344,11 @@ export class NapCatOneBot11Adapter {
} }
} }
}; };
msgListener.onKickedOffLine = async (kick) => {
let event = new BotOfflineEvent(this.core, kick.tipsTitle, kick.tipsDesc);
this.networkManager.emitEvent(event)
.catch(e => this.context.logger.logError.bind(this.context.logger)('处理Bot掉线失败', e));
}
this.context.session.getMsgService().addKernelMsgListener( this.context.session.getMsgService().addKernelMsgListener(
proxiedListenerOf(msgListener, this.context.logger), proxiedListenerOf(msgListener, this.context.logger),
); );

View File

@@ -36,7 +36,12 @@ const cmdOptions = program.opts();
// NapCat Shell App ES 入口文件 // NapCat Shell App ES 入口文件
export async function NCoreInitShell() { export async function NCoreInitShell() {
console.log('NapCat Shell App Loading...'); console.log('NapCat Shell App Loading...');
process.on('uncaughtException', (err) => {
console.log('[NapCat] [Error] Unhandled Exception:', err.message);
});
process.on('unhandledRejection', (reason, promise) => {
console.log('[NapCat] [Error] unhandledRejection:', reason);
});
const pathWrapper = new NapCatPathWrapper(); const pathWrapper = new NapCatPathWrapper();
const logger = new LogWrapper(pathWrapper.logsPath); const logger = new LogWrapper(pathWrapper.logsPath);
const basicInfoWrapper = new QQBasicInfoWrapper({ logger }); const basicInfoWrapper = new QQBasicInfoWrapper({ logger });

View File

@@ -164,7 +164,7 @@ async function onSettingWindowCreated(view) {
SettingItem( SettingItem(
'<span id="napcat-update-title">Napcat</span>', '<span id="napcat-update-title">Napcat</span>',
void 0, void 0,
SettingButton("V3.6.10", "napcat-update-button", "secondary") SettingButton("V3.6.13", "napcat-update-button", "secondary")
) )
]), ]),
SettingList([ SettingList([

View File

@@ -4,7 +4,7 @@ import { resolve } from 'path';
import nodeResolve from '@rollup/plugin-node-resolve'; import nodeResolve from '@rollup/plugin-node-resolve';
import { builtinModules } from 'module'; import { builtinModules } from 'module';
//依赖排除 //依赖排除
const external = ['silk-wasm', 'ws', 'express', 'fluent-ffmpeg', 'log4js', 'qrcode-terminal']; const external = ['silk-wasm', 'ws', 'express', 'fluent-ffmpeg', 'qrcode-terminal'];
const nodeModules = [...builtinModules, builtinModules.map(m => `node:${m}`)].flat(); const nodeModules = [...builtinModules, builtinModules.map(m => `node:${m}`)].flat();
function genCpModule(module: string) { function genCpModule(module: string) {
return { src: `./node_modules/${module}`, dest: `dist/node_modules/${module}`, flatten: false }; return { src: `./node_modules/${module}`, dest: `dist/node_modules/${module}`, flatten: false };