diff --git a/bot.py b/bot.py index 1164cc9..f907ef8 100644 --- a/bot.py +++ b/bot.py @@ -47,16 +47,19 @@ def console_input(): while not stop_event.is_set(): try: cmd = input().strip().lower() - if cmd == "stop": - print("\nОстановка бота...") - if "stop" in ALL_CONSOLE_COMMANDS: - ALL_CONSOLE_COMMANDS["stop"](stop_event, bot) - break - elif cmd: - if cmd in ALL_CONSOLE_COMMANDS: - ALL_CONSOLE_COMMANDS[cmd](stop_event, bot) + if cmd.startswith("!"): + cmd_name = cmd[1:] + if cmd_name == "stop": + print("\nОстановка бота...") + if "stop" in ALL_CONSOLE_COMMANDS: + ALL_CONSOLE_COMMANDS["stop"](stop_event, bot) + break + elif cmd_name in ALL_CONSOLE_COMMANDS: + ALL_CONSOLE_COMMANDS[cmd_name](stop_event, bot) else: print(f"Неизвестная команда: {cmd}") + elif cmd: + print(f"Неизвестная команда: {cmd}") except (EOFError, KeyboardInterrupt): stop_event.set() try: diff --git a/commands/__init__.py b/commands/__init__.py index bc47f31..6ffd849 100644 --- a/commands/__init__.py +++ b/commands/__init__.py @@ -1,3 +1,4 @@ from .pogoda import Pogoda +from .news import News -ALL_COMMANDS = [Pogoda] +ALL_COMMANDS = [Pogoda, News] diff --git a/commands/news.py b/commands/news.py new file mode 100644 index 0000000..69491a5 --- /dev/null +++ b/commands/news.py @@ -0,0 +1,105 @@ +import discord +from discord.ext import commands +import requests +from xml.etree import ElementTree + + +RSS_URL = "https://habr.com/ru/rss/hubs/artificial_intelligence/articles/rated10/?fl=ru" + + +class News(commands.Cog): + """Команда !news — свежие статьи по AI с Habr""" + + @commands.command(name="news") + async def news(self, ctx): + """Топ-5 свежих статей по AI с Habr""" + articles = self._fetch_rss() + if articles is None: + await ctx.send("Не удалось получить новости. Попробуйте позже.") + return + + if not articles: + await ctx.send("Новостей пока нет.") + return + + await self._format_and_send(ctx, articles) + + def _fetch_rss(self): + """Скачать и распарсить RSS-ленту (RSS 2.0 / Atom).""" + try: + response = requests.get(RSS_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 = ["**🤖 AI-новости с Habr**"] + 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("-", ".") + tags_str = ", ".join(article["tags"][:3]) if article["tags"] else "" + title = article["title"] + if len(title) > 60: + title = title[:60] + "..." + lines.append(f"{i}. {title}") + lines.append(f" {article['creator']} | {date_str} | {tags_str} ") + link = article["link"].replace("https://", "") + lines.append(f" {link}") + + message = "\n".join(lines).rstrip() + await ctx.send(message, allowed_mentions=discord.AllowedMentions.none()) diff --git a/console_commands/__init__.py b/console_commands/__init__.py index e540de2..19941b4 100644 --- a/console_commands/__init__.py +++ b/console_commands/__init__.py @@ -1,5 +1,7 @@ from .stop import stop +from .news import news ALL_CONSOLE_COMMANDS = { "stop": stop, + "news": news, } diff --git a/console_commands/news.py b/console_commands/news.py new file mode 100644 index 0000000..d31efcc --- /dev/null +++ b/console_commands/news.py @@ -0,0 +1,94 @@ +import requests +from xml.etree import ElementTree + + +RSS_URL = "https://habr.com/ru/rss/hubs/artificial_intelligence/articles/rated10/?fl=ru" + + +def news(stop_event, bot): + """Вывести топ-5 свежих статей по AI с Habr""" + articles = _fetch_rss() + if articles is None: + print("Не удалось получить новости.") + return + + if not articles: + print("Новостей пока нет.") + return + + from datetime import datetime + print("**AI-новости с Habr**") + for i, article in enumerate(articles[:5], 1): + 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("-", ".") + tags_str = ", ".join(article["tags"][:3]) if article["tags"] else "" + link = article["link"].replace("https://", "") + title = article["title"] + if len(title) > 60: + title = title[:60] + "..." + print(f"{i}. {title}") + print(f" {article['creator']} | {date_str} | {tags_str}") + print(f" {link}") + print() + + +def _fetch_rss(): + """Скачать и распарсить RSS-ленту (RSS 2.0 / Atom).""" + try: + response = requests.get(RSS_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