fix: 摘要化 QQBot 图片消息日志
This commit is contained in:
parent
2c833e83f2
commit
73280d379b
@ -128,6 +128,16 @@ export class ToolsService {
|
|||||||
return this.toTrimmedString(value).replace(/\s+/g, ' ');
|
return this.toTrimmedString(value).replace(/\s+/g, ' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toStoredMessageText(value: unknown, maxLength = 4000) {
|
||||||
|
const text = this.toTrimmedString(value).replace(
|
||||||
|
/\[CQ:image,file=base64:\/\/([A-Za-z0-9+/=]+)\]/g,
|
||||||
|
(_match, payload: string) =>
|
||||||
|
`[CQ:image,file=base64://<${payload.length} chars>]`,
|
||||||
|
);
|
||||||
|
if (text.length <= maxLength) return text;
|
||||||
|
return `${text.slice(0, maxLength)}...<truncated ${text.length - maxLength} chars>`;
|
||||||
|
}
|
||||||
|
|
||||||
toStringId(value: number | string | undefined | null) {
|
toStringId(value: number | string | undefined | null) {
|
||||||
return value === undefined || value === null ? '' : `${value}`;
|
return value === undefined || value === null ? '' : `${value}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -179,7 +179,9 @@ export class QqbotCommandService {
|
|||||||
input: JSON.stringify(params.input || {}),
|
input: JSON.stringify(params.input || {}),
|
||||||
operationKey: params.command.operationKey,
|
operationKey: params.command.operationKey,
|
||||||
output:
|
output:
|
||||||
params.output === undefined ? null : JSON.stringify(params.output),
|
params.output === undefined
|
||||||
|
? null
|
||||||
|
: this.stringifyStoredOutput(params.output),
|
||||||
pluginKey: params.command.pluginKey,
|
pluginKey: params.command.pluginKey,
|
||||||
rawMessage: params.message.messageText,
|
rawMessage: params.message.messageText,
|
||||||
selfId: params.message.selfId,
|
selfId: params.message.selfId,
|
||||||
@ -288,6 +290,21 @@ export class QqbotCommandService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private stringifyStoredOutput(output: any) {
|
||||||
|
if (
|
||||||
|
output &&
|
||||||
|
typeof output === 'object' &&
|
||||||
|
!Array.isArray(output) &&
|
||||||
|
typeof output.replyText === 'string'
|
||||||
|
) {
|
||||||
|
return JSON.stringify({
|
||||||
|
...output,
|
||||||
|
replyText: this.toolsService.toStoredMessageText(output.replyText),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return JSON.stringify(output);
|
||||||
|
}
|
||||||
|
|
||||||
private toRawBody(command: QqbotCommand): QqbotCommandBodyDto {
|
private toRawBody(command: QqbotCommand): QqbotCommandBodyDto {
|
||||||
return {
|
return {
|
||||||
aliases: this.parseList(command.aliases),
|
aliases: this.parseList(command.aliases),
|
||||||
|
|||||||
@ -103,12 +103,19 @@ export class QqbotSendService {
|
|||||||
this.rateLimitService.assertCanSend(account.selfId, params.targetId);
|
this.rateLimitService.assertCanSend(account.selfId, params.targetId);
|
||||||
|
|
||||||
const { action, actionParams } = this.buildAction(params);
|
const { action, actionParams } = this.buildAction(params);
|
||||||
|
const storedMessageText = this.toolsService.toStoredMessageText(
|
||||||
|
params.message,
|
||||||
|
);
|
||||||
|
const storedActionParams = this.toStoredActionParams(
|
||||||
|
actionParams,
|
||||||
|
storedMessageText,
|
||||||
|
);
|
||||||
|
|
||||||
const log = await this.sendLogRepository.save(
|
const log = await this.sendLogRepository.save(
|
||||||
this.sendLogRepository.create({
|
this.sendLogRepository.create({
|
||||||
action,
|
action,
|
||||||
messageText: params.message,
|
messageText: storedMessageText,
|
||||||
params: actionParams,
|
params: storedActionParams,
|
||||||
selfId: account.selfId,
|
selfId: account.selfId,
|
||||||
status: 'pending',
|
status: 'pending',
|
||||||
targetId: params.targetId,
|
targetId: params.targetId,
|
||||||
@ -151,7 +158,7 @@ export class QqbotSendService {
|
|||||||
if (success) {
|
if (success) {
|
||||||
await this.messageService.saveOutgoing({
|
await this.messageService.saveOutgoing({
|
||||||
messageId,
|
messageId,
|
||||||
messageText: params.message,
|
messageText: storedMessageText,
|
||||||
messageType: params.targetType,
|
messageType: params.targetType,
|
||||||
selfId: account.selfId,
|
selfId: account.selfId,
|
||||||
targetId: params.targetId,
|
targetId: params.targetId,
|
||||||
@ -175,9 +182,8 @@ export class QqbotSendService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async getReverseWsService(): Promise<QqbotReverseActionSender> {
|
private async getReverseWsService(): Promise<QqbotReverseActionSender> {
|
||||||
const { QqbotReverseWsService } = await import(
|
const { QqbotReverseWsService } =
|
||||||
'../connection/qqbot-reverse-ws.service'
|
await import('../connection/qqbot-reverse-ws.service');
|
||||||
);
|
|
||||||
return this.moduleRef.get<QqbotReverseActionSender>(QqbotReverseWsService, {
|
return this.moduleRef.get<QqbotReverseActionSender>(QqbotReverseWsService, {
|
||||||
strict: false,
|
strict: false,
|
||||||
});
|
});
|
||||||
@ -212,4 +218,16 @@ export class QqbotSendService {
|
|||||||
actionParams: { message: params.message, user_id: params.targetId },
|
actionParams: { message: params.message, user_id: params.targetId },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private toStoredActionParams(
|
||||||
|
actionParams: Record<string, any>,
|
||||||
|
storedMessageText: string,
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
...actionParams,
|
||||||
|
...(actionParams.message === undefined
|
||||||
|
? {}
|
||||||
|
: { message: storedMessageText }),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
21
test/common/tool.service.spec.ts
Normal file
21
test/common/tool.service.spec.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { ToolsService } from '@/common';
|
||||||
|
|
||||||
|
describe('ToolsService', () => {
|
||||||
|
const service = new ToolsService();
|
||||||
|
|
||||||
|
it('summarizes CQ image base64 payloads before storing message text', () => {
|
||||||
|
const stored = service.toStoredMessageText(
|
||||||
|
`before [CQ:image,file=base64://${'a'.repeat(70000)}] after`,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(stored).toBe('before [CQ:image,file=base64://<70000 chars>] after');
|
||||||
|
expect(stored.length).toBeLessThan(100);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('truncates oversized stored message text after image summarization', () => {
|
||||||
|
const stored = service.toStoredMessageText('x'.repeat(4100), 100);
|
||||||
|
|
||||||
|
expect(stored).toContain('<truncated 4000 chars>');
|
||||||
|
expect(stored.length).toBeLessThan(140);
|
||||||
|
});
|
||||||
|
});
|
||||||
73
test/qqbot/send/qqbot-send.service.spec.ts
Normal file
73
test/qqbot/send/qqbot-send.service.spec.ts
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import { ToolsService } from '@/common';
|
||||||
|
import { QqbotSendService } from '@/qqbot/send/qqbot-send.service';
|
||||||
|
|
||||||
|
describe('QqbotSendService', () => {
|
||||||
|
it('stores summarized CQ image payloads while sending the original message', async () => {
|
||||||
|
const originalMessage = `[CQ:image,file=base64://${'a'.repeat(70000)}]`;
|
||||||
|
const reverseWsService = {
|
||||||
|
sendAction: jest.fn().mockResolvedValue({
|
||||||
|
data: { message_id: 'message-1' },
|
||||||
|
retcode: 0,
|
||||||
|
status: 'ok',
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
const sendLogRepository = {
|
||||||
|
create: jest.fn((payload) => payload),
|
||||||
|
save: jest.fn(async (payload) => ({ ...payload, id: 'log-1' })),
|
||||||
|
update: jest.fn(),
|
||||||
|
};
|
||||||
|
const messageService = {
|
||||||
|
saveOutgoing: jest.fn(),
|
||||||
|
};
|
||||||
|
const busService = {
|
||||||
|
publish: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const service = new QqbotSendService(
|
||||||
|
sendLogRepository as any,
|
||||||
|
{
|
||||||
|
getDefaultAccount: jest.fn().mockResolvedValue({ selfId: 'bot-1' }),
|
||||||
|
} as any,
|
||||||
|
busService as any,
|
||||||
|
messageService as any,
|
||||||
|
{ get: jest.fn(() => reverseWsService) } as any,
|
||||||
|
{ assertCanSend: jest.fn() } as any,
|
||||||
|
new ToolsService(),
|
||||||
|
);
|
||||||
|
(service as any).getReverseWsService = jest
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue(reverseWsService);
|
||||||
|
|
||||||
|
await service.sendText({
|
||||||
|
message: originalMessage,
|
||||||
|
selfId: 'bot-1',
|
||||||
|
targetId: 'group-1',
|
||||||
|
targetType: 'group',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(sendLogRepository.save).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
messageText: '[CQ:image,file=base64://<70000 chars>]',
|
||||||
|
params: expect.objectContaining({
|
||||||
|
message: '[CQ:image,file=base64://<70000 chars>]',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(busService.publish).toHaveBeenCalledWith(
|
||||||
|
expect.any(String),
|
||||||
|
expect.objectContaining({
|
||||||
|
params: expect.objectContaining({ message: originalMessage }),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(reverseWsService.sendAction).toHaveBeenCalledWith(
|
||||||
|
'bot-1',
|
||||||
|
'send_group_msg',
|
||||||
|
expect.objectContaining({ message: originalMessage }),
|
||||||
|
);
|
||||||
|
expect(messageService.saveOutgoing).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
messageText: '[CQ:image,file=base64://<70000 chars>]',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user