From cef53197b15a2931010eb5adf23b0bfad86ed99f Mon Sep 17 00:00:00 2001 From: deadzilla Date: Thu, 9 Jul 2026 13:28:39 +0500 Subject: [PATCH] =?UTF-8?q?feat:=20!morning=20=E2=80=94=20=D0=B7=D0=B0?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D0=B8=D1=82=D1=8C=20embed=20=D0=BD=D0=B0=20p?= =?UTF-8?q?lain=20text,=20=D0=BA=D0=BE=D1=82=20=D0=BE=D1=82=D0=B4=D0=B5?= =?UTF-8?q?=D0=BB=D1=8C=D0=BD=D1=8B=D0=BC=20=D1=81=D0=BE=D0=BE=D0=B1=D1=89?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Переименован format_weather_for_embed -> format_weather_for_message - Убран markdown жирный (**text**) из заголовков погоды и статей - Утренний дайджест отправляется как обычное текстовое сообщение - Котик отправляется отдельным сообщением перед дайджестом - Truncation через truncate_message (лимит 2000 символов) - Обновлены тесты: проверка 2 send() вместо embed --- tests/test_format_articles.py | 6 ++-- tests/test_morning_runner.py | 54 +++++++++++++++++------------------ utils/__init__.py | 4 +-- utils/morning_runner.py | 45 ++++++++++++++--------------- utils/news.py | 2 +- utils/pogoda.py | 6 ++-- 6 files changed, 57 insertions(+), 60 deletions(-) diff --git a/tests/test_format_articles.py b/tests/test_format_articles.py index a62d521..d9f236e 100644 --- a/tests/test_format_articles.py +++ b/tests/test_format_articles.py @@ -99,7 +99,7 @@ class TestFormatArticles: ] result = format_articles(articles, "Заголовок", "https://habr.com/feed") assert len(result) == 3 # заголовок + 2 статьи - assert result[0] == "**Заголовок**\n" + assert result[0] == "Заголовок\n" assert result[1] == "Статья 1\n28.05.2026 " assert result[2] == "Статья 2\n29.05.2026 " @@ -122,7 +122,7 @@ class TestFormatArticles: def test_format_articles_empty_list(self) -> None: """Пустой список должен вернуть только заголовок.""" result = format_articles([], "Заголовок", "https://habr.com/feed") - assert result == ["**Заголовок**\n"] + assert result == ["Заголовок\n"] assert len(result) == 1 def test_format_articles_none(self) -> None: @@ -143,7 +143,7 @@ class TestFormatArticles: ] result = format_articles(articles, "Новости AI", "https://habr.com/ai") assert len(result) == 2 - assert result[0] == "**Новости AI**\n" + assert result[0] == "Новости AI\n" assert result[1] == "Единственная статья\n28.05.2026 " def test_format_articles_long_title_truncated(self) -> None: diff --git a/tests/test_morning_runner.py b/tests/test_morning_runner.py index c8c0808..0969681 100644 --- a/tests/test_morning_runner.py +++ b/tests/test_morning_runner.py @@ -101,8 +101,8 @@ class TestRunMorning: """Тесты run_morning.""" @pytest.mark.asyncio - async def test_run_morning_sends_embed(self) -> None: - """run_morning должен отправлять embed в канал.""" + async def test_run_morning_sends_plain_message(self) -> None: + """run_morning должен отправлять plain text в канал.""" bot = AsyncMock() channel = AsyncMock() channel.name = "test-channel" @@ -153,22 +153,29 @@ class TestRunMorning: "utils.morning_runner.fetch_cat", new=AsyncMock(return_value="http://cat.jpg"), ), - patch("utils.morning_runner.discord.Embed"), ): await run_morning(bot, channel) - channel.send.assert_called_once() - call_args = channel.send.call_args[1] - assert "embed" in call_args - assert call_args["embed"] is not None + # Два сообщения: кот + дайджест + assert channel.send.call_count == 2 + + # Первое сообщение — URL кота + first_call = channel.send.call_args_list[0] + assert first_call[0][0] == "http://cat.jpg" + + # Второе сообщение — текстовый дайджест + second_call = channel.send.call_args_list[1] + message_text = second_call[0][0] + assert "Утренний дайджест" in message_text + assert "Погода в Магнитогорске" in message_text class TestRunMorningWithFallback: - """Тесты fallback в пустом embed.""" + """Тесты fallback в plain text.""" @pytest.mark.asyncio - async def test_run_morning_empty_embed_fallback(self) -> None: - """run_morning должен добавлять fallback сообщение при пустых данных.""" + async def test_run_morning_empty_fallback(self) -> None: + """run_morning должен отправлять fallback сообщение при пустых данных.""" bot = AsyncMock() channel = AsyncMock() channel.name = "test-channel" @@ -182,21 +189,14 @@ class TestRunMorningWithFallback: ), patch("utils.morning_runner.fetch_rss", new=AsyncMock(return_value=None)), patch("utils.morning_runner.fetch_cat", new=AsyncMock(return_value=None)), - patch("utils.morning_runner.discord.Embed") as mock_embed_class, ): - embed_mock = AsyncMock() - mock_embed_class.return_value = embed_mock - await run_morning(bot, channel) - # Убедимся, что send был вызван + # Только одно сообщение (без кота) с fallback текстом channel.send.assert_called_once() - call_args = channel.send.call_args[1] - assert "embed" in call_args - - # Проверяем, что description содержит fallback сообщение - embed_description = call_args["embed"].description - assert "Не удалось получить данные из внешних источников" in embed_description + call_args = channel.send.call_args + message_text = call_args[0][0] + assert "Не удалось получить данные из внешних источников" in message_text @pytest.mark.asyncio async def test_run_morning_only_weather_data(self) -> None: @@ -233,13 +233,13 @@ class TestRunMorningWithFallback: ): await run_morning(bot, channel) + # Одно сообщение (погода + отсутствие новостей, без кота) channel.send.assert_called_once() - call_args = channel.send.call_args[1] - assert "embed" in call_args + call_args = channel.send.call_args + message_text = call_args[0][0] - # Проверяем, что в embed есть только погода и нет fallback сообщения - embed_description = call_args["embed"].description - assert "Погода в Магнитогорске" in embed_description + # Проверяем, что в тексте есть погода и нет fallback сообщения + assert "Погода в Магнитогорске" in message_text assert ( - "Не удалось получить данные из внешних источников" not in embed_description + "Не удалось получить данные из внешних источников" not in message_text ) diff --git a/utils/__init__.py b/utils/__init__.py index 5147d57..0f12141 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -3,7 +3,7 @@ from .pogoda import ( fetch_weather, fetch_open_meteo, format_weather_data_for_console, - format_weather_for_embed, + format_weather_for_message, pressure_to_mmhg, translate_weather, wmo_to_russian, @@ -23,7 +23,7 @@ __all__ = [ "fetch_weather", "fetch_open_meteo", "format_weather_data_for_console", - "format_weather_for_embed", + "format_weather_for_message", "pressure_to_mmhg", "translate_weather", "wmo_to_russian", diff --git a/utils/morning_runner.py b/utils/morning_runner.py index 63fc5a1..fa7fac0 100644 --- a/utils/morning_runner.py +++ b/utils/morning_runner.py @@ -13,14 +13,14 @@ from discord.ext import commands from utils.pogoda import ( API_URL_WEATHER, fetch_weather, - format_weather_for_embed, + format_weather_for_message, ) from utils.news import ( fetch_rss, format_articles, RSS_URL_ARTICLES, RSS_URL_POSTS, - truncate_embed_text, + truncate_message, ) from utils.cat import fetch_cat @@ -54,29 +54,27 @@ async def gather_morning() -> MorningData: async def run_morning(bot: "commands.Bot", channel: discord.TextChannel) -> None: - """Выполнить утренний дайджест и отправить в канал.""" + """Выполнить утренний дайджест и отправить в канал как plain text.""" try: data = await gather_morning() - # --- Формируем embed --- - embed = discord.Embed(title="Утренний дайджест", color=0xF4A460) - - # Котик как thumbnail + # --- Котик отдельным сообщением --- if data.cat_url: - embed.set_thumbnail(url=data.cat_url) + await channel.send(data.cat_url) - description_lines = [] + # --- Формируем plain text --- + message_lines = ["Утренний дайджест", ""] has_real_data = False # --- Погода --- - weather_text = format_weather_for_embed(data.weather) + weather_text = format_weather_for_message(data.weather) if weather_text: has_real_data = True - description_lines.append(weather_text) + message_lines.append(weather_text) else: - description_lines.append("Не удалось получить данные о погоде.") + message_lines.append("Не удалось получить данные о погоде.") - description_lines.append("") + message_lines.append("") # --- Новости: статьи --- if data.articles is not None: @@ -87,13 +85,13 @@ async def run_morning(bot: "commands.Bot", channel: discord.TextChannel) -> None "Лучшие статьи за сутки / Искусственный интеллект / Хабr", "https://habr.com/ru/hubs/artificial_intelligence/articles/top/daily/", ) - description_lines.append("\n".join(lines)) + message_lines.extend(lines) else: - description_lines.append("Новостей пока нет.") + message_lines.append("Новостей пока нет.") else: - description_lines.append("Не удалось получить новости.") + message_lines.append("Не удалось получить новости.") - description_lines.append("") + message_lines.append("") # --- Новости: посты --- if data.posts is not None: @@ -104,22 +102,21 @@ async def run_morning(bot: "commands.Bot", channel: discord.TextChannel) -> None "Лучшие новости за сутки / Искусственный интеллект / Хабr", "https://habr.com/ru/hubs/artificial_intelligence/news/top/daily/", ) - description_lines.append("\n".join(lines)) + message_lines.extend(lines) else: - description_lines.append("Новостей пока нет.") + message_lines.append("Новостей пока нет.") else: - description_lines.append("Не удалось получить новости.") + message_lines.append("Не удалось получить новости.") # Fallback для пустых данных if not has_real_data: - description_lines = [ + message_lines = [ "Не удалось получить данные из внешних источников.", "Проверьте доступность API и повторите попытку позже.", ] - description = "\n".join(description_lines) - embed.description = truncate_embed_text(description) - await channel.send(embed=embed) + message = truncate_message("\n".join(message_lines)) + await channel.send(message) logger.info("Утренний дайджест отправлен в #%s", channel.name) except Exception as e: diff --git a/utils/news.py b/utils/news.py index 31480b1..89a4e04 100644 --- a/utils/news.py +++ b/utils/news.py @@ -123,7 +123,7 @@ def truncate_message(text: str, max_len: int = 2000) -> str: def format_articles(articles: list[dict], title: str, link: str) -> list[str]: """Сформировать список строк для вывода статей/постов.""" - lines = [f"**{title}**\n<{link}>"] + lines = [f"{title}\n<{link}>"] for i, article in enumerate(articles[:5], 1): date_str = _parse_date(article["pub_date"]) short_title = truncate_title(article["title"]) diff --git a/utils/pogoda.py b/utils/pogoda.py index 8818f9e..ad267ce 100644 --- a/utils/pogoda.py +++ b/utils/pogoda.py @@ -215,14 +215,14 @@ def format_weather_data_for_console(data: Optional[dict]) -> Optional[list[str]] ] -def format_weather_for_embed(data: Optional[dict]) -> Optional[str]: - """Форматировать погоду для Discord embed (с заголовком).""" +def format_weather_for_message(data: Optional[dict]) -> Optional[str]: + """Форматировать погоду для plain text сообщения (с заголовком).""" if data is None: return None lines = format_weather_data_for_console(data) if not lines: return None - return "**Погода в Магнитогорске:**\n" + "\n".join(lines) + return "Погода в Магнитогорске:\n" + "\n".join(lines) def pressure_to_mmhg(mb: Any) -> float | str: