add buttons

This commit is contained in:
2026-03-26 10:15:41 +03:00
parent d18270de43
commit 20531d1029
4 changed files with 113 additions and 68 deletions

90
bot.py
View File

@@ -7,35 +7,39 @@ from aiogram import Bot, Dispatcher, F, types
from aiogram.filters import Command
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import ReplyKeyboardMarkup, KeyboardButton
from aiogram.types import ReplyKeyboardMarkup, KeyboardButton, ReplyKeyboardRemove
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from dotenv import load_dotenv
from weather import get_daily_weather_summary
from weather import get_daily_weather_summary, get_coords_by_city
# Загрузка переменных окружения
load_dotenv()
BOT_TOKEN = os.getenv("BOT_TOKEN")
# Настройка логирования
logging.basicConfig(level=logging.INFO)
# Инициализация
bot = Bot(token=BOT_TOKEN, parse_mode="Markdown")
dp = Dispatcher()
scheduler = AsyncIOScheduler()
# Формат: user_id: {"lat": float, "lon": float, "time": "HH:MM", "job_id": str}
# Импровизированная БД
users_db = {}
# Состояния
class WeatherSetup(StatesGroup):
waiting_for_location = State()
waiting_for_time = State()
# --- Клавиатуры ---
def get_main_keyboard():
"""Главное меню бота"""
return ReplyKeyboardMarkup(
keyboard=[
[KeyboardButton(text="🌤 Погода сейчас")],
[
KeyboardButton(text="📍 Обновить локацию", request_location=True),
KeyboardButton(text="📍 Изменить город"),
KeyboardButton(text="⏰ Изменить время")
]
],
@@ -43,17 +47,16 @@ def get_main_keyboard():
)
def get_location_keyboard():
"""Клавиатура для первичного запроса локации"""
return ReplyKeyboardMarkup(
keyboard=[[KeyboardButton(text="📍 Отправить геолокацию", request_location=True)]],
resize_keyboard=True
keyboard=[[KeyboardButton(text="📍 Отправить текущую геопозицию", request_location=True)]],
resize_keyboard=True,
input_field_placeholder="Или напишите название города..."
)
# --- Логика планировщика ---
# --- Задача для планировщика ---
async def send_weather_update(user_id: int):
"""Функция планировщика. Берет актуальные координаты из БД на момент отправки."""
user_data = users_db.get(user_id)
if not user_data:
if not user_data or not user_data.get("lat"):
return
weather_text = await get_daily_weather_summary(user_data["lat"], user_data["lon"])
@@ -62,43 +65,67 @@ async def send_weather_update(user_id: int):
except Exception as e:
logging.error(f"Ошибка отправки пользователю {user_id}: {e}")
# --- Обработчики команд ---
# --- Обработчики ---
@dp.message(Command("start"))
async def cmd_start(message: types.Message, state: FSMContext):
await state.set_state(WeatherSetup.waiting_for_location)
await message.answer(
"Привет! Я погодный бот. Отправь мне свою локацию, чтобы я знал, где смотреть погоду.",
"Привет! Я погодный бот.\n\nОтправь мне свою геопозицию кнопкой ниже или **просто напиши название своего города**:",
reply_markup=get_location_keyboard()
)
@dp.message(F.content_type == "location")
async def process_location(message: types.Message, state: FSMContext):
"""Срабатывает как при первой настройке, так и при нажатии 'Обновить локацию'"""
lat = message.location.latitude
lon = message.location.longitude
@dp.message(F.text == "📍 Изменить город")
async def cmd_change_city(message: types.Message, state: FSMContext):
await state.set_state(WeatherSetup.waiting_for_location)
await message.answer(
"Напиши название нового города или отправь геопозицию:",
reply_markup=get_location_keyboard()
)
@dp.message(WeatherSetup.waiting_for_location)
async def process_location_input(message: types.Message, state: FSMContext):
user_id = message.from_user.id
if user_id in users_db:
# Если пользователь уже зарегистрирован, просто обновляем координаты
city_name_display = "Выбранная локация"
if message.location:
lat = message.location.latitude
lon = message.location.longitude
elif message.text:
lat, lon, found_name = await get_coords_by_city(message.text)
if not lat:
await message.answer("К сожалению, я не нашел такой город 😔. Попробуй уточнить название или отправить геопозицию.")
return
city_name_display = found_name
else:
await message.answer("Пожалуйста, отправь геопозицию или напиши название города текстом.")
return
# Сохраняем или обновляем пользователя в БД
if user_id not in users_db:
users_db[user_id] = {"lat": lat, "lon": lon, "time": None, "job_id": None}
else:
users_db[user_id]["lat"] = lat
users_db[user_id]["lon"] = lon
await message.answer("📍 Локация успешно обновлена!", reply_markup=get_main_keyboard())
# Если время уже было настроено ранее
if users_db[user_id].get("time"):
await message.answer(f"📍 Локация успешно обновлена на: **{city_name_display}**!", reply_markup=get_main_keyboard())
await state.clear()
else:
# Первичная настройка
users_db[user_id] = {"lat": lat, "lon": lon, "time": None, "job_id": None}
await state.set_state(WeatherSetup.waiting_for_time)
await message.answer(
"Отлично! Теперь напиши время, в которое хочешь получать прогноз (в формате ЧЧ:ММ):",
reply_markup=types.ReplyKeyboardRemove()
f"Отлично! Локация **{city_name_display}** найдена.\n\nТеперь напиши время, в которое хочешь получать ежедневный прогноз (в формате ЧЧ:ММ):",
reply_markup=ReplyKeyboardRemove()
)
@dp.message(F.text == "⏰ Изменить время")
async def cmd_change_time(message: types.Message, state: FSMContext):
if message.from_user.id not in users_db:
await message.answer("Сначала отправьте локацию через меню или команду /start.")
await message.answer("Сначала отправьте локацию через команду /start.")
return
await state.set_state(WeatherSetup.waiting_for_time)
await message.answer("Введите новое время для ежедневного прогноза (в формате ЧЧ:ММ):", reply_markup=types.ReplyKeyboardRemove())
await message.answer("Введите новое время для ежедневного прогноза (в формате ЧЧ:ММ):", reply_markup=ReplyKeyboardRemove())
@dp.message(WeatherSetup.waiting_for_time, F.text)
async def process_time(message: types.Message, state: FSMContext):
@@ -108,12 +135,10 @@ async def process_time(message: types.Message, state: FSMContext):
try:
dt = datetime.strptime(time_text, "%H:%M")
# Удаляем старую задачу, если она была
old_job_id = users_db[user_id].get("job_id")
if old_job_id and scheduler.get_job(old_job_id):
scheduler.remove_job(old_job_id)
# Создаем новую задачу (передаем только user_id)
job = scheduler.add_job(
send_weather_update,
trigger='cron',
@@ -143,14 +168,11 @@ async def cmd_weather_now(message: types.Message):
await message.answer("Сначала отправьте локацию через команду /start.")
return
# Отправляем "заглушку", пока идет запрос к API
loading_msg = await message.answer("⏳ Узнаю погоду...")
weather_text = await get_daily_weather_summary(user_data["lat"], user_data["lon"])
# Редактируем сообщение-заглушку готовым текстом
await loading_msg.edit_text(weather_text)
# --- Запуск ---
async def main():
scheduler.start()
await bot.delete_webhook(drop_pending_updates=True)