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\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\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())