131 lines
4.9 KiB
TypeScript
131 lines
4.9 KiB
TypeScript
import { gunzipSync, inflateSync } from 'node:zlib';
|
|
import { describe, expect, it, vi } from 'vitest';
|
|
import { NapProtoMsg } from 'napcat-protobuf';
|
|
import { ChatType, ElementType, FaceType } from '@/napcat-core/types/msg';
|
|
import { PacketMsgConverter } from '@/napcat-core/packet/message/converter';
|
|
import { PacketMsgFaceElement } from '@/napcat-core/packet/message/element';
|
|
import { LongMsgResult, QBigFaceExtra, SendLongMsgReq } from '@/napcat-core/packet/transformer/proto';
|
|
import UploadForwardMsgV2 from '@/napcat-core/packet/transformer/message/UploadForwardMsgV2';
|
|
import { OneBotMsgApi } from '@/napcat-onebot/api/msg';
|
|
import { OB11MessageData, OB11MessageDataType } from '@/napcat-onebot/types/message';
|
|
|
|
// Keep the real message types without starting the adapter's WebUI/network modules.
|
|
vi.mock('@/napcat-onebot/index', async () => await import('@/napcat-onebot/types'));
|
|
vi.mock('napcat-core/packet/transformer/proto', async () => await import('@/napcat-core/packet/transformer/proto'));
|
|
|
|
const text = (value: string): OB11MessageData => ({
|
|
type: OB11MessageDataType.text,
|
|
data: { text: value },
|
|
});
|
|
const face = (id: string): OB11MessageData => ({
|
|
type: OB11MessageDataType.face,
|
|
data: { id },
|
|
});
|
|
|
|
async function buildForwardElements (content: OB11MessageData[], groupUin = 0) {
|
|
const api = new OneBotMsgApi(
|
|
{} as ConstructorParameters<typeof OneBotMsgApi>[0],
|
|
{} as ConstructorParameters<typeof OneBotMsgApi>[1]
|
|
);
|
|
const { sendElements } = await api.createSendElements(content, {
|
|
chatType: groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C,
|
|
peerUid: groupUin ? String(groupUin) : 'u_friend',
|
|
guildId: '',
|
|
});
|
|
const msg = new PacketMsgConverter().rawMsgWithSendMsgToPacketMsg({
|
|
senderUin: 123456,
|
|
senderName: '测试',
|
|
groupId: groupUin,
|
|
time: 1700000000,
|
|
msg: sendElements,
|
|
});
|
|
const packet = UploadForwardMsgV2.build('u_self', [{ actionCommand: 'MultiMsg', actionMsg: [msg] }], groupUin);
|
|
const request = new NapProtoMsg(SendLongMsgReq).decode(packet.data);
|
|
const payload = new NapProtoMsg(LongMsgResult).decode(gunzipSync(request.info.payload!));
|
|
return payload.action[0]!.actionData.msgBody[0]!.body!.richText!.elems;
|
|
}
|
|
|
|
describe.each([
|
|
{ name: 'private', groupUin: 0 },
|
|
{ name: 'group', groupUin: 654321 },
|
|
])('$name forward face encoding', ({ groupUin }) => {
|
|
it('keeps the text immediately after the animated face in issue #2097', async () => {
|
|
const elems = await buildForwardElements([text('1234'), face('317'), text('5678'), text('在')], groupUin);
|
|
|
|
expect(elems).toMatchObject([
|
|
{ text: { str: '1234' } },
|
|
{ commonElem: { serviceType: 37 } },
|
|
{ text: { str: '/菜汪' } },
|
|
{ text: { str: '5678' } },
|
|
{ text: { str: '在' } },
|
|
]);
|
|
expect(new NapProtoMsg(QBigFaceExtra).decode(elems[1]!.commonElem!.pbElem).faceId).toBe(317);
|
|
});
|
|
|
|
it('gives each consecutive animated face its own compatibility text', async () => {
|
|
const elems = await buildForwardElements([face('317'), face('318'), text('after')], groupUin);
|
|
|
|
expect(elems).toMatchObject([
|
|
{ commonElem: { serviceType: 37 } },
|
|
{ text: { str: '/菜汪' } },
|
|
{ commonElem: { serviceType: 37 } },
|
|
{ text: { str: '/崇拜' } },
|
|
{ text: { str: 'after' } },
|
|
]);
|
|
});
|
|
|
|
it('includes compatibility text when the animated face ends the node', async () => {
|
|
const elems = await buildForwardElements([face('317')], groupUin);
|
|
|
|
expect(elems).toMatchObject([
|
|
{ commonElem: { serviceType: 37 } },
|
|
{ text: { str: '/菜汪' } },
|
|
]);
|
|
});
|
|
|
|
it('keeps a non-text segment immediately after an animated face', async () => {
|
|
const json = JSON.stringify({ app: 'test', prompt: 'card' });
|
|
const elems = await buildForwardElements([
|
|
face('317'),
|
|
{ type: OB11MessageDataType.json, data: { data: json } },
|
|
text('after'),
|
|
], groupUin);
|
|
|
|
expect(elems).toMatchObject([
|
|
{ commonElem: { serviceType: 37 } },
|
|
{ text: { str: '/菜汪' } },
|
|
{ lightAppElem: {} },
|
|
{ text: { str: 'after' } },
|
|
]);
|
|
expect(inflateSync(elems[2]!.lightAppElem!.data.subarray(1)).toString()).toBe(json);
|
|
});
|
|
|
|
it('does not insert compatibility text after legacy or small faces', async () => {
|
|
const elems = await buildForwardElements([face('14'), text('legacy'), face('264'), text('small')], groupUin);
|
|
|
|
expect(elems).toMatchObject([
|
|
{ face: { index: 14 } },
|
|
{ text: { str: 'legacy' } },
|
|
{ commonElem: { serviceType: 33 } },
|
|
{ text: { str: 'small' } },
|
|
]);
|
|
});
|
|
});
|
|
|
|
it.each([undefined, ''])('provides non-empty compatibility text when faceText is %s', (faceText) => {
|
|
const face = new PacketMsgFaceElement({
|
|
elementType: ElementType.FACE,
|
|
elementId: '',
|
|
faceElement: {
|
|
faceIndex: 317,
|
|
faceType: FaceType.AniSticke,
|
|
faceText,
|
|
},
|
|
});
|
|
|
|
expect(face.buildElement()).toMatchObject([
|
|
{ commonElem: { serviceType: 37 } },
|
|
{ text: { str: '[表情]' } },
|
|
]);
|
|
});
|