Compare commits

...

22 Commits

Author SHA1 Message Date
手瓜一十雪
39d9c8fa74 release: v3.1.7 2024-10-26 16:26:30 +08:00
手瓜一十雪
8823895a03 Merge pull request #466 from cnxysoft/upmain
perf: 群成员拉取
2024-10-26 16:18:17 +08:00
手瓜一十雪
b44a9e696c Merge branch 'main' into pr/466 2024-10-26 15:47:13 +08:00
手瓜一十雪
cf28a3dc17 fix: ai solve 2024-10-26 10:49:18 +08:00
手瓜一十雪
7416e6caf6 feat: GoCQHTTPDeleteFriend 2024-10-26 10:36:41 +08:00
手瓜一十雪
90f6896f3c feat: GoCQ兼容性提高 2024-10-26 10:22:04 +08:00
Alen
eebcd0700d Merge branch 'main' into upmain 2024-10-26 07:22:25 +08:00
Alen
133eee0c66 perf: 群成员拉取
getgroupmemberlist启用no_cache
2024-10-26 07:20:40 +08:00
pk5ls20
640fb75f74 feat: support for customizing the timestamp of fake forwardMsg 2024-10-26 04:06:42 +08:00
Alen
51dcc1add6 Merge branch 'main' into upmain 2024-10-25 23:58:18 +08:00
Alen
730c928f91 Merge pull request #465 from cnxysoft/upmain
refactor: 群成员列表获取
2024-10-25 23:49:27 +08:00
Alen
c3b7e111b9 style: 2024-10-25 22:26:18 +08:00
pk5ls20
1874e48925 Merge pull request #464 from clansty/feat/nested-forward
feat: 嵌套合并转发消息
2024-10-25 22:13:32 +08:00
pk5ls20
e7a082c91c feat: better recursive parsing with depth limits 2024-10-25 22:10:24 +08:00
Alen
5d4f45407e fix: 群成员拉取 2024-10-25 21:50:19 +08:00
Clansty
17c37ec32f feat: 嵌套合并转发消息 2024-10-25 19:37:04 +08:00
手瓜一十雪
b5f8140c79 feat: v3 Logo 2024-10-25 19:31:44 +08:00
手瓜一十雪
63f746c237 style: lint 2024-10-25 18:09:41 +08:00
手瓜一十雪
dac6709f27 feat: 6.9.56-28418-mac 2024-10-25 17:57:28 +08:00
Alen
2f08b72d69 fix: 群成员拉取 2024-10-24 23:00:38 +08:00
Alen
0081000ef0 Merge branch 'main' into upmain 2024-10-23 01:08:56 +08:00
Alen
ad4d6a1070 refactor: 群成员获取 2024-10-23 01:07:52 +08:00
39 changed files with 426 additions and 186 deletions

BIN
logo.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 KiB

After

Width:  |  Height:  |  Size: 335 KiB

View File

@@ -4,7 +4,7 @@
"name": "NapCatQQ", "name": "NapCatQQ",
"slug": "NapCat.Framework", "slug": "NapCat.Framework",
"description": "高性能的 OneBot 11 协议实现", "description": "高性能的 OneBot 11 协议实现",
"version": "3.1.6", "version": "3.1.7",
"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.1.6", "version": "3.1.7",
"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",

View File

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

View File

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

View File

@@ -378,8 +378,8 @@ export class NTQQFileApi {
}; };
try { try {
if (this.core.apis.PacketApi.available) { if (this.core.apis.PacketApi.available) {
let rkey_expired_private = !this.packetRkey || this.packetRkey[0].time + Number(this.packetRkey[0].ttl) < Date.now() / 1000; const rkey_expired_private = !this.packetRkey || this.packetRkey[0].time + Number(this.packetRkey[0].ttl) < Date.now() / 1000;
let rkey_expired_group = !this.packetRkey || this.packetRkey[0].time + Number(this.packetRkey[0].ttl) < Date.now() / 1000; const rkey_expired_group = !this.packetRkey || this.packetRkey[0].time + Number(this.packetRkey[0].ttl) < Date.now() / 1000;
if (rkey_expired_private || rkey_expired_group) { if (rkey_expired_private || rkey_expired_group) {
this.packetRkey = await this.core.apis.PacketApi.sendRkeyPacket(); this.packetRkey = await this.core.apis.PacketApi.sendRkeyPacket();
} }

View File

@@ -34,7 +34,13 @@ export class NTQQFriendApi {
data.forEach((value) => retMap.set(value.uin!, value.uid!)); data.forEach((value) => retMap.set(value.uin!, value.uid!));
return retMap; return retMap;
} }
async delBuudy(uid: string, tempBlock = false, tempBothDel = false) {
return this.context.session.getBuddyService().delBuddy({
friendUid: uid,
tempBlock: tempBlock,
tempBothDel: tempBothDel
});
}
async getBuddyV2ExWithCate(refresh = false) { async getBuddyV2ExWithCate(refresh = false) {
const categoryMap: Map<string, any> = new Map(); const categoryMap: Map<string, any> = new Map();
const buddyService = this.context.session.getBuddyService(); const buddyService = this.context.session.getBuddyService();

View File

@@ -316,18 +316,42 @@ export class NTQQGroupApi {
return undefined; return undefined;
} }
async getGroupMembersV2(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> { async tryGetGroupMembersV2(modeListener = false, groupQQ: string, num = 30, timeout = 100): Promise<{
const sceneId = this.context.session.getGroupService().createMemberListScene(groupQQ, 'groupMemberList_MainWindow'); infos: Map<string, GroupMember>;
const once = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onMemberListChange', 1, 2000, (params) => params.sceneId === sceneId) finish: boolean;
.catch(); hasNext: boolean | undefined;
const result = await this.context.session.getGroupService().getNextMemberList(sceneId!, undefined, num); }>{
const sceneId = this.context.session.getGroupService().createMemberListScene(groupQQ, 'groupMemberList_MainWindow_1');
const once = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onMemberListChange', 0, timeout, (params) => params.sceneId === sceneId)
.catch(() => {});
const result = await this.context.session.getGroupService().getNextMemberList(sceneId, undefined, num);
if (result.errCode !== 0) { if (result.errCode !== 0) {
throw new Error('获取群成员列表出错,' + result.errMsg); throw new Error('获取群成员列表出错,' + result.errMsg);
} }
if (result.result.infos.size === 0) { let resMode2;
return (await once)[0].infos; if (modeListener) {
const ret = (await once)?.[0];
if (ret) {
resMode2 = ret;
}
} }
return result.result.infos; this.context.session.getGroupService().destroyMemberListScene(sceneId);
return {
infos: resMode2?.infos || result.result.infos,
finish: result.result.finish,
hasNext: resMode2?.hasNext,
};
}
async getGroupMembersV2(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {
let res = await this.tryGetGroupMembersV2(true, groupQQ);
if (res.hasNext || !res.finish || res.infos.size === 0) {
res = await this.tryGetGroupMembersV2(false, groupQQ, num);
}
if ((res.infos.size === 0 || res.infos.size === 30) && res.finish) {
res = await this.tryGetGroupMembersV2(true, groupQQ, num);
}
return res.infos;
} }
async getGroupMembers(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> { async getGroupMembers(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {
@@ -425,7 +449,7 @@ export class NTQQGroupApi {
} }
async getGroupRemainAtTimes(GroupCode: string) { async getGroupRemainAtTimes(GroupCode: string) {
this.context.session.getGroupService().getGroupRemainAtTimes(GroupCode); return this.context.session.getGroupService().getGroupRemainAtTimes(GroupCode);
} }
async getMemberExtInfo(groupCode: string, uin: string) { async getMemberExtInfo(groupCode: string, uin: string) {

View File

@@ -3,7 +3,7 @@ import { ChatType, InstanceContext, NapCatCore } from '..';
import offset from '@/core/external/offset.json'; import offset from '@/core/external/offset.json';
import { PacketClient, RecvPacketData } from '@/core/packet/client'; import { PacketClient, RecvPacketData } from '@/core/packet/client';
import { PacketSession } from "@/core/packet/session"; import { PacketSession } from "@/core/packet/session";
import {OidbPacket, PacketHexStr} from "@/core/packet/packer"; import { OidbPacket, PacketHexStr } from "@/core/packet/packer";
import { NapProtoMsg } from '@/core/packet/proto/NapProto'; import { NapProtoMsg } from '@/core/packet/proto/NapProto';
import { OidbSvcTrpcTcp0X9067_202_Rsp_Body } from '@/core/packet/proto/oidb/Oidb.0x9067_202'; import { OidbSvcTrpcTcp0X9067_202_Rsp_Body } from '@/core/packet/proto/oidb/Oidb.0x9067_202';
import { OidbSvcTrpcTcpBase, OidbSvcTrpcTcpBaseRsp } from '@/core/packet/proto/oidb/OidbBase'; import { OidbSvcTrpcTcpBase, OidbSvcTrpcTcpBaseRsp } from '@/core/packet/proto/oidb/OidbBase';

View File

@@ -34,5 +34,9 @@
"3.2.12-28971-arm64": { "3.2.12-28971-arm64": {
"send": "6E91318", "send": "6E91318",
"recv": "6E94B50" "recv": "6E94B50"
},
"6.9.56-28418-arm64": {
"send": "4471360",
"recv": "4473BCC"
} }
} }

View File

@@ -71,7 +71,8 @@ export class NodeIKernelGroupListener {
sceneId: string, sceneId: string,
ids: string[], ids: string[],
infos: Map<string, GroupMember>, // uid -> GroupMember infos: Map<string, GroupMember>, // uid -> GroupMember
finish: boolean, hasPrev: boolean,
hasNext: boolean,
hasRobot: boolean hasRobot: boolean
}) { }) {
} }

View File

@@ -167,7 +167,7 @@ export class PacketHighwaySession {
}); });
await this.packetHighwayClient.upload( await this.packetHighwayClient.upload(
1004, 1004,
fs.createReadStream(img.path, {highWaterMark: BlockSize}), fs.createReadStream(img.path, { highWaterMark: BlockSize }),
img.size, img.size,
md5, md5,
extend extend
@@ -207,7 +207,7 @@ export class PacketHighwaySession {
}); });
await this.packetHighwayClient.upload( await this.packetHighwayClient.upload(
1003, 1003,
fs.createReadStream(img.path, {highWaterMark: BlockSize}), fs.createReadStream(img.path, { highWaterMark: BlockSize }),
img.size, img.size,
md5, md5,
extend extend
@@ -244,10 +244,10 @@ export class PacketHighwaySession {
hash: { hash: {
fileSha1: await calculateSha1StreamBytes(video.filePath!) fileSha1: await calculateSha1StreamBytes(video.filePath!)
} }
}) });
await this.packetHighwayClient.upload( await this.packetHighwayClient.upload(
1005, 1005,
fs.createReadStream(video.filePath!, {highWaterMark: BlockSize}), fs.createReadStream(video.filePath!, { highWaterMark: BlockSize }),
+video.fileSize!, +video.fileSize!,
md5, md5,
extend extend
@@ -275,7 +275,7 @@ export class PacketHighwaySession {
}); });
await this.packetHighwayClient.upload( await this.packetHighwayClient.upload(
1006, 1006,
fs.createReadStream(video.thumbPath!, {highWaterMark: BlockSize}), fs.createReadStream(video.thumbPath!, { highWaterMark: BlockSize }),
+video.thumbSize!, +video.thumbSize!,
md5, md5,
extend extend
@@ -312,10 +312,10 @@ export class PacketHighwaySession {
hash: { hash: {
fileSha1: await calculateSha1StreamBytes(video.filePath!) fileSha1: await calculateSha1StreamBytes(video.filePath!)
} }
}) });
await this.packetHighwayClient.upload( await this.packetHighwayClient.upload(
1001, 1001,
fs.createReadStream(video.filePath!, {highWaterMark: BlockSize}), fs.createReadStream(video.filePath!, { highWaterMark: BlockSize }),
+video.fileSize!, +video.fileSize!,
md5, md5,
extend extend
@@ -343,7 +343,7 @@ export class PacketHighwaySession {
}); });
await this.packetHighwayClient.upload( await this.packetHighwayClient.upload(
1002, 1002,
fs.createReadStream(video.thumbPath!, {highWaterMark: BlockSize}), fs.createReadStream(video.thumbPath!, { highWaterMark: BlockSize }),
+video.thumbSize!, +video.thumbSize!,
md5, md5,
extend extend
@@ -379,10 +379,10 @@ export class PacketHighwaySession {
hash: { hash: {
fileSha1: [sha1] fileSha1: [sha1]
} }
}) });
await this.packetHighwayClient.upload( await this.packetHighwayClient.upload(
1008, 1008,
fs.createReadStream(ptt.filePath, {highWaterMark: BlockSize}), fs.createReadStream(ptt.filePath, { highWaterMark: BlockSize }),
ptt.fileSize, ptt.fileSize,
md5, md5,
extend extend
@@ -418,10 +418,10 @@ export class PacketHighwaySession {
hash: { hash: {
fileSha1: [sha1] fileSha1: [sha1]
} }
}) });
await this.packetHighwayClient.upload( await this.packetHighwayClient.upload(
1007, 1007,
fs.createReadStream(ptt.filePath, {highWaterMark: BlockSize}), fs.createReadStream(ptt.filePath, { highWaterMark: BlockSize }),
ptt.fileSize, ptt.fileSize,
md5, md5,
extend extend
@@ -484,10 +484,10 @@ export class PacketHighwaySession {
} }
}, },
unknown200: 0, unknown200: 0,
}) });
await this.packetHighwayClient.upload( await this.packetHighwayClient.upload(
71, 71,
fs.createReadStream(file.filePath, {highWaterMark: BlockSize}), fs.createReadStream(file.filePath, { highWaterMark: BlockSize }),
file.fileSize, file.fileSize,
file.fileMd5, file.fileMd5,
ext ext
@@ -549,10 +549,10 @@ export class PacketHighwaySession {
}, },
unknown200: 1, unknown200: 1,
unknown3: 0 unknown3: 0
}) });
await this.packetHighwayClient.upload( await this.packetHighwayClient.upload(
95, 95,
fs.createReadStream(file.filePath, {highWaterMark: BlockSize}), fs.createReadStream(file.filePath, { highWaterMark: BlockSize }),
file.fileSize, file.fileSize,
file.fileMd5, file.fileMd5,
ext ext

View File

@@ -30,7 +30,7 @@ abstract class HighwayUploader {
reject(new Error(`[Highway] timeout after ${this.trans.timeout}s`)); reject(new Error(`[Highway] timeout after ${this.trans.timeout}s`));
}, (this.trans.timeout ?? Infinity) * 1000 }, (this.trans.timeout ?? Infinity) * 1000
); );
}) });
} }
buildPicUpHead(offset: number, bodyLength: number, bodyMd5: Uint8Array): Uint8Array { buildPicUpHead(offset: number, bodyLength: number, bodyMd5: Uint8Array): Uint8Array {
@@ -100,7 +100,7 @@ export class HighwayTcpUploader extends HighwayUploader {
const upload = new Promise<void>((resolve, reject) => { const upload = new Promise<void>((resolve, reject) => {
const highwayTransForm = new HighwayTcpUploaderTransform(this); const highwayTransForm = new HighwayTcpUploaderTransform(this);
const socket = net.connect(this.trans.port, this.trans.server, () => { const socket = net.connect(this.trans.port, this.trans.server, () => {
this.trans.data.pipe(highwayTransForm).pipe(socket, {end: false}); this.trans.data.pipe(highwayTransForm).pipe(socket, { end: false });
}); });
const handleRspHeader = (header: Buffer) => { const handleRspHeader = (header: Buffer) => {
const rsp = new NapProtoMsg(RespDataHighwayHead).decode(header); const rsp = new NapProtoMsg(RespDataHighwayHead).decode(header);
@@ -159,7 +159,7 @@ export class HighwayHttpUploader extends HighwayUploader {
try { try {
await this.uploadBlock(block, offset); await this.uploadBlock(block, offset);
} catch (err) { } catch (err) {
throw new Error(`[Highway] httpUpload Error uploading block at offset ${offset}: ${err}`) throw new Error(`[Highway] httpUpload Error uploading block at offset ${offset}: ${err}`);
} }
offset += block.length; offset += block.length;
} }

View File

@@ -15,9 +15,9 @@ export class PacketMsgBuilder {
protected static failBackText = new PacketMsgTextElement( protected static failBackText = new PacketMsgTextElement(
{ {
textElement: {content: "[该消息类型暂不支持查看]"}! textElement: { content: "[该消息类型暂不支持查看]" }!
} as SendTextElement } as SendTextElement
) );
buildFakeMsg(selfUid: string, element: PacketMsg[]): NapProtoEncodeStructType<typeof PushMsgBody>[] { buildFakeMsg(selfUid: string, element: PacketMsg[]): NapProtoEncodeStructType<typeof PushMsgBody>[] {
return element.map((node): NapProtoEncodeStructType<typeof PushMsgBody> => { return element.map((node): NapProtoEncodeStructType<typeof PushMsgBody> => {
@@ -50,7 +50,7 @@ export class PacketMsgBuilder {
divSeq: node.groupId ? undefined : 4, divSeq: node.groupId ? undefined : 4,
msgId: crypto.randomBytes(4).readUInt32LE(0), msgId: crypto.randomBytes(4).readUInt32LE(0),
sequence: crypto.randomBytes(4).readUInt32LE(0), sequence: crypto.randomBytes(4).readUInt32LE(0),
timeStamp: Math.floor(Date.now() / 1000), timeStamp: +node.time.toString().substring(0, 10),
field7: BigInt(1), field7: BigInt(1),
field8: 0, field8: 0,
field9: 0, field9: 0,

View File

@@ -89,11 +89,11 @@ export class PacketMsgAtElement extends PacketMsgTextElement {
text: { text: {
str: this.text, str: this.text,
pbReserve: new NapProtoMsg(MentionExtra).encode({ pbReserve: new NapProtoMsg(MentionExtra).encode({
type: this.atAll ? 1 : 2, type: this.atAll ? 1 : 2,
uin: 0, uin: 0,
field5: 0, field5: 0,
uid: this.targetUid, uid: this.targetUid,
} }
) )
} }
}]; }];
@@ -308,7 +308,7 @@ export class PacketMsgVideoElement extends IPacketMsgElement<SendVideoElement> {
this.filePath = element.videoElement.filePath; this.filePath = element.videoElement.filePath;
this.thumbSize = element.videoElement.thumbSize; this.thumbSize = element.videoElement.thumbSize;
this.thumbPath = element.videoElement.thumbPath?.get(0); this.thumbPath = element.videoElement.thumbPath?.get(0);
this.fileMd5 = element.videoElement.videoMd5 this.fileMd5 = element.videoElement.videoMd5;
this.thumbMd5 = element.videoElement.thumbMd5; this.thumbMd5 = element.videoElement.thumbMd5;
this.thumbWidth = element.videoElement.thumbWidth; this.thumbWidth = element.videoElement.thumbWidth;
this.thumbHeight = element.videoElement.thumbHeight; this.thumbHeight = element.videoElement.thumbHeight;
@@ -355,7 +355,7 @@ export class PacketMsgPttElement extends IPacketMsgElement<SendPttElement> {
} }
buildElement(): NapProtoEncodeStructType<typeof Elem>[] { buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
return [] return [];
// if (!this.msgInfo) return []; // if (!this.msgInfo) return [];
// return [{ // return [{
// commonElem: { // commonElem: {
@@ -382,7 +382,7 @@ export class PacketMsgFileElement extends IPacketMsgElement<SendFileElement> {
isGroupFile?: boolean; isGroupFile?: boolean;
_private_send_uid?: string; _private_send_uid?: string;
_private_recv_uid?: string; _private_recv_uid?: string;
_e37_800_rsp?: NapProtoEncodeStructType<typeof OidbSvcTrpcTcp0XE37_800Response> _e37_800_rsp?: NapProtoEncodeStructType<typeof OidbSvcTrpcTcp0XE37_800Response>;
constructor(element: SendFileElement) { constructor(element: SendFileElement) {
super(element); super(element);
@@ -423,7 +423,7 @@ export class PacketMsgFileElement extends IPacketMsgElement<SendFileElement> {
destUid: this._private_recv_uid, destUid: this._private_recv_uid,
} }
} }
}) });
} }
buildElement(): NapProtoEncodeStructType<typeof Elem>[] { buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
@@ -443,7 +443,7 @@ export class PacketMsgFileElement extends IPacketMsgElement<SendFileElement> {
fileMd5: this.fileMd5, fileMd5: this.fileMd5,
} }
} }
}) });
lb.writeUInt16BE(transElemVal.length); lb.writeUInt16BE(transElemVal.length);
return [{ return [{
transElem: { transElem: {

View File

@@ -61,7 +61,7 @@ export class PacketPacker {
return { return {
cmd: `OidbSvcTrpcTcp.0x${cmd.toString(16).toUpperCase()}_${subCmd}`, cmd: `OidbSvcTrpcTcp.0x${cmd.toString(16).toUpperCase()}_${subCmd}`,
data: this.packetPacket(data) data: this.packetPacket(data)
} };
} }
packPokePacket(peer: number, group?: number): OidbPacket { packPokePacket(peer: number, group?: number): OidbPacket {
@@ -114,7 +114,7 @@ export class PacketPacker {
packStatusPacket(uin: number): OidbPacket { packStatusPacket(uin: number): OidbPacket {
const oidb_0xfe1_2 = new NapProtoMsg(OidbSvcTrpcTcp0XFE1_2).encode({ const oidb_0xfe1_2 = new NapProtoMsg(OidbSvcTrpcTcp0XFE1_2).encode({
uin: uin, uin: uin,
key: [{key: 27372}] key: [{ key: 27372 }]
}); });
return this.packOidbPacket(0xfe1, 2, oidb_0xfe1_2); return this.packOidbPacket(0xfe1, 2, oidb_0xfe1_2);
} }
@@ -240,68 +240,68 @@ export class PacketPacker {
async packUploadC2CImgReq(peerUin: string, img: PacketMsgPicElement): Promise<OidbPacket> { async packUploadC2CImgReq(peerUin: string, img: PacketMsgPicElement): Promise<OidbPacket> {
const req = new NapProtoMsg(NTV2RichMediaReq).encode({ const req = new NapProtoMsg(NTV2RichMediaReq).encode({
reqHead: { reqHead: {
common: { common: {
requestId: 1, requestId: 1,
command: 100 command: 100
},
scene: {
requestType: 2,
businessType: 1,
sceneType: 1,
c2C: {
accountType: 2,
targetUid: peerUin
}, },
scene: { },
requestType: 2, client: {
businessType: 1, agentType: 2,
sceneType: 1, }
c2C: { },
accountType: 2, upload: {
targetUid: peerUin uploadInfo: [
{
fileInfo: {
fileSize: +img.size,
fileHash: img.md5,
fileSha1: img.sha1!,
fileName: img.name,
type: {
type: 1,
picFormat: img.picType, //TODO: extend NapCat imgType /cc @MliKiowa
videoFormat: 0,
voiceFormat: 0,
},
width: img.width,
height: img.height,
time: 0,
original: 1
}, },
subFileType: 0,
}
],
tryFastUploadCompleted: true,
srvSendMsg: false,
clientRandomId: crypto.randomBytes(8).readBigUInt64BE() & BigInt('0x7FFFFFFFFFFFFFFF'),
compatQMsgSceneType: 1,
extBizInfo: {
pic: {
bytesPbReserveTroop: Buffer.from("0800180020004200500062009201009a0100a2010c080012001800200028003a00", 'hex'),
textSummary: "Nya~", // TODO:
}, },
client: { video: {
agentType: 2, bytesPbReserve: Buffer.alloc(0),
},
ptt: {
bytesPbReserve: Buffer.alloc(0),
bytesReserve: Buffer.alloc(0),
bytesGeneralFlags: Buffer.alloc(0),
} }
}, },
upload: { clientSeq: 0,
uploadInfo: [ noNeedCompatMsg: false,
{
fileInfo: {
fileSize: +img.size,
fileHash: img.md5,
fileSha1: img.sha1!,
fileName: img.name,
type: {
type: 1,
picFormat: img.picType, //TODO: extend NapCat imgType /cc @MliKiowa
videoFormat: 0,
voiceFormat: 0,
},
width: img.width,
height: img.height,
time: 0,
original: 1
},
subFileType: 0,
}
],
tryFastUploadCompleted: true,
srvSendMsg: false,
clientRandomId: crypto.randomBytes(8).readBigUInt64BE() & BigInt('0x7FFFFFFFFFFFFFFF'),
compatQMsgSceneType: 1,
extBizInfo: {
pic: {
bytesPbReserveTroop: Buffer.from("0800180020004200500062009201009a0100a2010c080012001800200028003a00", 'hex'),
textSummary: "Nya~", // TODO:
},
video: {
bytesPbReserve: Buffer.alloc(0),
},
ptt: {
bytesPbReserve: Buffer.alloc(0),
bytesReserve: Buffer.alloc(0),
bytesGeneralFlags: Buffer.alloc(0),
}
},
clientSeq: 0,
noNeedCompatMsg: false,
}
} }
}
); );
return this.packOidbPacket(0x11c5, 100, req, true, false); return this.packOidbPacket(0x11c5, 100, req, true, false);
} }
@@ -538,7 +538,7 @@ export class PacketPacker {
clientSeq: 0, clientSeq: 0,
noNeedCompatMsg: false noNeedCompatMsg: false
} }
}) });
return this.packOidbPacket(0x126E, 100, req, true, false); return this.packOidbPacket(0x126E, 100, req, true, false);
} }
@@ -600,7 +600,7 @@ export class PacketPacker {
clientSeq: 0, clientSeq: 0,
noNeedCompatMsg: false noNeedCompatMsg: false
} }
}) });
return this.packOidbPacket(0x126D, 100, req, true, false); return this.packOidbPacket(0x126D, 100, req, true, false);
} }
@@ -642,7 +642,7 @@ export class PacketPacker {
businessId: 3, businessId: 3,
clientType: 1, clientType: 1,
flagSupportMediaPlatform: 1 flagSupportMediaPlatform: 1
}) });
return this.packOidbPacket(0xE37, 1700, body, false, false); return this.packOidbPacket(0xE37, 1700, body, false, false);
} }
@@ -664,13 +664,13 @@ export class PacketPacker {
packGroupFileDownloadReq(groupUin: number, fileUUID: string): OidbPacket { packGroupFileDownloadReq(groupUin: number, fileUUID: string): OidbPacket {
return this.packOidbPacket(0x6D6, 2, new NapProtoMsg(OidbSvcTrpcTcp0x6D6).encode({ return this.packOidbPacket(0x6D6, 2, new NapProtoMsg(OidbSvcTrpcTcp0x6D6).encode({
download: { download: {
groupUin: groupUin, groupUin: groupUin,
appId: 7, appId: 7,
busId: 102, busId: 102,
fileId: fileUUID fileId: fileUUID
} }
}), true, false }), true, false
); );
} }

View File

@@ -118,7 +118,7 @@ export const FileExtra = {
export const PrivateFileExtra = { export const PrivateFileExtra = {
field2: ProtoField(2, () => PrivateFileExtraField2), field2: ProtoField(2, () => PrivateFileExtraField2),
} };
export const PrivateFileExtraField2 = { export const PrivateFileExtraField2 = {
field1: ProtoField(1, ScalarType.UINT32), field1: ProtoField(1, ScalarType.UINT32),
@@ -131,7 +131,7 @@ export const PrivateFileExtraField2 = {
fileHash: ProtoField(14, ScalarType.STRING), fileHash: ProtoField(14, ScalarType.STRING),
selfUid: ProtoField(15, ScalarType.STRING), selfUid: ProtoField(15, ScalarType.STRING),
destUid: ProtoField(16, ScalarType.STRING), destUid: ProtoField(16, ScalarType.STRING),
} };
export const GroupFileExtra = { export const GroupFileExtra = {
field1: ProtoField(1, ScalarType.UINT32), field1: ProtoField(1, ScalarType.UINT32),

View File

@@ -1,6 +1,6 @@
import { ScalarType } from "@protobuf-ts/runtime"; import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto"; import { ProtoField } from "../NapProto";
import {OidbSvcTrpcTcp0XE37_800_1200Metadata} from "@/core/packet/proto/oidb/Oidb.0xE37_1200"; import { OidbSvcTrpcTcp0XE37_800_1200Metadata } from "@/core/packet/proto/oidb/Oidb.0xE37_1200";
export const OidbSvcTrpcTcp0XE37_800 = { export const OidbSvcTrpcTcp0XE37_800 = {
subCommand: ProtoField(1, ScalarType.UINT32), subCommand: ProtoField(1, ScalarType.UINT32),
@@ -59,4 +59,4 @@ export const OidbSvcTrpcTcp0XE37_800Response = {
export const OidbSvcTrpcTcp0XE37_800ResponseBody = { export const OidbSvcTrpcTcp0XE37_800ResponseBody = {
field10: ProtoField(10, ScalarType.UINT32, true), field10: ProtoField(10, ScalarType.UINT32, true),
field30: ProtoField(30, () => OidbSvcTrpcTcp0XE37_800_1200Metadata, true), field30: ProtoField(30, () => OidbSvcTrpcTcp0XE37_800_1200Metadata, true),
} };

View File

@@ -8,7 +8,7 @@ export const OidbSvcTrpcTcp0XE37_1700 = {
businessId: ProtoField(101, ScalarType.INT32, true), businessId: ProtoField(101, ScalarType.INT32, true),
clientType: ProtoField(102, ScalarType.INT32, true), clientType: ProtoField(102, ScalarType.INT32, true),
flagSupportMediaPlatform: ProtoField(200, ScalarType.INT32, true), flagSupportMediaPlatform: ProtoField(200, ScalarType.INT32, true),
} };
export const ApplyUploadReqV3 = { export const ApplyUploadReqV3 = {
senderUid: ProtoField(10, ScalarType.STRING, true), senderUid: ProtoField(10, ScalarType.STRING, true),
@@ -20,4 +20,4 @@ export const ApplyUploadReqV3 = {
localPath: ProtoField(70, ScalarType.STRING, true), localPath: ProtoField(70, ScalarType.STRING, true),
md5CheckSum: ProtoField(110, ScalarType.BYTES, true), md5CheckSum: ProtoField(110, ScalarType.BYTES, true),
sha3CheckSum: ProtoField(120, ScalarType.BYTES, true), sha3CheckSum: ProtoField(120, ScalarType.BYTES, true),
} };

View File

@@ -9,4 +9,4 @@ export const OidbSvcTrpcTcp0XEB7_Body = {
export const OidbSvcTrpcTcp0XEB7 = { export const OidbSvcTrpcTcp0XEB7 = {
body: ProtoField(2, () => OidbSvcTrpcTcp0XEB7_Body), body: ProtoField(2, () => OidbSvcTrpcTcp0XEB7_Body),
} };

View File

@@ -2,7 +2,7 @@
import * as crypto from 'crypto'; import * as crypto from 'crypto';
import * as stream from 'stream'; import * as stream from 'stream';
import * as fs from 'fs'; import * as fs from 'fs';
import {CalculateStreamBytesTransform} from "@/core/packet/utils/crypto/sha1StreamBytesTransform"; import { CalculateStreamBytesTransform } from "@/core/packet/utils/crypto/sha1StreamBytesTransform";
function sha1Stream(readable: stream.Readable) { function sha1Stream(readable: stream.Readable) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {

View File

@@ -73,7 +73,7 @@ export class Sha1Stream {
this._count[1] = (this._count[1] + (dataLen >>> 29)) >>> 0; this._count[1] = (this._count[1] + (dataLen >>> 29)) >>> 0;
let partLen = (this.Sha1BlockSize - index) >>> 0; const partLen = (this.Sha1BlockSize - index) >>> 0;
let i = 0; let i = 0;
if (dataLen >= partLen) { if (dataLen >= partLen) {
@@ -100,11 +100,11 @@ export class Sha1Stream {
public final(): Buffer { public final(): Buffer {
const digest = Buffer.allocUnsafe(this.Sha1DigestSize); const digest = Buffer.allocUnsafe(this.Sha1DigestSize);
const bits = Buffer.allocUnsafe(8) const bits = Buffer.allocUnsafe(8);
bits.writeUInt32BE(this._count[1], 0); bits.writeUInt32BE(this._count[1], 0);
bits.writeUInt32BE(this._count[0], 4); bits.writeUInt32BE(this._count[0], 4);
let index = ((this._count[0] >>> 3) & 0x3F) >>> 0; const index = ((this._count[0] >>> 3) & 0x3F) >>> 0;
const padLen = ((index < 56) ? (56 - index) : (120 - index)) >>> 0; const padLen = ((index < 56) ? (56 - index) : (120 - index)) >>> 0;
this.update(this._padding, padLen); this.update(this._padding, padLen);
this.update(bits); this.update(bits);

View File

@@ -1,5 +1,5 @@
import * as stream from "node:stream"; import * as stream from "node:stream";
import {Sha1Stream} from "@/core/packet/utils/crypto/sha1Stream"; import { Sha1Stream } from "@/core/packet/utils/crypto/sha1Stream";
export class CalculateStreamBytesTransform extends stream.Transform { export class CalculateStreamBytesTransform extends stream.Transform {
private readonly blockSize = 1024 * 1024; private readonly blockSize = 1024 * 1024;

View File

@@ -66,7 +66,11 @@ export interface NodeIKernelBuddyService {
accept: boolean; accept: boolean;
}): Promise<void>; }): Promise<void>;
delBuddy(uid: number): void; delBuddy(param: {
friendUid: string;
tempBlock: boolean;
tempBothDel: boolean;
}): Promise<unknown>;
delBatchBuddy(uids: number[]): void; delBatchBuddy(uids: number[]): void;

View File

@@ -115,7 +115,7 @@ export interface NodeIKernelGroupService {
destroyMemberListScene(SceneId: string): void; destroyMemberListScene(SceneId: string): void;
getNextMemberList(sceneId: string, a: undefined, num: number): Promise<{ getNextMemberList(sceneId: string, a: undefined, num: number): Promise<{
errCode: number, errCode: number,
errMsg: string, errMsg: string,
result: { ids: string[], infos: Map<string, GroupMember>, finish: boolean, hasRobot: boolean } result: { ids: string[], infos: Map<string, GroupMember>, finish: boolean, hasRobot: boolean }
}>; }>;
@@ -225,7 +225,15 @@ export interface NodeIKernelGroupService {
getGroupStatisticInfo(groupCode: string): unknown; getGroupStatisticInfo(groupCode: string): unknown;
getGroupRemainAtTimes(groupCode: string): number; getGroupRemainAtTimes(groupCode: string): Promise<GeneralCallResult & {
atInfo: {
canAtAll: boolean
RemainAtAllCountForUin: number
RemainAtAllCountForGroup: number
atTimesMsg: string
canNotAtAllMsg: ''
}
}>;
getJoinGroupNoVerifyFlag(groupCode: string): unknown; getJoinGroupNoVerifyFlag(groupCode: string): unknown;

View File

@@ -86,7 +86,7 @@ export interface NodeQQNTWrapperUtil {
calcThumbSize(arg0: number, arg1: number, arg2: unknown): unknown; calcThumbSize(arg0: number, arg1: number, arg2: unknown): unknown;
fullWordToHalfWord(arg0: string): unknown; fullWordToHalfWord(word: string): unknown;
getNTUserDataInfoConfig(): unknown; getNTUserDataInfoConfig(): unknown;

View File

@@ -0,0 +1,30 @@
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
const SchemaData = {
type: 'object',
properties: {
group_id: { type: ['number', 'string'] }
},
required: ['group_id'],
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class GoCQHTTPGetGroupAtAllRemain extends BaseAction<Payload, any> {
actionName = ActionName.GoCQHTTP_GetGroupAtAllRemain;
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 = {
can_at_all: ret.atInfo.canAtAll,
remain_at_all_count_for_group: ret.atInfo.RemainAtAllCountForGroup,
remain_at_all_count_for_uin: ret.atInfo.RemainAtAllCountForUin
};
return data;
}
}

View File

@@ -0,0 +1,21 @@
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
const SchemaData = {
type: 'object',
properties: {
url: { type: 'string' },
},
required: ['url'],
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class GoCQHTTPCheckUrlSafely extends BaseAction<Payload, any> {
actionName = ActionName.GoCQHTTP_CheckUrlSafely;
payloadSchema = SchemaData;
async _handle(payload: Payload) {
return { level: 1 };
}
}

View File

@@ -0,0 +1,38 @@
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
const SchemaData = {
type: 'object',
properties: {
friend_id: { type: ['string', 'number'] },
temp_block: { type: 'boolean' },
temp_both_del: { type: 'boolean' },
},
required: ['friend_id'],
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class GoCQHTTPDeleteFriend extends BaseAction<Payload, any> {
actionName = ActionName.GoCQHTTP_DeleteFriend;
payloadSchema = SchemaData;
async _handle(payload: Payload) {
let uid = await this.core.apis.UserApi.getUidByUinV2(payload.friend_id.toString());
if (!uid) {
return {
valid: false,
message: '好友不存在',
};
}
let isBuddy = await this.core.apis.FriendApi.isBuddy(uid);
if (!isBuddy) {
return {
valid: false,
message: '不是好友',
};
}
return await this.core.apis.FriendApi.delBuudy(uid, payload.temp_block, payload.temp_both_del);
}
}

View File

@@ -0,0 +1,28 @@
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
const SchemaData = {
type: 'object',
properties: {
model: { type: 'string' },
}
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class GoCQHTTPGetModelShow extends BaseAction<Payload, any> {
actionName = ActionName.GoCQHTTP_GetModelShow;
payloadSchema = SchemaData;
async _handle(payload: Payload) {
if (!payload.model) {
payload.model = 'napcat';
}
return [{
variants: {
model_show: "napcat",
need_pay: false
}
}];
}
}

View File

@@ -0,0 +1,19 @@
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
const SchemaData = {
type: 'object',
properties: {},
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
//兼容性代码
export class GoCQHTTPSetModelShow extends BaseAction<Payload, any> {
actionName = ActionName.GoCQHTTP_SetModelShow;
payloadSchema = SchemaData;
async _handle(payload: Payload) {
return null;
}
}

View File

@@ -1,10 +0,0 @@
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
export default class SetModelShow extends BaseAction<null, null> {
actionName = ActionName.SetModelShow;
async _handle(payload: null): Promise<null> {
return null;
}
}

View File

@@ -21,7 +21,14 @@ export class GetGroupMemberList extends BaseAction<Payload, OB11GroupMember[]> {
async _handle(payload: Payload) { async _handle(payload: Payload) {
const groupIdStr = payload.group_id.toString(); const groupIdStr = payload.group_id.toString();
const groupMembers = await this.core.apis.GroupApi.getGroupMembersV2(groupIdStr); const noCache = payload.no_cache ? this.stringToBoolean(payload.no_cache) : false;
const memberCache = this.core.apis.GroupApi.groupMemberCache;
let groupMembers;
if (noCache) {
groupMembers = await this.core.apis.GroupApi.getGroupMembersV2(groupIdStr);
} else {
groupMembers = memberCache.get(groupIdStr) ?? await this.core.apis.GroupApi.getGroupMembersV2(groupIdStr);
}
const memberPromises = Array.from(groupMembers.values()).map(item => const memberPromises = Array.from(groupMembers.values()).map(item =>
OB11Entities.groupMember(groupIdStr, item) OB11Entities.groupMember(groupIdStr, item)
@@ -30,4 +37,7 @@ export class GetGroupMemberList extends BaseAction<Payload, OB11GroupMember[]> {
const MemberMap = new Map(_groupMembers.map(member => [member.user_id, member])); const MemberMap = new Map(_groupMembers.map(member => [member.user_id, member]));
return Array.from(MemberMap.values()); return Array.from(MemberMap.values());
} }
} stringToBoolean(str: string | boolean): boolean {
return typeof str === 'boolean' ? str : str.toLowerCase() === "true";
}
}

View File

@@ -71,7 +71,6 @@ import { FetchUserProfileLike } from './extends/FetchUserProfileLike';
import { NapCatCore } from '@/core'; import { NapCatCore } from '@/core';
import { NapCatOneBot11Adapter } from '@/onebot'; import { NapCatOneBot11Adapter } from '@/onebot';
import GetGuildProfile from './guild/GetGuildProfile'; import GetGuildProfile from './guild/GetGuildProfile';
import SetModelShow from './go-cqhttp/SetModelShow';
import { SetInputStatus } from './extends/SetInputStatus'; import { SetInputStatus } from './extends/SetInputStatus';
import { GetCSRF } from './system/GetCSRF'; import { GetCSRF } from './system/GetCSRF';
import { DelGroupNotice } from './group/DelGroupNotice'; import { DelGroupNotice } from './group/DelGroupNotice';
@@ -94,6 +93,11 @@ import { GetPacketStatus } from "@/onebot/action/packet/GetPacketStatus";
import { FriendPoke } from "@/onebot/action/user/FriendPoke"; import { FriendPoke } from "@/onebot/action/user/FriendPoke";
import { GetCredentials } from './system/GetCredentials'; import { GetCredentials } from './system/GetCredentials';
import { SetGroupSign } from './extends/SetGroupSign'; import { SetGroupSign } from './extends/SetGroupSign';
import { GoCQHTTPGetGroupAtAllRemain } from './go-cqhttp/GetGroupAtAllRemain';
import { GoCQHTTPCheckUrlSafely } from './go-cqhttp/GoCQHTTPCheckUrlSafely';
import { GoCQHTTPGetModelShow } from './go-cqhttp/GoCQHTTPGetModelShow';
import { GoCQHTTPSetModelShow } from './go-cqhttp/GoCQHTTPSetModelShow';
import { GoCQHTTPDeleteFriend } from './go-cqhttp/GoCQHTTPDeleteFriend';
export type ActionMap = Map<string, BaseAction<any, any>>; export type ActionMap = Map<string, BaseAction<any, any>>;
@@ -151,6 +155,8 @@ export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCo
new GetRobotUinRange(obContext, core), new GetRobotUinRange(obContext, core),
new GetFriendWithCategory(obContext, core), new GetFriendWithCategory(obContext, core),
//以下为go-cqhttp api //以下为go-cqhttp api
new GoCQHTTPDeleteFriend(obContext, core),
new GoCQHTTPCheckUrlSafely(obContext, core),
new GetOnlineClient(obContext, core), new GetOnlineClient(obContext, core),
new OCRImage(obContext, core), new OCRImage(obContext, core),
new IOCRImage(obContext, core), new IOCRImage(obContext, core),
@@ -158,6 +164,7 @@ export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCo
new SendGroupNotice(obContext, core), new SendGroupNotice(obContext, core),
new GetGroupNotice(obContext, core), new GetGroupNotice(obContext, core),
new GetGroupEssence(obContext, core), new GetGroupEssence(obContext, core),
new GoCQHTTPGetGroupAtAllRemain(obContext, core),
new GoCQHTTPSendForwardMsg(obContext, core), new GoCQHTTPSendForwardMsg(obContext, core),
new GoCQHTTPSendGroupForwardMsg(obContext, core), new GoCQHTTPSendGroupForwardMsg(obContext, core),
new GoCQHTTPSendPrivateForwardMsg(obContext, core), new GoCQHTTPSendPrivateForwardMsg(obContext, core),
@@ -180,7 +187,9 @@ export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCo
new FetchCustomFace(obContext, core), new FetchCustomFace(obContext, core),
new GoCQHTTPUploadPrivateFile(obContext, core), new GoCQHTTPUploadPrivateFile(obContext, core),
new GetGuildProfile(obContext, core), new GetGuildProfile(obContext, core),
new SetModelShow(obContext, core), new GoCQHTTPGetModelShow(obContext, core),
new GoCQHTTPSetModelShow(obContext, core),
new GoCQHTTPCheckUrlSafely(obContext, core),
new SetInputStatus(obContext, core), new SetInputStatus(obContext, core),
new GetCSRF(obContext, core), new GetCSRF(obContext, core),
new GetCredentials(obContext, core), new GetCredentials(obContext, core),

View File

@@ -116,9 +116,17 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
if (getSpecialMsgNum(payload, OB11MessageDataType.node)) { if (getSpecialMsgNum(payload, OB11MessageDataType.node)) {
const packetMode = this.core.apis.PacketApi.available; const packetMode = this.core.apis.PacketApi.available;
const returnMsgAndResId = packetMode let returnMsgAndResId: { message: RawMessage | null, res_id?: string } | null;
? await this.handleForwardedNodesPacket(peer, messages as OB11MessageNode[], payload.source, payload.news, payload.summary, payload.prompt) try {
: await this.handleForwardedNodes(peer, messages as OB11MessageNode[]); returnMsgAndResId = packetMode
? await this.handleForwardedNodesPacket(peer, messages as OB11MessageNode[], payload.source, payload.news, payload.summary, payload.prompt)
: await this.handleForwardedNodes(peer, messages as OB11MessageNode[]);
} catch (e) {
throw Error(packetMode ? `发送伪造合并转发消息失败: ${e}` : `发送合并转发消息失败: ${e}`);
}
if (!returnMsgAndResId) {
throw Error('发送合并转发消息失败returnMsgAndResId 为空!');
}
if (returnMsgAndResId.message) { if (returnMsgAndResId.message) {
const msgShortId = MessageUnique.createUniqueMsgId({ const msgShortId = MessageUnique.createUniqueMsgId({
guildId: '', guildId: '',
@@ -129,7 +137,6 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
} else if (returnMsgAndResId.res_id && !returnMsgAndResId.message) { } else if (returnMsgAndResId.res_id && !returnMsgAndResId.message) {
throw Error(`发送转发消息res_id${returnMsgAndResId.res_id} 失败`); throw Error(`发送转发消息res_id${returnMsgAndResId.res_id} 失败`);
} }
throw Error('发送转发消息失败');
} else { } else {
// if (getSpecialMsgNum(payload, OB11MessageDataType.music)) { // if (getSpecialMsgNum(payload, OB11MessageDataType.music)) {
// const music: OB11MessageCustomMusic = messages[0] as OB11MessageCustomMusic; // const music: OB11MessageCustomMusic = messages[0] as OB11MessageCustomMusic;
@@ -145,22 +152,42 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
return { message_id: returnMsg!.id! }; return { message_id: returnMsg!.id! };
} }
// TODO: recursively handle forwarded nodes private async uploadForwardedNodesPacket(msgPeer: Peer, messageNodes: OB11MessageNode[], source?: string, news?: {
private async handleForwardedNodesPacket(msgPeer: Peer, messageNodes: OB11MessageNode[], source?: string, news?: { text: string }[], summary?: string, prompt?: string): Promise<{ text: string
message: RawMessage | null, }[], summary?: string, prompt?: string, parentMeta?: {
user_id: string,
nickname: string,
}, dp: number = 0): Promise<{
finallySendElements: SendArkElement,
res_id?: string res_id?: string
}> { } | null> {
const logger = this.core.context.logger; const logger = this.core.context.logger;
const packetMsg: PacketMsg[] = []; const packetMsg: PacketMsg[] = [];
for (const node of messageNodes) { for (const node of messageNodes) {
if (dp >= 3) {
logger.logWarn('转发消息深度超过3层将停止解析');
break;
}
if ((node.data.id && typeof node.data.content !== "string") || !node.data.id) { if ((node.data.id && typeof node.data.content !== "string") || !node.data.id) {
const OB11Data = normalize(node.data.content); const OB11Data = normalize(node.type === OB11MessageDataType.node ? node.data.content : node);
const { sendElements } = await this.obContext.apis.MsgApi.createSendElements(OB11Data, msgPeer); let sendElements: SendMessageElement[];
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用户",
}, dp + 1);
sendElements = uploadReturnData?.finallySendElements ? [uploadReturnData.finallySendElements] : [];
} else {
const sendElementsCreateReturn = await this.obContext.apis.MsgApi.createSendElements(OB11Data, msgPeer);
sendElements = sendElementsCreateReturn.sendElements;
}
const packetMsgElements: rawMsgWithSendMsg = { const packetMsgElements: rawMsgWithSendMsg = {
senderUin: Number(node.data.user_id) ?? +this.core.selfInfo.uin, senderUin: Number(node.data.user_id ?? parentMeta?.user_id) || +this.core.selfInfo.uin,
senderName: node.data.nickname, senderName: node.data.nickname ?? parentMeta?.nickname ?? "QQ用户",
groupId: msgPeer.chatType === ChatType.KCHATTYPEGROUP ? +msgPeer.peerUid : undefined, groupId: msgPeer.chatType === ChatType.KCHATTYPEGROUP ? +msgPeer.peerUid : undefined,
time: Date.now(), time: Number(node.data.time) || Date.now(),
msg: sendElements, msg: sendElements,
}; };
logger.logDebug(`handleForwardedNodesPacket 开始转换 ${JSON.stringify(packetMsgElements)}`); logger.logDebug(`handleForwardedNodesPacket 开始转换 ${JSON.stringify(packetMsgElements)}`);
@@ -171,22 +198,37 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
logger.logDebug(`handleForwardedNodesPacket 跳过元素 ${JSON.stringify(node)}`); logger.logDebug(`handleForwardedNodesPacket 跳过元素 ${JSON.stringify(node)}`);
} }
} }
if (packetMsg.length === 0) {
logger.logWarn('handleForwardedNodesPacket 元素为空!');
return null;
}
const resid = await this.core.apis.PacketApi.sendUploadForwardMsg(packetMsg, msgPeer.chatType === ChatType.KCHATTYPEGROUP ? +msgPeer.peerUid : 0); const resid = await this.core.apis.PacketApi.sendUploadForwardMsg(packetMsg, msgPeer.chatType === ChatType.KCHATTYPEGROUP ? +msgPeer.peerUid : 0);
const forwardJson = ForwardMsgBuilder.fromPacketMsg(resid, packetMsg, source, news, summary, prompt); const forwardJson = ForwardMsgBuilder.fromPacketMsg(resid, packetMsg, source, news, summary, prompt);
const finallySendElements = { return {
elementType: ElementType.ARK, finallySendElements: {
elementId: "", elementType: ElementType.ARK,
arkElement: { elementId: "",
bytesData: JSON.stringify(forwardJson), arkElement: {
}, bytesData: JSON.stringify(forwardJson),
} as SendArkElement; },
let returnMsg: RawMessage | undefined; } as SendArkElement,
try { res_id: resid,
returnMsg = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(msgPeer, [finallySendElements], [], true).catch(_ => undefined); };
} catch (e) { }
logger.logWarn("发送伪造合并转发消息失败!", e);
} private async handleForwardedNodesPacket(msgPeer: Peer, messageNodes: OB11MessageNode[], source?: string, news?: {
return { message: returnMsg ?? null, res_id: resid }; text: string
}[], summary?: string, prompt?: string): Promise<{
message: RawMessage | null,
res_id?: string
}> {
let returnMsg: RawMessage | undefined, res_id: string | undefined;
const uploadReturnData = await this.uploadForwardedNodesPacket(msgPeer, messageNodes, source, news, summary, prompt);
res_id = uploadReturnData?.res_id;
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};
} }
private async handleForwardedNodes(destPeer: Peer, messageNodes: OB11MessageNode[]): Promise<{ private async handleForwardedNodes(destPeer: Peer, messageNodes: OB11MessageNode[]): Promise<{

View File

@@ -57,11 +57,11 @@ export enum ActionName {
// go-cqhttp // go-cqhttp
SetQQProfile = 'set_qq_profile', SetQQProfile = 'set_qq_profile',
// QidianGetAccountInfo = 'qidian_get_account_info', // QidianGetAccountInfo = 'qidian_get_account_info',
// GetModelShow = '_get_model_show', GoCQHTTP_GetModelShow = '_get_model_show',
// SetModelShow = '_set_model_show', GoCQHTTP_SetModelShow = '_set_model_show',
GetOnlineClient = 'get_online_clients', GetOnlineClient = 'get_online_clients',
// GetUnidirectionalFriendList = 'get_unidirectional_friend_list', // GetUnidirectionalFriendList = 'get_unidirectional_friend_list',
// DeleteFriend = 'delete_friend', GoCQHTTP_DeleteFriend = 'delete_friend',
// DeleteUnidirectionalFriendList = 'delete_unidirectional_friend', // DeleteUnidirectionalFriendList = 'delete_unidirectional_friend',
GoCQHTTP_MarkMsgAsRead = 'mark_msg_as_read', GoCQHTTP_MarkMsgAsRead = 'mark_msg_as_read',
GoCQHTTP_SendGroupForwardMsg = 'send_group_forward_msg', GoCQHTTP_SendGroupForwardMsg = 'send_group_forward_msg',
@@ -71,7 +71,7 @@ export enum ActionName {
IOCRImage = '.ocr_image', IOCRImage = '.ocr_image',
GetGroupSystemMsg = 'get_group_system_msg', GetGroupSystemMsg = 'get_group_system_msg',
GoCQHTTP_GetEssenceMsg = 'get_essence_msg_list', GoCQHTTP_GetEssenceMsg = 'get_essence_msg_list',
// GetGroupAtAllRemain = 'get_group_at_all_remain', GoCQHTTP_GetGroupAtAllRemain = 'get_group_at_all_remain',
SetGroupPortrait = 'set_group_portrait', SetGroupPortrait = 'set_group_portrait',
SetEssenceMsg = 'set_essence_msg', SetEssenceMsg = 'set_essence_msg',
DelEssenceMsg = 'delete_essence_msg', DelEssenceMsg = 'delete_essence_msg',
@@ -88,8 +88,8 @@ export enum ActionName {
GOCQHTTP_UploadPrivateFile = 'upload_private_file', GOCQHTTP_UploadPrivateFile = 'upload_private_file',
// GOCQHTTP_ReloadEventFilter = 'reload_event_filter', // GOCQHTTP_ReloadEventFilter = 'reload_event_filter',
GoCQHTTP_DownloadFile = 'download_file', GoCQHTTP_DownloadFile = 'download_file',
// GoCQHTTP_CheckUrlSafely = 'check_url_safely', GoCQHTTP_CheckUrlSafely = 'check_url_safely',
// GoCQHTTP_GetWordSlices = '.get_word_slices', GoCQHTTP_GetWordSlices = '.get_word_slices',
GoCQHTTP_HandleQuickAction = '.handle_quick_operation', GoCQHTTP_HandleQuickAction = '.handle_quick_operation',
// 以下为扩展napcat扩展 // 以下为扩展napcat扩展

View File

@@ -152,6 +152,11 @@ export interface OB11MessageNode {
user_id?: number | string // number user_id?: number | string // number
nickname: string nickname: string
content: OB11MessageMixType content: OB11MessageMixType
source?: string,
news?: { text: string }[],
summary?: string,
prompt?: string
time?: string
}; };
} }
@@ -225,6 +230,7 @@ export interface OB11PostSendMsg {
news?: { text: string }[], news?: { text: string }[],
summary?: string, summary?: string,
prompt?: string prompt?: string
time?: string
} }
export interface OB11PostContext { export interface OB11PostContext {
message_type?: 'private' | 'group' message_type?: 'private' | 'group'

View File

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

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.1.6", "napcat-update-button", "secondary") SettingButton("V3.1.7", "napcat-update-button", "secondary")
) )
]), ]),
SettingList([ SettingList([