mirror of
https://github.com/qwibitai/nanoclaw.git
synced 2026-06-04 10:14:47 +08:00
feat(telegram-pairing): accept bare 4-digit codes
Require the message to be exactly the 4 digits (optionally prefixed by @botname). Loose matches like "my pin is 0349" are rejected to avoid false positives from chat traffic that happens to contain a 4-digit number. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -68,7 +68,7 @@ Otherwise, run `/manage-channels` to wire this channel to an agent group.
|
||||
|
||||
- **type**: `telegram`
|
||||
- **terminology**: Telegram calls them "groups" and "chats." A "group" has multiple members; a "chat" is a 1:1 conversation with the bot.
|
||||
- **how-to-find-id**: Do NOT ask the user for a chat ID. Telegram registration uses pairing — run `npx tsx setup/index.ts --step pair-telegram -- --intent <main|wire-to:folder|new-agent:folder>`, show the user the 4-digit `CODE` from the `PAIR_TELEGRAM_ISSUED` block, and tell them to send `@<botname> CODE` from the chat they want to register (DM the bot for `main`, post in the group otherwise). The step waits up to 5 minutes and emits a `PAIR_TELEGRAM` block with `PLATFORM_ID`, `IS_GROUP`, and `ADMIN_USER_ID` once the user echoes the code. The service must be running for this to work (the polling adapter is what observes the code).
|
||||
- **how-to-find-id**: Do NOT ask the user for a chat ID. Telegram registration uses pairing — run `npx tsx setup/index.ts --step pair-telegram -- --intent <main|wire-to:folder|new-agent:folder>`, show the user the 4-digit `CODE` from the `PAIR_TELEGRAM_ISSUED` block, and tell them to send just the 4 digits as a message from the chat they want to register (DM the bot for `main`, post in the group otherwise). In groups with Group Privacy ON, prefix with the bot handle: `@<botname> CODE`. The step waits up to 5 minutes and emits a `PAIR_TELEGRAM` block with `PLATFORM_ID`, `IS_GROUP`, and `ADMIN_USER_ID` once the user echoes the code. The service must be running for this to work (the polling adapter is what observes the code).
|
||||
- **supports-threads**: no
|
||||
- **typical-use**: Interactive chat — direct messages or small groups
|
||||
- **default-isolation**: Same agent group if you're the only participant across multiple chats. Separate agent group if different people are in different groups.
|
||||
|
||||
@@ -45,15 +45,20 @@ describe('extractAddressedText', () => {
|
||||
});
|
||||
|
||||
describe('extractCode', () => {
|
||||
it('finds 4-digit code after @botname', () => {
|
||||
it('accepts a bare 4-digit code', () => {
|
||||
expect(extractCode('0349', 'nanobot')).toBe('0349');
|
||||
});
|
||||
it('accepts 4-digit code after @botname', () => {
|
||||
expect(extractCode('@nanobot 0042', 'nanobot')).toBe('0042');
|
||||
});
|
||||
it('rejects non-4-digit numbers', () => {
|
||||
expect(extractCode('@nanobot 12345', 'nanobot')).toBeNull();
|
||||
expect(extractCode('@nanobot 12', 'nanobot')).toBeNull();
|
||||
expect(extractCode('12345', 'nanobot')).toBeNull();
|
||||
});
|
||||
it('returns null without addressing', () => {
|
||||
expect(extractCode('1234', 'nanobot')).toBeNull();
|
||||
it('rejects loose matches with surrounding text', () => {
|
||||
expect(extractCode('my pin is 0349', 'nanobot')).toBeNull();
|
||||
expect(extractCode('0349 thanks', 'nanobot')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -103,7 +108,7 @@ describe('tryConsume', () => {
|
||||
expect(out).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null without @botname addressing', async () => {
|
||||
it('matches a bare code without @botname addressing', async () => {
|
||||
const r = await createPairing('main');
|
||||
const out = await tryConsume({
|
||||
text: r.code,
|
||||
@@ -111,7 +116,8 @@ describe('tryConsume', () => {
|
||||
platformId: 'x',
|
||||
isGroup: false,
|
||||
});
|
||||
expect(out).toBeNull();
|
||||
expect(out).not.toBeNull();
|
||||
expect(out!.status).toBe('consumed');
|
||||
});
|
||||
|
||||
it('cannot be consumed twice', async () => {
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
*
|
||||
* BotFather hands out tokens with no user binding, so anyone who guesses the
|
||||
* bot's username can DM it. Pairing closes that gap: setup creates a one-time
|
||||
* 4-digit code and the operator echoes it back as `@botname CODE` from the
|
||||
* chat they want to register. The inbound interceptor in telegram.ts matches
|
||||
* the code and records the chat (with admin_user_id) before it ever reaches
|
||||
* the router.
|
||||
* 4-digit code and the operator echoes it back from the chat they want to
|
||||
* register. The message must be exactly the 4 digits (optionally prefixed by
|
||||
* `@botname ` for groups with privacy ON) — arbitrary messages that happen to
|
||||
* contain a 4-digit number do NOT match. The inbound interceptor in
|
||||
* telegram.ts matches the code and records the chat (with admin_user_id)
|
||||
* before it ever reaches the router.
|
||||
*
|
||||
* Storage is a JSON file at data/telegram-pairings.json — single-process,
|
||||
* read-modify-write under an in-process mutex.
|
||||
@@ -144,11 +146,15 @@ export function extractAddressedText(text: string, botUsername: string): string
|
||||
return trimmed.slice(m[0].length).trim();
|
||||
}
|
||||
|
||||
/** Find a 4-digit code in `@botname CODE`-style text. Returns null if none. */
|
||||
/**
|
||||
* Extract a pairing code from an inbound message. The message must be exactly
|
||||
* 4 digits (optionally prefixed by `@botname `) — loose matches like
|
||||
* "my pin is 1234" are rejected to avoid false positives from chatter.
|
||||
*/
|
||||
export function extractCode(text: string, botUsername: string): string | null {
|
||||
const remainder = extractAddressedText(text, botUsername);
|
||||
if (remainder === null) return null;
|
||||
const m = remainder.match(/\b(\d{4})\b/);
|
||||
const addressed = extractAddressedText(text, botUsername);
|
||||
const candidate = (addressed !== null ? addressed : text).trim();
|
||||
const m = candidate.match(/^(\d{4})$/);
|
||||
return m ? m[1] : null;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user