mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
Compare commits
5 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ce8760a39a | ||
![]() |
ff952956de | ||
![]() |
28f3ff4971 | ||
![]() |
19e728c3cb | ||
![]() |
269773ed6b |
@@ -4,7 +4,7 @@
|
||||
"name": "NapCatQQ",
|
||||
"slug": "NapCat.Framework",
|
||||
"description": "高性能的 OneBot 11 协议实现",
|
||||
"version": "3.3.25",
|
||||
"version": "3.3.27",
|
||||
"icon": "./logo.png",
|
||||
"authors": [
|
||||
{
|
||||
|
@@ -2,7 +2,7 @@
|
||||
"name": "napcat",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"version": "3.3.25",
|
||||
"version": "3.3.27",
|
||||
"scripts": {
|
||||
"build:framework": "vite build --mode framework",
|
||||
"build:shell": "vite build --mode shell",
|
||||
|
@@ -245,3 +245,36 @@ export function stringifyWithBigInt(obj: any) {
|
||||
typeof value === 'bigint' ? value.toString() : value
|
||||
);
|
||||
}
|
||||
|
||||
export function parseAppidFromMajor(nodeMajor: string): string | undefined {
|
||||
const hexSequence = "A4 09 00 00 00 35";
|
||||
const sequenceBytes = Buffer.from(hexSequence.replace(/ /g, ""), "hex");
|
||||
const filePath = path.resolve(nodeMajor);
|
||||
const fileContent = fs.readFileSync(filePath);
|
||||
|
||||
let searchPosition = 0;
|
||||
while (true) {
|
||||
const index = fileContent.indexOf(sequenceBytes, searchPosition);
|
||||
if (index === -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
const start = index + sequenceBytes.length - 1;
|
||||
const end = fileContent.indexOf(0x00, start);
|
||||
if (end === -1) {
|
||||
break;
|
||||
}
|
||||
const content = fileContent.subarray(start, end);
|
||||
if (!content.every(byte => byte === 0x00)) {
|
||||
try {
|
||||
return content.toString("utf-8");
|
||||
} catch (error) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
searchPosition = end + 1;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
@@ -1,8 +1,9 @@
|
||||
import fs from 'node:fs';
|
||||
import { systemPlatform } from '@/common/system';
|
||||
import { getDefaultQQVersionConfigInfo, getQQPackageInfoPath, getQQVersionConfigPath } from './helper';
|
||||
import { getDefaultQQVersionConfigInfo, getQQPackageInfoPath, getQQVersionConfigPath, parseAppidFromMajor } from './helper';
|
||||
import AppidTable from '@/core/external/appid.json';
|
||||
import { LogWrapper } from './log';
|
||||
import { getMajorPath } from '@/core';
|
||||
|
||||
export class QQBasicInfoWrapper {
|
||||
QQMainPath: string | undefined;
|
||||
@@ -72,6 +73,7 @@ export class QQBasicInfoWrapper {
|
||||
}
|
||||
|
||||
getAppidV2(): { appid: string; qua: string } {
|
||||
// 通过已有表 性能好
|
||||
const appidTbale = AppidTable as unknown as QQAppidTableType;
|
||||
const fullVersion = this.getFullQQVesion();
|
||||
if (fullVersion) {
|
||||
@@ -80,10 +82,22 @@ export class QQBasicInfoWrapper {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
// else
|
||||
// 通过Major拉取 性能差
|
||||
try {
|
||||
let majorAppid = this.getAppidV2ByMajor(fullVersion);
|
||||
if (majorAppid) { return { appid: majorAppid, qua: this.getQUAFallback() }; }
|
||||
} catch (error) {
|
||||
this.context.logger.log(`[QQ版本兼容性检测] 通过Major 获取Appid异常 请检测NapCat/QQNT是否正常`);
|
||||
}
|
||||
// 最终兜底为老版本
|
||||
this.context.logger.log(`[QQ版本兼容性检测] 获取Appid异常 请检测NapCat/QQNT是否正常`);
|
||||
this.context.logger.log(`[QQ版本兼容性检测] ${fullVersion} 版本兼容性不佳,可能会导致一些功能无法正常使用`,);
|
||||
return { appid: this.getAppIdFallback(), qua: this.getQUAFallback() };
|
||||
}
|
||||
getAppidV2ByMajor(QQVersion: string) {
|
||||
let majorPath = getMajorPath(QQVersion);
|
||||
let appid = parseAppidFromMajor(majorPath);
|
||||
return appid;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1 +1 @@
|
||||
export const napCatVersion = '3.3.25';
|
||||
export const napCatVersion = '3.3.27';
|
||||
|
@@ -3,6 +3,9 @@ import { InstanceContext, NapCatCore } from '@/core';
|
||||
import { GeneralCallResult } from '@/core/services/common';
|
||||
|
||||
export class NTQQMsgApi {
|
||||
getMsgByClientSeqAndTime(peer: Peer, replyMsgClientSeq: string, replyMsgTime: string) {
|
||||
return this.context.session.getMsgService().getMsgByClientSeqAndTime(peer, replyMsgClientSeq, replyMsgTime);
|
||||
}
|
||||
// nt_qq//global//nt_data//Emoji//emoji-resource//sysface_res/apng/ 下可以看到所有QQ表情预览
|
||||
// nt_qq\global\nt_data\Emoji\emoji-resource\face_config.json 里面有所有表情的id, 自带表情id是QSid, 标准emoji表情id是QCid
|
||||
// 其实以官方文档为准是最好的,https://bot.q.qq.com/wiki/develop/api-v2/openapi/emoji/model.html#EmojiType
|
||||
@@ -22,7 +25,9 @@ export class NTQQMsgApi {
|
||||
async sendShowInputStatusReq(peer: Peer, eventType: number) {
|
||||
return this.context.session.getMsgService().sendShowInputStatusReq(peer.chatType, eventType, peer.peerUid);
|
||||
}
|
||||
|
||||
async getSourceOfReplyMsgV2(peer: Peer, clientSeq: string, time: string) {
|
||||
return this.context.session.getMsgService().getSourceOfReplyMsgV2(peer, clientSeq, time);
|
||||
}
|
||||
async getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, count: number = 20) {
|
||||
//注意此处emojiType 可选值一般为1-2 2好像是unicode表情dec值 大部分情况 Taged Mlikiowa
|
||||
return this.context.session.getMsgService().getMsgEmojiLikesList(peer, msgSeq, emojiId, emojiType, '', false, count);
|
||||
@@ -106,9 +111,9 @@ export class NTQQMsgApi {
|
||||
pageLimit: 1,
|
||||
});
|
||||
}
|
||||
//@deprecated
|
||||
async getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, z: boolean) {
|
||||
return await this.context.session.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, z);
|
||||
// 客户端还在用别慌
|
||||
async getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, isReverseOrder: boolean) {
|
||||
return await this.context.session.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, isReverseOrder);
|
||||
}
|
||||
async getMsgExBySeq(peer: Peer, msgSeq: string) {
|
||||
const DateNow = Math.floor(Date.now() / 1000);
|
||||
|
@@ -62,7 +62,26 @@ export function loadQQWrapper(QQVersion: string): WrapperNodeApi {
|
||||
process.dlopen(nativemodule, wrapperNodePath);
|
||||
return nativemodule.exports;
|
||||
}
|
||||
|
||||
export function getMajorPath(QQVersion: string): string {
|
||||
// major.node
|
||||
let appPath;
|
||||
if (os.platform() === 'darwin') {
|
||||
appPath = path.resolve(path.dirname(process.execPath), '../Resources/app');
|
||||
} else if (os.platform() === 'linux') {
|
||||
appPath = path.resolve(path.dirname(process.execPath), './resources/app');
|
||||
} else {
|
||||
appPath = path.resolve(path.dirname(process.execPath), `./versions/${QQVersion}/`);
|
||||
}
|
||||
let majorPath = path.resolve(appPath, 'major.node');
|
||||
if (!fs.existsSync(majorPath)) {
|
||||
majorPath = path.join(appPath, `./resources/app/major.node`);
|
||||
}
|
||||
//老版本兼容 未来去掉
|
||||
if (!fs.existsSync(majorPath)) {
|
||||
majorPath = path.join(path.dirname(process.execPath), `./resources/app/versions/${QQVersion}/major.node`);
|
||||
}
|
||||
return majorPath;
|
||||
}
|
||||
export class NapCatCore {
|
||||
readonly context: InstanceContext;
|
||||
readonly apis: StableNTApiWrapper;
|
||||
|
@@ -172,7 +172,7 @@ export interface NodeIKernelMsgService {
|
||||
msgList: RawMessage[]
|
||||
}>;
|
||||
//@deprecated
|
||||
getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, unknownArg: boolean): Promise<GeneralCallResult & {
|
||||
getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, isReverseOrder: boolean): Promise<GeneralCallResult & {
|
||||
msgList: RawMessage[]
|
||||
}>;
|
||||
|
||||
@@ -186,27 +186,29 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
getSingleMsg(Peer: Peer, msgSeq: string): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
|
||||
|
||||
getSourceOfReplyMsg(peer: Peer, MsgId: string, SourceSeq: string): unknown;
|
||||
// 下面的msgid全部不真实
|
||||
getSourceOfReplyMsg(peer: Peer, msgId: string, sourceSeq: string): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
|
||||
|
||||
getSourceOfReplyMsgV2(peer: Peer, RootMsgId: string, ReplyMsgId: string): unknown;
|
||||
//用法和聊天记录一样
|
||||
getSourceOfReplyMsgV2(peer: Peer, rootMsgId: string, replyMsgId: string): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
|
||||
|
||||
getMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string): unknown;
|
||||
getMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
|
||||
|
||||
getSourceOfReplyMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string): unknown;
|
||||
getSourceOfReplyMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string, replyMsgId: string): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
|
||||
|
||||
getMsgsByTypeFilter(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilter: {
|
||||
type: number,
|
||||
subtype: Array<number>
|
||||
}): unknown;
|
||||
}): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
|
||||
|
||||
getMsgsByTypeFilters(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilters: Array<{
|
||||
type: number,
|
||||
subtype: Array<number>
|
||||
}>): unknown;
|
||||
}>): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
|
||||
|
||||
getMsgWithAbstractByFilterParam(...args: unknown[]): unknown;
|
||||
getMsgWithAbstractByFilterParam(...args: unknown[]): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
|
||||
|
||||
queryMsgsWithFilter(...args: unknown[]): unknown;
|
||||
queryMsgsWithFilter(...args: unknown[]): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
|
||||
|
||||
//queryMsgsWithFilterVer2(MsgId: string, MsgTime: string, param: QueryMsgsParams): Promise<unknown>;
|
||||
|
||||
|
@@ -5,9 +5,11 @@ import { promises as fs } from 'fs';
|
||||
import { decode } from 'silk-wasm';
|
||||
const FFMPEG_PATH = process.env.FFMPEG_PATH || 'ffmpeg';
|
||||
|
||||
interface Payload extends GetFilePayload {
|
||||
out_format: 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac';
|
||||
}
|
||||
const out_format = ['mp3' , 'amr' , 'wma' , 'm4a' , 'spx' , 'ogg' , 'wav' , 'flac'];
|
||||
|
||||
type Payload = {
|
||||
out_format : string
|
||||
} & GetFilePayload
|
||||
|
||||
export default class GetRecord extends GetFileBase {
|
||||
actionName = ActionName.GetRecord;
|
||||
@@ -17,12 +19,19 @@ export default class GetRecord extends GetFileBase {
|
||||
if (payload.out_format && typeof payload.out_format === 'string') {
|
||||
const inputFile = res.file;
|
||||
if (!inputFile) throw new Error('file not found');
|
||||
if (!out_format.includes(payload.out_format)) {
|
||||
throw new Error('转换失败 out_format 字段可能格式不正确');
|
||||
}
|
||||
const pcmFile = `${inputFile}.pcm`;
|
||||
const outputFile = `${inputFile}.${payload.out_format}`;
|
||||
try {
|
||||
await fs.access(inputFile);
|
||||
await this.decodeFile(inputFile, pcmFile);
|
||||
await this.convertFile(pcmFile, outputFile, payload.out_format);
|
||||
try {
|
||||
await fs.access(outputFile);
|
||||
} catch (error) {
|
||||
await this.decodeFile(inputFile, pcmFile);
|
||||
await this.convertFile(pcmFile, outputFile, payload.out_format);
|
||||
}
|
||||
const base64Data = await fs.readFile(outputFile, { encoding: 'base64' });
|
||||
res.file = outputFile;
|
||||
res.url = outputFile;
|
||||
@@ -48,7 +57,8 @@ export default class GetRecord extends GetFileBase {
|
||||
|
||||
private convertFile(inputFile: string, outputFile: string, format: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const ffmpeg = spawn(FFMPEG_PATH, ['-f', 's16le', '-ar', '24000', '-ac', '1', '-i', inputFile, outputFile]);
|
||||
const params = format === 'amr' ? ['-f', 's16le', '-ar', '24000', '-ac', '1', '-i', inputFile, '-ar', '8000', '-b:a', '12.2k', outputFile] : ['-f', 's16le', '-ar', '24000', '-ac', '1', '-i', inputFile, outputFile];
|
||||
const ffmpeg = spawn(FFMPEG_PATH, params);
|
||||
|
||||
ffmpeg.on('close', (code) => {
|
||||
if (code === 0) {
|
||||
@@ -63,4 +73,4 @@ export default class GetRecord extends GetFileBase {
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -217,20 +217,34 @@ export class OneBotMsgApi {
|
||||
if (records.peerUin === '284840486' || records.peerUin === '1094950020') {
|
||||
return createReplyData(records.msgId);
|
||||
}
|
||||
let replyMsgList = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeqV2(peer, element.replayMsgSeq, element.replyMsgTime, [element.senderUidStr])).msgList;
|
||||
let replyMsgList = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeqV2(peer, element.replayMsgSeq, records.msgTime, [element.senderUidStr])).msgList;
|
||||
let replyMsg = replyMsgList.find(msg => msg.msgRandom === records.msgRandom);
|
||||
|
||||
if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) {
|
||||
// 我猜测可能是时间参数未对上 导致找不到引用消息 或者msgList 存在问题
|
||||
this.core.context.logger.logWarn.bind(this.core.context.logger)(
|
||||
'初次筛选消息失败,获取不到引用的消息 Seq:',
|
||||
this.core.context.logger.logError.bind(this.core.context.logger)(
|
||||
'筛选结果,筛选消息失败,将使用Fallback-1 Seq: ',
|
||||
element.replayMsgSeq,
|
||||
',消息长度:',
|
||||
replyMsgList.length
|
||||
);
|
||||
replyMsgList = (await this.core.apis.MsgApi.getMsgsBySeqAndCount(peer, element.replayMsgSeq, 1, true, true)).msgList;
|
||||
replyMsg = replyMsgList.find(msg => msg.msgRandom === records.msgRandom);
|
||||
}
|
||||
|
||||
if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) {
|
||||
this.core.context.logger.logWarn.bind(this.core.context.logger)(
|
||||
'筛选消息失败,将使用Fallback-2 Seq:',
|
||||
element.replayMsgSeq,
|
||||
',消息长度:',
|
||||
replyMsgList.length
|
||||
);
|
||||
// 再次筛选
|
||||
replyMsgList = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeqV3(peer, element.replayMsgSeq, [element.senderUidStr])).msgList;
|
||||
replyMsg = replyMsgList.find(msg => msg.msgRandom === records.msgRandom);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 丢弃该消息段
|
||||
if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) {
|
||||
this.core.context.logger.logError.bind(this.core.context.logger)(
|
||||
'最终筛选结果,筛选消息失败,获取不到引用的消息 Seq: ',
|
||||
|
@@ -164,7 +164,7 @@ async function onSettingWindowCreated(view) {
|
||||
SettingItem(
|
||||
'<span id="napcat-update-title">Napcat</span>',
|
||||
void 0,
|
||||
SettingButton("V3.3.25", "napcat-update-button", "secondary")
|
||||
SettingButton("V3.3.27", "napcat-update-button", "secondary")
|
||||
)
|
||||
]),
|
||||
SettingList([
|
||||
|
Reference in New Issue
Block a user