Files
FunguyBot/plugins/news.py
T

193 lines
6.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
News Aggregator Plugin for Funguy Bot
Fetches latest headlines from various news categories using GNews API.
Free tier: 100 requests/day
"""
import logging
import aiohttp
import os
import simplematrixbotlib as botlib
from plugins.common import html_escape, collapsible_summary
# API key loaded centrally
GNEWS_API_KEY = os.getenv("GNEWS_API_KEY")
DEFAULT_ARTICLES = 5
MAX_ARTICLES = 10
CATEGORIES = {
"top": "general",
"world": "world",
"tech": "technology",
"business": "business",
"entertainment": "entertainment",
"science": "science",
"health": "health",
"sports": "sports",
"crypto": "cryptocurrency"
}
def _format_news_article(article, index):
"""Format a single news article as an HTML list item."""
title = html_escape(article.get("title", "No title"))
source = html_escape((article.get("source") or {}).get("name", "Unknown"))
url = article.get("url", "#")
description = html_escape(article.get("description", "No description available"))
if len(description) > 300:
description = description[:297] + "..."
published = article.get("publishedAt", "")
date_str = ""
if published:
try:
from datetime import datetime
dt = datetime.fromisoformat(published.replace('Z', '+00:00'))
date_str = f" | 📅 {dt.strftime('%Y-%m-%d %H:%M')}"
except:
pass
return (
f"<li>\n"
f"<strong>{index}. {title}</strong><br/>\n"
f"📰 <strong>Source:</strong> {source}{date_str}<br/>\n"
f"📝 <strong>Summary:</strong> {description}<br/>\n"
f"🔗 <a href='{url}'>{url}</a>\n"
f"</li>"
)
async def _fetch_news(category="general", query=None, limit=DEFAULT_ARTICLES):
if not GNEWS_API_KEY:
logging.error("GNews API key not configured. Set GNEWS_API_KEY in .env file")
return None
base_url = "https://gnews.io/api/v4"
if query:
url = f"{base_url}/search"
params = {
"q": query,
"apikey": GNEWS_API_KEY,
"lang": "en",
"max": limit,
"country": "us"
}
else:
url = f"{base_url}/top-headlines"
params = {
"apikey": GNEWS_API_KEY,
"lang": "en",
"country": "us",
"max": limit
}
if category and category != "general":
params["category"] = category
try:
async with aiohttp.ClientSession() as session:
async with session.get(url, params=params) as response:
if response.status == 200:
data = await response.json()
return data.get("articles", [])
else:
logging.error(f"GNews API error: {response.status}")
return None
except Exception as e:
logging.error(f"Error fetching news: {e}")
return None
def setup(bot):
"""Initialize plugin with bot instance."""
global GNEWS_API_KEY
GNEWS_API_KEY = os.getenv("GNEWS_API_KEY")
if GNEWS_API_KEY:
logging.info("News plugin loaded with API key")
else:
logging.warning("News plugin loaded but GNEWS_API_KEY not set in .env file")
async def handle_command(room, message, bot, prefix, config):
"""Handle !news commands."""
import simplematrixbotlib as botlib
match = botlib.MessageMatch(room, message, bot, prefix)
if not (match.is_not_from_this_bot() and match.prefix() and match.command("news")):
return
global GNEWS_API_KEY
if not GNEWS_API_KEY:
GNEWS_API_KEY = os.getenv("GNEWS_API_KEY")
if not GNEWS_API_KEY:
await bot.api.send_text_message(
room.room_id,
"⚠️ News plugin is not configured. Please set GNEWS_API_KEY in .env file and restart the bot."
)
return
args = match.args()
# Parse command arguments
category = "top"
query = None
limit = DEFAULT_ARTICLES
if args:
command = args[0].lower()
if len(args) >= 2 and args[-1].isdigit():
limit = min(int(args[-1]), MAX_ARTICLES)
args = args[:-1]
if args:
command = args[0].lower()
if command == "search" and len(args) >= 2:
query = " ".join(args[1:])
category = None
elif command in CATEGORIES:
category = CATEGORIES[command]
else:
query = " ".join(args)
category = None
# Fetch news
if query:
safe_title = html_escape(query)
await bot.api.send_text_message(room.room_id, f"🔍 Searching for: *{safe_title}*...")
articles = await _fetch_news(query=query, limit=limit)
title = f"Search Results: '{safe_title}'"
else:
articles = await _fetch_news(category=category, limit=limit)
category_name = next((k for k, v in CATEGORIES.items() if v == category), category)
title = f"Top {category_name.title()} News"
if not articles:
await bot.api.send_text_message(room.room_id, "❌ Failed to fetch news or no results found.")
return
# Build content as HTML list
content = "<ul>\n"
for i, article in enumerate(articles[:limit], 1):
content += _format_news_article(article, i) + "\n"
content += f"</ul>\n\n<em>Fetched {len(articles[:limit])} articles</em>"
# Format as collapsible and send
response = collapsible_summary(title, content)
await bot.api.send_markdown_message(room.room_id, response)
logging.info(f"Sent news to {room.room_id}: category={category}, query={query}")
# ---------------------------------------------------------------------------
# Plugin Metadata
# ---------------------------------------------------------------------------
__version__ = "1.0.0"
__author__ = "Funguy Bot"
__description__ = "News headlines via GNews API"
__help__ = """
<details>
<summary><strong>!news</strong> Latest news headlines</summary>
<ul>
<li><code>!news [top|world|tech|business|science|health|sports|crypto]</code></li>
<li><code>!news search &lt;query&gt;</code></li>
<li>You can append a number: <code>!news tech 8</code></li>
</ul>
<p>Requires <strong>GNEWS_API_KEY</strong> env var.</p>
</details>
"""