From 2895e6c03458d128a46a8d607c4dea937e342e50 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Wed, 8 Oct 2025 01:14:10 -0700 Subject: [PATCH] =?UTF-8?q?newNews=F0=9F=9A=A8=F0=9F=9A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit the location of news.txt changed FYI 🚨 now you can read more files --- README.md | 11 ++++++++--- config.template | 2 +- data/mesh_news.txt | 1 + mesh_bot.py | 22 +++++++++++++++++++++- modules/filemon.py | 37 +++++++++++++++++++++++++++---------- modules/settings.py | 2 +- news.txt | 1 - 7 files changed, 59 insertions(+), 17 deletions(-) create mode 100644 data/mesh_news.txt delete mode 100644 news.txt diff --git a/README.md b/README.md index 92eb772..1cc4861 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ Welcome to the Mesh Bot project! This feature-rich bot is designed to enhance yo ### File Monitor Alerts - **File Monitor**: Monitor a flat/text file for changes, broadcast the contents of the message to the mesh channel. -- **News File**: On request of news, the contents of the file are returned. +- **News File**: On request of news, the contents of the file are returned. Can also call multiple news sources or files. - **Shell Command Access**: Pass commands via DM directly to the host OS ### Data Reporting @@ -147,7 +147,7 @@ git clone https://github.com/spudgunman/meshing-around |---------|-------------|- | `askai` and `ask:` | Ask Ollama LLM AI for a response. Example: `askai what temp do I cook chicken` | ✅ | | `messages` | Replays the last messages heard on device, like Store and Forward, returns the PublicChannel and Current | ✅ | -| `readnews` | returns the contents of a file (news.txt, by default) via the chunker on air | ✅ | +| `readnews` | returns the contents of a file (data/news.txt, by default) can also `news mesh` via the chunker on air | ✅ | | `satpass` | returns the pass info from API for defined NORAD ID in config or Example: `satpass 25544,33591`| | | `wiki:` | Searches Wikipedia and returns the first few sentences of the first result if a match. Example: `wiki: lora radio` | | `howfar` | returns the distance you have traveled since your last HowFar. `howfar reset` to start over | ✅ | @@ -451,7 +451,12 @@ rtl_fm -f 162425000 -s 22050 | multimon-ng -t raw -a EAS /dev/stdin | python eas ``` #### Newspaper on mesh -a newspaper could be built by external scripts. could use Ollama to compile text via news web pages and write news.txt +Maintain multiple news sources. Each source should be a file named `{source}_news.txt` in the `data/` directory (for example, `data/mesh_news.txt`). +- To read the default news, use the `readnews` command (reads from `data/news.txt`. +- To read a specific source, use `readnews abc` to read from `data/abc_news.txt`. + +This allows you to organize and access different news feeds or categories easily. +External scripts can update these files as needed, and the bot will serve the latest content on request. ### Greet new nodes QRZ module This isnt QRZ.com this is Q code for who is calling me, this will track new nodes and say hello diff --git a/config.template b/config.template index defdeab..ecdd4e8 100644 --- a/config.template +++ b/config.template @@ -284,7 +284,7 @@ broadcastCh = 2 # news command will return the contents of a text file enable_read_news = False -news_file_path = news.txt +news_file_path = ../data/news.txt # only return a single random line from the news file news_random_line = False diff --git a/data/mesh_news.txt b/data/mesh_news.txt new file mode 100644 index 0000000..1017712 --- /dev/null +++ b/data/mesh_news.txt @@ -0,0 +1 @@ +Today in meshtastic you are looking at the coolest bot on the block. \ No newline at end of file diff --git a/mesh_bot.py b/mesh_bot.py index fbe5da4..db5ce0c 100755 --- a/mesh_bot.py +++ b/mesh_bot.py @@ -75,7 +75,7 @@ def auto_response(message, snr, rssi, hop, pkiStatus, message_from_id, channel_n "pong": lambda: "🏓PING!!🛜", "q:": lambda: quizHandler(message, message_from_id, deviceID), "quiz": lambda: quizHandler(message, message_from_id, deviceID), - "readnews": lambda: read_news(), + "readnews": lambda: handleNews(message_from_id, deviceID, message, isDM), "riverflow": lambda: handle_riverFlow(message, message_from_id, deviceID), "rlist": lambda: handle_repeaterQuery(message_from_id, deviceID, channel_number), "satpass": lambda: handle_satpass(message_from_id, deviceID, channel_number, message), @@ -333,6 +333,26 @@ def handle_wxalert(message_from_id, deviceID, message): weatherAlert = weatherAlert[0] return weatherAlert +def handleNews(message_from_id, deviceID, message, isDM): + news = '' + # if news source is provided pass that to read_news() + if "?" in message.lower(): + return "returns the news. Add a source e.g. 📰readnews mesh" + elif "readnews" in message.lower(): + source = message.lower().replace("readnews", "").strip() + if source: + news = read_news(source) + else: + news = read_news() + + if news: + # if not a DM add the username to the beginning of msg + if not useDMForResponse and not isDM: + news = "@" + get_name_from_number(message_from_id, 'short', deviceID) + " " + news + return news + else: + return "No news for you!" + def handle_howfar(message, message_from_id, deviceID, isDM): msg = '' location = get_node_location(message_from_id, deviceID) diff --git a/modules/filemon.py b/modules/filemon.py index 53079b3..faa6334 100644 --- a/modules/filemon.py +++ b/modules/filemon.py @@ -9,11 +9,12 @@ import subprocess trap_list_filemon = ("readnews",) -def read_file(file_monitor_file_path, random_line_only=False): +NEWS_DATA_DIR = os.path.join(os.path.dirname(__file__), '..', 'data') +newsSourcesList = [] +def read_file(file_monitor_file_path, random_line_only=False): try: if not os.path.exists(file_monitor_file_path): - logger.warning(f"FileMon: File not found: {file_monitor_file_path}") if file_monitor_file_path == "bee.txt": return "🐝buzz 💐buzz buzz🍯" if random_line_only: @@ -29,21 +30,25 @@ def read_file(file_monitor_file_path, random_line_only=False): except Exception as e: logger.warning(f"FileMon: Error reading file: {file_monitor_file_path}") return None - -def read_news(): - # read the news file on demand - return read_file(news_file_path, news_random_line_only) +def read_news(source=None): + # Reads the news file. If a source is provided, reads {source}_news.txt. + if source: + file_path = os.path.join(NEWS_DATA_DIR, f"{source}_news.txt") + else: + file_path = os.path.join(NEWS_DATA_DIR, news_file_path) + return read_file(file_path, news_random_line_only) def write_news(content, append=False): # write the news file on demand try: - with open(news_file_path, 'a' if append else 'w', encoding='utf-8') as f: - f.write(content) - logger.info(f"FileMon: Updated {news_file_path}") + file_path = os.path.join(NEWS_DATA_DIR, news_file_path) + with open(file_path, 'a' if append else 'w', encoding='utf-8') as f: + #f.write(content) + logger.info(f"FileMon: Updated {file_path}") return True except Exception as e: - logger.warning(f"FileMon: Error writing file: {news_file_path}") + logger.warning(f"FileMon: Error writing file: {file_path}") return False async def watch_file(): @@ -119,3 +124,15 @@ def handleShellCmd(message, message_from_id, channel_number, isDM, deviceID): return "x: command is disabled" return "x: command executed with no output" + +def initNewsSources(): + #check for the files _news.txt and add to the newsHeadlines list + global newsSourcesList + newsSourcesList = [] + for file in os.listdir(NEWS_DATA_DIR): + if file.endswith('_news.txt'): + source = file[:-9] # remove _news.txt + newsSourcesList.append(source) + +#initialize the headlines on startup +initNewsSources() diff --git a/modules/settings.py b/modules/settings.py index 4ca7340..82de6cf 100644 --- a/modules/settings.py +++ b/modules/settings.py @@ -359,7 +359,7 @@ try: file_monitor_file_path = config['fileMon'].get('file_path', 'alert.txt') # default alert.txt file_monitor_broadcastCh = config['fileMon'].get('broadcastCh', '2').split(',') # default Channel 2 read_news_enabled = config['fileMon'].getboolean('enable_read_news', False) # default disabled - news_file_path = config['fileMon'].get('news_file_path', 'news.txt') # default news.txt + news_file_path = config['fileMon'].get('news_file_path', '../data/news.txt') # default ../data/news.txt news_random_line_only = config['fileMon'].getboolean('news_random_line', False) # default False enable_runShellCmd = config['fileMon'].getboolean('enable_runShellCmd', False) # default False allowXcmd = config['fileMon'].getboolean('allowXcmd', False) # default False diff --git a/news.txt b/news.txt deleted file mode 100644 index 6f3fc71..0000000 --- a/news.txt +++ /dev/null @@ -1 +0,0 @@ -no new news is good news! \ No newline at end of file