Compare commits

..

36 Commits

Author SHA1 Message Date
手瓜一十雪
c3dc53eaaf release: v3.3.12 2024-10-27 22:14:17 +08:00
手瓜一十雪
ffdc34cfe2 Merge pull request #470 from pohgxz/main
修复<get_group_at_all_remain>接口总是返回<Error: atInfo not found>
2024-10-27 22:10:55 +08:00
手瓜一十雪
4825a0e341 fix: type Error 2024-10-27 22:07:11 +08:00
Nepenthe
95a00d7f35 修复<get_group_at_all_remain>接口总是返回<Error: atInfo not found> 2024-10-27 22:04:48 +08:00
手瓜一十雪
d885bab426 feat: 类型修复 2024-10-27 22:03:22 +08:00
手瓜一十雪
e2a6a0bc02 release: v3.2.12 2024-10-27 20:56:27 +08:00
手瓜一十雪
ff7d8609ce fix: #452 修复seq搜索的老问题 可能修好了 2024-10-27 20:52:09 +08:00
手瓜一十雪
7507b90e03 fix: #458 2024-10-27 20:38:02 +08:00
手瓜一十雪
2b226a4b27 release: v3.1.11 2024-10-27 19:29:42 +08:00
手瓜一十雪
8b0232c4fe fix: error 2024-10-27 11:17:01 +08:00
手瓜一十雪
0728ee9ad6 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-10-27 11:09:00 +08:00
手瓜一十雪
8c6f04d0bc feat: #469 回收连接(未测试) 2024-10-27 11:08:48 +08:00
pk5ls20
c67fad789e fix: compatibility bigint 2024-10-27 10:50:48 +08:00
手瓜一十雪
4072339d70 release: v3.1.10 2024-10-27 10:03:07 +08:00
手瓜一十雪
3a244f5804 style: lint 2024-10-27 10:02:42 +08:00
pk5ls20
f12cf59137 feat: enhance compatibility of upload_forward_msg with go-cqhttp 2024-10-27 09:59:38 +08:00
手瓜一十雪
c76f556a11 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-10-27 09:44:26 +08:00
手瓜一十雪
e0f3d07b98 release: 3.1.9 2024-10-27 09:44:14 +08:00
手瓜一十雪
378d85dc67 Merge pull request #468 from NapNeko/refactor/msg-element
refactor: core msg entity & packet msg converter & resolve #455
2024-10-27 09:40:41 +08:00
pk5ls20
875e91fc0e chore: simplify logic 2024-10-27 09:37:17 +08:00
pk5ls20
15f7cd9814 feat: better fake forwardMsg logic & display 2024-10-27 09:33:20 +08:00
pk5ls20
1eb5cd6237 fix: downloadRawMsgMedia edge case 2024-10-27 09:04:24 +08:00
pk5ls20
ad2f843c8f fix: downloadRawMsgMedia 2024-10-27 07:31:32 +08:00
pk5ls20
8e550e216e chore: i18n for packet log messages 2024-10-27 07:04:53 +08:00
pk5ls20
9f07b07c82 feat: support node id in fake forward (with auto download richMedia in reference message) 2024-10-27 06:50:59 +08:00
pk5ls20
0be6effc32 feat: support node id in fake forward (broken impl) 2024-10-27 05:19:53 +08:00
pk5ls20
7ab6a10fc9 refactor & fix: refactor msg entity & adjust some wrong definition 2024-10-27 04:16:15 +08:00
手瓜一十雪
fb09af0e64 release: v3.1.8 2024-10-26 21:25:03 +08:00
手瓜一十雪
0d99d30b2d feat: version hint 2024-10-26 21:24:24 +08:00
手瓜一十雪
0000ec8b5b fix: fetchFavEmojiList 2024-10-26 21:15:11 +08:00
手瓜一十雪
0085bd8a1f fix: q-gate 2024-10-26 20:46:38 +08:00
手瓜一十雪
617139dfa4 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-10-26 20:36:59 +08:00
手瓜一十雪
4eb4a612d0 fix: type 2024-10-26 20:25:34 +08:00
pk5ls20
cda5e784f6 fix: payload basic check in GetPacketStatusDepends 2024-10-26 19:51:43 +08:00
手瓜一十雪
d93a280ab3 fix: 进一步getNextMemberList 2024-10-26 18:40:21 +08:00
手瓜一十雪
f7e2b3a4a7 feat: new function 2024-10-26 18:10:08 +08:00
22 changed files with 313 additions and 300 deletions

View File

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

View File

@@ -2,7 +2,7 @@
"name": "napcat",
"private": true,
"type": "module",
"version": "3.1.7",
"version": "3.3.12",
"scripts": {
"build:framework": "vite build --mode framework",
"build:shell": "vite build --mode shell",

View File

@@ -56,7 +56,7 @@ export class ForwardMsgBuilder {
if (!source) {
source = isGroupMsg ? "群聊的聊天记录" :
msg.length
? Array.from(new Set(msg.map(m => m.senderName)))
? Array.from(new Set(msg.slice(0, 4).map(m => m.senderName)))
.join('和') + '的聊天记录'
: '聊天记录';
}

View File

@@ -239,3 +239,9 @@ export function calcQQLevel(level?: QQLevel) {
const { crownNum, sunNum, moonNum, starNum } = level;
return crownNum * 64 + sunNum * 16 + moonNum * 4 + starNum;
}
export function stringifyWithBigInt(obj: any) {
return JSON.stringify(obj, (key, value) =>
typeof value === 'bigint' ? value.toString() : value
);
}

View File

@@ -1 +1 @@
export const napCatVersion = '3.1.7';
export const napCatVersion = '3.3.12';

View File

@@ -6,6 +6,7 @@ import {
Peer,
PicElement,
PicType,
RawMessage,
SendFileElement,
SendPicElement,
SendPttElement,
@@ -238,7 +239,7 @@ export class NTQQFileApi {
fileName: fileName,
filePath: path,
md5HexStr: md5,
fileSize: fileSize,
fileSize: fileSize.toString(),
duration: duration ?? 1,
formatType: 1,
voiceType: 1,
@@ -267,6 +268,53 @@ export class NTQQFileApi {
return fileTransNotifyInfo.filePath;
}
async downloadRawMsgMedia(msg: RawMessage[]) {
const res = await Promise.all(
msg.map(m =>
Promise.all(
m.elements
.filter(element =>
element.elementType === ElementType.PIC ||
element.elementType === ElementType.VIDEO ||
element.elementType === ElementType.PTT ||
element.elementType === ElementType.FILE
)
.map(element =>
this.downloadMedia(m.msgId, m.chatType, m.peerUid, element.elementId, '', '', 1000 * 60 * 2, true)
)
)
)
);
msg.forEach((m, msgIndex) => {
const elementResults = res[msgIndex];
let elementIndex = 0;
m.elements.forEach(element => {
if (
element.elementType === ElementType.PIC ||
element.elementType === ElementType.VIDEO ||
element.elementType === ElementType.PTT ||
element.elementType === ElementType.FILE
) {
switch (element.elementType) {
case ElementType.PIC:
element.picElement!.sourcePath = elementResults[elementIndex];
break;
case ElementType.VIDEO:
element.videoElement!.filePath = elementResults[elementIndex];
break;
case ElementType.PTT:
element.pttElement!.filePath = elementResults[elementIndex];
break;
case ElementType.FILE:
element.fileElement!.filePath = elementResults[elementIndex];
break;
}
elementIndex++;
}
});
});
}
async downloadMedia(msgId: string, chatType: ChatType, peerUid: string, elementId: string, thumbPath: string, sourcePath: string, timeout = 1000 * 60 * 2, force: boolean = false) {
// 用于下载收到的消息中的图片等
if (sourcePath && fs.existsSync(sourcePath)) {
@@ -296,7 +344,7 @@ export class NTQQFileApi {
filePath: thumbPath,
}],
() => true,
(arg) => arg.msgId === msgId,
(arg) => arg.msgElementId === elementId && arg.msgId === msgId,
1,
timeout,
);

View File

@@ -82,6 +82,18 @@ export class NTQQMsgApi {
pageLimit: 1,
});
}
async queryMsgsWithFilterExWithSeqV3(peer: Peer, msgSeq: string, SendersUid: string[]) {
return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, {
chatInfo: peer,
filterMsgType: [],
filterSendersUid: SendersUid,
filterMsgToTime: '0',
filterMsgFromTime: '0',
isReverseOrder: false,
isIncludeCurrent: true,
pageLimit: 1,
});
}
async queryFirstMsgBySeq(peer: Peer, msgSeq: string) {
return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, {
chatInfo: peer,

View File

@@ -49,7 +49,7 @@ export class NTQQPacketApi {
.then()
.catch(this.core.context.logger.logError.bind(this.core.context.logger));
} else {
this.core.context.logger.logWarn('PacketServer is not set, will not init NapCat.Packet!');
this.core.context.logger.logWarn('PacketServer未配置,NapCat.Packet将不会加载!');
}
}
@@ -62,7 +62,10 @@ export class NTQQPacketApi {
this.qqVersion = qqversion;
const offsetTable: OffsetType = offset;
const table = offsetTable[qqversion + '-' + os.arch()];
if (!table) return false;
if (!table) {
this.logger.logError('PacketServer Offset table not found for QQVersion: ', qqversion + '-' + os.arch());
return false;
}
const url = 'ws://' + this.serverUrl + '/ws';
this.packetSession = new PacketSession(this.core.context.logger, new PacketClient(url, this.core));
const cb = () => {
@@ -103,7 +106,7 @@ export class NTQQPacketApi {
let status = 0;
try {
const packet = this.packetSession?.packer.packStatusPacket(uin);
const ret = await this.sendOidbPacket( packet!, true);
const ret = await this.sendOidbPacket(packet!, true);
const data = Buffer.from(ret.hex_data, 'hex');
const ext = new NapProtoMsg(OidbSvcTrpcTcp0XFE1_2RSP).decode(new NapProtoMsg(OidbSvcTrpcTcpBase).decode(data).body).data.status.value;
// ext & 0xff00 + ext >> 16 & 0xff
@@ -146,7 +149,7 @@ export class NTQQPacketApi {
peerUid: groupUin ? String(groupUin) : this.core.selfInfo.uid
}, e));
}
if (e instanceof PacketMsgFileElement){
if (e instanceof PacketMsgFileElement) {
reqList.push(this.packetSession?.highwaySession.uploadFile({
chatType: groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C,
peerUid: groupUin ? String(groupUin) : this.core.selfInfo.uid
@@ -155,10 +158,10 @@ export class NTQQPacketApi {
}
}
const res = await Promise.allSettled(reqList);
this.logger.log(`上传资源${res.length}个, 失败${res.filter(r => r.status === 'rejected').length}`);
this.logger.log(`上传资源${res.length}个,失败${res.filter(r => r.status === 'rejected').length}`);
res.forEach((result, index) => {
if (result.status === 'rejected') {
this.logger.logError(`${index + 1}个失败:${result.reason}`);
this.logger.logError(`上传${index + 1}资源失败:${result.reason}`);
}
});
}

View File

@@ -27,94 +27,70 @@ export interface GetFileListParam {
export enum ElementType {
UNKNOWN = 0,
TEXT = 1,
PIC = 2,
FILE = 3,
PTT = 4,
VIDEO = 5,
FACE = 6,
REPLY = 7,
GreyTip = 8, // “小灰条”,包括拍一拍 (Poke)、撤回提示等
WALLET = 9,
/**
* “小灰条”,包括拍一拍 (Poke)、撤回提示等
*/
GreyTip = 8,
ARK = 10,
MFACE = 11,
LIVEGIFT = 12,
STRUCTLONGMSG = 13,
MARKDOWN = 14,
GIPHY = 15,
MULTIFORWARD = 16,
INLINEKEYBOARD = 17,
INTEXTGIFT = 18,
CALENDAR = 19,
YOLOGAMERESULT = 20,
AVRECORD = 21,
FEED = 22,
TOFURECORD = 23,
ACEBUBBLE = 24,
ACTIVITY = 25,
TOFU = 26,
FACEBUBBLE = 27,
SHARELOCATION = 28,
TASKTOPMSG = 29,
RECOMMENDEDMSG = 43,
ACTIONBAR = 44
}
type ElementFullBase = Omit<MessageElement, 'elementType' | 'elementId' | 'extBufForUI'>;
type ElementBase<
K extends keyof ElementFullBase,
S extends Partial<{ [P in K]: keyof NonNullable<ElementFullBase[P]> | Array<keyof NonNullable<ElementFullBase[P]>> }> = {}
> = {
[P in K]:
S[P] extends Array<infer U>
? Pick<NonNullable<ElementFullBase[P]>, U & keyof NonNullable<ElementFullBase[P]>>
: S[P] extends keyof NonNullable<ElementFullBase[P]>
? Pick<NonNullable<ElementFullBase[P]>, S[P]>
: NonNullable<ElementFullBase[P]>;
};
export interface SendElementBase<ET extends ElementType> {
elementType: ET;
elementId: string;
extBufForUI?: string;
}
export interface ActionBarElement {
rows: InlineKeyboardRow[];
botAppid: string;
}
export interface SendActionBarElement {
elementType: ElementType.ACTIONBAR;
elementId: string;
actionBarElement: ActionBarElement;
}
export interface RecommendedMsgElement {
rows: InlineKeyboardRow[];
botAppid: string;
}
export interface SendRecommendedMsgElement {
elementType: ElementType.RECOMMENDEDMSG;
elementId: string;
recommendedMsgElement: RecommendedMsgElement;
}
export type SendRecommendedMsgElement = SendElementBase<ElementType.RECOMMENDEDMSG> & ElementBase<'recommendedMsgElement'>;
export interface InlineKeyboardButton {
id: string;
@@ -171,11 +147,7 @@ export enum NTMsgType {
KMSGTYPEWALLET = 10
}
export interface SendTaskTopMsgElement {
elementType: ElementType.TASKTOPMSG;
elementId: string;
taskTopMsgElement: TaskTopMsgElement;
}
export type SendTaskTopMsgElement = SendElementBase<ElementType.TASKTOPMSG> & ElementBase<'taskTopMsgElement'>;
export interface TofuRecordElement {
type: number;
@@ -194,11 +166,7 @@ export interface TofuRecordElement {
onscreennotify: boolean;
}
export interface SendTofuRecordElement {
elementType: ElementType.TOFURECORD;
elementId: string;
tofuRecordElement: TofuRecordElement;
}
export type SendTofuRecordElement = SendElementBase<ElementType.TOFURECORD> & ElementBase<'tofuRecordElement'>;
export interface FaceBubbleElement {
faceCount: number;
@@ -216,12 +184,7 @@ export interface FaceBubbleElement {
};
}
export interface SendFaceBubbleElement {
elementType: ElementType.FACEBUBBLE;
elementId: string;
faceBubbleElement: FaceBubbleElement;
}
export type SendFaceBubbleElement = SendElementBase<ElementType.FACEBUBBLE> & ElementBase<'faceBubbleElement'>;
export interface AvRecordElement {
type: number;
@@ -232,11 +195,7 @@ export interface AvRecordElement {
extraType: number;
}
export interface SendavRecordElement {
elementType: ElementType.AVRECORD;
elementId: string;
avRecordElement: AvRecordElement;
}
export type SendAvRecordElement = SendElementBase<ElementType.AVRECORD> & ElementBase<'avRecordElement'>;
export interface YoloUserInfo {
uid: string;
@@ -245,24 +204,13 @@ export interface YoloUserInfo {
bizId: string;
}
export interface SendInlineKeyboardElement {
elementType: ElementType.INLINEKEYBOARD;
elementId: string;
inlineKeyboardElement: {
rows: number;
botAppid: string;
};
}
export type SendInlineKeyboardElement = SendElementBase<ElementType.INLINEKEYBOARD> & ElementBase<'inlineKeyboardElement'>;
export interface YoloGameResultElement {
UserInfo: YoloUserInfo[];
}
export interface SendYoloGameResultElement {
elementType: ElementType.YOLOGAMERESULT;
yoloGameResultElement: YoloGameResultElement;
}
export type SendYoloGameResultElement = SendElementBase<ElementType.YOLOGAMERESULT> & ElementBase<'yoloGameResultElement'>;
export interface GiphyElement {
id: string;
@@ -271,17 +219,9 @@ export interface GiphyElement {
height: number;
}
export interface SendGiphyElement {
elementType: ElementType.GIPHY;
elementId: string;
giphyElement: GiphyElement;
}
export type SendGiphyElement = SendElementBase<ElementType.GIPHY> & ElementBase<'giphyElement'>;
export interface SendWalletElement {
elementType: ElementType.UNKNOWN;//不做 设置位置
elementId: string;
walletElement: Record<string, never>;
}
export type SendWalletElement = SendElementBase<ElementType.UNKNOWN> & ElementBase<'walletElement'>;
export interface CalendarElement {
summary: string;
@@ -291,49 +231,16 @@ export interface CalendarElement {
schema: string;
}
export interface SendCalendarElement {
elementType: ElementType.CALENDAR;
elementId: string;
calendarElement: CalendarElement;
}
export type SendCalendarElement = SendElementBase<ElementType.CALENDAR> & ElementBase<'calendarElement'>;
export interface SendliveGiftElement {
elementType: ElementType.LIVEGIFT;
elementId: string;
liveGiftElement: Record<string, never>;
}
export type SendLiveGiftElement = SendElementBase<ElementType.LIVEGIFT> & ElementBase<'liveGiftElement'>;
export interface SendTextElement {
elementType: ElementType.TEXT;
elementId: string;
textElement: {
content: string;
atType: number;
atUid: string;
atTinyId: string;
atNtUid: string;
};
}
export type SendTextElement = SendElementBase<ElementType.TEXT> & ElementBase<'textElement'>;
export interface SendPttElement {
elementType: ElementType.PTT;
elementId: string;
pttElement: {
fileName: string;
filePath: string;
md5HexStr: string;
fileSize: number;
duration: number; // 单位是秒
formatType: number;
voiceType: number;
voiceChangeType: number;
canConvert2Text: boolean;
waveAmplitudes: number[];
fileSubId: string;
playState: number;
autoConvertText: number;
};
}
export type SendPttElement = SendElementBase<ElementType.PTT> & ElementBase<'pttElement', {
pttElement: ['fileName', 'filePath', 'md5HexStr', 'fileSize', 'duration', 'formatType', 'voiceType',
'voiceChangeType', 'canConvert2Text', 'waveAmplitudes', 'fileSubId', 'playState', 'autoConvertText']
}>;
export enum PicType {
gif = 2000,
@@ -359,11 +266,7 @@ export enum NTMsgAtType {
ATTYPEUNKNOWN = 0
}
export interface SendPicElement {
elementType: ElementType.PIC;
elementId: string;
picElement: PicElement;
}
export type SendPicElement = SendElementBase<ElementType.PIC> & ElementBase<'picElement'>;
export interface ReplyElement {
sourceMsgIdInRecords?: string;
@@ -375,53 +278,27 @@ export interface ReplyElement {
replyMsgClientSeq?: string;
}
export interface SendReplyElement {
elementType: ElementType.REPLY;
elementId: string;
replyElement: ReplyElement;
}
export type SendReplyElement = SendElementBase<ElementType.REPLY> & ElementBase<'replyElement'>;
export interface SendFaceElement {
elementType: ElementType.FACE;
elementId: string;
faceElement: FaceElement;
}
export type SendFaceElement = SendElementBase<ElementType.FACE> & ElementBase<'faceElement'>;
export interface SendMarketFaceElement {
elementType: ElementType.MFACE;
marketFaceElement: MarketFaceElement;
}
export type SendMarketFaceElement = SendElementBase<ElementType.MFACE> & ElementBase<'marketFaceElement'>;
export interface SendStructLongMsgElement {
elementType: ElementType.STRUCTLONGMSG;
elementId: string;
structLongMsgElement: StructLongMsgElement;
}
export type SendStructLongMsgElement = SendElementBase<ElementType.STRUCTLONGMSG> & ElementBase<'structLongMsgElement'>;
export interface StructLongMsgElement {
xmlContent: string;
resId: string;
}
export interface SendactionBarElement {
elementType: ElementType.ACTIONBAR;
elementId: string;
actionBarElement: {
rows: number;
botAppid: string;
};
}
export type SendActionBarElement = SendElementBase<ElementType.ACTIONBAR> & ElementBase<'actionBarElement'>;
export interface ShareLocationElement {
text: string;
ext: string;
}
export interface SendShareLocationElement {
elementType: ElementType.SHARELOCATION;
elementId: string;
shareLocationElement?: ShareLocationElement;
}
export type SendShareLocationElement = SendElementBase<ElementType.SHARELOCATION> & ElementBase<'shareLocationElement'>;
export interface FileElement {
fileMd5?: string;
@@ -441,29 +318,13 @@ export interface FileElement {
fileBizId?: number;
}
export interface SendFileElement {
elementType: ElementType.FILE;
elementId: string;
fileElement: FileElement;
}
export type SendFileElement = SendElementBase<ElementType.FILE> & ElementBase<'fileElement'>;
export interface SendVideoElement {
elementType: ElementType.VIDEO;
elementId: string;
videoElement: VideoElement;
}
export type SendVideoElement = SendElementBase<ElementType.VIDEO> & ElementBase<'videoElement'>;
export interface SendArkElement {
elementType: ElementType.ARK;
elementId: string;
arkElement: ArkElement;
}
export type SendArkElement = SendElementBase<ElementType.ARK> & ElementBase<'arkElement'>;
export interface SendMarkdownElement {
elementType: ElementType.MARKDOWN;
elementId: string;
markdownElement: MarkdownElement;
}
export type SendMarkdownElement = SendElementBase<ElementType.MARKDOWN> & ElementBase<'markdownElement'>;
export type SendMessageElement = SendTextElement | SendPttElement |
SendPicElement | SendReplyElement | SendFaceElement | SendMarketFaceElement | SendFileElement |
@@ -480,7 +341,7 @@ export interface TextElement {
export interface MessageElement {
elementType: ElementType,
elementId: string,
extBufForUI: string,//"0x",
extBufForUI?: string, //"0x",
textElement?: TextElement;
faceElement?: FaceElement,
marketFaceElement?: MarketFaceElement,
@@ -509,7 +370,6 @@ export interface MessageElement {
taskTopMsgElement?: TaskTopMsgElement,
recommendedMsgElement?: RecommendedMsgElement,
actionBarElement?: ActionBarElement
}
export enum AtType {
@@ -578,7 +438,7 @@ export interface PttElement {
fileSize: string; // "4261"
fileSubId: string; // "0"
fileUuid: string; // "90j3z7rmRphDPrdVgP9udFBaYar#oK0TWZIV"
formatType: string; // 1
formatType: number; // 1
invalidState: number; // 0
md5HexStr: string; // "e4d09c784d5a2abcb2f9980bdc7acfe6"
playState: number; // 0
@@ -589,6 +449,7 @@ export interface PttElement {
voiceChangeType: number; // 0
voiceType: number; // 0
waveAmplitudes: number[];
autoConvertText: number;
}
export interface ArkElement {
@@ -794,7 +655,8 @@ export interface InlineKeyboardElementRowButton {
export interface InlineKeyboardElement {
rows: [{
buttons: InlineKeyboardElementRowButton[]
}];
}],
botAppid: string;
}
export interface TipAioOpGrayTipElement { // 这是什么提示来着?

View File

@@ -55,7 +55,7 @@ export class PacketClient {
this.websocket.onopen = () => {
this.isConnected = true;
this.reconnectAttempts = 0;
this.logger.log.bind(this.logger)(`[Core] [Packet Server] Connected to ${this.clientUrl}`);
this.logger.log.bind(this.logger)(`[Core] [Packet Server] 已连接到 ${this.clientUrl}`);
cb();
resolve();
};
@@ -85,14 +85,14 @@ export class PacketClient {
this.reconnectAttempts++;
setTimeout(() => {
this.connect(cb).catch((error) => {
this.logger.logError.bind(this.logger)(`[Core] [Packet Server] Reconnecting attempt failed,${error.message}`);
this.logger.logError.bind(this.logger)(`[Core] [Packet Server] 尝试重连失败:${error.message}`);
});
}, 5000 * this.reconnectAttempts);
} else {
this.logger.logError.bind(this.logger)(`[Core] [Packet Server] Max reconnect attempts reached. ${this.clientUrl}`);
this.logger.logError.bind(this.logger)(`[Core] [Packet Server] ${this.clientUrl} 已达到最大重连次数!`);
}
} catch (error: any) {
this.logger.logError.bind(this.logger)(`Error attempting to reconnect: ${error.message}`);
this.logger.logError.bind(this.logger)(`[Core] [Packet Server] 重连时出错: ${error.message}`);
}
}
@@ -165,7 +165,7 @@ export class PacketClient {
// 校验失败和异常 可能返回undefined
return new Promise((resolve, reject) => {
if (!this.available) {
this.logger.logError('NapCat.Packet is not init');
this.logger.logError('NapCat.Packet 未初始化!');
return undefined;
}
const md5 = crypto.createHash('md5').update(data).digest('hex');

View File

@@ -1,4 +1,7 @@
import {
Peer,
ChatType,
ElementType,
MessageElement,
RawMessage,
SendArkElement,
@@ -31,27 +34,39 @@ import {
import { PacketMsg, PacketSendMsgElement } from "@/core/packet/msg/message";
import { LogWrapper } from "@/common/log";
type SendMessageElementMap = {
textElement: SendTextElement,
picElement: SendPicElement,
replyElement: SendReplyElement,
faceElement: SendFaceElement,
marketFaceElement: SendMarketFaceElement,
videoElement: SendVideoElement,
fileElement: SendFileElement,
pttElement: SendPttElement,
arkElement: SendArkElement,
markdownElement: SendMarkdownElement,
structLongMsgElement: SendStructLongMsgElement
const SupportedElementTypes = [
ElementType.TEXT,
ElementType.PIC,
ElementType.REPLY,
ElementType.FACE,
ElementType.MFACE,
ElementType.VIDEO,
ElementType.FILE,
ElementType.PTT,
ElementType.ARK,
ElementType.MARKDOWN,
ElementType.STRUCTLONGMSG
];
type SendMessageTypeElementMap = {
[ElementType.TEXT]: SendTextElement,
[ElementType.PIC]: SendPicElement,
[ElementType.FILE]: SendFileElement,
[ElementType.PTT]: SendPttElement,
[ElementType.VIDEO]: SendVideoElement,
[ElementType.FACE]: SendFaceElement,
[ElementType.REPLY]: SendReplyElement,
[ElementType.ARK]: SendArkElement,
[ElementType.MFACE]: SendMarketFaceElement,
[ElementType.STRUCTLONGMSG]: SendStructLongMsgElement,
[ElementType.MARKDOWN]: SendMarkdownElement,
};
type RawToPacketMsgConverters = {
[K in keyof SendMessageElementMap]: (
element: SendMessageElementMap[K],
msg?: RawMessage,
elementWrapper?: MessageElement,
) => IPacketMsgElement<SendMessageElementMap[K]> | null;
};
type ElementToPacketMsgConverters = {
[K in keyof SendMessageTypeElementMap]: (
sendElement: MessageElement
) => IPacketMsgElement<SendMessageTypeElementMap[K]>;
}
export type rawMsgWithSendMsg = {
senderUin: number;
@@ -69,6 +84,10 @@ export class PacketMsgConverter {
this.logger = logger;
}
private isValidElementType(type: ElementType): type is keyof ElementToPacketMsgConverters {
return SupportedElementTypes.includes(type);
}
rawMsgWithSendMsgToPacketMsg(msg: rawMsgWithSendMsg): PacketMsg {
return {
senderUid: msg.senderUid ?? '',
@@ -77,55 +96,68 @@ export class PacketMsgConverter {
groupId: msg.groupId,
time: msg.time,
msg: msg.msg.map((element) => {
const key = (Object.keys(this.rawToPacketMsgConverters) as Array<keyof SendMessageElementMap>).find(
(k) => (element as any)[k] !== undefined // TODO:
);
if (key) {
const elementData = (element as any)[key]; // TODO:
if (elementData) return this.rawToPacketMsgConverters[key](element as any);
}
return null;
if (!this.isValidElementType(element.elementType)) return null;
return this.rawToPacketMsgConverters[element.elementType](element as MessageElement);
}).filter((e) => e !== null)
};
}
private rawToPacketMsgConverters: RawToPacketMsgConverters = {
textElement: (element: SendTextElement) => {
if (element.textElement.atType) {
return new PacketMsgAtElement(element);
rawMsgToPacketMsg(msg: RawMessage, ctxPeer: Peer): PacketMsg {
return {
seq: +msg.msgSeq,
groupId: ctxPeer.chatType === ChatType.KCHATTYPEGROUP ? +msg.peerUid : undefined,
senderUid: msg.senderUid,
senderUin: +msg.senderUin,
senderName: msg.sendMemberName && msg.sendMemberName !== ''
? msg.sendMemberName
: msg.sendNickName && msg.sendNickName !== ''
? msg.sendNickName
: "QQ用户",
time: +msg.msgTime,
msg: msg.elements.map((element) => {
if (!this.isValidElementType(element.elementType)) return null;
return this.rawToPacketMsgConverters[element.elementType](element);
}).filter((e) => e !== null)
};
}
private rawToPacketMsgConverters: ElementToPacketMsgConverters = {
[ElementType.TEXT]: (element) => {
if (element.textElement?.atType) {
return new PacketMsgAtElement(element as SendTextElement);
}
return new PacketMsgTextElement(element);
return new PacketMsgTextElement(element as SendTextElement);
},
picElement: (element: SendPicElement) => {
return new PacketMsgPicElement(element);
[ElementType.PIC]: (element) => {
return new PacketMsgPicElement(element as SendPicElement);
},
replyElement: (element: SendReplyElement) => {
return new PacketMsgReplyElement(element);
[ElementType.REPLY]: (element) => {
return new PacketMsgReplyElement(element as SendReplyElement);
},
faceElement: (element: SendFaceElement) => {
return new PacketMsgFaceElement(element);
[ElementType.FACE]: (element) => {
return new PacketMsgFaceElement(element as SendFaceElement);
},
marketFaceElement: (element: SendMarketFaceElement) => {
return new PacketMsgMarkFaceElement(element);
[ElementType.MFACE]: (element) => {
return new PacketMsgMarkFaceElement(element as SendMarketFaceElement);
},
videoElement: (element: SendVideoElement) => {
return new PacketMsgVideoElement(element);
[ElementType.VIDEO]: (element) => {
return new PacketMsgVideoElement(element as SendVideoElement);
},
fileElement: (element: SendFileElement) => {
return new PacketMsgFileElement(element);
[ElementType.FILE]: (element) => {
return new PacketMsgFileElement(element as SendFileElement);
},
pttElement: (element: SendPttElement) => {
return new PacketMsgPttElement(element);
[ElementType.PTT]: (element) => {
return new PacketMsgPttElement(element as SendPttElement);
},
arkElement: (element: SendArkElement) => {
return new PacketMsgLightAppElement(element);
[ElementType.ARK]: (element) => {
return new PacketMsgLightAppElement(element as SendArkElement);
},
markdownElement: (element: SendMarkdownElement) => {
return new PacketMsgMarkDownElement(element);
[ElementType.MARKDOWN]: (element) => {
return new PacketMsgMarkDownElement(element as SendMarkdownElement);
},
// TODO: check this logic, move it in arkElement?
structLongMsgElement: (element: SendStructLongMsgElement) => {
return new PacketMultiMsgElement(element);
[ElementType.STRUCTLONGMSG]: (element) => {
return new PacketMultiMsgElement(element as SendStructLongMsgElement);
}
};
}

View File

@@ -12,6 +12,13 @@ import {
import { GeneralCallResult } from '@/core/services/common';
export interface NodeIKernelGroupService {
// --->
// 待启用 For Next Version 3.2.0
// isTroopMember ? 0 : 111
getGroupMemberMaxNum(groupCode: string, serviceType: number): Promise<unknown>;
getAllGroupPrivilegeFlag(troopUinList: string[], serviceType: number): Promise<unknown>;
// <---
getGroupExt0xEF0Info(enableGroupCodes: string[], bannedGroupCodes: string[], filter: GroupExt0xEF0InfoFilter, forceFetch: boolean):
Promise<GeneralCallResult & { result: { groupExtInfos: Map<string, any> } }>;
@@ -114,7 +121,7 @@ export interface NodeIKernelGroupService {
destroyMemberListScene(SceneId: string): void;
getNextMemberList(sceneId: string, a: undefined, num: number): Promise<{
getNextMemberList(sceneId: string, groupMemberInfoListId: { index: number, uid: string } | undefined, num: number): Promise<{
errCode: number,
errMsg: string,
result: { ids: string[], infos: Map<string, GroupMember>, finish: boolean, hasRobot: boolean }
@@ -225,7 +232,8 @@ export interface NodeIKernelGroupService {
getGroupStatisticInfo(groupCode: string): unknown;
getGroupRemainAtTimes(groupCode: string): Promise<GeneralCallResult & {
getGroupRemainAtTimes(groupCode: string): Promise<Omit<GeneralCallResult, 'result'> & {
errCode: number,
atInfo: {
canAtAll: boolean
RemainAtAllCountForUin: number

View File

@@ -327,8 +327,7 @@ export interface NodeIKernelMsgService {
setPttPlayedState(...args: unknown[]): unknown;
//uk1 uk2 true
fetchFavEmojiList(str: string, num: number, uk1: boolean, uk2: boolean): Promise<GeneralCallResult & {
fetchFavEmojiList(str: string, num: number, backward: boolean, forceRefresh: boolean): Promise<GeneralCallResult & {
emojiInfoList: Array<{
uin: string,
emoId: number,

View File

@@ -16,11 +16,8 @@ export class GoCQHTTPGetGroupAtAllRemain extends BaseAction<Payload, any> {
payloadSchema = SchemaData;
async _handle(payload: Payload) {
let ret = await this.core.apis.GroupApi.getGroupRemainAtTimes(payload.group_id.toString());
if (!ret.atInfo || ret.result !== 0) {
throw new Error('atInfo not found');
}
let data = {
const ret = await this.core.apis.GroupApi.getGroupRemainAtTimes(payload.group_id.toString());
const data = {
can_at_all: ret.atInfo.canAtAll,
remain_at_all_count_for_group: ret.atInfo.RemainAtAllCountForGroup,
remain_at_all_count_for_uin: ret.atInfo.RemainAtAllCountForUin

View File

@@ -18,7 +18,7 @@ export class GoCQHTTPDeleteFriend extends BaseAction<Payload, any> {
payloadSchema = SchemaData;
async _handle(payload: Payload) {
let uid = await this.core.apis.UserApi.getUidByUinV2(payload.friend_id.toString());
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.friend_id.toString());
if (!uid) {
return {
@@ -26,7 +26,7 @@ export class GoCQHTTPDeleteFriend extends BaseAction<Payload, any> {
message: '好友不存在',
};
}
let isBuddy = await this.core.apis.FriendApi.isBuddy(uid);
const isBuddy = await this.core.apis.FriendApi.isBuddy(uid);
if (!isBuddy) {
return {
valid: false,

View File

@@ -14,6 +14,7 @@ import BaseAction from '../BaseAction';
import { rawMsgWithSendMsg } from "@/core/packet/msg/converter";
import { PacketMsg } from "@/core/packet/msg/message";
import { ForwardMsgBuilder } from "@/common/forward-msg-builder";
import { stringifyWithBigInt } from "@/common/helper";
export interface ReturnDataType {
message_id: number;
@@ -168,14 +169,14 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
logger.logWarn('转发消息深度超过3层将停止解析');
break;
}
if ((node.data.id && typeof node.data.content !== "string") || !node.data.id) {
if (!node.data.id) {
const OB11Data = normalize(node.type === OB11MessageDataType.node ? node.data.content : node);
let sendElements: SendMessageElement[];
if (getSpecialMsgNum({message: OB11Data}, OB11MessageDataType.node)) {
if (getSpecialMsgNum({ message: OB11Data }, OB11MessageDataType.node)) {
const uploadReturnData = await this.uploadForwardedNodesPacket(msgPeer, OB11Data as OB11MessageNode[], node.data.source, node.data.news, node.data.summary, node.data.prompt, {
user_id: node.data.user_id?.toString() ?? parentMeta?.user_id ?? this.core.selfInfo.uin,
nickname: node.data.nickname ?? parentMeta?.nickname ?? "QQ用户",
user_id: (node.data.user_id || node.data.uin)?.toString() ?? parentMeta?.user_id ?? this.core.selfInfo.uin,
nickname: (node.data.nickname || node.data.name) ?? parentMeta?.nickname ?? "QQ用户",
}, dp + 1);
sendElements = uploadReturnData?.finallySendElements ? [uploadReturnData.finallySendElements] : [];
} else {
@@ -184,18 +185,31 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
}
const packetMsgElements: rawMsgWithSendMsg = {
senderUin: Number(node.data.user_id ?? parentMeta?.user_id) || +this.core.selfInfo.uin,
senderName: node.data.nickname ?? parentMeta?.nickname ?? "QQ用户",
senderUin: Number((node.data.user_id || node.data.uin) ?? parentMeta?.user_id) || +this.core.selfInfo.uin,
senderName: (node.data.nickname || node.data.name) ?? parentMeta?.nickname ?? "QQ用户",
groupId: msgPeer.chatType === ChatType.KCHATTYPEGROUP ? +msgPeer.peerUid : undefined,
time: Number(node.data.time) || Date.now(),
msg: sendElements,
};
logger.logDebug(`handleForwardedNodesPacket 开始转换 ${JSON.stringify(packetMsgElements)}`);
logger.logDebug(`handleForwardedNodesPacket[SendRaw] 开始转换 ${stringifyWithBigInt(packetMsgElements)}`);
const transformedMsg = this.core.apis.PacketApi.packetSession?.packer.packetConverter.rawMsgWithSendMsgToPacketMsg(packetMsgElements);
logger.logDebug(`handleForwardedNodesPacket 转换为 ${JSON.stringify(transformedMsg)}`);
logger.logDebug(`handleForwardedNodesPacket[SendRaw] 转换为 ${stringifyWithBigInt(transformedMsg)}`);
packetMsg.push(transformedMsg!);
} else if (node.data.id) {
const id = node.data.id;
const nodeMsg = MessageUnique.getMsgIdAndPeerByShortId(+id) || MessageUnique.getPeerByMsgId(id);
if (!nodeMsg) {
logger.logError.bind(this.core.context.logger)('转发消息失败,未找到消息', id);
continue;
}
const msg = (await this.core.apis.MsgApi.getMsgsByMsgId(nodeMsg.Peer, [nodeMsg.MsgId])).msgList[0];
logger.logDebug(`handleForwardedNodesPacket[PureRaw] 开始转换 ${stringifyWithBigInt(msg)}`);
await this.core.apis.FileApi.downloadRawMsgMedia([msg]);
const transformedMsg = this.core.apis.PacketApi.packetSession?.packer.packetConverter.rawMsgToPacketMsg(msg, msgPeer);
logger.logDebug(`handleForwardedNodesPacket[PureRaw] 转换为 ${stringifyWithBigInt(transformedMsg)}`);
packetMsg.push(transformedMsg!);
} else {
logger.logDebug(`handleForwardedNodesPacket 跳过元素 ${JSON.stringify(node)}`);
logger.logDebug(`handleForwardedNodesPacket 跳过元素 ${stringifyWithBigInt(node)}`);
}
}
if (packetMsg.length === 0) {
@@ -228,7 +242,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
const finallySendElements = uploadReturnData?.finallySendElements;
if (!finallySendElements) throw Error('转发消息失败,生成节点为空');
returnMsg = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(msgPeer, [finallySendElements], [], true).catch(_ => undefined);
return {message: returnMsg ?? null, res_id};
return { message: returnMsg ?? null, res_id };
}
private async handleForwardedNodes(destPeer: Peer, messageNodes: OB11MessageNode[]): Promise<{
@@ -262,6 +276,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
logger.logError.bind(this.core.context.logger)('子消息中包含非node消息 跳过不合法部分');
continue;
}
// @ts-ignore
const nodeMsg = await this.handleForwardedNodes(selfPeer, OB11Data.filter(e => e.type === OB11MessageDataType.node));
if (nodeMsg) {
nodeMsgIds.push(nodeMsg.message!.msgId);

View File

@@ -5,16 +5,14 @@ import { ActionName, BaseCheckResult } from '../types';
export abstract class GetPacketStatusDepends<PT, RT> extends BaseAction<PT, RT> {
actionName = ActionName.GetPacketStatus;
protected async check(): Promise<BaseCheckResult>{
protected async check(payload: PT): Promise<BaseCheckResult>{
if (!this.core.apis.PacketApi.available) {
return {
valid: false,
message: "packetServer不可用请参照文档 https://napneko.github.io/config/advanced 检查packetServer状态或进行配置",
};
}
return {
valid: true,
};
return await super.check(payload);
}
}

View File

@@ -120,7 +120,7 @@ export class OneBotMsgApi {
url: await this.core.apis.FileApi.getImageUrl(element),
path: element.filePath,
file_size: element.fileSize,
file_unique: element.fileName
file_unique: element.md5HexStr ?? element.fileName,
},
};
} catch (e: any) {
@@ -141,9 +141,9 @@ export class OneBotMsgApi {
file: element.fileName,
path: element.filePath,
url: pathToFileURL(element.filePath).href,
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, element.fileUuid,"." + element.fileName),
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, element.fileUuid, "." + element.fileName),
file_size: element.fileSize,
file_unique: element.fileName,
file_unique: element.fileMd5 ?? element.fileSha ?? element.fileName,
},
};
},
@@ -204,7 +204,7 @@ export class OneBotMsgApi {
guildId: '',
};
if (!records || !element.replyMsgTime || !element.senderUidStr) {
this.core.context.logger.logError.bind(this.core.context.logger)('获取不到引用的消息', element.replayMsgSeq);
this.core.context.logger.logError.bind(this.core.context.logger)('似乎是旧版客户端,获取不到引用的消息', element.replayMsgSeq);
return null;
}
@@ -218,11 +218,27 @@ export class OneBotMsgApi {
if (records.peerUin === '284840486' || records.peerUin === '1094950020') {
return createReplyData(records.msgId);
}
const replyMsg = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeqV2(peer, element.replayMsgSeq, element.replyMsgTime, [element.senderUidStr]))
.msgList.find(msg => msg.msgRandom === records.msgRandom);
let replyMsgList = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeqV2(peer, element.replayMsgSeq, element.replyMsgTime, [element.senderUidStr])).msgList;
let replyMsg = replyMsgList.find(msg => msg.msgRandom === records.msgRandom);
if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) {
this.core.context.logger.logError.bind(this.core.context.logger)('获取不到引用消息', element.replayMsgSeq);
// 我猜测可能是时间参数未对上 导致找不到引用消息 或者msgList 存在问题
this.core.context.logger.logWarn.bind(this.core.context.logger)(
'初次筛选消息失败,获取不到引用的消息 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: ',
element.replayMsgSeq,
',消息长度:',
replyMsgList.length
);
return null;
}
return createReplyData(replyMsg.msgId);
@@ -285,7 +301,7 @@ export class OneBotMsgApi {
url: videoDownUrl ?? pathToFileURL(element.filePath).href,
file_id: fileCode,
file_size: element.fileSize,
file_unique: element.fileName,
file_unique: element.videoMd5 ?? element.thumbMd5 ?? element.fileName,
},
};
},
@@ -305,7 +321,7 @@ export class OneBotMsgApi {
url: pathToFileURL(element.filePath).href,
file_id: fileCode,
file_size: element.fileSize,
file_unique: element.fileName
file_unique: element.fileUuid
},
};
},
@@ -466,6 +482,7 @@ export class OneBotMsgApi {
},
}) => ({
elementType: ElementType.MFACE,
elementId: '',
marketFaceElement: {
emojiPackageId: emoji_package_id,
emojiId: emoji_id,
@@ -625,13 +642,13 @@ export class OneBotMsgApi {
[OB11MessageDataType.miniapp]: async () => undefined,
[OB11MessageDataType.contact]: async ({ data: { type = "qq", id } }, context) => {
if(type === "qq"){
if (type === "qq") {
const arkJson = await this.core.apis.UserApi.getBuddyRecommendContactArkJson(id.toString(), '');
return this.ob11ToRawConverters.json({
data: { data: arkJson.arkMsg },
type: OB11MessageDataType.json
}, context);
}else if(type === "group"){
} else if (type === "group") {
const arkJson = await this.core.apis.GroupApi.getGroupRecommendContactArkJson(id.toString());
return this.ob11ToRawConverters.json({
data: { data: arkJson.arkJson },

View File

@@ -119,11 +119,25 @@ export class OB11PassiveWebSocketAdapter implements IOB11NetworkAdapter {
async close() {
this.isOpen = false;
this.wsServer.close();
this.wsServer.close((err) => {
if (err) {
this.logger.logError.bind(this.logger)('[OneBot] [WebSocket Server] Error closing server:', err.message);
} else {
this.logger.log.bind(this.logger)('[OneBot] [WebSocket Server] Server Closed');
}
});
if (this.heartbeatIntervalId) {
clearInterval(this.heartbeatIntervalId);
this.heartbeatIntervalId = null;
}
await this.wsClientsMutex.runExclusive(async () => {
this.wsClients.forEach((wsClient) => {
wsClient.close();
});
this.wsClients = [];
this.wsClientWithEvent = [];
});
}
private registerHeartBeat() {

View File

@@ -150,7 +150,9 @@ export interface OB11MessageNode {
data: {
id?: string
user_id?: number | string // number
uin?: number | string // number, compatible with go-cqhttp
nickname: string
name?: string // compatible with go-cqhttp
content: OB11MessageMixType
source?: string,
news?: { text: string }[],

View File

@@ -30,7 +30,7 @@ async function onSettingWindowCreated(view: Element) {
SettingItem(
'<span id="napcat-update-title">Napcat</span>',
undefined,
SettingButton('V3.1.7', 'napcat-update-button', 'secondary'),
SettingButton('V3.3.12', 'napcat-update-button', 'secondary'),
),
]),
SettingList([

View File

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