Merge pull request #2 from TheCommsChannel/main

sync up
This commit is contained in:
pitbullcoder 2024-07-24 20:01:29 -04:00 committed by GitHub
commit 774478fe25
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 134 additions and 24 deletions

View File

@ -212,12 +212,18 @@ A video of it in use is available on our YouTube channel:
## Thanks ## Thanks
**Meshtastic:**
Big thanks to [Meshtastic](https://github.com/meshtastic) and [pdxlocations](https://github.com/pdxlocations) for the great Python examples: 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) [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) [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 ## License
GNU General Public License v3.0 GNU General Public License v3.0

View File

@ -1,3 +1,4 @@
import configparser
import logging import logging
import random import random
import time import time
@ -16,17 +17,51 @@ from utils import (
update_user_state 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): def handle_help_command(sender_id, interface, menu_name=None):
if menu_name: if menu_name:
update_user_state(sender_id, {'command': 'MENU', 'menu': menu_name, 'step': 1}) update_user_state(sender_id, {'command': 'MENU', 'menu': menu_name, 'step': 1})
if menu_name == 'bbs': 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': 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: else:
update_user_state(sender_id, {'command': 'MAIN_MENU', 'step': 1}) # Reset to main menu state 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) send_message(response, sender_id, interface)
@ -76,8 +111,12 @@ def handle_fortune_command(sender_id, interface):
def handle_stats_steps(sender_id, message, step, 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: if step == 1:
choice = message.lower() choice = message
if choice == 'x': if choice == 'x':
handle_help_command(sender_id, interface) handle_help_command(sender_id, interface)
return return
@ -120,7 +159,6 @@ def handle_stats_steps(sender_id, message, step, interface):
handle_stats_command(sender_id, interface) handle_stats_command(sender_id, interface)
def handle_bb_steps(sender_id, message, step, state, interface, bbs_nodes): def handle_bb_steps(sender_id, message, step, state, interface, bbs_nodes):
boards = {0: "General", 1: "Info", 2: "News", 3: "Urgent"} boards = {0: "General", 1: "Info", 2: "News", 3: "Urgent"}
if step == 1: if step == 1:
@ -190,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): 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: if step == 1:
choice = message.lower() choice = message
if choice == 'r': if choice == 'r':
sender_node_id = get_node_id_from_num(sender_id, interface) sender_node_id = get_node_id_from_num(sender_id, interface)
mail = get_mail(sender_node_id) mail = get_mail(sender_node_id)
@ -318,8 +360,12 @@ def handle_channel_directory_command(sender_id, interface):
def handle_channel_directory_steps(sender_id, message, step, state, 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: if step == 1:
choice = message.lower() choice = message
if choice == 'x': if choice == 'x':
handle_help_command(sender_id, interface) handle_help_command(sender_id, interface)
return return
@ -426,8 +472,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) 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}" response = f"Date: {date}\nFrom: {sender}\nSubject: {subject}\n\n{content}"
send_message(response, sender_id, interface) 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) 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}) 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: except ValueError:
send_message("Invalid input. Please enter a valid message number.", sender_id, interface) send_message("Invalid input. Please enter a valid message number.", sender_id, interface)
@ -438,22 +484,30 @@ def handle_read_mail_command(sender_id, message, state, interface):
def handle_delete_mail_confirmation(sender_id, message, state, interface, bbs_nodes): def handle_delete_mail_confirmation(sender_id, message, state, interface, bbs_nodes):
try: try:
choice = message.lower() choice = message.lower().strip()
if choice == 'y': if len(choice) == 2 and choice[1] == 'x':
choice = choice[0]
if choice == 'd':
unique_id = state['unique_id'] unique_id = state['unique_id']
sender_node_id = get_node_id_from_num(sender_id, interface) sender_node_id = get_node_id_from_num(sender_id, interface)
delete_mail(unique_id, sender_node_id, bbs_nodes, interface) delete_mail(unique_id, sender_node_id, bbs_nodes, interface)
send_message("The message has been deleted 🗑️", sender_id, 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: else:
send_message("The message has been kept in your inbox.✉️", sender_id, interface) 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: except Exception as e:
logging.error(f"Error processing delete mail confirmation: {e}") logging.error(f"Error processing delete mail confirmation: {e}")
send_message("Error processing delete mail confirmation.", sender_id, interface) send_message("Error processing delete mail confirmation.", sender_id, interface)
def handle_post_bulletin_command(sender_id, message, interface, bbs_nodes): def handle_post_bulletin_command(sender_id, message, interface, bbs_nodes):
try: try:
parts = message.split(",,", 3) parts = message.split(",,", 3)

View File

@ -47,6 +47,41 @@ type = serial
# allowed_nodes = !17d7e4b7 # 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 #### #### JS8Call Settings ####
########################## ##########################

View File

@ -214,15 +214,19 @@ class JS8CallClient:
self.connected = False self.connected = False
def handle_js8call_command(sender_id, interface): 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" response = "JS8Call Menu:\n[G]roup Messages\n[S]tation Messages\n[U]rgent Messages\nE[X]IT"
send_message(response, sender_id, interface) send_message(response, sender_id, interface)
update_user_state(sender_id, {'command': 'JS8CALL_MENU', 'step': 1}) update_user_state(sender_id, {'command': 'JS8CALL_MENU', 'step': 1})
def handle_js8call_steps(sender_id, message, step, interface, state): 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: if step == 1:
choice = message.lower() choice = message
if choice == 'x': if choice == 'x':
handle_help_command(sender_id, interface, 'bbs') handle_help_command(sender_id, interface, 'bbs')
return 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) send_message("Invalid option. Please choose again.", sender_id, interface)
handle_js8call_command(sender_id, interface) handle_js8call_command(sender_id, interface)
def handle_group_messages_command(sender_id, interface): def handle_group_messages_command(sender_id, interface):
conn = sqlite3.connect('js8call.db') conn = sqlite3.connect('js8call.db')
c = conn.cursor() c = conn.cursor()

View File

@ -55,9 +55,13 @@ board_action_handlers = {
def process_message(sender_id, message, interface, is_sync_message=False): def process_message(sender_id, message, interface, is_sync_message=False):
state = get_user_state(sender_id) state = get_user_state(sender_id)
message_lower = message.lower() message_lower = message.lower().strip()
bbs_nodes = interface.bbs_nodes 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 is_sync_message:
if message.startswith("BULLETIN|"): if message.startswith("BULLETIN|"):
parts = message.split("|") 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) handle_js8call_steps(sender_id, message, step, interface, state)
elif command == 'GROUP_MESSAGES': elif command == 'GROUP_MESSAGES':
handle_group_message_selection(sender_id, message, step, state, interface) handle_group_message_selection(sender_id, message, step, state, interface)
else:
handle_help_command(sender_id, interface)
else: else:
handle_help_command(sender_id, interface) handle_help_command(sender_id, interface)
def on_receive(packet, interface): def on_receive(packet, interface):
try: try:
if 'decoded' in packet and packet['decoded']['portnum'] == 'TEXT_MESSAGE_APP': if 'decoded' in packet and packet['decoded']['portnum'] == 'TEXT_MESSAGE_APP':

View File

@ -16,12 +16,18 @@ def send_message(message, destination, interface):
max_payload_size = 200 max_payload_size = 200
for i in range(0, len(message), max_payload_size): for i in range(0, len(message), max_payload_size):
chunk = message[i:i + max_payload_size] chunk = message[i:i + max_payload_size]
interface.sendText( try:
text=chunk, d = interface.sendText(
destinationId=destination, text=chunk,
wantAck=False, destinationId=destination,
wantResponse=False 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) time.sleep(2)