Модуль 9: Практика — Telegram-бот

Перший бот: Hello World

📖 Теорія
Створимо простого бота, який:
• Відповідає на команду /start
• Відповідає на команду /help
• Ехо-повторює будь-яке повідомлення

Асинхронність у Python:
aiogram використовує async/await — це асинхронне програмування. Бот може одночасно обробляти багато користувачів, не блокуючи один одного.

Ключові концепції:
• Router — маршрутизатор для обробників
• @router.message() — декоратор для обробки повідомлень
• CommandStart() — фільтр для команди /start
• message.answer() — відповісти користувачу
💡 Приклад коду
Вивід:

                        
📝 ЗАВДАННЯ (3)
1.
Завдання 1: Бот-калькулятор
40 XP
Розширте ехо-бота: додайте команду /calc. Коли користувач пише /calc 10 + 5, бот повинен обчислити та відповісти результатом. Підтримайте операції +, -, *, /.
💡 Підказка: Розберіть текст команди через message.text.split(). Обробіть помилки.
🔓 Розв'язок:
@router.message(Command('calc'))
async def cmd_calc(message: Message):
    try:
        parts = message.text.split()
        if len(parts) != 4:
            await message.answer('Формат: /calc число операція число')
            return
        a, op, b = float(parts[1]), parts[2], float(parts[3])
        if op == '+': result = a + b
        elif op == '-': result = a - b
        elif op == '*': result = a * b
        elif op == '/':
            if b == 0: raise ZeroDivisionError
            result = a / b
        else:
            await message.answer('Невідома операція')
            return
        await message.answer(f'Результат: {result}')
    except (ValueError, ZeroDivisionError) as e:
        await message.answer(f'Помилка: {e}')
Вивід:

                                

2.
Завдання 2: Бот-вікторина
50 XP
Додайте команду /quiz. Бот ставить 3 запитання з Python (по одному за раз), користувач відповідає номером варіанту. Наприкінці бот виводить результат: 'Правильно X з 3'.
💡 Підказка: Використовуйте FSM (Finite State Machine) з aiogram для відстеження поточного запитання. Або простий підхід: зберігайте стан у словнику за user_id.
🔓 Розв'язок:
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup

class Quiz(StatesGroup):
    question = State()

questions = [
    {'q': 'Що виведе print(2 + 2)?', 'opts': ['22', '4', 'Помилка'], 'ans': 1},
    {'q': 'Який тип у 3.14?', 'opts': ['int', 'float', 'str'], 'ans': 1},
    {'q': 'Що робить len()?', 'opts': ['Видаляє', 'Рахує довжину', 'Сортує'], 'ans': 1}
]

@router.message(Command('quiz'))
async def start_quiz(message: Message, state: FSMContext):
    await state.set_data({'current': 0, 'score': 0})
    q = questions[0]
    opts = '\n'.join(f'{i+1}. {o}' for i, o in enumerate(q['opts']))
    await message.answer(f'{q["q"]}\n{opts}')
    await state.set_state(Quiz.question)
Вивід:

                                

3.
Завдання 3: Бот з inline-кнопками
60 XP
Створіть бота, який при команді /menu показує inline-кнопки: 'Погода', 'Курс валют', 'Жарт'. При натисканні на кнопку бот відповідає заготовленим текстом (справжні API поки не потрібні).
💡 Підказка: Використовуйте InlineKeyboardMarkup та InlineKeyboardButton з aiogram.types. Для обробки натискань — CallbackQuery.
🔓 Розв'язок:
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery

@router.message(Command('menu'))
async def show_menu(message: Message):
    kb = InlineKeyboardMarkup(inline_keyboard=[
        [InlineKeyboardButton(text='Погода', callback_data='weather')],
        [InlineKeyboardButton(text='Курс валют', callback_data='currency')],
        [InlineKeyboardButton(text='Жарт', callback_data='joke')]
    ])
    await message.answer('Оберіть:', reply_markup=kb)

@router.callback_query(lambda c: c.data in ('weather', 'currency', 'joke'))
async def handle_callback(callback: CallbackQuery):
    responses = {
        'weather': 'Сьогодні сонячно, +22°C',
        'currency': '1 USD = 41.5 UAH',
        'joke': 'Чому програмісти плутають Гелловін і Різдво? Бо Oct 31 = Dec 25'
    }
    await callback.message.answer(responses[callback.data])
    await callback.answer()
Вивід: