mirror of
https://github.com/lockin-bot/react-telegram.git
synced 2026-01-12 15:13:56 +08:00
feat: add input
This commit is contained in:
128
src/examples/quiz-bot.tsx
Normal file
128
src/examples/quiz-bot.tsx
Normal file
@@ -0,0 +1,128 @@
|
||||
/// <reference path="../jsx.d.ts" />
|
||||
import React, { useState } from 'react';
|
||||
import { MtcuteAdapter } from '../mtcute-adapter';
|
||||
|
||||
// Quiz questions
|
||||
const questions = [
|
||||
{ question: "What is 2 + 2?", answer: "4" },
|
||||
{ question: "What is the capital of France?", answer: "paris" },
|
||||
{ question: "What color is the sky?", answer: "blue" }
|
||||
];
|
||||
|
||||
const QuizBot: React.FC = () => {
|
||||
const [currentQuestion, setCurrentQuestion] = useState(0);
|
||||
const [score, setScore] = useState(0);
|
||||
const [waitingForAnswer, setWaitingForAnswer] = useState(true);
|
||||
const [lastAnswer, setLastAnswer] = useState<string | null>(null);
|
||||
const [gameOver, setGameOver] = useState(false);
|
||||
|
||||
const handleAnswer = (text: string) => {
|
||||
if (!waitingForAnswer || gameOver) return;
|
||||
|
||||
setWaitingForAnswer(false);
|
||||
setLastAnswer(text);
|
||||
|
||||
const isCorrect = text.toLowerCase().trim() === questions[currentQuestion]?.answer;
|
||||
if (isCorrect) {
|
||||
setScore(score + 1);
|
||||
}
|
||||
|
||||
// Move to next question after a short delay
|
||||
setTimeout(() => {
|
||||
if (currentQuestion < questions.length - 1) {
|
||||
setCurrentQuestion(currentQuestion + 1);
|
||||
setWaitingForAnswer(true);
|
||||
setLastAnswer(null);
|
||||
} else {
|
||||
setGameOver(true);
|
||||
}
|
||||
}, 100);
|
||||
};
|
||||
|
||||
const restart = () => {
|
||||
setCurrentQuestion(0);
|
||||
setScore(0);
|
||||
setWaitingForAnswer(true);
|
||||
setLastAnswer(null);
|
||||
setGameOver(false);
|
||||
};
|
||||
|
||||
if (gameOver) {
|
||||
return (
|
||||
<>
|
||||
<b>🎉 Quiz Complete!</b>
|
||||
{'\n\n'}
|
||||
Your final score: <b>{score}/{questions.length}</b>
|
||||
{'\n\n'}
|
||||
{score === questions.length ?
|
||||
"Perfect score! Well done! 🌟" :
|
||||
score >= questions.length / 2 ?
|
||||
"Good job! 👍" :
|
||||
"Better luck next time! 📚"
|
||||
}
|
||||
{'\n\n'}
|
||||
<row>
|
||||
<button onClick={restart}>Play Again</button>
|
||||
</row>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const currentQ = questions[currentQuestion];
|
||||
|
||||
return (
|
||||
<>
|
||||
<b>Quiz Bot 🤖</b>
|
||||
{'\n'}
|
||||
Question {currentQuestion + 1} of {questions.length}
|
||||
{'\n'}
|
||||
Score: {score}/{currentQuestion}
|
||||
{'\n\n'}
|
||||
|
||||
<b>{currentQ?.question}</b>
|
||||
{'\n\n'}
|
||||
|
||||
{lastAnswer !== null && (
|
||||
<>
|
||||
Your answer: <code>{lastAnswer}</code>
|
||||
{'\n'}
|
||||
{lastAnswer.toLowerCase().trim() === currentQ?.answer ?
|
||||
"✅ Correct!" :
|
||||
`❌ Wrong! The answer was: ${currentQ?.answer}`
|
||||
}
|
||||
{'\n\n'}
|
||||
{currentQuestion < questions.length - 1 && "Next question coming up..."}
|
||||
{'\n'}
|
||||
</>
|
||||
)}
|
||||
|
||||
{waitingForAnswer && !lastAnswer && (
|
||||
<>
|
||||
<i>Reply to this message with your answer!</i>
|
||||
{'\n'}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Input handler for answers */}
|
||||
{waitingForAnswer && <input onSubmit={handleAnswer} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// 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 quiz command
|
||||
adapter.onCommand('quiz', () => <QuizBot />);
|
||||
|
||||
// Start the bot
|
||||
await adapter.start(process.env.BOT_TOKEN!);
|
||||
console.log('Quiz bot is running! Send /quiz to start.');
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
70
src/input-example.tsx
Normal file
70
src/input-example.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
/// <reference path="./jsx.d.ts" />
|
||||
import React, { useState } from 'react';
|
||||
import { createContainer } from './reconciler';
|
||||
|
||||
const InputExample: React.FC = () => {
|
||||
const [messages, setMessages] = useState<string[]>([]);
|
||||
const [waitingForInput, setWaitingForInput] = useState(true);
|
||||
|
||||
const handleInput = (text: string) => {
|
||||
setMessages(prev => [...prev, `You said: ${text}`]);
|
||||
// Keep waiting for more input
|
||||
};
|
||||
|
||||
const clearMessages = () => {
|
||||
setMessages([]);
|
||||
setWaitingForInput(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<b>Input Example</b>
|
||||
{'\n\n'}
|
||||
|
||||
{messages.length === 0 ? (
|
||||
<>
|
||||
<i>Reply to this message to send text!</i>
|
||||
{'\n\n'}
|
||||
The bot will echo whatever you type.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<b>Message History:</b>
|
||||
{'\n'}
|
||||
{messages.map((msg, idx) => (
|
||||
<React.Fragment key={idx}>
|
||||
{msg}
|
||||
{'\n'}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
|
||||
{'\n\n'}
|
||||
|
||||
{/* Input handler - will process any reply to this message */}
|
||||
{waitingForInput && <input onSubmit={handleInput} />}
|
||||
|
||||
{/* Button to clear history */}
|
||||
{messages.length > 0 && (
|
||||
<row>
|
||||
<button onClick={clearMessages}>Clear History</button>
|
||||
<button onClick={() => setWaitingForInput(!waitingForInput)}>
|
||||
{waitingForInput ? 'Stop Input' : 'Start Input'}
|
||||
</button>
|
||||
</row>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// Create container and render
|
||||
const { render, container } = createContainer();
|
||||
render(<InputExample />);
|
||||
|
||||
// Log the output for debugging
|
||||
setTimeout(() => {
|
||||
console.log('Input example output:');
|
||||
console.log(JSON.stringify(container.root, null, 2));
|
||||
console.log(`Input callbacks registered: ${container.inputCallbacks.length}`);
|
||||
}, 0);
|
||||
11
src/jsx.d.ts
vendored
11
src/jsx.d.ts
vendored
@@ -55,6 +55,10 @@ declare module 'react' {
|
||||
onClick?: () => void;
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
input: {
|
||||
onSubmit?: (text: string) => void;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -115,6 +119,11 @@ export interface TelegramRowNode {
|
||||
children: TelegramButtonNode[];
|
||||
}
|
||||
|
||||
export interface TelegramInputNode {
|
||||
type: 'input';
|
||||
onSubmit?: (text: string) => void;
|
||||
}
|
||||
|
||||
export interface TelegramRootNode {
|
||||
type: 'root';
|
||||
children: (
|
||||
@@ -125,6 +134,7 @@ export interface TelegramRootNode {
|
||||
| TelegramCodeBlockNode
|
||||
| TelegramBlockQuoteNode
|
||||
| TelegramRowNode
|
||||
| TelegramInputNode
|
||||
)[];
|
||||
}
|
||||
|
||||
@@ -137,4 +147,5 @@ export type TelegramNode =
|
||||
| TelegramBlockQuoteNode
|
||||
| TelegramButtonNode
|
||||
| TelegramRowNode
|
||||
| TelegramInputNode
|
||||
| TelegramRootNode;
|
||||
@@ -57,6 +57,12 @@ export class MtcuteAdapter {
|
||||
await this.sendReactMessage(msg.chat.id, app);
|
||||
}
|
||||
}
|
||||
} else if (msg.text) {
|
||||
this.activeContainers.forEach(container => {
|
||||
container.container.inputCallbacks.forEach(callback => {
|
||||
callback(msg.text!);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import type {
|
||||
TelegramBlockQuoteNode as BlockQuoteNode,
|
||||
TelegramButtonNode as ButtonNode,
|
||||
TelegramRowNode as RowNode,
|
||||
TelegramInputNode as InputNode,
|
||||
TelegramRootNode as RootNode,
|
||||
TelegramNode as Node
|
||||
} from './jsx';
|
||||
@@ -23,6 +24,7 @@ export type {
|
||||
BlockQuoteNode,
|
||||
ButtonNode,
|
||||
RowNode,
|
||||
InputNode,
|
||||
RootNode,
|
||||
Node
|
||||
};
|
||||
@@ -30,6 +32,7 @@ export type {
|
||||
interface Container {
|
||||
root: RootNode;
|
||||
buttonHandlers: Map<string, () => void>;
|
||||
inputCallbacks: Array<(text: string) => void>;
|
||||
onRenderContainer?: (root: RootNode) => void;
|
||||
}
|
||||
|
||||
@@ -91,6 +94,8 @@ const hostConfig: ReactReconciler.HostConfig<
|
||||
return { type: 'button', id: '', text: buttonText, onClick: props.onClick };
|
||||
case 'row':
|
||||
return { type: 'row', children: [] };
|
||||
case 'input':
|
||||
return { type: 'input', onSubmit: props.onSubmit };
|
||||
default:
|
||||
return { type: 'formatted', format: 'bold', children: [] };
|
||||
}
|
||||
@@ -152,6 +157,14 @@ const hostConfig: ReactReconciler.HostConfig<
|
||||
}
|
||||
});
|
||||
|
||||
// Collect input callbacks
|
||||
container.inputCallbacks = [];
|
||||
container.root.children.forEach((child: any) => {
|
||||
if (child.type === 'input' && child.onSubmit) {
|
||||
container.inputCallbacks.push(child.onSubmit);
|
||||
}
|
||||
});
|
||||
|
||||
// Don't log automatically
|
||||
},
|
||||
|
||||
@@ -313,6 +326,7 @@ export function createContainer() {
|
||||
const container: Container = {
|
||||
root: { type: 'root', children: [] },
|
||||
buttonHandlers: new Map(),
|
||||
inputCallbacks: [],
|
||||
};
|
||||
|
||||
const reconcilerContainer = TelegramReconciler.createContainer(
|
||||
|
||||
Reference in New Issue
Block a user