feat: add Telegram Topics (forum mode) support
Some checks failed
Bump version / bump-version (push) Has been cancelled
Merge-forward skill branches / merge-forward (push) Has been cancelled
Update token count / update-tokens (push) Has been cancelled

- 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:
Andy
2026-03-14 12:04:06 +00:00
parent c0902877fa
commit 8e24a31bd4
26 changed files with 5190 additions and 717 deletions

View File

@@ -22,6 +22,16 @@ describe('JID ownership patterns', () => {
const jid = '12345678@s.whatsapp.net';
expect(jid.endsWith('@s.whatsapp.net')).toBe(true);
});
it('Telegram JID: starts with tg:', () => {
const jid = 'tg:123456789';
expect(jid.startsWith('tg:')).toBe(true);
});
it('Telegram group JID: starts with tg: and has negative ID', () => {
const jid = 'tg:-1001234567890';
expect(jid.startsWith('tg:')).toBe(true);
});
});
// --- getAvailableGroups ---
@@ -167,4 +177,103 @@ describe('getAvailableGroups', () => {
const groups = getAvailableGroups();
expect(groups).toHaveLength(0);
});
it('includes Telegram chat JIDs', () => {
storeChatMetadata(
'tg:100200300',
'2024-01-01T00:00:01.000Z',
'Telegram Chat',
'telegram',
true,
);
storeChatMetadata(
'user@s.whatsapp.net',
'2024-01-01T00:00:02.000Z',
'User DM',
'whatsapp',
false,
);
const groups = getAvailableGroups();
expect(groups).toHaveLength(1);
expect(groups[0].jid).toBe('tg:100200300');
});
it('returns Telegram group JIDs with negative IDs', () => {
storeChatMetadata(
'tg:-1001234567890',
'2024-01-01T00:00:01.000Z',
'TG Group',
'telegram',
true,
);
const groups = getAvailableGroups();
expect(groups).toHaveLength(1);
expect(groups[0].jid).toBe('tg:-1001234567890');
expect(groups[0].name).toBe('TG Group');
});
it('marks registered Telegram chats correctly', () => {
storeChatMetadata(
'tg:100200300',
'2024-01-01T00:00:01.000Z',
'TG Registered',
'telegram',
true,
);
storeChatMetadata(
'tg:999999',
'2024-01-01T00:00:02.000Z',
'TG Unregistered',
'telegram',
true,
);
_setRegisteredGroups({
'tg:100200300': {
name: 'TG Registered',
folder: 'tg-registered',
trigger: '@Andy',
added_at: '2024-01-01T00:00:00.000Z',
},
});
const groups = getAvailableGroups();
const tgReg = groups.find((g) => g.jid === 'tg:100200300');
const tgUnreg = groups.find((g) => g.jid === 'tg:999999');
expect(tgReg?.isRegistered).toBe(true);
expect(tgUnreg?.isRegistered).toBe(false);
});
it('mixes WhatsApp and Telegram chats ordered by activity', () => {
storeChatMetadata(
'wa@g.us',
'2024-01-01T00:00:01.000Z',
'WhatsApp',
'whatsapp',
true,
);
storeChatMetadata(
'tg:100',
'2024-01-01T00:00:03.000Z',
'Telegram',
'telegram',
true,
);
storeChatMetadata(
'wa2@g.us',
'2024-01-01T00:00:02.000Z',
'WhatsApp 2',
'whatsapp',
true,
);
const groups = getAvailableGroups();
expect(groups).toHaveLength(3);
expect(groups[0].jid).toBe('tg:100');
expect(groups[1].jid).toBe('wa2@g.us');
expect(groups[2].jid).toBe('wa@g.us');
});
});