From 953fb8d5b457538a1d6f5b10f5ca244f4d39228b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?TC=C2=B2?= <130875305+TheCommsChannel@users.noreply.github.com> Date: Sun, 21 Jul 2024 07:59:25 -0400 Subject: [PATCH 1/6] Update README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index c743287..3c7b1c3 100644 --- a/README.md +++ b/README.md @@ -212,12 +212,18 @@ A video of it in use is available on our YouTube channel: ## Thanks +**Meshtastic:** + Big thanks to [Meshtastic](https://github.com/meshtastic) and [pdxlocations](https://github.com/pdxlocations) for the great Python examples: [python/examples at master · meshtastic/python (github.com)](https://github.com/meshtastic/python/tree/master/examples) [pdxlocations/Meshtastic-Python-Examples (github.com)](https://github.com/pdxlocations/Meshtastic-Python-Examples) +**JS8Call** + +For the JS8Call side of things, big thanks to Jordan Sherer for JS8Call and the [example API Python script](https://bitbucket.org/widefido/js8call/src/js8call/tcp.py) + ## License GNU General Public License v3.0 From de1f9079c01cd5d2aa3fac1c09121f30ddc65993 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?TC=C2=B2?= <130875305+TheCommsChannel@users.noreply.github.com> Date: Sun, 21 Jul 2024 07:59:48 -0400 Subject: [PATCH 2/6] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3c7b1c3..c7cd143 100644 --- a/README.md +++ b/README.md @@ -220,7 +220,7 @@ Big thanks to [Meshtastic](https://github.com/meshtastic) and [pdxlocations](htt [pdxlocations/Meshtastic-Python-Examples (github.com)](https://github.com/pdxlocations/Meshtastic-Python-Examples) -**JS8Call** +**JS8Call:** For the JS8Call side of things, big thanks to Jordan Sherer for JS8Call and the [example API Python script](https://bitbucket.org/widefido/js8call/src/js8call/tcp.py) From 3839b050b924c7f2a9041d1d40bacce46f2693e3 Mon Sep 17 00:00:00 2001 From: fti7 Date: Mon, 22 Jul 2024 20:53:04 +0200 Subject: [PATCH 3/6] add reply debug logging --- utils.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/utils.py b/utils.py index 4c4c360..a6f9bc4 100644 --- a/utils.py +++ b/utils.py @@ -16,12 +16,18 @@ def send_message(message, destination, interface): max_payload_size = 200 for i in range(0, len(message), max_payload_size): chunk = message[i:i + max_payload_size] - interface.sendText( - text=chunk, - destinationId=destination, - wantAck=False, - wantResponse=False - ) + try: + d = interface.sendText( + text=chunk, + destinationId=destination, + wantAck=False, + wantResponse=False + ) + logging.info(f"REPLY SEND ID={d.id}") + except Exception as e: + logging.info(f"REPLY SEND ERROR {e.message}") + + time.sleep(2) From fc86fe923ecf3a55432de24794d8fecab2845edf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?TC=C2=B2?= <130875305+TheCommsChannel@users.noreply.github.com> Date: Tue, 23 Jul 2024 12:05:40 -0400 Subject: [PATCH 4/6] Add ability to remove menu items Added the ability to remove items from menus for those who may not be using features like JS8call, etc.. --- command_handlers.py | 41 ++++++++++++++++++++++++++++++++++++++--- example_config.ini | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 3 deletions(-) diff --git a/command_handlers.py b/command_handlers.py index 46bb9ad..619c960 100644 --- a/command_handlers.py +++ b/command_handlers.py @@ -1,3 +1,4 @@ +import configparser import logging import random import time @@ -16,17 +17,51 @@ from utils import ( update_user_state ) +# Read the configuration for menu options +config = configparser.ConfigParser() +config.read('config.ini') + +main_menu_items = config['menu']['main_menu_items'].split(',') +bbs_menu_items = config['menu']['bbs_menu_items'].split(',') +utilities_menu_items = config['menu']['utilities_menu_items'].split(',') + + +def build_menu(items, menu_name): + menu_str = f"{menu_name}\n" + for item in items: + if item.strip() == 'Q': + menu_str += "[Q]uick Commands\n" + elif item.strip() == 'B': + menu_str += "[B]BS\n" + elif item.strip() == 'U': + menu_str += "[U]tilities\n" + elif item.strip() == 'X': + menu_str += "E[X]IT\n" + elif item.strip() == 'M': + menu_str += "[M]ail\n" + elif item.strip() == 'C': + menu_str += "[C]hannel Dir\n" + elif item.strip() == 'J': + menu_str += "[J]S8CALL\n" + elif item.strip() == 'S': + menu_str += "[S]tats\n" + elif item.strip() == 'F': + menu_str += "[F]ortune\n" + elif item.strip() == 'W': + menu_str += "[W]all of Shame\n" + return menu_str + def handle_help_command(sender_id, interface, menu_name=None): if menu_name: update_user_state(sender_id, {'command': 'MENU', 'menu': menu_name, 'step': 1}) if menu_name == 'bbs': - response = "📰BBS Menu📰\n[M]ail\n[B]ulletins\n[C]hannel Dir\n[J]S8CALL\nE[X]IT" + response = build_menu(bbs_menu_items, "📰BBS Menu📰") elif menu_name == 'utilities': - response = "🛠️Utilities Menu🛠️\n[S]tats\n[F]ortune\n[W]all of Shame\nE[X]IT" + response = build_menu(utilities_menu_items, "🛠️Utilities Menu🛠️") else: update_user_state(sender_id, {'command': 'MAIN_MENU', 'step': 1}) # Reset to main menu state - response = "💾TC² BBS💾\n[Q]uick Commands\n[B]BS\n[U]tilities\nE[X]IT" + response = build_menu(main_menu_items, "💾TC² BBS💾") send_message(response, sender_id, interface) diff --git a/example_config.ini b/example_config.ini index 1b6913a..89ca851 100644 --- a/example_config.ini +++ b/example_config.ini @@ -47,6 +47,41 @@ type = serial # allowed_nodes = !17d7e4b7 +#################### +#### Menu Items #### +#################### +# Remove any menu items you don't plan on using with your BBS below +# +[menu] +# Default Main Menu options for reference +# [Q]uick Commands +# [B]BS +# [U]tilities +# E[X]IT +# +# Remove any menu items from the list below that you want to exclude from the main menu +main_menu_items = Q, B, U, X + +# Default BBS Menu options for reference +# [M]ail +# [B]ulletins +# [C]hannel Dir +# [J]S8CALL +# E[X]IT +# +# Remove any menu items from the list below that you want to exclude from the BBS menu +bbs_menu_items = M, B, C, J, X + +# Default BBS Menu option for reference +# [S]tats +# [F]ortune +# [W]all of Shame +# E[X]IT +# +# Remove any menu items from the list below that you want to exclude from the utilities menu +utilities_menu_items = S, F, W, X + + ########################## #### JS8Call Settings #### ########################## From ddc3fbf1f119e357c08cae091438ac623d4c3da5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?TC=C2=B2?= <130875305+TheCommsChannel@users.noreply.github.com> Date: Tue, 23 Jul 2024 12:31:49 -0400 Subject: [PATCH 5/6] Add reply option to CM command --- command_handlers.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/command_handlers.py b/command_handlers.py index 619c960..95f4e0a 100644 --- a/command_handlers.py +++ b/command_handlers.py @@ -461,8 +461,8 @@ def handle_read_mail_command(sender_id, message, state, interface): sender, date, subject, content, unique_id = get_mail_content(mail_id, sender_node_id) response = f"Date: {date}\nFrom: {sender}\nSubject: {subject}\n\n{content}" send_message(response, sender_id, interface) - send_message("Would you like to delete this message now that you've read it? Y/N", sender_id, interface) - update_user_state(sender_id, {'command': 'CHECK_MAIL', 'step': 2, 'mail_id': mail_id, 'unique_id': unique_id}) + send_message("What would you like to do with this message?\n[K]eep [D]elete [R]eply", sender_id, interface) + update_user_state(sender_id, {'command': 'CHECK_MAIL', 'step': 2, 'mail_id': mail_id, 'unique_id': unique_id, 'sender': sender, 'subject': subject, 'content': content}) except ValueError: send_message("Invalid input. Please enter a valid message number.", sender_id, interface) @@ -474,15 +474,19 @@ def handle_read_mail_command(sender_id, message, state, interface): def handle_delete_mail_confirmation(sender_id, message, state, interface, bbs_nodes): try: choice = message.lower() - if choice == 'y': + if choice == 'd': unique_id = state['unique_id'] sender_node_id = get_node_id_from_num(sender_id, interface) delete_mail(unique_id, sender_node_id, bbs_nodes, interface) send_message("The message has been deleted 🗑️", sender_id, interface) + update_user_state(sender_id, None) + elif choice == 'r': + sender = state['sender'] + send_message(f"Send your reply to {sender} now, followed by a message with END", sender_id, interface) + update_user_state(sender_id, {'command': 'MAIL', 'step': 7, 'reply_to_mail_id': state['mail_id'], 'subject': f"Re: {state['subject']}", 'content': ''}) else: send_message("The message has been kept in your inbox.✉️", sender_id, interface) - - update_user_state(sender_id, None) + update_user_state(sender_id, None) except Exception as e: logging.error(f"Error processing delete mail confirmation: {e}") From e9709fd781a5ddbbc6bb2331175948940bfc27cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?TC=C2=B2?= <130875305+TheCommsChannel@users.noreply.github.com> Date: Tue, 23 Jul 2024 16:07:13 -0400 Subject: [PATCH 6/6] Add ability to add "x" to commands This is to allow web client users to use the BBS since the web client doesn't allow you to send single characters --- command_handlers.py | 25 ++++++++++++++++++++----- js8call_integration.py | 10 ++++++++-- message_processing.py | 9 ++++++--- 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/command_handlers.py b/command_handlers.py index 95f4e0a..798c110 100644 --- a/command_handlers.py +++ b/command_handlers.py @@ -111,8 +111,12 @@ def handle_fortune_command(sender_id, interface): def handle_stats_steps(sender_id, message, step, interface): + message = message.lower().strip() + if len(message) == 2 and message[1] == 'x': + message = message[0] + if step == 1: - choice = message.lower() + choice = message if choice == 'x': handle_help_command(sender_id, interface) return @@ -155,7 +159,6 @@ def handle_stats_steps(sender_id, message, step, interface): handle_stats_command(sender_id, interface) - def handle_bb_steps(sender_id, message, step, state, interface, bbs_nodes): boards = {0: "General", 1: "Info", 2: "News", 3: "Urgent"} if step == 1: @@ -225,8 +228,12 @@ def handle_bb_steps(sender_id, message, step, state, interface, bbs_nodes): def handle_mail_steps(sender_id, message, step, state, interface, bbs_nodes): + message = message.lower().strip() + if len(message) == 2 and message[1] == 'x': + message = message[0] + if step == 1: - choice = message.lower() + choice = message if choice == 'r': sender_node_id = get_node_id_from_num(sender_id, interface) mail = get_mail(sender_node_id) @@ -353,8 +360,12 @@ def handle_channel_directory_command(sender_id, interface): def handle_channel_directory_steps(sender_id, message, step, state, interface): + message = message.lower().strip() + if len(message) == 2 and message[1] == 'x': + message = message[0] + if step == 1: - choice = message.lower() + choice = message if choice == 'x': handle_help_command(sender_id, interface) return @@ -473,7 +484,10 @@ def handle_read_mail_command(sender_id, message, state, interface): def handle_delete_mail_confirmation(sender_id, message, state, interface, bbs_nodes): try: - choice = message.lower() + choice = message.lower().strip() + if len(choice) == 2 and choice[1] == 'x': + choice = choice[0] + if choice == 'd': unique_id = state['unique_id'] sender_node_id = get_node_id_from_num(sender_id, interface) @@ -493,6 +507,7 @@ def handle_delete_mail_confirmation(sender_id, message, state, interface, bbs_no send_message("Error processing delete mail confirmation.", sender_id, interface) + def handle_post_bulletin_command(sender_id, message, interface, bbs_nodes): try: parts = message.split(",,", 3) diff --git a/js8call_integration.py b/js8call_integration.py index bf0a0cc..9cbc787 100644 --- a/js8call_integration.py +++ b/js8call_integration.py @@ -214,15 +214,19 @@ class JS8CallClient: self.connected = False - def handle_js8call_command(sender_id, interface): response = "JS8Call Menu:\n[G]roup Messages\n[S]tation Messages\n[U]rgent Messages\nE[X]IT" send_message(response, sender_id, interface) update_user_state(sender_id, {'command': 'JS8CALL_MENU', 'step': 1}) + def handle_js8call_steps(sender_id, message, step, interface, state): + message = message.lower().strip() + if len(message) == 2 and message[1] == 'x': + message = message[0] + if step == 1: - choice = message.lower() + choice = message if choice == 'x': handle_help_command(sender_id, interface, 'bbs') return @@ -236,6 +240,8 @@ def handle_js8call_steps(sender_id, message, step, interface, state): send_message("Invalid option. Please choose again.", sender_id, interface) handle_js8call_command(sender_id, interface) + + def handle_group_messages_command(sender_id, interface): conn = sqlite3.connect('js8call.db') c = conn.cursor() diff --git a/message_processing.py b/message_processing.py index d2cc379..e9b7440 100644 --- a/message_processing.py +++ b/message_processing.py @@ -55,9 +55,13 @@ board_action_handlers = { def process_message(sender_id, message, interface, is_sync_message=False): state = get_user_state(sender_id) - message_lower = message.lower() + message_lower = message.lower().strip() bbs_nodes = interface.bbs_nodes + # Handle repeated characters for single character commands using a prefix + if len(message_lower) == 2 and message_lower[1] == 'x': + message_lower = message_lower[0] + if is_sync_message: if message.startswith("BULLETIN|"): parts = message.split("|") @@ -164,11 +168,10 @@ def process_message(sender_id, message, interface, is_sync_message=False): handle_js8call_steps(sender_id, message, step, interface, state) elif command == 'GROUP_MESSAGES': handle_group_message_selection(sender_id, message, step, state, interface) - else: - handle_help_command(sender_id, interface) else: handle_help_command(sender_id, interface) + def on_receive(packet, interface): try: if 'decoded' in packet and packet['decoded']['portnum'] == 'TEXT_MESSAGE_APP':