mirror of
https://github.com/TheCommsChannel/TC2-BBS-mesh.git
synced 2024-11-09 22:24:06 -08:00
commit
774478fe25
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 ####
|
||||||
##########################
|
##########################
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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':
|
||||||
|
|
18
utils.py
18
utils.py
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue