first commit

This commit is contained in:
2026-03-26 09:37:50 +03:00
commit d18270de43
6 changed files with 231 additions and 0 deletions

160
bot.py Normal file
View File

@@ -0,0 +1,160 @@
import asyncio
import logging
import os
from datetime import datetime
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 apscheduler.schedulers.asyncio import AsyncIOScheduler
from dotenv import load_dotenv
from weather import get_daily_weather_summary
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_time = State()
# --- Клавиатуры ---
def get_main_keyboard():
"""Главное меню бота"""
return ReplyKeyboardMarkup(
keyboard=[
[KeyboardButton(text="🌤 Погода сейчас")],
[
KeyboardButton(text="📍 Обновить локацию", request_location=True),
KeyboardButton(text="⏰ Изменить время")
]
],
resize_keyboard=True
)
def get_location_keyboard():
"""Клавиатура для первичного запроса локации"""
return ReplyKeyboardMarkup(
keyboard=[[KeyboardButton(text="📍 Отправить геолокацию", request_location=True)]],
resize_keyboard=True
)
# --- Логика планировщика ---
async def send_weather_update(user_id: int):
"""Функция планировщика. Берет актуальные координаты из БД на момент отправки."""
user_data = users_db.get(user_id)
if not user_data:
return
weather_text = await get_daily_weather_summary(user_data["lat"], user_data["lon"])
try:
await bot.send_message(chat_id=user_id, text=weather_text)
except Exception as e:
logging.error(f"Ошибка отправки пользователю {user_id}: {e}")
# --- Обработчики команд ---
@dp.message(Command("start"))
async def cmd_start(message: types.Message, state: FSMContext):
await message.answer(
"Привет! Я погодный бот. Отправь мне свою локацию, чтобы я знал, где смотреть погоду.",
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
user_id = message.from_user.id
if user_id in users_db:
# Если пользователь уже зарегистрирован, просто обновляем координаты
users_db[user_id]["lat"] = lat
users_db[user_id]["lon"] = lon
await message.answer("📍 Локация успешно обновлена!", reply_markup=get_main_keyboard())
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()
)
@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.")
return
await state.set_state(WeatherSetup.waiting_for_time)
await message.answer("Введите новое время для ежедневного прогноза (в формате ЧЧ:ММ):", reply_markup=types.ReplyKeyboardRemove())
@dp.message(WeatherSetup.waiting_for_time, F.text)
async def process_time(message: types.Message, state: FSMContext):
time_text = message.text.strip()
user_id = message.from_user.id
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',
hour=dt.hour,
minute=dt.minute,
args=[user_id]
)
users_db[user_id]["time"] = time_text
users_db[user_id]["job_id"] = job.id
await message.answer(
f"✅ Готово! Ежедневный прогноз установлен на {time_text}.",
reply_markup=get_main_keyboard()
)
await state.clear()
except ValueError:
await message.answer("Неверный формат времени. Пожалуйста, используй формат ЧЧ:ММ (например, 07:00).")
@dp.message(F.text == "🌤 Погода сейчас")
async def cmd_weather_now(message: types.Message):
user_id = message.from_user.id
user_data = users_db.get(user_id)
if not user_data or not user_data.get("lat"):
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)
await dp.start_polling(bot)
if __name__ == "__main__":
asyncio.run(main())