- Добавить экспоненциальную задержку между попытками retry (1с, 2с, 4с) в pogoda.py - Заменить time.sleep на await asyncio.sleep для неблокирующих ожиданий - Обернуть requests.get в asyncio.to_thread для предотвращения блокировки event loop (news.py, cat.py, pogoda.py) - Добавить правило AGENTS.md: не использовать эмодзи - Добавить ISSUES.md с фиксацией проблем проекта
124 lines
5.4 KiB
Python
124 lines
5.4 KiB
Python
import discord
|
||
from discord.ext import commands
|
||
import asyncio
|
||
import requests
|
||
from xml.etree import ElementTree
|
||
|
||
|
||
RSS_URL_ARTICLES = "https://habr.com/ru/rss/hubs/artificial_intelligence/articles/top/daily/?fl=ru"
|
||
RSS_URL_POSTS = "https://habr.com/ru/rss/hubs/artificial_intelligence/news/top/daily/?fl=ru"
|
||
|
||
|
||
class News(commands.Cog):
|
||
"""Команда !news — свежие статьи по AI с Habr"""
|
||
|
||
@commands.command(name="news")
|
||
async def news(self, ctx):
|
||
"""Топ-5 свежих статей по AI с Habr"""
|
||
articles = await self._fetch_rss(RSS_URL_ARTICLES)
|
||
if articles is None:
|
||
await ctx.send("Не удалось получить новости. Попробуйте позже.")
|
||
return
|
||
|
||
if not articles:
|
||
await ctx.send("Новостей пока нет.")
|
||
return
|
||
|
||
await self._format_and_send(ctx, articles)
|
||
|
||
async def _fetch_rss(self, url):
|
||
"""Скачать и распарсить RSS-ленту (RSS 2.0 / Atom)."""
|
||
try:
|
||
response = await asyncio.to_thread(requests.get, url, timeout=10)
|
||
response.raise_for_status()
|
||
root = ElementTree.fromstring(response.content)
|
||
|
||
# RSS 2.0
|
||
ns_dc = {"dc": "http://purl.org/dc/elements/1.1/"}
|
||
items = root.findall(".//item")
|
||
if not items:
|
||
# Atom
|
||
ns = {"atom": "http://www.w3.org/2005/Atom"}
|
||
items = root.findall("atom:entry", ns)
|
||
if not items:
|
||
return []
|
||
|
||
articles = []
|
||
for entry in items:
|
||
# RSS 2.0
|
||
title_el = entry.find("title")
|
||
date_el = entry.find("pubDate")
|
||
creator_el = entry.find("dc:creator", ns_dc)
|
||
categories = entry.findall("category")
|
||
|
||
# guid с isPermaLink="true" для чистого URL
|
||
guid_el = entry.find("guid[@isPermaLink='true']")
|
||
link = guid_el.text if guid_el is not None else ""
|
||
|
||
# Atom fallback
|
||
if title_el is None:
|
||
ns = {"atom": "http://www.w3.org/2005/Atom"}
|
||
title_el = entry.find("atom:title", ns)
|
||
link_el = entry.find("atom:link", ns)
|
||
link = link_el.get("href", "") if link_el is not None else ""
|
||
date_el = entry.find("atom:published", ns)
|
||
creator_el = entry.find("atom:author/atom:name", ns)
|
||
categories = entry.findall("atom:category", ns)
|
||
|
||
title = title_el.text if title_el is not None else "Без названия"
|
||
pub_date = date_el.text if date_el is not None else ""
|
||
creator = creator_el.text if creator_el is not None else ""
|
||
tags = [cat.text for cat in categories if cat.text] if categories else []
|
||
|
||
articles.append({
|
||
"title": title,
|
||
"link": link,
|
||
"pub_date": pub_date,
|
||
"creator": creator,
|
||
"tags": tags,
|
||
})
|
||
return articles[:10]
|
||
except requests.RequestException:
|
||
return None
|
||
|
||
async def _format_and_send(self, ctx, articles):
|
||
"""Сформировать текст и отправить в чат."""
|
||
lines = ["**Лучшие статьи за сутки / Искусственный интеллект / Хабr**\n<https://habr.com/ru/hubs/artificial_intelligence/articles/top/daily/>\n"]
|
||
for i, article in enumerate(articles[:5], 1):
|
||
from datetime import datetime
|
||
date_str = ""
|
||
if article["pub_date"]:
|
||
try:
|
||
d = article["pub_date"].replace(" GMT", " +0000")
|
||
dt = datetime.strptime(d, "%a, %d %b %Y %H:%M:%S %z")
|
||
date_str = dt.strftime("%d.%m.%Y")
|
||
except ValueError:
|
||
date_str = article["pub_date"][:10].replace("-", ".")
|
||
title = article["title"]
|
||
if len(title) > 60:
|
||
title = title[:60] + "..."
|
||
lines.append(f"{title}\n{date_str} <{article['link']}>")
|
||
|
||
# Второй блок: посты
|
||
posts = await self._fetch_rss(RSS_URL_POSTS)
|
||
if posts:
|
||
lines.append("")
|
||
lines.append("**Лучшие новости за сутки / Искусственный интеллект / Хабr**\n<https://habr.com/ru/hubs/artificial_intelligence/news/top/daily/>\n")
|
||
for i, article in enumerate(posts[:5], 1):
|
||
from datetime import datetime
|
||
date_str = ""
|
||
if article["pub_date"]:
|
||
try:
|
||
d = article["pub_date"].replace(" GMT", " +0000")
|
||
dt = datetime.strptime(d, "%a, %d %b %Y %H:%M:%S %z")
|
||
date_str = dt.strftime("%d.%m.%Y")
|
||
except ValueError:
|
||
date_str = article["pub_date"][:10].replace("-", ".")
|
||
title = article["title"]
|
||
if len(title) > 60:
|
||
title = title[:60] + "..."
|
||
lines.append(f"{title}\n{date_str} <{article['link']}>")
|
||
|
||
message = "\n".join(lines).rstrip()
|
||
await ctx.send(message, allowed_mentions=discord.AllowedMentions.none())
|