From e07c5a923edf0b11e73b2411bc69475578441f97 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Wed, 29 Oct 2025 23:28:14 -0700 Subject: [PATCH] headline headline command which uses NewsAPI.org --- config.template | 6 ++++- mesh_bot.py | 10 +++++---- modules/README.md | 55 +++++++++++++++++++++++++++++++++++++++++++++ modules/rss.py | 40 ++++++++++++++++++++++++++++++++- modules/settings.py | 3 +++ modules/system.py | 11 +++++---- 6 files changed, 115 insertions(+), 10 deletions(-) diff --git a/config.template b/config.template index 2e61b63..2265e77 100644 --- a/config.template +++ b/config.template @@ -62,7 +62,11 @@ rssFeedURL = http://www.hackaday.com/rss.xml,http://rss.slashdot.org/Slashdot/sl rssFeedNames = default,slashdot,mesh rssMaxItems = 3 rssTruncate = 100 - +# enable or disable the headline command which uses NewsAPI.org key at https://newsapi.org/register +enableNewsAPI = False +newsAPI_KEY = +newsAPIregion = us +# # enable or disable the wikipedia search module wikipedia = True # Use local Kiwix server instead of online Wikipedia diff --git a/mesh_bot.py b/mesh_bot.py index 02c6972..76d8900 100755 --- a/mesh_bot.py +++ b/mesh_bot.py @@ -84,6 +84,7 @@ def auto_response(message, snr, rssi, hop, pkiStatus, message_from_id, channel_n "cartremove": lambda: handle_inventory(message, message_from_id, deviceID), "cartsell": lambda: handle_inventory(message, message_from_id, deviceID), "joke": lambda: tell_joke(message_from_id), + "latest": lambda: get_newsAPI(message), "leaderboard": lambda: get_mesh_leaderboard(message, message_from_id, deviceID), "lemonstand": lambda: handleLemonade(message, message_from_id, deviceID), "lheard": lambda: handle_lheard(message, message_from_id, deviceID, isDM), @@ -1560,12 +1561,14 @@ def handle_boot(mesh=True): if my_settings.wikipedia_enabled: if my_settings.use_kiwix_server: - logger.debug(f"System: Wikipedia search Enabled using Kiwix server at {kiwix_url}") + logger.debug(f"System: Wikipedia search Enabled using Kiwix server at {my_settings.kiwix_url}") else: logger.debug("System: Wikipedia search Enabled") if my_settings.rssEnable: - logger.debug(f"System: RSS Feed Reader Enabled for feeds: {rssFeedNames}") + logger.debug(f"System: RSS Feed Reader Enabled for feeds: {my_settings.rssFeedNames}") + if my_settings.enable_headlines: + logger.debug("System: News Headlines Enabled from NewsAPI.org") if my_settings.radio_detection_enabled: logger.debug(f"System: Radio Detection Enabled using rigctld at {my_settings.rigControlServerAddress} broadcasting to channels: {my_settings.sigWatchBroadcastCh}") @@ -1579,8 +1582,7 @@ def handle_boot(mesh=True): if my_settings.read_news_enabled: logger.debug(f"System: File Monitor News Reader Enabled for {my_settings.news_file_path}") if my_settings.bee_enabled: - logger.debug("System: File Monitor Bee Monitor Enabled for bee.txt") - + logger.debug("System: File Monitor Bee Monitor Enabled for 🐝bee.txt") if my_settings.usAlerts: logger.debug(f"System: Emergency Alert Broadcast Enabled on channel {my_settings.emergency_responder_alert_channel} for interface {my_settings.emergency_responder_alert_interface}") if my_settings.enableDEalerts: diff --git a/modules/README.md b/modules/README.md index c8bb750..7d6ff1d 100644 --- a/modules/README.md +++ b/modules/README.md @@ -475,7 +475,62 @@ Configure in `[wikipedia]` section of `config.ini`. --- +## News & Headlines (`latest` Command) +The `latest` command allows you to fetch current news headlines or articles on any topic using the NewsAPI integration. This is useful for quickly checking the latest developments on a subject, even from the mesh. + +### Usage + +- **Get the latest headlines on a topic:** + ``` + latest + ``` + Example: + ``` + latest meshtastic + ``` + This will return the most recent news articles about "meshtastic". + +- **General latest news:** + ``` + latest + ``` + Returns the latest general news headlines. + +### How It Works + +- The bot queries NewsAPI.org for the most recent articles matching your topic. +- Each result includes the article title and a short description. + +You need to go register for the developer key and read terms of use. + +```ini +# enable or disable the headline command which uses NewsAPI.org +enableNewsAPI = True +newsAPI_KEY = key at https://newsapi.org/register +newsAPIregion = us +``` + +### Example Output + +``` +🗞️:📰Meshtastic project launches new firmware +The open-source mesh radio project Meshtastic has released a major firmware update... + +📰How Meshtastic is changing off-grid communication +A look at how Meshtastic devices are being used for emergency response... + +📰Meshtastic featured at DEF CON 2025 +The Meshtastic team presented new features at DEF CON, drawing large crowds... +``` + +### Notes + +- You can search for any topic, e.g., `latest wildfire`, `latest ham radio`, etc. +- The number of results can be adjusted in the configuration. +- Requires internet access for the bot to fetch news. + +___ ## DX Spotter Module The DX Spotter module allows you to fetch and display recent DX cluster spots from [spothole.app](https://spothole.app) directly in your mesh-bot. diff --git a/modules/rss.py b/modules/rss.py index 2ad64c6..b268a21 100644 --- a/modules/rss.py +++ b/modules/rss.py @@ -1,11 +1,13 @@ # rss feed module for meshing-around 2025 from modules.log import logger -from modules.settings import rssFeedURL, rssFeedNames, rssMaxItems, rssTruncate, urlTimeoutSeconds, ERROR_FETCHING_DATA +from modules.settings import rssFeedURL, rssFeedNames, rssMaxItems, rssTruncate, urlTimeoutSeconds, ERROR_FETCHING_DATA, newsAPI_KEY import urllib.request import xml.etree.ElementTree as ET import html from html.parser import HTMLParser import bs4 as bs +import requests +import datetime # Common User-Agent for all RSS requests COMMON_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3' @@ -136,3 +138,39 @@ def get_rss_feed(msg): logger.error(f"Error fetching RSS feed from {feed_url}: {e}") return ERROR_FETCHING_DATA +def get_newsAPI(user_search="meshtastic"): + # Fetch news from NewsAPI.org + user_search = user_search.lower().replace("latest ", "", 1).strip() + try: + headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome'} + last_week = datetime.datetime.now() - datetime.timedelta(days=7) + newsAPIurl = ( + f"https://newsapi.org/v2/everything?" + f"q={user_search}&language=en&from={last_week.strftime('%Y-%m-%d')}&sortBy=popularity&pageSize=7&apiKey={newsAPI_KEY}" + ) + + response = requests.get(newsAPIurl, headers=headers, timeout=10) + news_data = response.json() + + if news_data.get("status") != "ok": + error_message = news_data.get("message", "Unknown error") + logger.error(f"NewsAPI error: {error_message}") + return [f"NewsAPI error: {error_message}"] + logger.debug(f"System: NewsAPI Searching for '{user_search}' got {news_data.get('totalResults', 0)} results") + articles = news_data.get("articles", [])[:3] + news_list = [] + for article in articles: + title = article.get("title", "No Title") + url = article.get("url", "") + description = article.get("description", '') + news_list.append(f"📰{title}\n{description}") + + + # Make a nice newspaper style output + msg = f"🗞️:" + for item in news_list: + msg += item + "\n\n" + return msg.strip() + except Exception as e: + logger.error(f"Error fetching news: {e}") + return [f"Exception: {e}"] \ No newline at end of file diff --git a/modules/settings.py b/modules/settings.py index fc95d8e..3608937 100644 --- a/modules/settings.py +++ b/modules/settings.py @@ -276,6 +276,9 @@ try: rssMaxItems = config['general'].getint('rssMaxItems', 3) # default 3 items rssTruncate = config['general'].getint('rssTruncate', 100) # default 100 characters rssFeedNames = config['general'].get('rssFeedNames', 'default,arrl').split(',') + newsAPI_KEY = config['general'].get('newsAPI_KEY', '') # default empty + newsAPIregion = config['general'].get('newsAPIregion', 'us') # default us + enable_headlines = config['general'].getboolean('enableNewsAPI', False) # default False # sentry sentry_enabled = config['sentry'].getboolean('SentryEnabled', False) # default False diff --git a/modules/system.py b/modules/system.py index 5d1147e..05a5518 100644 --- a/modules/system.py +++ b/modules/system.py @@ -153,10 +153,14 @@ if wikipedia_enabled or use_kiwix_server: help_message = help_message + ", wiki" # RSS Feed Configuration -if rssEnable: +if rssEnable or enable_headlines: from modules.rss import * # from the spudgunman/meshing-around repo - trap_list = trap_list + ("readrss",) - help_message = help_message + ", readrss" + if rssEnable: + trap_list = trap_list + ("readrss",) + help_message = help_message + ", readrss" + if enable_headlines: + trap_list = trap_list + ("latest",) + help_message = help_message + ", latest" # LLM Configuration if llm_enabled: @@ -1153,7 +1157,6 @@ def handleAlertBroadcast(deviceID=1): if enableDEalerts: deAlerts = get_nina_alerts() - if usAlerts: alert_types = [ ("fema", alertFema, ipawsAlertEnabled),