Compare commits

..

24 Commits

Author SHA1 Message Date
手瓜一十雪
b5ef6ce6b0 feat: mface key 扩展 2024-11-13 15:54:31 +08:00
Mlikiowa
704ac11cbb release: v3.6.15 2024-11-13 05:16:36 +00:00
手瓜一十雪
aa9663d85e fix: 移除chalk 2024-11-13 13:16:09 +08:00
Mlikiowa
05291f34fb release: v3.6.14 2024-11-13 04:31:12 +00:00
手瓜一十雪
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
手瓜一十雪
8bfd102232 fix: macos arm64 28971 2024-11-11 20:16:44 +08:00
Mlikiowa
65e784f169 release: v3.6.10 2024-11-11 02:45:39 +00:00
手瓜一十雪
0fc81c672f fix: once升级 2024-11-11 10:45:15 +08:00
手瓜一十雪
62ae0f4321 feat: 文档镜像 2024-11-10 12:40:18 +08:00
Mlikiowa
a01a0a1a18 release: v3.6.9 2024-11-10 04:11:16 +00:00
手瓜一十雪
4c30cc69ad fix: #73 2024-11-10 12:10:43 +08:00
Mlikiowa
1d43b75df4 release: v3.6.8 2024-11-09 10:22:17 +00:00
24 changed files with 188 additions and 200 deletions

View File

@@ -127,10 +127,6 @@ jobs:
zip -q -r NapCat.Framework.Windows.Once.zip *
cd ..
mv ./NapCat.Framework.Windows.Once/NapCat.Framework.Windows.Once.zip ./
mv ./external/packet/napcat.packet.arm64 ./
mv ./external/packet/napcat.packet.exe ./
mv ./external/packet/napcat.packet.linux ./
mv ./external/packet/napcat.packet.production.py ./
- name: Extract version from tag
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
@@ -147,8 +143,4 @@ jobs:
NapCat.Framework.zip
NapCat.Shell.zip
NapCat.Framework.Windows.Once.zip
napcat.packet.arm64
napcat.packet.exe
napcat.packet.linux
napcat.packet.production.py
draft: true

View File

@@ -30,6 +30,10 @@ NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现
[Cloudflare.Pages](https://napneko.pages.dev/)
[Server.China](https://napneko.com/)
[Server.Other](https://napcat.cyou/)
[Github.IO](https://napneko.github.io/)
## 回家旅途
[QQ Group](https://qm.qq.com/q/VfjAq5HIMS)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -2,7 +2,7 @@
"name": "napcat",
"private": true,
"type": "module",
"version": "3.6.7",
"version": "3.6.15",
"scripts": {
"build:framework": "vite build --mode framework",
"build:shell": "vite build --mode shell",
@@ -30,7 +30,6 @@
"@typescript-eslint/parser": "^8.3.0",
"ajv": "^8.13.0",
"async-mutex": "^0.5.0",
"chalk": "^5.3.0",
"commander": "^12.1.0",
"cors": "^2.8.5",
"eslint": "^9.14.0",
@@ -45,12 +44,12 @@
"typescript-eslint": "^8.13.0",
"vite": "^5.2.6",
"vite-plugin-cp": "^4.0.8",
"vite-tsconfig-paths": "^5.1.0"
"vite-tsconfig-paths": "^5.1.0",
"winston": "^3.17.0"
},
"dependencies": {
"express": "^5.0.0",
"fluent-ffmpeg": "^2.1.2",
"log4js": "^6.9.1",
"qrcode-terminal": "^0.12.0",
"silk-wasm": "^3.6.1",
"ws": "^8.18.0"

View File

@@ -1,7 +1,7 @@
import log4js, { Configuration } from 'log4js';
import winston, { format, transports } from 'winston';
import { truncateString } from '@/common/helper';
import path from 'node:path';
import chalk from 'chalk';
import fs from 'node:fs';
import { AtType, ChatType, ElementType, MessageElement, RawMessage, SelfInfo } from '@/core';
export enum LogLevel {
@@ -27,97 +27,132 @@ function getFormattedTimestamp() {
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;
logger: winston.Logger;
constructor(logDir: string) {
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.logger = winston.createLogger({
level: 'debug',
format: format.combine(
format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
format.printf(({ timestamp, level, message, ...meta }) => {
const userInfo = meta.userInfo ? `${meta.userInfo} | ` : '';
return `${timestamp} [${level}] ${userInfo}${message}`;
})
),
transports: [
new transports.File({
filename: logPath,
level: 'debug',
maxsize: 5 * 1024 * 1024, // 5MB
maxFiles: 5
}),
new transports.Console({
format: format.combine(
format.colorize(),
format.printf(({ timestamp, level, message, ...meta }) => {
const userInfo = meta.userInfo ? `${meta.userInfo} | ` : '';
return `${timestamp} [${level}] ${userInfo}${message}`;
})
)
})
]
});
this.setLogSelfInfo({ nick: '', uin: '', uid: '' });
this.cleanOldLogs(logDir);
}
cleanOldLogs(logDir: string) {
const oneWeekAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;
fs.readdir(logDir, (err, files) => {
if (err) {
this.logger.error('Failed to read log directory', err);
return;
}
files.forEach(file => {
const filePath = path.join(logDir, file);
fs.stat(filePath, (err, stats) => {
if (err) {
this.logger.error('Failed to get file stats', err);
return;
}
if (stats.mtime.getTime() < oneWeekAgo) {
fs.unlink(filePath, err => {
if (err) {
if (err.code === 'ENOENT') {
this.logger.warn(`File already deleted: ${file}`);
} else {
this.logger.error('Failed to delete old log file', err);
}
} else {
this.logger.info(`Deleted old log file: ${file}`);
}
});
}
});
});
});
}
setFileAndConsoleLogLevel(fileLogLevel: LogLevel, consoleLogLevel: LogLevel) {
this.logConfig.categories.file.level = fileLogLevel;
this.logConfig.categories.console.level = consoleLogLevel;
log4js.configure(this.logConfig);
this.logger.transports.forEach((transport) => {
if (transport instanceof transports.File) {
transport.level = fileLogLevel;
} else if (transport instanceof transports.Console) {
transport.level = consoleLogLevel;
}
});
}
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);
this.logger.defaultMeta = { userInfo };
}
setFileLogEnabled(isEnabled: boolean) {
this.fileLogEnabled = isEnabled;
this.logger.transports.forEach((transport) => {
if (transport instanceof transports.File) {
transport.silent = !isEnabled;
}
});
}
setConsoleLogEnabled(isEnabled: boolean) {
this.consoleLogEnabled = isEnabled;
this.logger.transports.forEach((transport) => {
if (transport instanceof transports.Console) {
transport.silent = !isEnabled;
}
});
}
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;
return msg.map(msgItem => {
if (msgItem instanceof Error) {
return msgItem.stack;
} else if (typeof msgItem === 'object') {
return JSON.stringify(truncateString(JSON.parse(JSON.stringify(msgItem, null, 2))));
}
logMsg += msgItem + ' ';
}
return logMsg;
return msgItem;
}).join(' ');
}
_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, ''));
const message = this.formatMsg(args);
if (this.consoleLogEnabled && this.fileLogEnabled) {
this.logger.log(level, message);
} else if (this.consoleLogEnabled) {
this.logger.log(level, message);
} else if (this.fileLogEnabled) {
this.logger.log(level, message.replace(/\x1B[@-_][0-?]*[ -/]*[@-~]/g, ''));
}
}
log(...args: any[]) {
// info 等级
this._log(LogLevel.INFO, ...args);
}
@@ -140,12 +175,11 @@ export class LogWrapper {
logMessage(msg: RawMessage, selfInfo: SelfInfo) {
const isSelfSent = msg.senderUin === selfInfo.uin;
// Intercept grey tip
if (msg.elements[0]?.elementType === ElementType.GreyTip) {
return;
}
this.log(`${isSelfSent ? '发送 ->' : '接收 <-' } ${rawMessageToText(msg)}`);
this.log(`${isSelfSent ? '发送 ->' : '接收 <-'} ${rawMessageToText(msg)}`);
}
}
@@ -167,12 +201,10 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
}
} else if (msg.chatType == ChatType.KCHATTYPEDATALINE) {
tokens.push('移动设备');
} else /* temp */ {
} else {
tokens.push(`临时消息 (${msg.peerUin})`);
}
// message content
function msgElementToText(element: MessageElement) {
if (element.textElement) {
if (element.textElement.atType === AtType.notAt) {
@@ -190,11 +222,11 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
record => element.replyElement!.sourceMsgIdInRecords === record.msgId,
);
return `[回复消息 ${recordMsgOrNull &&
recordMsgOrNull.peerUin != '284840486' && recordMsgOrNull.peerUin != '1094950020'// 非转发消息; 否则定位不到
recordMsgOrNull.peerUin != '284840486' && recordMsgOrNull.peerUin != '1094950020'
?
rawMessageToText(recordMsgOrNull, recursiveLevel + 1) :
`未找到消息记录 (MsgId = ${element.replyElement.sourceMsgIdInRecords})`
}]`;
}]`;
}
if (element.picElement) {
@@ -245,4 +277,4 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
}
return tokens.join(' ');
}
}

View File

@@ -1 +1 @@
export const napCatVersion = '3.6.7';
export const napCatVersion = '3.6.15';

View File

@@ -1,8 +1,4 @@
{
"6.9.56-28418-arm64": {
"send": "4471360",
"recv": "4473BCC"
},
"3.2.12-28418-x64": {
"recv": "A0723E0",
"send": "A06EAE0"
@@ -11,6 +7,14 @@
"recv": "37A9004",
"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": {
"recv": "37A9004",
"send": "37A4BD0"
@@ -39,10 +43,14 @@
"send": "6E91318",
"recv": "6E94B50"
},
"6.9.58-28971-arm64": {
"6.9.58-28971-x64": {
"send": "449ACA0",
"recv": "449D50C"
},
"6.9.58-28971-arm64": {
"send": "3FE0DB0",
"recv": "3FE35C8"
},
"9.9.16-29271-x64": {
"send": "3833510",
"recv": "3837944"
@@ -66,5 +74,13 @@
"3.2.13-29456-arm64": {
"send": "6ECA130",
"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';
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...');
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 logger = new LogWrapper(pathWrapper.logsPath);
const basicInfoWrapper = new QQBasicInfoWrapper({ logger });

Binary file not shown.

View File

@@ -42,7 +42,7 @@ export default class GoCQHTTPUploadGroupFile extends BaseAction<Payload, null> {
deleteAfterSentFiles: []
};
const sendFileEle = await this.core.apis.FileApi.createValidSendFileElement(msgContext, downloadResult.path, payload.name, payload.folder ?? payload.folder_id);
await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(peer, [sendFileEle], [], true);
await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(peer, [sendFileEle], msgContext.deleteAfterSentFiles, true);
return null;
}
}

View File

@@ -53,7 +53,7 @@ export default class GoCQHTTPUploadPrivateFile extends BaseAction<Payload, null>
deleteAfterSentFiles: []
};
const sendFileEle: SendFileElement = await this.core.apis.FileApi.createValidSendFileElement(msgContext, downloadResult.path, payload.name);
await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(await this.getPeer(payload), [sendFileEle], [], true);
await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(await this.getPeer(payload), [sendFileEle], msgContext.deleteAfterSentFiles, true);
return null;
}
}

View File

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

View File

@@ -190,6 +190,7 @@ export class OneBotMsgApi {
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, "", "." + _.key + ".jpg"),
path: url,
url: url,
key: _.key,
file_unique: _.key
},
};

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 { Native } from '@/native';
import { decodeMessage, decodeRecallGroup } from '@/core/packet/proto/old/Message';
import { BotOfflineEvent } from './event/notice/BotOfflineEvent';
//OneBot实现类
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(
proxiedListenerOf(msgListener, this.context.logger),
);

View File

@@ -36,7 +36,12 @@ const cmdOptions = program.opts();
// NapCat Shell App ES 入口文件
export async function NCoreInitShell() {
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 logger = new LogWrapper(pathWrapper.logsPath);
const basicInfoWrapper = new QQBasicInfoWrapper({ logger });
@@ -227,7 +232,7 @@ export async function NCoreInitShell() {
logger.log(`可用于快速登录的 QQ\n${historyLoginList
.map((u, index) => `${index + 1}. ${u.uin} ${u.nickName}`)
.join('\n')
}`);
}`);
}
loginService.getQRCodePicture();
}

View File

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

View File

@@ -4,7 +4,7 @@ import { resolve } from 'path';
import nodeResolve from '@rollup/plugin-node-resolve';
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();
function genCpModule(module: string) {
return { src: `./node_modules/${module}`, dest: `dist/node_modules/${module}`, flatten: false };