October 13, 2025

Туристический Бот

Многофункциональный Telegram-бот для путешественников. Помогает узнавать погоду, переводить текст, получать случайные фотографии, космические снимки, вести учёт расходов, отслеживать курсы валют, а также просто развлекаться в дороге.

🚀 Основные возможности

📊 Информационные сервисы

  • ☁️ Погода: получение актуальной информации о погоде в любом городе с возможностью обновления данных
  • 🌐 Перевод: перевод текста на разные языки
  • 💱 Валюта: актуальные курсы валют (доллар США, евро к рублю)
  • 💰 Финансы: ведение учета расходов по категориям
  • 📊 Статистика расходов: подробный отчет о ваших тратах с процентами и диаграммой (кнопка в информационном меню)

🎉 Развлечения

  • 🖼️ Фото: случайные красивые фотографии с Unsplash
  • 🚀 Космос: фото дня и случайные снимки от NASA
  • 🐱 Коты: случайные породы котов с описанием и фото

👤 Пользователи

  • 📝 Регистрация: система регистрации пользователей
  • 💾 Сохранение данных: автоматическое сохранение данных о городе и финансовых данных

🏗️ Структура проекта

travel-bot/
├── main.py                 # Основной файл запуска бота
├── config.py              # Конфигурация и переменные окружения
├── requirements.txt       # Зависимости проекта
├── README.md             # Документация
├── routers/              # Обработчики команд и кнопок
│   ├── start.py          # Стартовое меню и навигация
│   ├── weather.py        # Работа с погодой
│   ├── translate.py      # Перевод текста
│   ├── currency.py       # Курсы валют
│   ├── finance.py        # Учет финансов
│   ├── registration.py   # Регистрация пользователей
│   ├── photo.py          # Случайные фото
│   ├── nasa.py           # Космические фото
│   └── cat.py            # Коты и их породы
├── keyboards/            # Генерация inline-клавиатур
│   └── inline_keyboard.py
├── services/             # Интеграция с внешними API
│   ├── weather.py        # API погоды (OpenWeatherMap)
│   ├── currency.py       # API валют (ExchangeRate-API)
│   ├── translate.py      # Перевод (Google Translator)
│   ├── photo.py          # Фото (Unsplash)
│   ├── nasa.py           # Космос (NASA API)
│   └── cat.py            # Коты (The Cat API)
└── db/                   # Работа с базой данных
    └── users.py          # Функции для работы с пользователями

⚙️ Установка

  1. Клонируйте репозиторий:git clone https://github.com/ваше_имя_пользователя/travel-bot.git cd travel-bot
  2. Установите зависимости:pip install -r requirements.txt
  3. Создайте файл .env в корне проекта и добавьте ключи API:BOT_TOKEN=ваш_токен_telegram_бота API_KEY2=ваш_api_ключ_openweathermap UNSPLASH_ACCESS_KEY=ваш_access_key_unsplash CAT_API_KEY=ваш_cat_api_key NASA_API_KEY=ваш_nasa_api_key

🚀 Запуск

python main.py

📱 Использование

  1. Запустите бота в Telegram
  2. Нажмите «👤 Регистрация», чтобы создать учётную запись
  3. Используйте главное меню для выбора функций:
    • 🧭 Информация — погода, перевод, валюта, финансы, статистика расходов
    • 🎉 Развлечения — фото, космос, коты

💰 Финансы и статистика

  • Выберите в меню пункт «💰 Финансы», чтобы ввести данные о новых расходах
  • Чтобы просмотреть отчёт о расходах, нажмите «📊 Статистика расходов» в информационном меню
  • Получите красивый отчёт с процентами и диаграммой

☁️ Погода

  • Выберите «☁️ Погода» в меню
  • Введите название города
  • Получайте актуальную информацию о погоде с возможностью обновления

🛠️ Технические особенности

  • Асинхронная архитектура: все HTTP-запросы используют aiohttp для повышения производительности
  • База данных: SQLite для хранения данных пользователей
  • FSM (конечный автомат): для пошагового ввода данных (финансы)
  • Встроенная клавиатура: удобная навигация по функциям
  • Обработка ошибок: корректная обработка ошибок API и сети

📋 Зависимости

  • aiogram — Платформа API Telegram-бота
  • aiohttp — Асинхронный HTTP-клиент
  • sqlite3 — База данных (встроенная)
  • deep-translator — Перевод текста
  • python-dotenv — Загрузка переменных окружения

Код:

✉ db

↪️users.py:

# === ФАЙЛ: db/users.py ===
import sqlite3
# Функция инициализации БД
def init_db():
    with sqlite3.connect('users.db') as conn:
        cursor = conn.cursor()
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS users (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                tg_id INTEGER UNIQUE,
                name TEXT,
                city TEXT,
                category1 TEXT,
                category2 TEXT,
                category3 TEXT,
                expenses1 REAL,
                expenses2 REAL,
                expenses3 REAL
            )
        ''')
        conn.commit()
# Функция сохранения данных о пользователе
def save_user(tg_id: int, name: str, city: str):
    with sqlite3.connect('users.db') as conn:
        cursor = conn.cursor()
        cursor.execute('''
            INSERT OR REPLACE INTO users (tg_id, name, city)
            VALUES (?, ?, ?)
        ''', (tg_id, name, city))
        conn.commit()
# Функция получения данных о пользователе
def get_user(tg_id: int):
    with sqlite3.connect('users.db') as conn:
        cursor = conn.cursor()
        cursor.execute('SELECT * FROM users WHERE tg_id = ?', (tg_id,))
        return cursor.fetchone()
# Функция обновления финансовых данных
def update_finance(tg_id: int, data: dict):
    with sqlite3.connect('users.db') as conn:
        cursor = conn.cursor()
        cursor.execute('''
            UPDATE users
            SET category1 = ?, category2 = ?, category3 = ?,
                expenses1 = ?, expenses2 = ?, expenses3 = ?
            WHERE tg_id = ?
        ''', (
            data.get('category1'),
            data.get('category2'),
            data.get('category3'),
            data.get('expenses1'),
            data.get('expenses2'),
            data.get('expenses3'),
            tg_id
        ))
        conn.commit()
# Функция получения финансовых данных с расчетом статистики
def get_finance_data(tg_id: int):
    with sqlite3.connect('users.db') as conn:
        cursor = conn.cursor()
        cursor.execute('''
            SELECT category1, category2, category3, expenses1, expenses2, expenses3
            FROM users WHERE tg_id = ?
        ''', (tg_id,))
        result = cursor.fetchone()
        
        if not result:
            return None
            
        categories = [result[0], result[1], result[2]]
        expenses = [result[3] or 0, result[4] or 0, result[5] or 0]
        
        # Фильтруем только заполненные категории
        valid_data = [(cat, exp) for cat, exp in zip(categories, expenses) if cat and exp > 0]
        
        if not valid_data:
            return None
            
        total = sum(exp for _, exp in valid_data)
        
        # Рассчитываем проценты
        percentages = []
        for cat, exp in valid_data:
            percentage = (exp / total * 100) if total > 0 else 0
            percentages.append((cat, exp, percentage))
            
        return {
            'total': total,
            'breakdown': percentages
        }
           

✉ keyboards

↪️inline_keyboard.py

from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
# Главное меню
async def get_main_menu():
    keyboard_main =InlineKeyboardBuilder()
    keyboard_main.add(
        InlineKeyboardButton(text="🧭 Информация", callback_data="info_menu"),
                InlineKeyboardButton(text="🎉 Развлечения", callback_data="fun_menu"),
                InlineKeyboardButton(text="👤 Регистрация", callback_data="registration"),
                InlineKeyboardButton(text="❓ Что я умею", callback_data="help")
    )
    return keyboard_main.adjust(2).as_markup()
# Подменю "Информация для путешествия"
async def get_info_menu():
    keyboard = InlineKeyboardBuilder()
    keyboard.add(
                #InlineKeyboardButton(text="📍 Где я?", callback_data="location"),
                InlineKeyboardButton(text="☁️ Погода", callback_data="weather"),
                InlineKeyboardButton(text="🌐 Перевод", callback_data="translate"),
                InlineKeyboardButton(text="💱 Валюта", callback_data="currency"),
                InlineKeyboardButton(text="💰 Финансы", callback_data="finance"),
                InlineKeyboardButton(text="📊 Статистика расходов", callback_data="show_finance"),
                InlineKeyboardButton(text="🔙 Назад", callback_data="main_menu")
                 )
    return keyboard.adjust(2).as_markup()
# Подменю "Развлечения в дороге"
async def get_fun_menu():
    keyboard = InlineKeyboardBuilder()
    keyboard.add(InlineKeyboardButton(text="🖼️ Фото", callback_data="photo"),
                InlineKeyboardButton(text="🚀 Космос", callback_data="space"),
                InlineKeyboardButton(text="🐱 Коты", callback_data="cat"),
                InlineKeyboardButton(text="🔙 Назад", callback_data="main_menu")
                )
    return keyboard.adjust(2).as_markup()
# Кнопка "Ещё фото"
def photo_again_keyboard():
    keyboard = InlineKeyboardBuilder()
    keyboard.add(InlineKeyboardButton(text="🖼️ Ещё фото", callback_data="photo_again"),
                InlineKeyboardButton(text="🔙 Назад", callback_data="fun_menu")
                )
    return keyboard.adjust(2).as_markup()
# Кнопка "Ещё фото из космоса"
def nasa_again_keyboard():
    keyboard = InlineKeyboardBuilder()
    keyboard.add(InlineKeyboardButton(text="🖼️ Ещё фото из космоса", callback_data="nasa_again"),
                InlineKeyboardButton(text="🔙 Назад", callback_data="fun_menu")
                )
    return keyboard.adjust(2).as_markup()
# Кнопка "Ещё кота"
def cat_again_keyboard():
    keyboard = InlineKeyboardBuilder()
    keyboard.add(
        InlineKeyboardButton(text="🐾 Ещё кота", callback_data="cat_again"),
        InlineKeyboardButton(text="🔙 Назад", callback_data="fun_menu")
    )
    return keyboard.adjust(2).as_markup()
# Кнопка "Обновить погоду"
def weather_refresh_keyboard(city: str):
     keyboard = InlineKeyboardBuilder()
     keyboard.add(
        InlineKeyboardButton(text="🔄 Обновить погоду", callback_data=f"refresh_weather:{city}"),
        InlineKeyboardButton(text="🔙 Назад", callback_data="main_menu")
     )
     return keyboard.adjust(2).as_markup()
# Кнопки для финансов
def finance_keyboard():
    keyboard = InlineKeyboardBuilder()
    keyboard.add(
        InlineKeyboardButton(text="📊 Показать статистику", callback_data="show_finance"),
        InlineKeyboardButton(text="🔙 Назад", callback_data="main_menu")
    )
    return keyboard.adjust(2).as_markup()

✉routers

↪️cat.py

# from aiogram import Router, F
# from aiogram.filters import Command
# from aiogram.types import Message, CallbackQuery
# from services.cat import get_breed_info, get_cat_image
#
# cat_router = Router()
#
# @cat_router.callback_query(F.data == "cat")
# async def handle_cat(callback: CallbackQuery):
#     await callback.message.answer("Напиши породу кота:")
#     await callback.answer()
#
# @cat_router.message(F.text.regexp(r"^[^/].*"))
# async def cat_breed(message: Message):
#     breed_name = message.text
#     breed_info = get_breed_info(breed_name)
#     if breed_info:
#         cat_image = get_cat_image(breed_info["id"])
#         info = (f"Порода: {breed_info['name']}\n"
#                 f"Описание: {breed_info['description']}\n"
#                 f"Темперамент: {breed_info['temperament']}\n"
#                 f"Страна: {breed_info['origin']}\n"
#                 f"Ссылка: {breed_info['wikipedia_url']}")
#         await message.answer_photo(photo=cat_image, caption=info)
#     else:
#         await message.answer("Порода не найдена.")
from aiogram import Router, F
from aiogram.types import CallbackQuery
from keyboards.inline_keyboard import cat_again_keyboard
from services.cat import get_random_breed, get_cat_image
cat_router = Router()
@cat_router.callback_query(F.data == "cat")
async def handle_cat(callback: CallbackQuery):
    breed_info = await get_random_breed()
    if breed_info:
        cat_image = await get_cat_image(breed_info["id"])
        info = (
            f"🐱 Порода: {breed_info['name']}\n"
            f"📄 Описание: {breed_info['description']}\n"
            f"🎭 Темперамент: {breed_info['temperament']}\n"
            f"🌍 Страна: {breed_info['origin']}\n"
            f"🔗 Подробнее: {breed_info['wikipedia_url']}"
        )
        await callback.message.delete()
        await callback.message.answer_photo(photo=cat_image, caption=info, reply_markup=cat_again_keyboard())
    else:
        await callback.message.answer("❌ Не удалось получить породу.")
    await callback.answer()
@cat_router.callback_query(F.data == "cat_again")
async def cat_again(callback: CallbackQuery):
    breed_info = await get_random_breed()
    if breed_info:
        cat_image = await get_cat_image(breed_info["id"])
        info = (
            f"🐱 Порода: {breed_info['name']}\n"
            f"📄 Описание: {breed_info['description']}\n"
            f"🎭 Темперамент: {breed_info['temperament']}\n"
            f"🌍 Страна: {breed_info['origin']}\n"
            f"🔗 Подробнее: {breed_info['wikipedia_url']}"
        )
        await callback.message.answer_photo(photo=cat_image, caption=info, reply_markup=cat_again_keyboard())
    else:
        await callback.message.answer("❌ Не удалось получить породу.")
    await callback.answer()

↪️currency.py

from aiogram import Router, F
from aiogram.types import Message, CallbackQuery
from services.currency import get_currency
# === Инициализация роутера ===
currency_router = Router()
# Обработка кнопки "currency"
@currency_router.callback_query(F.data == "currency")
async def handle_currency(callback: CallbackQuery):
    try:
        currency_data = await get_currency()
        await callback.message.answer(currency_data)
    except Exception as e:
        await callback.message.answer("Произошла ошибка при получении курса валют")
        print(f"Ошибка при получении курса валют: {e}")
    await callback.answer()

↪️finance.py

from aiogram import Router, F
from aiogram.types import Message, CallbackQuery
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from keyboards.inline_keyboard import get_main_menu, finance_keyboard
from db.users import update_finance, get_finance_data
# === Инициализация роутера ===
finance_router = Router()
# Обработка кнопки "Финансы"
class FinanceForm(StatesGroup):
    category1 = State()
    expenses1 = State()
    category2 = State()
    expenses2 = State()
    category3 = State()
    expenses3 = State()
# Обработка кнопки "Финансы"
@finance_router.callback_query(F.data == "finance")
async def start_finance(callback: CallbackQuery, state: FSMContext):
    print(f"DEBUG: Кнопка 'Финансы' нажата пользователем {callback.from_user.id}")
    await state.set_state(FinanceForm.category1)
    await callback.message.answer("Введите первую категорию расходов:")
    await callback.answer()
# Обработка кнопки "Показать статистику"
@finance_router.callback_query(F.data == "show_finance")
async def show_finance_stats(callback: CallbackQuery):
    tg_id = callback.from_user.id
    finance_data = get_finance_data(tg_id)
    
    if finance_data:
        # Формируем красивый отчет
        report = f"💰 <b>Ваши расходы:</b>\n\n"
        report += f"📊 <b>Общая сумма:</b> {finance_data['total']:,.2f} ₽\n\n"
        
        for category, amount, percentage in finance_data['breakdown']:
            # Создаем визуальный индикатор процента
            bar_length = int(percentage / 5)  # 5% = 1 символ
            bar = "█" * bar_length + "░" * (20 - bar_length)
            
            report += f"📌 <b>{category}:</b>\n"
            report += f"   💵 {amount:,.2f} ₽ ({percentage:.1f}%)\n"
            report += f"   {bar}\n\n"
        
        await callback.message.answer(report, parse_mode="HTML", reply_markup=finance_keyboard())
    else:
        await callback.message.answer("📝 У вас пока нет данных о расходах. Нажмите '💰 Финансы' для добавления.", reply_markup=finance_keyboard())
    
    await callback.answer()
# Обработка кнопки "Назад" в финансах
@finance_router.callback_query(F.data == "main_menu")
async def back_to_main_menu(callback: CallbackQuery):
    await callback.message.edit_text(
        text="📋 Главное меню. Выбери действие:",
        reply_markup=await get_main_menu()
    )
    await callback.answer()
# Обработка ввода первой категории
@finance_router.message(FinanceForm.category1)
async def category1_handler(message: Message, state: FSMContext):
    await state.update_data(category1=message.text)
    await state.set_state(FinanceForm.expenses1)
    await message.answer("Введите расходы для категории 1:")
# Обработка ввода расходов
@finance_router.message(FinanceForm.expenses1)
async def expenses1_handler(message: Message, state: FSMContext):
    try:
        expense = float(message.text)
        await state.update_data(expenses1=expense)
        await state.set_state(FinanceForm.category2)
        await message.answer("Введите вторую категорию расходов:")
    except ValueError:
        await message.answer("❌ Пожалуйста, введите корректную сумму (например: 1000.50)")
# Обработка ввода второй категории
@finance_router.message(FinanceForm.category2)
async def category2_handler(message: Message, state: FSMContext):
    await state.update_data(category2=message.text)
    await state.set_state(FinanceForm.expenses2)
    await message.answer("Введите расходы для категории 2:")
# Обработка ввода расходов
@finance_router.message(FinanceForm.expenses2)
async def expenses2_handler(message: Message, state: FSMContext):
    try:
        expense = float(message.text)
        await state.update_data(expenses2=expense)
        await state.set_state(FinanceForm.category3)
        await message.answer("Введите третью категорию расходов:")
    except ValueError:
        await message.answer("❌ Пожалуйста, введите корректную сумму (например: 1000.50)")
# Обработка ввода третьей категории
@finance_router.message(FinanceForm.category3)
async def category3_handler(message: Message, state: FSMContext):
    await state.update_data(category3=message.text)
    await state.set_state(FinanceForm.expenses3)
    await message.answer("Введите расходы для категории 3:")
# Обработка ввода расходов
@finance_router.message(FinanceForm.expenses3)
async def expenses3_handler(message: Message, state: FSMContext):
    try:
        expense = float(message.text)
        await state.update_data(expenses3=expense)
        data = await state.get_data()
        tg_id = message.from_user.id
        
        # Сохраняем данные
        update_finance(tg_id, data)
        
        # Получаем статистику
        finance_data = get_finance_data(tg_id)
        
        if finance_data:
            # Формируем красивый отчет
            report = f"💰 <b>Ваши расходы:</b>\n\n"
            report += f"📊 <b>Общая сумма:</b> {finance_data['total']:,.2f} ₽\n\n"
            
            for category, amount, percentage in finance_data['breakdown']:
                # Создаем визуальный индикатор процента
                bar_length = int(percentage / 5)  # 5% = 1 символ
                bar = "█" * bar_length + "░" * (20 - bar_length)
                
                report += f"📌 <b>{category}:</b>\n"
                report += f"   💵 {amount:,.2f} ₽ ({percentage:.1f}%)\n"
                report += f"   {bar}\n\n"
            
            await message.answer(report, parse_mode="HTML")
        else:
            await message.answer("✅ Данные сохранены!")
            
        await state.clear()
        
    except ValueError:
        await message.answer("❌ Пожалуйста, введите корректную сумму (например: 1000.50)")

↪️nasa.py

from aiogram import Router, F
from aiogram.types import Message, CallbackQuery
from services.nasa import get_random_apod
from keyboards.inline_keyboard import nasa_again_keyboard
nasa_router = Router()
@nasa_router.callback_query(F.data == "space")
async def handle_space(callback: CallbackQuery):
    try:
        apod = await get_random_apod()
        photo_url = apod["url"]
        title = apod["title"]
        explanation = apod["explanation"]
        await callback.message.answer_photo(photo=photo_url, caption=f"{title}\n\n{explanation}", reply_markup=nasa_again_keyboard())
    except Exception as e:
        await callback.message.answer("Извините, не удалось получить фото из космоса. Попробуйте позже.")
        print(f"Ошибка NASA API: {e}")
    await callback.answer()
@nasa_router.callback_query(F.data == "nasa_again")
async def handle_nasa_again(callback: CallbackQuery):
    try:
        apod = await get_random_apod()
        photo_url = apod["url"]
        title = apod["title"]
        explanation = apod["explanation"]
        await callback.message.answer_photo(photo=photo_url, caption=f"{title}\n\n{explanation}", reply_markup=nasa_again_keyboard())
    except Exception as e:
        await callback.message.answer("Извините, не удалось получить фото из космоса. Попробуйте позже.")
        print(f"Ошибка NASA API: {e}")
    await callback.answer()

↪️photo.py

from aiogram import Router, F
from aiogram.types import Message, CallbackQuery
from services.photo import get_random_photo
from keyboards.inline_keyboard import photo_again_keyboard
photo_router = Router()
# Обработка кнопки "photo"
@photo_router.callback_query(F.data == "photo")
async def handle_photo(callback: CallbackQuery):
    url = await get_random_photo()
    if url:
        await callback.message.delete()
        await callback.message.answer_photo(photo=url, caption="Вот случайное фото", reply_markup = photo_again_keyboard())
    else:
        await callback.message.answer("Не удалось получить фото.")
    await callback.answer()
# Обработка кнопки "photo_again"
@photo_router.callback_query(F.data == "photo_again")
async def photo_again(callback: CallbackQuery):
    url = await get_random_photo()
    if url:
        await callback.message.answer_photo(photo=url, caption="Ещё одно фото", reply_markup = photo_again_keyboard())
    else:
        await callback.message.answer("Не удалось получить фото.")
    await callback.answer()

↪️registration.py

from aiogram import Router, F
from aiogram.types import CallbackQuery
from db.users import save_user, get_user
# === Инициализация роутера ===
registration_router = Router()
# Обработка кнопки "Регистрация"
@registration_router.callback_query(F.data == "registration")
async def registration(callback: CallbackQuery):
    telegram_id = callback.from_user.id
    name = callback.from_user.full_name
    user = get_user(telegram_id)
    if user:
        await callback.message.answer("Ты уже зарегистрирован!")
    else:
        save_user(telegram_id, name, None)
        await callback.message.answer("Ты зарегистрирован!")
    await callback.answer()

↪️start.py

from aiogram import Router, F
from aiogram.types import Message, CallbackQuery
from aiogram.filters import CommandStart
from aiogram.fsm.state import State, StatesGroup
from aiogram.fsm.context import FSMContext
from keyboards.inline_keyboard import get_main_menu, get_info_menu, get_fun_menu
# === Инициализация роутера ===
start_router = Router()
# === Хэндлер на /start ===
@start_router.message(CommandStart())
async def cmd_start(message: Message):
    keyboard = await get_main_menu()
    await message.answer(
        "Привет! Я бот-помощник.\nЧто бы ты хотел сделать дальше?",
        reply_markup = keyboard
    )
# Обработка кнопки "Информация"
@start_router.callback_query(F.data == "info_menu")
async def handle_info(callback: CallbackQuery):
    await callback.message.edit_text(text="Меню информации", reply_markup = await get_info_menu())
    await callback.answer()
# Обработка кнопки "Развлечения"
@start_router.callback_query(F.data == "fun_menu")
async def handle_fun(callback: CallbackQuery):
    await callback.message.delete()
    await callback.message.answer("Меню развлечений", reply_markup=await get_fun_menu())
# Обработка кнопки "Назад"
@start_router.callback_query(F.data == "main_menu")
async def handle_back(callback: CallbackQuery):
    await callback.message.edit_text(text="📋 Главное меню. Выбери действие:", reply_markup = await get_main_menu())
    await callback.answer()
# Обработка кнопки "❓ Что я умею"
@start_router.callback_query(F.data == "help")
async def handle_help(callback: CallbackQuery):
    text = (
        "🤖 Я — ваш помощник в путешествиях!\n\n"
        "Вот что я умею:\n"
        "🧭 Информация — полезные сервисы для путешественника\n"
        "☁️ Погода — узнаю погоду в любом городе\n"
        "🌐 Перевод — помогу перевести фразы\n"
        "💱 Валюта — покажу актуальный курс валют\n"
        "💰 Финансы — помогу вести расходы\n"
        "🎉 Развлечения — фото, котики, космос и другое!\n\n"
        "Используй меню для выбора нужной функции."
    )
    await callback.message.answer(text)
    await callback.answer()

↪️translate.py

from aiogram import Router, F
from aiogram.types import Message, CallbackQuery
from services.translate import translate_text
translate_router = Router()
# Кнопка "Перевод" — предлагаем выбрать направление слово
@translate_router.callback_query(F.data == "translate")
async def handle_translate(callback: CallbackQuery):
    await callback.message.answer("Напиши язык и текст. Пример: `/translate en Привет`", parse_mode="Markdown")
    await callback.answer()
# Обработка ввода текста для перевода
@translate_router.message(lambda m: m.text.lower().startswith("/translate"))
async def handle_translate(message: Message):
    parts = message.text.split(maxsplit=2)
    if len(parts) < 3:
        await message.answer("Формат: /translate <язык> <текст>")
        return
    lang = parts[1].lower()
    text = parts[2]
    translated = translate_text(text, lang)
    if translated:
        await message.answer(f"Перевод: {translated}")
    else:
        await message.answer("Произошла ошибка при переводе.")

↪️weather.py

from aiogram import Router, F
from aiogram.types import Message, CallbackQuery
from services.weather import get_weather
from keyboards.inline_keyboard import weather_refresh_keyboard, get_main_menu
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from db.users import save_user
# === Состояние формы пользователя ===
class UserForm(StatesGroup):
    city = State()
weather_router = Router()
# Обработка кнопки "Погода" — спрашиваем город
@weather_router.callback_query(F.data == "weather")
async def ask_city(callback: CallbackQuery, state: FSMContext):
    await callback.message.answer("В каком городе ты хочешь узнать погоду?")
    await state.set_state(UserForm.city)
    await callback.answer()
# Обработка ввода города
@weather_router.message(UserForm.city)
async def process_city(message: Message, state: FSMContext):
    city = message.text
    user_id = message.from_user.id
    name = message.from_user.full_name
    # Сохраняем данные в БД
    save_user(user_id, name, city)
    await message.answer(f"✅ Город <b>{city}</b> сохранён!", parse_mode="HTML")
    # Показываем погоду сразу после сохранения
    await send_weather(message, city)
    # Сбрасываем состояние
    await state.clear()
# Обработка кнопки "refresh_weather"
@weather_router.callback_query(F.data.startswith("refresh_weather:"))
async def refresh_weather(callback: CallbackQuery):
    city = callback.data.split(":")[1]
    await send_weather(callback.message, city)
    await callback.answer()
# Обработка кнопки "Назад"
@weather_router.callback_query(F.data == "main_menu")
async def back_to_menu(callback: CallbackQuery):
    await callback.message.edit_text(
        text="📋 Главное меню. Выбери действие:",
        reply_markup=await get_main_menu()
    )
    await callback.answer()
# Функция для отправки погоды
async def send_weather(target, city: str):
    data = await get_weather(city)
    if not data:
        await target.answer(
            f"Не удалось получить погоду для {city}",
            reply_markup=weather_refresh_keyboard(city)
        )
        return
    temp = data['temp']
    desc = data['description'].capitalize()
    await target.answer(
        f"🌤 Погода в {city}: {temp}°C, {desc}",
        reply_markup=weather_refresh_keyboard(city)
    )

✉services

↪️cat.py

import asyncio
import random
import aiohttp
from config import CAT_API_KEY
# получить список пород котов
async def get_cat_breeds():
    url = f"https://api.thecatapi.com/v1/breeds"
    headers = {"x-api-key": CAT_API_KEY}
    async with aiohttp.ClientSession() as session:
        async with session.get(url, headers=headers) as response:
            return await response.json()
# получить фото кота по породе
async def get_cat_image(breed_id):
    url = f"https://api.thecatapi.com/v1/images/search?breed_ids={breed_id}"
    headers = {"x-api-key": CAT_API_KEY}
    async with aiohttp.ClientSession() as session:
        async with session.get(url, headers=headers) as response:
            data = await response.json()
            return data[0]["url"]
# # получить информацию о породе
# def get_breed_info(breed_name):
#     breeds = get_cat_breeds()
#     for breed in breeds:
#         if breed["name"].lower() == breed_name.lower():
#             return breed
#     return None
# получить случайную породу
async def get_random_breed():
    breeds = await get_cat_breeds()
    return random.choice(breeds)

↪️currency.py

import aiohttp
# код для получения курса валют
async def get_currency():
    url = "https://v6.exchangerate-api.com/v6/09edf8b2bb246e1f801cbfba/latest/USD"
    try:
        async with aiohttp.ClientSession() as session:
            async with session.get(url) as response:
                if response.status != 200:
                    return "Произошла ошибка при получении курса валют"
                data = await response.json()
                usd_to_rub = data["conversion_rates"]["RUB"]
                eur_to_usd = data["conversion_rates"]["EUR"]
                eur_to_rub = eur_to_usd * usd_to_rub
                return f"💵 1 USD = {usd_to_rub:.2f} RUB\n💶 1 EUR = {eur_to_rub:.2f} RUB"
    except Exception:
        return "Произошла ошибка при получении курса валют"

↪️nasa.py

import asyncio
import random
import aiohttp
from datetime import datetime, timedelta
from config import NASA_API_KEY
# получить изображение дня
async def get_random_apod():
    try:
        end_date = datetime.now()
        start_date = end_date - timedelta(days=365)
        random_date = start_date + (end_date - start_date) * random.random()
        date_str = random_date.strftime("%Y-%m-%d")
        url = f"https://api.nasa.gov/planetary/apod?date={date_str}&api_key={NASA_API_KEY}"
        async with aiohttp.ClientSession() as session:
            async with session.get(url, timeout=10) as response:
                response.raise_for_status()
                data = await response.json()
                # Проверяем наличие необходимых полей
                required_fields = ["url", "title", "explanation"]
                for field in required_fields:
                    if field not in data:
                        raise KeyError(f"Отсутствует поле {field} в ответе API")
                return data
    except Exception as e:
        print(f"Ошибка при работе с NASA API: {e}")
        raise

↪️photo.py

import aiohttp
from config import UNSPLASH_ACCESS_KEY
async def get_random_photo():
    try:
        url = "https://api.unsplash.com/photos/random"
        headers = {"Authorization": f"Client-ID {UNSPLASH_ACCESS_KEY}"}
        async with aiohttp.ClientSession() as session:
            async with session.get(url, headers=headers, timeout=5) as response:
                data = await response.json()
                return data.get("urls", {}).get("regular")
    except Exception as e:
        print(f"Ошибка фото: {e}")
        return None

↪️translate.py

from deep_translator import GoogleTranslator
def translate_text(text: str, target_lang: str = "en") -> str:
    try:
        return GoogleTranslator(source="auto", target=target_lang).translate(text)
    except Exception as e:
        print(f"Ошибка перевода: {e}")
        return ""

↪️weather.py

import aiohttp
from config import API_KEY2
async def get_weather(city: str):
    try:
        url = (
            f"https://api.openweathermap.org/data/2.5/weather?q={city}&appid={API_KEY2}&units=metric&lang=ru"
        )
        async with aiohttp.ClientSession() as session:
            async with session.get(url, timeout=15) as response:
                data = await response.json()
                if data.get("cod") != 200:
                    return None
                return {
                    "temp": data["main"]["temp"],
                    "description": data["weather"][0]["description"],
                }
    except Exception as e:
        print(f"Ошибка погоды: {e}")
        return None

↪.gitignore

# Python
__pycache__/
*.py[cod]
*.pyo
*.pyd
*.pyc
# Virtual environment
.venv/
venv/
ENV/
# IDE
.idea/
.vscode/
# OS
.DS_Store
Thumbs.db
# Database
*.db
# Environment variables
.env
.env.*
# Logs
*.log
# Jupyter
.ipynb_checkpoints/
# Backup files
*~
# Node.js (если вдруг есть фронтенд)
node_modules/
# Output
/dist/
/build/
# Misc
*.bak
# Ignore test cache
.pytest_cache/
# Ignore coverage reports
.coverage
htmlcov/
.coverage.*
# Ignore compiled C extensions
*.so
# Ignore migrations (если используются)
migrations/ 

↪️config.py

import os
from dotenv import load_dotenv
load_dotenv()
BOT_TOKEN = os.getenv("BOT_TOKEN")
API_KEY1 = "8cf9ed27dae755ecf7ed9d97d85a2615"
API_KEY2 = "3c46887207e54b72bedd56e1245243cc"
UNSPLASH_ACCESS_KEY = os.getenv("UNSPLASH_ACCESS_KEY")
CAT_API_KEY = os.getenv("CAT_API_KEY")
DOG_API_KEY = os.getenv("DOG_API_KEY")
NASA_API_KEY = os.getenv("NASA_API_KEY")

↪️main.py

import asyncio
import logging
from aiogram import Bot, Dispatcher
from aiogram.fsm.storage.memory import MemoryStorage
# Импорт конфигурации токена и роутеров
from config import BOT_TOKEN
from routers.start import start_router
from routers.cat import cat_router
from routers.weather import weather_router
from routers.photo import photo_router
from routers.translate import translate_router
from routers.nasa import nasa_router
from routers.finance import finance_router
from routers.currency import currency_router
from routers.registration import registration_router
from db.users import init_db  # Функция инициализации БД
# === НАСТРОЙКА ЛОГГЕРА ===
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Инициализация бота и диспетчера
bot = Bot(token=BOT_TOKEN)
storage = MemoryStorage()  # Используем in-memory хранилище состояний (можно заменить на Redis)
dp = Dispatcher(storage=storage)
async def main():
    # Инициализация базы данных
    init_db()
    # Подключение всех роутеров
    dp.include_router(start_router)
    dp.include_router(weather_router)
    dp.include_router(photo_router)
    dp.include_router(translate_router)
    dp.include_router(cat_router)
    dp.include_router(nasa_router)
    dp.include_router(finance_router)
    dp.include_router(currency_router)
    dp.include_router(registration_router)
    logger.info("Бот запущен")
    # Запуск polling (постоянное получение апдейтов)
    await dp.start_polling(bot)
# Точка входа
if __name__ == "__main__":
    asyncio.run(main())