From 8c1b225e3aefa86ac77483ea073f12dcf9f845a8 Mon Sep 17 00:00:00 2001 From: Kyle Fang Date: Tue, 1 Jul 2025 08:49:40 +0800 Subject: [PATCH] feat: add auto delete --- .claude/settings.local.json | 3 +- src/examples/input-autodelete-demo.tsx | 88 ++++++++++++++++++++++++++ src/examples/quiz-bot.tsx | 4 +- src/input-example.tsx | 2 +- src/jsx.d.ts | 2 + src/mtcute-adapter.ts | 23 ++++++- src/reconciler.ts | 16 ++++- 7 files changed, 129 insertions(+), 9 deletions(-) create mode 100644 src/examples/input-autodelete-demo.tsx diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 1909644..b8d38dd 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -14,7 +14,8 @@ "Bash(git add:*)", "Bash(git commit:*)", "Bash(bun tsc:*)", - "Bash(ls:*)" + "Bash(ls:*)", + "Bash(bun x tsc:*)" ], "deny": [] } diff --git a/src/examples/input-autodelete-demo.tsx b/src/examples/input-autodelete-demo.tsx new file mode 100644 index 0000000..d0effcd --- /dev/null +++ b/src/examples/input-autodelete-demo.tsx @@ -0,0 +1,88 @@ +/// +import React, { useState } from 'react'; +import { MtcuteAdapter } from '../mtcute-adapter'; + +const AutoDeleteDemo: React.FC = () => { + const [mode, setMode] = useState<'normal' | 'secret'>('normal'); + const [messages, setMessages] = useState([]); + + const handleNormalInput = (text: string) => { + setMessages(prev => [...prev, `Normal message: ${text}`]); + }; + + const handleSecretInput = (text: string) => { + setMessages(prev => [...prev, `Secret received (message deleted): ***`]); + }; + + const toggleMode = () => { + setMode(mode === 'normal' ? 'secret' : 'normal'); + setMessages([]); + }; + + return ( + <> + Input Auto-Delete Demo + {'\n\n'} + + Current mode: {mode === 'normal' ? '📝 Normal Mode' : '🤫 Secret Mode'} + {'\n\n'} + + {mode === 'normal' ? ( + <> + Send any message - it will stay in the chat. + {'\n'} + + + ) : ( + <> + Send a secret message - it will be deleted automatically! + {'\n'} + + + )} + + {'\n\n'} + + {messages.length > 0 && ( + <> + Received Messages: + {'\n'} + {messages.map((msg, idx) => ( + + • {msg} + {'\n'} + + ))} + {'\n'} + + )} + + + + {messages.length > 0 && ( + + )} + + + ); +}; + +// Set up the bot +async function main() { + const adapter = new MtcuteAdapter({ + apiId: parseInt(process.env.API_ID!), + apiHash: process.env.API_HASH!, + botToken: process.env.BOT_TOKEN! + }); + + // Register the demo command + adapter.onCommand('autodelete', () => ); + + // Start the bot + await adapter.start(process.env.BOT_TOKEN!); + console.log('Auto-delete demo bot is running! Send /autodelete to start.'); +} + +main().catch(console.error); \ No newline at end of file diff --git a/src/examples/quiz-bot.tsx b/src/examples/quiz-bot.tsx index 84c16cb..ca94772 100644 --- a/src/examples/quiz-bot.tsx +++ b/src/examples/quiz-bot.tsx @@ -103,8 +103,8 @@ const QuizBot: React.FC = () => { )} - {/* Input handler for answers */} - {waitingForAnswer && } + {/* Input handler for answers - auto-delete for cleaner chat */} + {waitingForAnswer && } ); }; diff --git a/src/input-example.tsx b/src/input-example.tsx index 202205f..5f629eb 100644 --- a/src/input-example.tsx +++ b/src/input-example.tsx @@ -43,7 +43,7 @@ const InputExample: React.FC = () => { {'\n\n'} {/* Input handler - will process any reply to this message */} - {waitingForInput && } + {waitingForInput && } {/* Button to clear history */} {messages.length > 0 && ( diff --git a/src/jsx.d.ts b/src/jsx.d.ts index 2fc386b..450290e 100644 --- a/src/jsx.d.ts +++ b/src/jsx.d.ts @@ -58,6 +58,7 @@ declare module 'react' { input: { onSubmit?: (text: string) => void; + autoDelete?: boolean; }; } } @@ -122,6 +123,7 @@ export interface TelegramRowNode { export interface TelegramInputNode { type: 'input'; onSubmit?: (text: string) => void; + autoDelete?: boolean; } export interface TelegramRootNode { diff --git a/src/mtcute-adapter.ts b/src/mtcute-adapter.ts index 1d47d06..190b298 100644 --- a/src/mtcute-adapter.ts +++ b/src/mtcute-adapter.ts @@ -58,11 +58,30 @@ export class MtcuteAdapter { } } } else if (msg.text) { + // Track if any input has autoDelete enabled + let shouldDelete = false; + this.activeContainers.forEach(container => { - container.container.inputCallbacks.forEach(callback => { - callback(msg.text!); + container.container.inputCallbacks.forEach(inputData => { + // Call the callback + inputData.callback(msg.text!); + + // Check if this input has autoDelete enabled + if (inputData.autoDelete) { + shouldDelete = true; + } }); }); + + // Delete the user's message if any input had autoDelete enabled + if (shouldDelete) { + try { + await msg.delete(); + } catch (err) { + // Ignore errors if message deletion fails (e.g., bot lacks permissions) + console.error('Failed to delete message:', err); + } + } } }); diff --git a/src/reconciler.ts b/src/reconciler.ts index ce08dd8..92ccbe4 100644 --- a/src/reconciler.ts +++ b/src/reconciler.ts @@ -29,10 +29,17 @@ export type { Node }; +export type { InputCallbackData }; + +interface InputCallbackData { + callback: (text: string) => void; + autoDelete?: boolean; +} + interface Container { root: RootNode; buttonHandlers: Map void>; - inputCallbacks: Array<(text: string) => void>; + inputCallbacks: Array; onRenderContainer?: (root: RootNode) => void; } @@ -95,7 +102,7 @@ const hostConfig: ReactReconciler.HostConfig< case 'row': return { type: 'row', children: [] }; case 'input': - return { type: 'input', onSubmit: props.onSubmit }; + return { type: 'input', onSubmit: props.onSubmit, autoDelete: props.autoDelete }; default: return { type: 'formatted', format: 'bold', children: [] }; } @@ -161,7 +168,10 @@ const hostConfig: ReactReconciler.HostConfig< container.inputCallbacks = []; container.root.children.forEach((child: any) => { if (child.type === 'input' && child.onSubmit) { - container.inputCallbacks.push(child.onSubmit); + container.inputCallbacks.push({ + callback: child.onSubmit, + autoDelete: child.autoDelete + }); } });