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 # Функции для работы с пользователями
⚙️ Установка
- Клонируйте репозиторий:git clone https://github.com/ваше_имя_пользователя/travel-bot.git cd travel-bot
- Установите зависимости:pip install -r requirements.txt
- Создайте файл
.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
📱 Использование
- Запустите бота в Telegram
- Нажмите «👤 Регистрация», чтобы создать учётную запись
- Используйте главное меню для выбора функций:
💰 Финансы и статистика
- Выберите в меню пункт «💰 Финансы», чтобы ввести данные о новых расходах
- Чтобы просмотреть отчёт о расходах, нажмите «📊 Статистика расходов» в информационном меню
- Получите красивый отчёт с процентами и диаграммой
☁️ Погода
- Выберите «☁️ Погода» в меню
- Введите название города
- Получайте актуальную информацию о погоде с возможностью обновления
🛠️ Технические особенности
- Асинхронная архитектура: все HTTP-запросы используют
aiohttpдля повышения производительности - База данных: SQLite для хранения данных пользователей
- FSM (конечный автомат): для пошагового ввода данных (финансы)
- Встроенная клавиатура: удобная навигация по функциям
- Обработка ошибок: корректная обработка ошибок API и сети
📋 Зависимости
aiogram— Платформа API Telegram-ботаaiohttp— Асинхронный HTTP-клиентsqlite3— База данных (встроенная)deep-translator— Перевод текстаpython-dotenv— Загрузка переменных окружения
Код:
✉ db
# === ФАЙЛ: 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
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
# 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()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()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)")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()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()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()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()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("Произошла ошибка при переводе.")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
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)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 "Произошла ошибка при получении курса валют"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}")
raiseimport 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 Nonefrom 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 ""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# 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/
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")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())