169 lines
6.9 KiB
Python
169 lines
6.9 KiB
Python
"""Утилита для запуска утреннего дайджеста."""
|
||
|
||
import asyncio
|
||
import logging
|
||
from datetime import datetime, timedelta
|
||
|
||
import discord
|
||
from discord import commands
|
||
from discord.ext import tasks
|
||
|
||
from utils.pogoda import fetch_weather, pressure_to_mmhg, translate_weather
|
||
from utils.news import fetch_rss, format_articles, RSS_URL_ARTICLES, RSS_URL_POSTS
|
||
from utils.cat import fetch_cat
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
async def run_morning(bot: "commands.Bot", channel: discord.TextChannel):
|
||
"""Выполнить утренний дайджест и отправить в канал."""
|
||
try:
|
||
api_url = "https://wttr.in/Magnitogorsk?format=j1&lang=ru"
|
||
|
||
# Параллельный запрос погоды, новостей и котика
|
||
weather_data, articles, posts, cat_url = await asyncio.gather(
|
||
fetch_weather(api_url),
|
||
fetch_rss(RSS_URL_ARTICLES),
|
||
fetch_rss(RSS_URL_POSTS),
|
||
fetch_cat(),
|
||
)
|
||
|
||
# --- Формируем embed ---
|
||
embed = discord.Embed(title="🌅 Утренний дайджест!", color=0xF4A460)
|
||
|
||
# Котик как thumbnail
|
||
if cat_url:
|
||
embed.set_thumbnail(url=cat_url)
|
||
|
||
description_lines = []
|
||
|
||
# --- Погода ---
|
||
if weather_data is not None:
|
||
current = weather_data.get("current_condition", [{}])[0]
|
||
if current:
|
||
temp = current.get("temp_C", "—")
|
||
feels_like = current.get("FeelsLikeC", "—")
|
||
description = translate_weather(current.get("weatherDesc", [{}])[0].get("value", "—"))
|
||
humidity = current.get("humidity", "—")
|
||
wind_kmh = current.get("windspeedKmph", "—")
|
||
try:
|
||
wind = round(int(wind_kmh) / 3.6, 1) if wind_kmh != "—" else "—"
|
||
except (ValueError, TypeError):
|
||
wind = "—"
|
||
pressure_mb = current.get("pressure", "—")
|
||
pressure_mm = pressure_to_mmhg(pressure_mb)
|
||
|
||
description_lines.append(
|
||
f"**Погода в Магнитогорске:**\n"
|
||
f"Температура: {temp}°C (ощущается как {feels_like}°C)\n"
|
||
f"Описание: {description}\n"
|
||
f"Влажность: {humidity}%\n"
|
||
f"Ветер: {wind} м/с\n"
|
||
f"Давление: {pressure_mm} мм рт. ст."
|
||
)
|
||
else:
|
||
description_lines.append("Не удалось получить данные о погоде.")
|
||
else:
|
||
description_lines.append("Не удалось получить данные о погоде.")
|
||
|
||
description_lines.append("")
|
||
|
||
# --- Новости: статьи ---
|
||
if articles is not None:
|
||
if articles:
|
||
lines = format_articles(articles,
|
||
"Лучшие статьи за сутки / Искусственный интеллект / Хабr",
|
||
"https://habr.com/ru/hubs/artificial_intelligence/articles/top/daily/")
|
||
description_lines.append("\n".join(lines))
|
||
else:
|
||
description_lines.append("Новостей пока нет.")
|
||
else:
|
||
description_lines.append("Не удалось получить новости.")
|
||
|
||
description_lines.append("")
|
||
|
||
# --- Новости: посты ---
|
||
if posts is not None:
|
||
if posts:
|
||
lines = format_articles(posts,
|
||
"Лучшие новости за сутки / Искусственный интеллект / Хабr",
|
||
"https://habr.com/ru/hubs/artificial_intelligence/news/top/daily/")
|
||
description_lines.append("\n".join(lines))
|
||
else:
|
||
description_lines.append("Новостей пока нет.")
|
||
else:
|
||
description_lines.append("Не удалось получить новости.")
|
||
|
||
embed.description = "\n".join(description_lines)
|
||
await channel.send(embed=embed)
|
||
logger.info("✅ Утренний дайджест отправлен в #%s", channel.name)
|
||
|
||
except Exception as e:
|
||
logger.error("Ошибка при выполнении утреннего дайджеста: %s", e, exc_info=True)
|
||
try:
|
||
await channel.send("❌ Не удалось выполнить утренний дайджест.")
|
||
except Exception:
|
||
pass
|
||
|
||
|
||
class Scheduler:
|
||
"""Планировщик ежедневных задач."""
|
||
|
||
def __init__(self, bot: commands.Bot, morning_time: str = "07:00"):
|
||
self.bot = bot
|
||
self.morning_time = morning_time
|
||
self._last_run_date = None
|
||
self.morning_loop = tasks.loop(seconds=1.0)(self._check_and_run_morning)
|
||
self._start_scheduler()
|
||
|
||
def _start_scheduler(self):
|
||
try:
|
||
self.morning_loop.start()
|
||
logger.info("Планировщик запущен (время: %s)", self.morning_time)
|
||
except RuntimeError:
|
||
logger.warning("Планировщик уже запущен")
|
||
|
||
def _stop_scheduler(self):
|
||
try:
|
||
self.morning_loop.stop()
|
||
logger.info("Планировщик остановлен")
|
||
except RuntimeError:
|
||
logger.warning("Планировщик уже остановлен")
|
||
|
||
def _calculate_next_run(self) -> datetime:
|
||
now = datetime.now()
|
||
hour, minute = map(int, self.morning_time.split(":"))
|
||
today_run = now.replace(hour=hour, minute=minute, second=0, microsecond=0)
|
||
|
||
if now >= today_run:
|
||
return today_run + timedelta(days=1)
|
||
return today_run
|
||
|
||
async def _check_and_run_morning(self):
|
||
now = datetime.now()
|
||
target = self._calculate_next_run()
|
||
|
||
if now >= target and now.day != self._last_run_date:
|
||
self._last_run_date = now.day
|
||
await self._run_morning()
|
||
|
||
async def _run_morning(self):
|
||
logger.info(f"Выполняю morning в {self.morning_time}")
|
||
|
||
for channel in self.bot.get_all_channels():
|
||
if isinstance(channel, discord.TextChannel):
|
||
if channel.permissions_for(channel.guild.me).send_messages:
|
||
try:
|
||
await channel.send("🌅 Утренний дайджест!")
|
||
await run_morning(self.bot, channel)
|
||
return
|
||
except Exception as e:
|
||
logger.error("Ошибка отправки в #%s: %s", channel.name, e)
|
||
continue
|
||
|
||
def start(self):
|
||
self._start_scheduler()
|
||
|
||
def stop(self):
|
||
self._stop_scheduler()
|