SnowLuma v2.4.2 source snapshot
Dev Build / cleanup (push) Waiting to run
Dev Build / typecheck (push) Waiting to run
Publish MCP to npm / Build, test, and publish MCP (push) Waiting to run
Publish SDK to npm / Build, test, and publish SDK (push) Waiting to run
Sync action catalog to docs / sync (push) Waiting to run
Publish UI to npm / Build and publish UI (push) Waiting to run
Build and Release / preflight (push) Waiting to run
Dev Build / cleanup (push) Waiting to run
Dev Build / typecheck (push) Waiting to run
Publish MCP to npm / Build, test, and publish MCP (push) Waiting to run
Publish SDK to npm / Build, test, and publish SDK (push) Waiting to run
Sync action catalog to docs / sync (push) Waiting to run
Publish UI to npm / Build and publish UI (push) Waiting to run
Build and Release / preflight (push) Waiting to run
This commit is contained in:
commit
fa9c73363d
1367 files changed
+355735
No files matched your search
@@ -0,0 +1,288 @@
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { convertFriendMessage } from '../src/event-converter/to-message';
|
||||
import {
|
||||
hashMessageIdInt32,
|
||||
PRIVATE_NT_MESSAGE_EVENT,
|
||||
PRIVATE_SENT_MESSAGE_EVENT,
|
||||
} from '../src/message-id';
|
||||
import { MessageStore } from '../src/message-store';
|
||||
import { createMessageIdResolver as productionResolver } from '../src/message-id-resolver';
|
||||
import type { ConverterContext } from '../src/event-converter';
|
||||
import type { FriendMessage } from '@snowluma/protocol/events';
|
||||
|
||||
const SELF_ID = 3961840894;
|
||||
const PEER_ID = 2705892349;
|
||||
|
||||
describe('private reply id matches send receipt (#417, #433)', () => {
|
||||
let dir: string;
|
||||
let store: MessageStore;
|
||||
|
||||
beforeEach(async () => {
|
||||
dir = fs.mkdtempSync(path.join(os.tmpdir(), 'sl-reply-id-'));
|
||||
store = new MessageStore(path.join(dir, 'messages.db'));
|
||||
await store.initialization;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await store.close();
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('matches quoted text instead of a later voice message in the same second (#502)', async () => {
|
||||
const sentAt = 1_790_000_000;
|
||||
|
||||
for (const [id, message] of [
|
||||
[101, [{ type: 'text', data: { text: 'the quoted text' } }]],
|
||||
[102, [{ type: 'record', data: { file: 'voice.amr' } }]],
|
||||
] as const) {
|
||||
await store.storeMeta(id, {
|
||||
isGroup: false,
|
||||
targetId: PEER_ID,
|
||||
sequence: id,
|
||||
sequenceAuthoritative: true,
|
||||
eventName: PRIVATE_NT_MESSAGE_EVENT,
|
||||
clientSequence: id + 1000,
|
||||
privateDirection: 'outgoing',
|
||||
random: id,
|
||||
timestamp: sentAt,
|
||||
});
|
||||
await store.storeEvent(id, false, PEER_ID, id, PRIVATE_NT_MESSAGE_EVENT, {
|
||||
time: sentAt,
|
||||
post_type: 'message_sent',
|
||||
message_type: 'private',
|
||||
sub_type: 'friend',
|
||||
message: JSON.parse(JSON.stringify(message)),
|
||||
});
|
||||
}
|
||||
|
||||
await store.close();
|
||||
store = new MessageStore(path.join(dir, 'messages.db'));
|
||||
await store.initialization;
|
||||
const ctx: ConverterContext = {
|
||||
selfId: SELF_ID,
|
||||
imageUrlResolver: null,
|
||||
mediaUrlResolver: null,
|
||||
mediaSegmentSink: null,
|
||||
messageIdResolver: await productionResolver(store),
|
||||
};
|
||||
const event: FriendMessage = {
|
||||
kind: 'friend_message',
|
||||
time: sentAt + 2,
|
||||
selfUin: SELF_ID,
|
||||
senderUin: PEER_ID,
|
||||
peerUin: PEER_ID,
|
||||
senderUid: 'u_peer',
|
||||
senderNick: 'peer',
|
||||
msgSeq: 2773,
|
||||
ntMsgSeq: 103,
|
||||
clientSeq: 2773,
|
||||
sequenceAuthoritative: true,
|
||||
msgId: 2,
|
||||
elements: [
|
||||
{
|
||||
type: 'reply',
|
||||
replySeq: 9999,
|
||||
replySenderUin: SELF_ID,
|
||||
replyTime: sentAt,
|
||||
replyElements: [{ type: 'text', text: 'the quoted text' }],
|
||||
},
|
||||
],
|
||||
};
|
||||
const json = await convertFriendMessage(ctx, event);
|
||||
expect(json.message).toEqual([{ type: 'reply', data: { id: '101' } }]);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: '#433',
|
||||
ntSeq: 682,
|
||||
localClientSeq: 201_616_628,
|
||||
quoteSeq: 27_892,
|
||||
sentAt: 1_788_102_428,
|
||||
sendId: -12_721_351,
|
||||
},
|
||||
{
|
||||
name: '#417',
|
||||
ntSeq: 655,
|
||||
localClientSeq: 509_147_526,
|
||||
quoteSeq: 63_878,
|
||||
sentAt: 1_787_409_444,
|
||||
sendId: 866_508_396,
|
||||
},
|
||||
])(
|
||||
'$name resolves a quote of a bot-sent private message to the send receipt id',
|
||||
async (fixture) => {
|
||||
const sendId = hashMessageIdInt32(fixture.ntSeq, PEER_ID, PRIVATE_NT_MESSAGE_EVENT);
|
||||
expect(sendId).toBe(fixture.sendId);
|
||||
expect(sendId).not.toBe(
|
||||
hashMessageIdInt32(fixture.quoteSeq, PEER_ID, PRIVATE_SENT_MESSAGE_EVENT),
|
||||
);
|
||||
|
||||
await store.storeMeta(sendId, {
|
||||
isGroup: false,
|
||||
targetId: PEER_ID,
|
||||
sequence: fixture.ntSeq,
|
||||
sequenceAuthoritative: true,
|
||||
eventName: PRIVATE_NT_MESSAGE_EVENT,
|
||||
clientSequence: fixture.localClientSeq,
|
||||
privateDirection: 'outgoing',
|
||||
random: 1,
|
||||
timestamp: fixture.sentAt,
|
||||
});
|
||||
|
||||
const ctx: ConverterContext = {
|
||||
selfId: SELF_ID,
|
||||
imageUrlResolver: null,
|
||||
mediaUrlResolver: null,
|
||||
mediaSegmentSink: null,
|
||||
messageIdResolver: await productionResolver(store),
|
||||
};
|
||||
|
||||
const event: FriendMessage = {
|
||||
kind: 'friend_message',
|
||||
time: fixture.sentAt + 2,
|
||||
selfUin: SELF_ID,
|
||||
senderUin: PEER_ID,
|
||||
peerUin: PEER_ID,
|
||||
senderUid: 'u_peer',
|
||||
senderNick: 'peer',
|
||||
msgSeq: 2773,
|
||||
ntMsgSeq: fixture.ntSeq + 1,
|
||||
clientSeq: 2773,
|
||||
sequenceAuthoritative: true,
|
||||
msgId: 2,
|
||||
elements: [
|
||||
{
|
||||
type: 'reply',
|
||||
replySeq: fixture.quoteSeq,
|
||||
replySenderUin: SELF_ID,
|
||||
replyTime: fixture.sentAt,
|
||||
},
|
||||
{ type: 'text', text: '这是对bot消息的回复消息' },
|
||||
],
|
||||
};
|
||||
|
||||
const json = await convertFriendMessage(ctx, event);
|
||||
const reply = (json.message as Array<{ type: string; data: { id: string } }>)[0];
|
||||
expect(reply).toEqual({ type: 'reply', data: { id: String(sendId) } });
|
||||
},
|
||||
);
|
||||
|
||||
it('resolves a bot quote when the quote time drifts from the send receipt (#465)', async () => {
|
||||
const ntSeq = 6008;
|
||||
const localClientSeq = 567_154_552;
|
||||
const quoteSeq = 6008;
|
||||
const sentAt = 1_789_720_450;
|
||||
const quoteTime = sentAt + 2;
|
||||
const sendId = hashMessageIdInt32(ntSeq, PEER_ID, PRIVATE_NT_MESSAGE_EVENT);
|
||||
expect(sendId).not.toBe(hashMessageIdInt32(quoteSeq, PEER_ID, PRIVATE_SENT_MESSAGE_EVENT));
|
||||
|
||||
await store.storeMeta(sendId, {
|
||||
isGroup: false,
|
||||
targetId: PEER_ID,
|
||||
sequence: ntSeq,
|
||||
sequenceAuthoritative: true,
|
||||
eventName: PRIVATE_NT_MESSAGE_EVENT,
|
||||
clientSequence: localClientSeq,
|
||||
privateDirection: 'outgoing',
|
||||
random: 1,
|
||||
timestamp: sentAt,
|
||||
});
|
||||
|
||||
const ctx: ConverterContext = {
|
||||
selfId: SELF_ID,
|
||||
imageUrlResolver: null,
|
||||
mediaUrlResolver: null,
|
||||
mediaSegmentSink: null,
|
||||
messageIdResolver: await productionResolver(store),
|
||||
};
|
||||
|
||||
const event: FriendMessage = {
|
||||
kind: 'friend_message',
|
||||
time: quoteTime + 1,
|
||||
selfUin: SELF_ID,
|
||||
senderUin: PEER_ID,
|
||||
peerUin: PEER_ID,
|
||||
senderUid: 'u_peer',
|
||||
senderNick: 'peer',
|
||||
msgSeq: 6010,
|
||||
ntMsgSeq: ntSeq + 1,
|
||||
clientSeq: 6010,
|
||||
sequenceAuthoritative: true,
|
||||
msgId: 2,
|
||||
elements: [
|
||||
{
|
||||
type: 'reply',
|
||||
replySeq: quoteSeq,
|
||||
replySenderUin: SELF_ID,
|
||||
replyTime: quoteTime,
|
||||
},
|
||||
{ type: 'text', text: '111' },
|
||||
],
|
||||
};
|
||||
|
||||
const json = await convertFriendMessage(ctx, event);
|
||||
const reply = (json.message as Array<{ type: string; data: { id: string } }>)[0];
|
||||
expect(reply).toEqual({ type: 'reply', data: { id: String(sendId) } });
|
||||
});
|
||||
|
||||
it('resolves a bot quote by nearby send time when the quote sequence is unrelated', async () => {
|
||||
const ntSeq = 682;
|
||||
const localClientSeq = 201_616_628;
|
||||
const quoteSeq = 27_892;
|
||||
const sentAt = 1_788_102_428;
|
||||
const quoteTime = sentAt + 2;
|
||||
const sendId = hashMessageIdInt32(ntSeq, PEER_ID, PRIVATE_NT_MESSAGE_EVENT);
|
||||
|
||||
await store.storeMeta(sendId, {
|
||||
isGroup: false,
|
||||
targetId: PEER_ID,
|
||||
sequence: ntSeq,
|
||||
sequenceAuthoritative: true,
|
||||
eventName: PRIVATE_NT_MESSAGE_EVENT,
|
||||
clientSequence: localClientSeq,
|
||||
privateDirection: 'outgoing',
|
||||
random: 1,
|
||||
timestamp: sentAt,
|
||||
});
|
||||
|
||||
const ctx: ConverterContext = {
|
||||
selfId: SELF_ID,
|
||||
imageUrlResolver: null,
|
||||
mediaUrlResolver: null,
|
||||
mediaSegmentSink: null,
|
||||
messageIdResolver: await productionResolver(store),
|
||||
};
|
||||
|
||||
const event: FriendMessage = {
|
||||
kind: 'friend_message',
|
||||
time: quoteTime + 1,
|
||||
selfUin: SELF_ID,
|
||||
senderUin: PEER_ID,
|
||||
peerUin: PEER_ID,
|
||||
senderUid: 'u_peer',
|
||||
senderNick: 'peer',
|
||||
msgSeq: 2773,
|
||||
ntMsgSeq: ntSeq + 1,
|
||||
clientSeq: 2773,
|
||||
sequenceAuthoritative: true,
|
||||
msgId: 2,
|
||||
elements: [
|
||||
{
|
||||
type: 'reply',
|
||||
replySeq: quoteSeq,
|
||||
replySenderUin: SELF_ID,
|
||||
replyTime: quoteTime,
|
||||
},
|
||||
{ type: 'text', text: '这是对bot消息的回复消息' },
|
||||
],
|
||||
};
|
||||
|
||||
const json = await convertFriendMessage(ctx, event);
|
||||
const reply = (json.message as Array<{ type: string; data: { id: string } }>)[0];
|
||||
expect(reply).toEqual({ type: 'reply', data: { id: String(sendId) } });
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user