diff --git a/package.json b/package.json index e123fa03..74dbd575 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ }, "devDependencies": { "@log4js-node/log4js-api": "^1.0.2", + "@protobuf-ts/plugin": "^2.9.4", "@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-typescript": "^11.1.6", "@types/cors": "^2.8.17", @@ -43,11 +44,11 @@ "vite": "^5.2.6", "vite-plugin-cp": "^4.0.8", "vite-plugin-dts": "^3.8.2", - "vite-tsconfig-paths": "^4.3.2", - "@protobuf-ts/plugin": "^2.9.4" + "vite-tsconfig-paths": "^4.3.2" }, "dependencies": { "ajv": "^8.13.0", + "chalk": "^5.3.0", "commander": "^12.0.0", "cors": "^2.8.5", "express": "^5.0.0-beta.2", diff --git a/src/common/utils/AsyncQueue.ts b/src/common/utils/AsyncQueue.ts index bbce654c..2f629842 100644 --- a/src/common/utils/AsyncQueue.ts +++ b/src/common/utils/AsyncQueue.ts @@ -1,5 +1,5 @@ import { sleep } from '@/common/utils/helper'; - +import { logError } from './log'; type AsyncQueueTask = (() => void) | (()=>Promise); @@ -26,7 +26,8 @@ export class AsyncQueue { await taskRet; } } catch (e) { - console.error(e); + // console.error(e); + logError(e); } this.tasks.shift(); await sleep(100); diff --git a/src/common/utils/QQBasicInfo.ts b/src/common/utils/QQBasicInfo.ts index 324cf610..0a0f6157 100644 --- a/src/common/utils/QQBasicInfo.ts +++ b/src/common/utils/QQBasicInfo.ts @@ -2,6 +2,7 @@ import path from 'node:path'; import fs from 'node:fs'; import os from 'node:os'; import { systemPlatform } from '@/common/utils/system'; +import { logError } from '@/common/utils/log'; export const exePath = process.execPath; @@ -49,7 +50,7 @@ if (fs.existsSync(configVersionInfoPath)) { const _ =JSON.parse(fs.readFileSync(configVersionInfoPath).toString()); _qqVersionConfigInfo = Object.assign(_qqVersionConfigInfo, _); } catch (e) { - console.error('Load QQ version config info failed, Use default version', e); + logError('Load QQ version config info failed, Use default version', e); } } diff --git a/src/common/utils/audio.ts b/src/common/utils/audio.ts index 246de8a0..f1b6d8da 100644 --- a/src/common/utils/audio.ts +++ b/src/common/utils/audio.ts @@ -24,7 +24,7 @@ export async function encodeSilk(filePath: string) { const fileHeader = buffer.toString('hex', 0, bytesToRead); return fileHeader; } catch (err) { - console.error('读取文件错误:', err); + logError('读取文件错误:', err); return; } } diff --git a/src/common/utils/cpmodule.ts b/src/common/utils/cpmodule.ts index 44574b07..ec3c7362 100644 --- a/src/common/utils/cpmodule.ts +++ b/src/common/utils/cpmodule.ts @@ -4,7 +4,6 @@ import fs from 'fs'; import { dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; - const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); export function getModuleWithArchName(moduleName: string) { diff --git a/src/common/utils/db.ts b/src/common/utils/db.ts index d4a12c0d..ca0fedad 100644 --- a/src/common/utils/db.ts +++ b/src/common/utils/db.ts @@ -498,7 +498,7 @@ class DBUtil extends DBUtilBase { if (err) logError(err), Promise.reject(), - console.log('插入入群时间失败', userId, groupId); + logError('插入入群时间失败', userId, groupId); } ); diff --git a/src/common/utils/file.ts b/src/common/utils/file.ts index 2afed7e6..81f95865 100644 --- a/src/common/utils/file.ts +++ b/src/common/utils/file.ts @@ -3,7 +3,7 @@ import fsPromise from 'fs/promises'; import crypto from 'crypto'; import util from 'util'; import path from 'node:path'; -import { log } from './log'; +import { log, logError } from './log'; import { dbUtil } from '@/common/utils/db'; import * as fileType from 'file-type'; import { v4 as uuidv4 } from 'uuid'; @@ -262,12 +262,12 @@ export async function copyFolder(sourcePath: string, destPath: string) { try { await fsPromise.copyFile(srcPath, dstPath); } catch (error) { - console.error(`无法复制文件 '${srcPath}' 到 '${dstPath}': ${error}`); + logError(`无法复制文件 '${srcPath}' 到 '${dstPath}': ${error}`); // 这里可以决定是否要继续复制其他文件 } } } } catch (error) { - console.error('复制文件夹时出错:', error); + logError('复制文件夹时出错:', error); } } diff --git a/src/common/utils/log.ts b/src/common/utils/log.ts index 99a5a8bb..fb43c68c 100644 --- a/src/common/utils/log.ts +++ b/src/common/utils/log.ts @@ -4,7 +4,7 @@ import path from 'node:path'; import { SelfInfo } from '@/core'; import { dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; - +import chalk from 'chalk'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); @@ -42,14 +42,14 @@ const logConfig: Configuration = { maxLoogSize: 10485760, // 日志文件的最大大小(单位:字节),这里设置为10MB layout: { type: 'pattern', - pattern: '%d{yyyy-MM-dd hh:mm:ss} [%p] - %m' + 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] - %m' + pattern: `%d{yyyy-MM-dd hh:mm:ss} [%[%p%]] ${chalk.magenta('%X{userInfo}')} | %m` } } }, @@ -61,7 +61,9 @@ const logConfig: Configuration = { }; log4js.configure(logConfig); - +const loggerConsole = log4js.getLogger('console'); +const loggerFile = log4js.getLogger('file'); +const loggerDefault = log4js.getLogger('default'); export function setLogLevel(fileLogLevel: LogLevel, consoleLogLevel: LogLevel) { logConfig.categories.file.level = fileLogLevel; @@ -70,12 +72,12 @@ export function setLogLevel(fileLogLevel: LogLevel, consoleLogLevel: LogLevel) { } export function setLogSelfInfo(selfInfo: SelfInfo) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - logConfig.appenders.FileAppender.layout.pattern = logConfig.appenders.ConsoleAppender.layout.pattern = - `%d{yyyy-MM-dd hh:mm:ss} [%p] ${selfInfo.nick}(${selfInfo.uin}) %m`; - log4js.configure(logConfig); + const userInfo = `${selfInfo.nick}(${selfInfo.uin})`; + loggerConsole.addContext('userInfo', userInfo); + loggerFile.addContext('userInfo', userInfo); + loggerDefault.addContext('userInfo', userInfo); } +setLogSelfInfo({ nick: '', uin: '', uid: '' }); let fileLogEnabled = true; let consoleLogEnabled = true; @@ -86,7 +88,7 @@ export function enableConsoleLog(enable: boolean) { consoleLogEnabled = enable; } -function formatMsg(msg: any[]){ +function formatMsg(msg: any[]) { let logMsg = ''; for (const msgItem of msg) { // 判断是否是对象 @@ -97,15 +99,18 @@ function formatMsg(msg: any[]){ } logMsg += msgItem + ' '; } - return '\n' + logMsg + '\n'; + return logMsg; } -function _log(level: LogLevel, ...args: any[]){ - if (consoleLogEnabled){ - log4js.getLogger('console')[level](formatMsg(args)); +// eslint-disable-next-line no-control-regex +const colorEscape = /\x1B[@-_][0-?]*[ -/]*[@-~]/g; + +function _log(level: LogLevel, ...args: any[]) { + if (consoleLogEnabled) { + loggerConsole[level](formatMsg(args)); } - if (fileLogEnabled){ - log4js.getLogger('file')[level](formatMsg(args)); + if (fileLogEnabled) { + loggerFile[level](formatMsg(args).replace(colorEscape, '')); } } @@ -121,3 +126,11 @@ export function logDebug(...args: any[]) { export function logError(...args: any[]) { _log(LogLevel.ERROR, ...args); } + +export function logWarn(...args: any[]) { + _log(LogLevel.WARN, ...args); +} + +export function logFatal(...args: any[]) { + _log(LogLevel.FATAL, ...args); +} \ No newline at end of file diff --git a/src/common/utils/video.ts b/src/common/utils/video.ts index c2192d54..2316e955 100644 --- a/src/common/utils/video.ts +++ b/src/common/utils/video.ts @@ -24,7 +24,7 @@ export async function getVideoInfo(filePath: string) { } else { const videoStream = metadata.streams.find((s: { codec_type: string; }) => s.codec_type === 'video'); if (videoStream) { - console.log(`视频尺寸: ${videoStream.width}x${videoStream.height}`); + log(`视频尺寸: ${videoStream.width}x${videoStream.height}`); } else { return reject('未找到视频流信息。'); } diff --git a/src/index.ts b/src/index.ts index 42178cc1..95e6a767 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,18 +5,19 @@ import fs from 'fs/promises'; import fsSync from 'fs'; import path from 'node:path'; import { checkVersion } from '@/common/utils/version'; -import { log, logDebug, logError, LogLevel, setLogLevel } from '@/common/utils/log'; +import { log, logDebug, logError, LogLevel, logWarn, setLogLevel } from '@/common/utils/log'; import { NapCatOnebot11 } from '@/onebot11/main'; import { InitWebUi } from './webui/index'; import { WebUiDataRuntime } from './webui/src/helper/Data'; import { UpdateConfig } from './common/utils/helper'; import { dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; +import chalk from 'chalk'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); - +const tagColor = chalk.cyan; program .option('-q, --qq ', 'QQ号') .parse(process.argv); @@ -31,19 +32,19 @@ checkVersion().then(async (remoteVersion: string) => { const localVersion = JSON.parse(fsSync.readFileSync(path.join(__dirname, 'package.json')).toString()).version; const localVersionList = localVersion.split('.'); const remoteVersionList = remoteVersion.split('.'); - log('[NapCat] 当前版本:', localVersion); + log(tagColor('[NapCat]'), '当前版本:', localVersion); for (const k of [0, 1, 2]) { if (parseInt(remoteVersionList[k]) > parseInt(localVersionList[k])) { - console.log(`[NapCat] 检测到更新,请前往 https://github.com/NapNeko/NapCatQQ 下载 NapCatQQ V ${remoteVersion}`); + logWarn(tagColor('[NapCat]'), `检测到更新,请前往 https://github.com/NapNeko/NapCatQQ 下载 NapCatQQ V ${remoteVersion}`); return; } else if (parseInt(remoteVersionList[k]) < parseInt(localVersionList[k])) { break; } } - logDebug('[NapCat] 当前已是最新版本'); + logDebug(tagColor('[NapCat]'),'当前已是最新版本'); return; }).catch((e) => { - logError('[NapCat] 检测更新失败', e); + logError(tagColor('[NapCat]'),'检测更新失败', e); }); // 不是很好待优化 const NapCat_OneBot11 = new NapCatOnebot11(); @@ -51,18 +52,18 @@ const NapCat_OneBot11 = new NapCatOnebot11(); WebUiDataRuntime.setOB11ConfigCall(NapCat_OneBot11.SetConfig); napCatCore.onLoginSuccess((uin, uid) => { - console.log('登录成功!'); + log('登录成功!'); WebUiDataRuntime.setQQLoginStatus(true); WebUiDataRuntime.setQQLoginUin(uin.toString()); }); const showQRCode = async (url: string, base64: string, buffer: Buffer) => { await WebUiDataRuntime.setQQLoginQrcodeURL(url); - console.log('请扫描下面的二维码,然后在手Q上授权登录:'); + logWarn('请扫描下面的二维码,然后在手Q上授权登录:'); const qrcodePath = path.join(__dirname, 'qrcode.png'); qrcode.generate(url, { small: true }, (res) => { - console.log(`${res}\n二维码解码URL: ${url}\n如果控制台二维码无法扫码,可以复制解码url到二维码生成网站生成二维码再扫码,也可以打开下方的二维码路径图片进行扫码`); + logWarn(`\n${res}\n二维码解码URL: ${url}\n如果控制台二维码无法扫码,可以复制解码url到二维码生成网站生成二维码再扫码,也可以打开下方的二维码路径图片进行扫码`); fs.writeFile(qrcodePath, buffer).then(() => { - console.log('二维码已保存到', qrcodePath); + logWarn('二维码已保存到', qrcodePath); }); }); }; @@ -86,7 +87,7 @@ WebUiDataRuntime.setQQQuickLoginCall(async (uin: string) => { } resolve({ result: true, message: '' }); }).catch((e) => { - console.error(e); + logError(e); resolve({ result: false, message: '快速登录发生错误' }); }); } else { @@ -104,7 +105,7 @@ if (quickLoginQQ) { logError('快速登录错误:', res.loginErrorInfo.errMsg); } }).catch((e) => { - console.error(e); + logError(e); napCatCore.qrLogin(showQRCode); }); } else { diff --git a/src/onebot11/action/system/CleanCache.ts b/src/onebot11/action/system/CleanCache.ts index 0951539c..0613cfeb 100644 --- a/src/onebot11/action/system/CleanCache.ts +++ b/src/onebot11/action/system/CleanCache.ts @@ -9,6 +9,7 @@ import { } from '@/core/entities'; import { dbUtil } from '@/common/utils/db'; import { NTQQFileApi, NTQQFileCacheApi } from '@/core/apis/file'; +import { logError } from '@/common/utils/log'; export default class CleanCache extends BaseAction { actionName = ActionName.CleanCache; @@ -62,7 +63,7 @@ export default class CleanCache extends BaseAction { await NTQQFileCacheApi.clearChatCache(chatCacheList, cacheFileList); res(); } catch(e) { - console.error('清理缓存时发生了错误'); + logError('清理缓存时发生了错误'); rej(e); } }); diff --git a/src/onebot11/log.ts b/src/onebot11/log.ts index 1c313ed7..e1e8f3e3 100644 --- a/src/onebot11/log.ts +++ b/src/onebot11/log.ts @@ -3,19 +3,23 @@ import { log } from '@/common/utils/log'; import { getGroup, getGroupMember, selfInfo } from '@/core/data'; import exp from 'constants'; import { Group } from '@/core'; +import chalk from 'chalk'; + +const spSegColor = chalk.blue;// for special segment +const spColor = chalk.cyan;// for special // todo: 应该放到core去用RawMessage解析打印 -export async function logMessage(ob11Message: OB11Message){ +export async function logMessage(ob11Message: OB11Message) { const isSelfSent = ob11Message.sender.user_id.toString() === selfInfo.uin; let prefix = ''; let group: Group | undefined; - if (isSelfSent){ + if (isSelfSent) { prefix = '发送消息 '; - if (ob11Message.message_type === 'private'){ + if (ob11Message.message_type === 'private') { prefix += '给私聊 '; prefix += `${ob11Message.target_id}`; } - else{ + else { prefix += '给群聊 '; } } @@ -25,57 +29,60 @@ export async function logMessage(ob11Message: OB11Message){ } let msgChain = ''; if (Array.isArray(ob11Message.message)) { + const msgParts = []; for (const segment of ob11Message.message) { if (segment.type === 'text') { - msgChain += segment.data.text + '\n'; + msgParts.push(segment.data.text); } else if (segment.type === 'at') { const groupMember = await getGroupMember(ob11Message.group_id!, segment.data.qq!); - msgChain += `@${groupMember?.cardName || groupMember?.nick}(${segment.data.qq}) `; + msgParts.push(spSegColor(`[@${groupMember?.cardName || groupMember?.nick}(${segment.data.qq})]`)); } else if (segment.type === 'reply') { - msgChain += `回复消息(id:${segment.data.id}) `; + msgParts.push(spSegColor(`[回复消息|id:${segment.data.id}]`)); } else if (segment.type === 'image') { - msgChain += `\n[图片]${segment.data.url}\n`; + msgParts.push(spSegColor(`[图片|${segment.data.url}]`)); } - else if (segment.type === 'face'){ - msgChain += `[表情](id:${segment.data.id}) `; + else if (segment.type === 'face') { + msgParts.push(spSegColor(`[表情|id:${segment.data.id}]`)); } - else if (segment.type === 'mface'){ - msgChain += `\n[商城表情]${segment.data.url}\n`; + else if (segment.type === 'mface') { + // @ts-expect-error 商城表情 url + msgParts.push(spSegColor(`[商城表情|${segment.data.url}]`)); } else if (segment.type === 'record') { - msgChain += `[语音]${segment.data.file} `; + msgParts.push(spSegColor(`[语音|${segment.data.file}]`)); } else if (segment.type === 'file') { - msgChain += `[文件]${segment.data.file} `; + msgParts.push(spSegColor(`[文件|${segment.data.file}]`)); } else if (segment.type === 'json') { - msgChain += `\n[json]${JSON.stringify(segment.data)}\n`; + msgParts.push(spSegColor(`[json|${JSON.stringify(segment.data)}]`)); } else if (segment.type === 'markdown') { - msgChain += `\n[json]${segment.data.content}\n`; + msgParts.push(spSegColor(`[markdown|${segment.data.content}]`)); } else { - msgChain += `${JSON.stringify(segment)}`; + msgParts.push(spSegColor(`[未实现|${JSON.stringify(segment)}]`)); } } + msgChain = msgParts.join(' '); } else { msgChain = ob11Message.message; } let msgString = `${prefix}${ob11Message.sender.nickname}(${ob11Message.sender.user_id}): ${msgChain}`; - if (isSelfSent){ + if (isSelfSent) { msgString = `${prefix}: ${msgChain}`; } log(msgString); } -export async function logNotice(ob11Notice: any){ - log('[notice]', ob11Notice); +export async function logNotice(ob11Notice: any) { + log(spColor('[Notice]'), ob11Notice); } -export async function logRequest(ob11Request: any){ - log('[request]', ob11Request); +export async function logRequest(ob11Request: any) { + log(spColor('[Request]'), ob11Request); } diff --git a/src/onebot11/main.ts b/src/onebot11/main.ts index 41a84774..4b14004f 100644 --- a/src/onebot11/main.ts +++ b/src/onebot11/main.ts @@ -112,7 +112,7 @@ export class NapCatOnebot11 { try { // 生产环境会自己去掉 if (import.meta.env.MODE == 'development') { - console.log(buf2hex(Buffer.from(protobufData))); + logDebug(buf2hex(Buffer.from(protobufData))); } const sysMsg = SysData.fromBinary(Buffer.from(protobufData)); const peeruin = sysMsg.header[0].peerNumber; @@ -258,7 +258,7 @@ export class NapCatOnebot11 { for (const member of members.values()) { //console.log(member?.isDelete, role, isPrivilege); if (member?.isDelete && !isPrivilege /*&& selfInfo.uin !== member.uin*/) { - console.log('[群聊] 群组 ', groupCode, ' 成员' + member.uin + '退出'); + log('[群聊] 群组 ', groupCode, ' 成员' + member.uin + '退出'); const groupDecreaseEvent = new OB11GroupDecreaseEvent(parseInt(groupCode), parseInt(member.uin), 0, 'leave');// 不知道怎么出去的 postOB11Event(groupDecreaseEvent, true); } diff --git a/src/webui/index.ts b/src/webui/index.ts index 00154f7a..57539704 100644 --- a/src/webui/index.ts +++ b/src/webui/index.ts @@ -7,6 +7,7 @@ import { WebUiConfig } from './src/helper/config'; const app = express(); import { dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; +import { log } from '@/common/utils/log'; const __filename = fileURLToPath(import.meta.url); @@ -32,8 +33,8 @@ export async function InitWebUi() { //挂载API接口 app.use('/api', ALLRouter); app.listen(config.port, async () => { - console.log(`[NapCat] [WebUi] Current WebUi is running at IP:${config.port}`); - console.log(`[NapCat] [WebUi] Login Token is ${config.token}`); + log(`[NapCat] [WebUi] Current WebUi is running at IP:${config.port}`); + log(`[NapCat] [WebUi] Login Token is ${config.token}`); }); } \ No newline at end of file diff --git a/src/webui/src/helper/config.ts b/src/webui/src/helper/config.ts index 58e1df7c..41e6df34 100644 --- a/src/webui/src/helper/config.ts +++ b/src/webui/src/helper/config.ts @@ -3,6 +3,7 @@ import { resolve } from 'node:path'; import * as net from 'node:net'; import { dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; +import { logError } from '@/common/utils/log'; const __filename = fileURLToPath(import.meta.url); @@ -78,7 +79,7 @@ class WebUiConfigWrapper { this.WebUiConfigData = parsedConfig; return this.WebUiConfigData; } catch (e) { - console.error('读取配置文件失败', e); + logError('读取配置文件失败', e); } return {} as WebUiConfigType; // 理论上这行代码到不了,为了保持函数完整性而保留 }