feat: add Telegram Topics (forum mode) support
- buildJid() constructs tg:{chatId}:{threadId} for topic messages
- parseJid() extracts chatId + threadId from JID for outbound routing
- /chatid command shows thread ID in forum topics
- sendMessage and setTyping pass message_thread_id when present
- All message handlers (text, photo, voice, media) use thread-aware JIDs
Allows each forum topic to be registered as an independent Nanoclaw group.
This commit is contained in:
113
src/db.test.ts
113
src/db.test.ts
@@ -2,14 +2,14 @@ import { describe, it, expect, beforeEach } from 'vitest';
|
||||
|
||||
import {
|
||||
_initTestDatabase,
|
||||
closeDatabase,
|
||||
createTask,
|
||||
deleteTask,
|
||||
getAllChats,
|
||||
getAllRegisteredGroups,
|
||||
getMessagesSince,
|
||||
getNewMessages,
|
||||
getTaskById,
|
||||
setRegisteredGroup,
|
||||
logTaskRun,
|
||||
storeChatMetadata,
|
||||
storeMessage,
|
||||
updateTask,
|
||||
@@ -391,94 +391,37 @@ describe('task CRUD', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// --- LIMIT behavior ---
|
||||
|
||||
describe('message query LIMIT', () => {
|
||||
beforeEach(() => {
|
||||
storeChatMetadata('group@g.us', '2024-01-01T00:00:00.000Z');
|
||||
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
store({
|
||||
id: `lim-${i}`,
|
||||
chat_jid: 'group@g.us',
|
||||
sender: 'user@s.whatsapp.net',
|
||||
sender_name: 'User',
|
||||
content: `message ${i}`,
|
||||
timestamp: `2024-01-01T00:00:${String(i).padStart(2, '0')}.000Z`,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('getNewMessages caps to limit and returns most recent in chronological order', () => {
|
||||
const { messages, newTimestamp } = getNewMessages(
|
||||
['group@g.us'],
|
||||
'2024-01-01T00:00:00.000Z',
|
||||
'Andy',
|
||||
3,
|
||||
);
|
||||
expect(messages).toHaveLength(3);
|
||||
expect(messages[0].content).toBe('message 8');
|
||||
expect(messages[2].content).toBe('message 10');
|
||||
// Chronological order preserved
|
||||
expect(messages[1].timestamp > messages[0].timestamp).toBe(true);
|
||||
// newTimestamp reflects latest returned row
|
||||
expect(newTimestamp).toBe('2024-01-01T00:00:10.000Z');
|
||||
});
|
||||
|
||||
it('getMessagesSince caps to limit and returns most recent in chronological order', () => {
|
||||
const messages = getMessagesSince(
|
||||
'group@g.us',
|
||||
'2024-01-01T00:00:00.000Z',
|
||||
'Andy',
|
||||
3,
|
||||
);
|
||||
expect(messages).toHaveLength(3);
|
||||
expect(messages[0].content).toBe('message 8');
|
||||
expect(messages[2].content).toBe('message 10');
|
||||
expect(messages[1].timestamp > messages[0].timestamp).toBe(true);
|
||||
});
|
||||
|
||||
it('returns all messages when count is under the limit', () => {
|
||||
const { messages } = getNewMessages(
|
||||
['group@g.us'],
|
||||
'2024-01-01T00:00:00.000Z',
|
||||
'Andy',
|
||||
50,
|
||||
);
|
||||
expect(messages).toHaveLength(10);
|
||||
describe('closeDatabase', () => {
|
||||
it('can be called without throwing', () => {
|
||||
expect(() => closeDatabase()).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
// --- RegisteredGroup isMain round-trip ---
|
||||
|
||||
describe('registered group isMain', () => {
|
||||
it('persists isMain=true through set/get round-trip', () => {
|
||||
setRegisteredGroup('main@s.whatsapp.net', {
|
||||
name: 'Main Chat',
|
||||
folder: 'whatsapp_main',
|
||||
trigger: '@Andy',
|
||||
added_at: '2024-01-01T00:00:00.000Z',
|
||||
isMain: true,
|
||||
describe('deleteTask atomicity', () => {
|
||||
it('deletes task and its logs', () => {
|
||||
createTask({
|
||||
id: 'task-del-1',
|
||||
group_folder: 'main',
|
||||
chat_jid: 'jid@g.us',
|
||||
prompt: 'test',
|
||||
schedule_type: 'once',
|
||||
schedule_value: '2026-01-01T00:00:00Z',
|
||||
context_mode: 'isolated',
|
||||
next_run: '2026-01-01T00:00:00Z',
|
||||
status: 'active',
|
||||
created_at: new Date().toISOString(),
|
||||
});
|
||||
logTaskRun({
|
||||
task_id: 'task-del-1',
|
||||
run_at: new Date().toISOString(),
|
||||
duration_ms: 100,
|
||||
status: 'success',
|
||||
result: 'ok',
|
||||
error: null,
|
||||
});
|
||||
|
||||
const groups = getAllRegisteredGroups();
|
||||
const group = groups['main@s.whatsapp.net'];
|
||||
expect(group).toBeDefined();
|
||||
expect(group.isMain).toBe(true);
|
||||
expect(group.folder).toBe('whatsapp_main');
|
||||
});
|
||||
deleteTask('task-del-1');
|
||||
|
||||
it('omits isMain for non-main groups', () => {
|
||||
setRegisteredGroup('group@g.us', {
|
||||
name: 'Family Chat',
|
||||
folder: 'whatsapp_family-chat',
|
||||
trigger: '@Andy',
|
||||
added_at: '2024-01-01T00:00:00.000Z',
|
||||
});
|
||||
|
||||
const groups = getAllRegisteredGroups();
|
||||
const group = groups['group@g.us'];
|
||||
expect(group).toBeDefined();
|
||||
expect(group.isMain).toBeUndefined();
|
||||
expect(getTaskById('task-del-1')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user