forked from mudhorn/TC2-BBS-mesh
Add files via upload
This commit is contained in:
parent
36ac328636
commit
339210401f
109
README.md
Normal file
109
README.md
Normal file
|
@ -0,0 +1,109 @@
|
|||
# TC²-BBS Meshtastic Version
|
||||
|
||||
This is the TC²-BBS system integrated with Meshtastic devices. The system allows for message handling, bulletin boards, mail systems, and a channel directory.
|
||||
|
||||
## Setup
|
||||
|
||||
### Requirements
|
||||
|
||||
- Python 3.x
|
||||
- Meshtastic
|
||||
- pypubsub
|
||||
|
||||
### Installation
|
||||
|
||||
1. Clone the repository:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/TheCommsChannel/TC2-BBS-mesh.git
|
||||
cd TC2-BBS-mesh
|
||||
```
|
||||
|
||||
2. Set up a Python virtual environment:
|
||||
|
||||
```sh
|
||||
python -m venv venv
|
||||
```
|
||||
|
||||
3. Activate the virtual environment:
|
||||
|
||||
|
||||
- On Windows:
|
||||
|
||||
```sh
|
||||
venv\Scripts\activate
|
||||
```
|
||||
|
||||
- On macOS and Linux:
|
||||
|
||||
```sh
|
||||
source venv/bin/activate
|
||||
```
|
||||
|
||||
4. Install the required packages:
|
||||
|
||||
```sh
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
5. Set up the configuration in `config.ini`:
|
||||
|
||||
**[interface]**
|
||||
|
||||
If using `type = serial` and you have multiple devices connected, you will need to uncomment the `port =` line and enter in the port of your device.
|
||||
|
||||
Linux Example:
|
||||
`port = /dev/ttyUSB0`
|
||||
|
||||
Windows Example:
|
||||
`port = COM3`
|
||||
|
||||
If using type = tcp you will need to uncomment the hostname = 192.168.x.x line and put in the IP address of your Meshtastic device
|
||||
|
||||
**[sync]**
|
||||
|
||||
Enter in a list of other BBS nodes you would like to sync messages and bulletins with. Separate each by comma and no spaces as shown in the example below.
|
||||
You can find the nodeID in the menu under `Radio Configuration > User` for each node, or use this script for getting nodedb data from a device:
|
||||
|
||||
[Meshtastic-Python-Examples/print-nodedb.py at main · pdxlocations/Meshtastic-Python-Examples (github.com)](https://github.com/pdxlocations/Meshtastic-Python-Examples/blob/main/print-nodedb.py)
|
||||
|
||||
Example Config:
|
||||
```ini
|
||||
[interface]
|
||||
type = serial
|
||||
# port = /dev/ttyUSB0
|
||||
# hostname = 192.168.x.x
|
||||
|
||||
[sync]
|
||||
bbs_nodes = !f53f4abc,!f3abc123
|
||||
```
|
||||
|
||||
### Running the Server
|
||||
|
||||
Run the server with:
|
||||
|
||||
```sh
|
||||
python server.py
|
||||
```
|
||||
|
||||
Be sure you've followed the Python virtual environment steps above and activated it before running.
|
||||
|
||||
## Features
|
||||
|
||||
- **Mail System**: Send and receive mail messages.
|
||||
- **Bulletin Boards**: Post and view bulletins on various boards.
|
||||
- **Channel Directory**: Add and view channels in the directory.
|
||||
- **Statistics**: View statistics about nodes, hardware, and roles.
|
||||
- **Wall of Shame**: View devices with low battery levels.
|
||||
- **Fortune Teller**: Get a random fortune. Pulls from the fortunes.txt file. Feel free to edit this file remove or add more if you like.
|
||||
|
||||
## Thanks
|
||||
|
||||
Big thanks to [Meshtastic]([Meshtastic (github.com)](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)
|
||||
|
||||
## License
|
||||
|
||||
GNU General Public License v3.0
|
392
command_handlers.py
Normal file
392
command_handlers.py
Normal file
|
@ -0,0 +1,392 @@
|
|||
import logging
|
||||
import random
|
||||
import time
|
||||
|
||||
from config_init import initialize_config
|
||||
from db_operations import (
|
||||
add_bulletin, add_mail, delete_mail,
|
||||
get_bulletin_content, get_bulletins,
|
||||
get_mail, get_mail_content,
|
||||
add_channel, get_channels
|
||||
)
|
||||
from utils import (
|
||||
get_node_id_from_num, get_node_info,
|
||||
get_node_short_name, send_message,
|
||||
update_user_state
|
||||
)
|
||||
|
||||
config, interface_type, hostname, port, bbs_nodes = initialize_config()
|
||||
|
||||
|
||||
def get_node_name(node_id, interface):
|
||||
node_info = interface.nodes.get(node_id)
|
||||
if node_info:
|
||||
return node_info['user']['longName']
|
||||
return f"Node {node_id}"
|
||||
|
||||
|
||||
def handle_mail_command(sender_id, interface):
|
||||
response = "✉️ MAIL MENU ✉️\nWhat would you like to do with mail?\n[0]Read [1]Send [2]Exit"
|
||||
send_message(response, sender_id, interface)
|
||||
update_user_state(sender_id, {'command': 'MAIL', 'step': 1})
|
||||
|
||||
|
||||
def handle_bulletin_command(sender_id, interface):
|
||||
response = "📰 BULLETIN MENU 📰\nWhich board would you like to enter?\n[0]General [1]Info [2]News [3]Urgent [4]Exit"
|
||||
send_message(response, sender_id, interface)
|
||||
update_user_state(sender_id, {'command': 'BULLETIN', 'step': 1})
|
||||
|
||||
|
||||
def handle_exit_command(sender_id, interface):
|
||||
send_message("Type 'HELP' for a list of commands.", sender_id, interface)
|
||||
update_user_state(sender_id, None)
|
||||
|
||||
|
||||
def handle_help_command(sender_id, interface, state=None):
|
||||
title = "█▓▒░ TC² BBS ░▒▓█\n"
|
||||
commands = [
|
||||
"[M]ail Menu",
|
||||
"[B]ulletin Menu",
|
||||
"[S]tats Menu",
|
||||
"[F]ortune",
|
||||
"[W]all of Shame",
|
||||
"[C]hannel Directory",
|
||||
"EXIT: Exit current menu",
|
||||
"[H]elp"
|
||||
]
|
||||
if state and 'command' in state:
|
||||
current_command = state['command']
|
||||
if current_command == 'MAIL':
|
||||
commands = [
|
||||
"[0]Read Mail",
|
||||
"[1]Send Mail",
|
||||
"[2]Exit Mail Menu"
|
||||
]
|
||||
elif current_command == 'BULLETIN':
|
||||
commands = [
|
||||
"[0]General Board",
|
||||
"[1]Info Board",
|
||||
"[2]News Board",
|
||||
"[3]Urgent Board",
|
||||
"[4]Exit Bulletin Menu"
|
||||
]
|
||||
elif current_command == 'STATS':
|
||||
commands = [
|
||||
"[0]Total Nodes",
|
||||
"[1]Total HW Models",
|
||||
"[2]Total Roles",
|
||||
"[3]Back"
|
||||
]
|
||||
response = title + "Available commands:\n" + "\n".join(commands)
|
||||
send_message(response, sender_id, interface)
|
||||
|
||||
|
||||
def handle_stats_command(sender_id, interface):
|
||||
response = "What stats would you like to view?\n[0]Node Numbers [1]Hardware [2]Roles [3]Main Menu"
|
||||
send_message(response, sender_id, interface)
|
||||
update_user_state(sender_id, {'command': 'STATS', 'step': 1})
|
||||
|
||||
|
||||
def handle_fortune_command(sender_id, interface):
|
||||
try:
|
||||
with open('fortunes.txt', 'r') as file:
|
||||
fortunes = file.readlines()
|
||||
if not fortunes:
|
||||
send_message("No fortunes available.", sender_id, interface)
|
||||
return
|
||||
fortune = random.choice(fortunes).strip()
|
||||
decorated_fortune = f"🔮 {fortune} 🔮"
|
||||
send_message(decorated_fortune, sender_id, interface)
|
||||
except Exception as e:
|
||||
send_message(f"Error generating fortune: {e}", sender_id, interface)
|
||||
|
||||
|
||||
def handle_stats_steps(sender_id, message, step, interface, bbs_nodes):
|
||||
if step == 1:
|
||||
choice = message.upper()
|
||||
if choice == '3':
|
||||
handle_help_command(sender_id, interface)
|
||||
return
|
||||
choice = int(choice)
|
||||
if choice == 0:
|
||||
response = "Select time period for total nodes:\n[0]ALL [1]Last 24 Hours [2]Last 8 Hours [3]Last Hour"
|
||||
send_message(response, sender_id, interface)
|
||||
update_user_state(sender_id, {'command': 'STATS', 'step': 2})
|
||||
elif choice == 1:
|
||||
hw_models = {}
|
||||
for node in interface.nodes.values():
|
||||
hw_model = node['user'].get('hwModel', 'Unknown')
|
||||
hw_models[hw_model] = hw_models.get(hw_model, 0) + 1
|
||||
response = "Hardware Models:\n" + "\n".join([f"{model}: {count}" for model, count in hw_models.items()])
|
||||
send_message(response, sender_id, interface)
|
||||
handle_stats_command(sender_id, interface)
|
||||
elif choice == 2:
|
||||
roles = {}
|
||||
for node in interface.nodes.values():
|
||||
role = node['user'].get('role', 'Unknown')
|
||||
roles[role] = roles.get(role, 0) + 1
|
||||
response = "Roles:\n" + "\n".join([f"{role}: {count}" for role, count in roles.items()])
|
||||
send_message(response, sender_id, interface)
|
||||
handle_stats_command(sender_id, interface)
|
||||
|
||||
elif step == 2:
|
||||
choice = int(message)
|
||||
current_time = int(time.time())
|
||||
if choice == 0:
|
||||
total_nodes = len(interface.nodes)
|
||||
send_message(f"Total nodes seen: {total_nodes}", sender_id, interface)
|
||||
else:
|
||||
time_limits = [86400, 28800, 3600] # Last 24 hours, Last 8 hours, Last hour
|
||||
time_limit = current_time - time_limits[choice - 1]
|
||||
total_nodes = 0
|
||||
for node in interface.nodes.values():
|
||||
last_heard = node.get('lastHeard', 0)
|
||||
if last_heard is not None and last_heard >= time_limit:
|
||||
total_nodes += 1
|
||||
logging.info(f"Node {node.get('user', {}).get('longName', 'Unknown')} heard at {last_heard}, within limit {time_limit}")
|
||||
timeframes = ["24 hours", "8 hours", "hour"]
|
||||
send_message(f"Total nodes seen in the last {timeframes[choice - 1]}: {total_nodes}", sender_id, interface)
|
||||
handle_stats_steps(sender_id, '0', 1, interface, bbs_nodes)
|
||||
|
||||
|
||||
def handle_bb_steps(sender_id, message, step, state, interface, bbs_nodes):
|
||||
boards = {0: "General", 1: "News", 2: "Info", 3: "Urgent"}
|
||||
|
||||
if step == 1:
|
||||
if message == '4':
|
||||
handle_help_command(sender_id, interface)
|
||||
return
|
||||
board_name = boards.get(int(message))
|
||||
if board_name:
|
||||
response = f"What would you like to do in the {board_name} board?\n[0]View Bulletins [1]Post Bulletin [2]Exit"
|
||||
send_message(response, sender_id, interface)
|
||||
update_user_state(sender_id, {'command': 'BULLETIN', 'step': 2, 'board': board_name})
|
||||
else:
|
||||
handle_help_command(sender_id, interface)
|
||||
update_user_state(sender_id, None)
|
||||
|
||||
elif step == 2:
|
||||
if message == '2':
|
||||
# Return to the bulletin menu
|
||||
response = "📰 BULLETIN MENU 📰\nWhich board would you like to enter?\n[0]General [1]Info [2]News [3]Urgent [4]Exit"
|
||||
send_message(response, sender_id, interface)
|
||||
update_user_state(sender_id, {'command': 'BULLETIN', 'step': 1})
|
||||
return
|
||||
if message == '0':
|
||||
board_name = state['board']
|
||||
bulletins = get_bulletins(board_name)
|
||||
if (bulletins):
|
||||
send_message(f"Select a bulletin number to view from {board_name}:", sender_id, interface)
|
||||
for bulletin in bulletins:
|
||||
send_message(f"[{bulletin[0]}] {bulletin[1]}", sender_id, interface)
|
||||
update_user_state(sender_id, {'command': 'BULLETIN', 'step': 3, 'board': board_name})
|
||||
else:
|
||||
send_message(f"No bulletins in {board_name}.", sender_id, interface)
|
||||
# Go back to the board menu
|
||||
response = f"What would you like to do in the {board_name} board?\n[0]View Bulletins [1]Post Bulletin [2]Exit"
|
||||
send_message(response, sender_id, interface)
|
||||
update_user_state(sender_id, {'command': 'BULLETIN', 'step': 2, 'board': board_name})
|
||||
|
||||
elif message == '1':
|
||||
send_message("What is the subject of your bulletin? Keep it short.", sender_id, interface)
|
||||
update_user_state(sender_id, {'command': 'BULLETIN', 'step': 4, 'board': state['board']})
|
||||
|
||||
elif step == 3:
|
||||
bulletin_id = int(message)
|
||||
sender_short_name, date, subject, content, unique_id = get_bulletin_content(bulletin_id)
|
||||
send_message(f"From: {sender_short_name}\nDate: {date}\nSubject: {subject}\n- - - - - - -\n{content}", sender_id, interface)
|
||||
board_name = state['board']
|
||||
response = f"What would you like to do in the {board_name} board?\n[0]View Bulletins [1]Post Bulletin [2]Exit"
|
||||
send_message(response, sender_id, interface)
|
||||
update_user_state(sender_id, {'command': 'BULLETIN', 'step': 2, 'board': board_name})
|
||||
|
||||
elif step == 4:
|
||||
subject = message
|
||||
send_message("Send the contents of your bulletin. Send a message with END when finished.", sender_id, interface)
|
||||
update_user_state(sender_id, {'command': 'BULLETIN', 'step': 6, 'board': state['board'], 'subject': subject, 'content': ''})
|
||||
|
||||
elif step == 5:
|
||||
if message.lower() == "y":
|
||||
bulletins = get_bulletins(state['board'])
|
||||
send_message(f"Select a bulletin number to view from {state['board']}:", sender_id, interface)
|
||||
for bulletin in bulletins:
|
||||
send_message(f"[{bulletin[0]}]\nSubject: {bulletin[1]}", sender_id, interface)
|
||||
update_user_state(sender_id, {'command': 'BULLETIN', 'step': 3, 'board': state['board']})
|
||||
else:
|
||||
send_message("Okay, feel free to send another command.", sender_id, interface)
|
||||
update_user_state(sender_id, None)
|
||||
|
||||
elif step == 6:
|
||||
if message.lower() == "end":
|
||||
board = state['board']
|
||||
subject = state['subject']
|
||||
content = state['content']
|
||||
node_id = get_node_id_from_num(sender_id, interface)
|
||||
node_info = interface.nodes.get(node_id)
|
||||
if node_info is None:
|
||||
send_message("Error: Unable to retrieve your node information.", sender_id, interface)
|
||||
update_user_state(sender_id, None)
|
||||
return
|
||||
sender_short_name = node_info['user'].get('shortName', f"Node {sender_id}")
|
||||
unique_id = add_bulletin(board, sender_short_name, subject, content, bbs_nodes, interface)
|
||||
send_message(f"Your bulletin '{subject}' has been posted to {board}.\n(╯°□°)╯📄📌[{board}]", sender_id, interface)
|
||||
response = f"What would you like to do in the {board} board?\n[0]View Bulletins [1]Post Bulletin [2]Exit"
|
||||
send_message(response, sender_id, interface)
|
||||
update_user_state(sender_id, {'command': 'BULLETIN', 'step': 2, 'board': board})
|
||||
else:
|
||||
state['content'] += message + "\n"
|
||||
update_user_state(sender_id, state)
|
||||
|
||||
|
||||
def handle_mail_steps(sender_id, message, step, state, interface, bbs_nodes):
|
||||
if step == 1:
|
||||
choice = message
|
||||
if choice == '0':
|
||||
sender_node_id = get_node_id_from_num(sender_id, interface)
|
||||
mail = get_mail(sender_node_id)
|
||||
if mail:
|
||||
send_message(f"You have {len(mail)} mail messages. Select a message number to read:", sender_id, interface)
|
||||
for msg in mail:
|
||||
send_message(f"✉️ {msg[0]} ✉️\nDate: {msg[3]}\nFrom: {msg[1]}\nSubject: {msg[2]}", sender_id, interface)
|
||||
update_user_state(sender_id, {'command': 'MAIL', 'step': 2})
|
||||
else:
|
||||
send_message("There are no messages in your mailbox.\n(`⌒`)", sender_id, interface)
|
||||
update_user_state(sender_id, None)
|
||||
elif choice == '1':
|
||||
send_message("What is the Short Name of the node you want to leave a message for?", sender_id, interface)
|
||||
update_user_state(sender_id, {'command': 'MAIL', 'step': 3})
|
||||
elif choice == '2':
|
||||
handle_help_command(sender_id, interface)
|
||||
|
||||
elif step == 2:
|
||||
mail_id = int(message)
|
||||
sender, date, subject, content, unique_id = get_mail_content(mail_id)
|
||||
send_message(f"Date: {date}\nFrom: {sender}\nSubject: {subject}\n{content}", sender_id, interface)
|
||||
send_message("Would you like to delete this message now that you've viewed it? Y/N", sender_id, interface)
|
||||
update_user_state(sender_id, {'command': 'MAIL', 'step': 4, 'mail_id': mail_id, 'unique_id': unique_id})
|
||||
|
||||
elif step == 3:
|
||||
short_name = message
|
||||
nodes = get_node_info(interface, short_name)
|
||||
if not nodes:
|
||||
send_message("I'm unable to find that node in my database.", sender_id, interface)
|
||||
handle_mail_command(sender_id, interface)
|
||||
elif len(nodes) == 1:
|
||||
recipient_id = nodes[0]['num']
|
||||
recipient_name = get_node_name(recipient_id, interface)
|
||||
send_message(f"What is the subject of your message to {recipient_name}?\nKeep it short.", sender_id, interface)
|
||||
update_user_state(sender_id, {'command': 'MAIL', 'step': 5, 'recipient_id': recipient_id})
|
||||
else:
|
||||
send_message("There are multiple nodes with that short name. Which one would you like to leave a message for?", sender_id, interface)
|
||||
for i, node in enumerate(nodes):
|
||||
send_message(f"[{i}] {node['longName']}", sender_id, interface)
|
||||
update_user_state(sender_id, {'command': 'MAIL', 'step': 6, 'nodes': nodes})
|
||||
|
||||
elif step == 4:
|
||||
if message.lower() == "y":
|
||||
unique_id = state['unique_id']
|
||||
delete_mail(unique_id, bbs_nodes, interface)
|
||||
send_message("The message has been deleted 🗑️", sender_id, interface)
|
||||
else:
|
||||
send_message("The message has been kept in your inbox.✉️\nJust don't let it get as messy as your regular email inbox (ಠ_ಠ)", sender_id, interface)
|
||||
update_user_state(sender_id, None)
|
||||
|
||||
elif step == 5:
|
||||
subject = message
|
||||
send_message("Send your message. You can send it in multiple messages if it's too long for one.\nSend a single message with END when you're done", sender_id, interface)
|
||||
update_user_state(sender_id, {'command': 'MAIL', 'step': 7, 'recipient_id': state['recipient_id'], 'subject': subject, 'content': ''})
|
||||
|
||||
elif step == 6:
|
||||
selected_node_index = int(message)
|
||||
selected_node = state['nodes'][selected_node_index]
|
||||
recipient_id = selected_node['num']
|
||||
recipient_name = get_node_name(recipient_id, interface)
|
||||
send_message(f"What is the subject of your message to {recipient_name}?\nKeep it short.", sender_id, interface)
|
||||
update_user_state(sender_id, {'command': 'MAIL', 'step': 5, 'recipient_id': recipient_id})
|
||||
|
||||
elif step == 7:
|
||||
if message.lower() == "end":
|
||||
recipient_id = state['recipient_id']
|
||||
subject = state['subject']
|
||||
content = state['content']
|
||||
recipient_name = get_node_name(recipient_id, interface)
|
||||
sender_short_name = get_node_short_name(get_node_id_from_num(sender_id, interface), interface)
|
||||
unique_id = add_mail(get_node_id_from_num(sender_id, interface), sender_short_name, recipient_id, subject, content, bbs_nodes, interface)
|
||||
send_message(f"Mail has been posted to the mailbox of {recipient_name}.\n(╯°□°)╯📨📬", sender_id, interface)
|
||||
|
||||
# Send notification to the recipient
|
||||
notification_message = f"You have a new mail message from {sender_short_name}. Check your mailbox by responding to this message with M."
|
||||
send_message(notification_message, recipient_id, interface)
|
||||
|
||||
update_user_state(sender_id, None)
|
||||
update_user_state(sender_id, {'command': 'MAIL', 'step': 8})
|
||||
else:
|
||||
state['content'] += message + "\n"
|
||||
update_user_state(sender_id, state)
|
||||
|
||||
elif step == 8:
|
||||
if message.lower() == "y":
|
||||
handle_mail_command(sender_id, interface)
|
||||
else:
|
||||
send_message("Okay, feel free to send another command.", sender_id, interface)
|
||||
update_user_state(sender_id, None)
|
||||
|
||||
|
||||
def handle_wall_of_shame_command(sender_id, interface):
|
||||
response = "Devices with battery levels below 20%:\n"
|
||||
for node_id, node in interface.nodes.items():
|
||||
metrics = node.get('deviceMetrics', {})
|
||||
battery_level = metrics.get('batteryLevel', 101)
|
||||
if battery_level < 20:
|
||||
long_name = node['user']['longName']
|
||||
response += f"{long_name} - Battery {battery_level}%\n"
|
||||
if response == "Devices with battery levels below 20%:\n":
|
||||
response = "No devices with battery levels below 20% found."
|
||||
send_message(response, sender_id, interface)
|
||||
|
||||
|
||||
def handle_channel_directory_command(sender_id, interface):
|
||||
response = "📚 CHANNEL DIRECTORY 📚\nWhat would you like to do in the Channel Directory?\n[0]View [1]Post [2]Exit"
|
||||
send_message(response, sender_id, interface)
|
||||
update_user_state(sender_id, {'command': 'CHANNEL_DIRECTORY', 'step': 1})
|
||||
|
||||
|
||||
def handle_channel_directory_steps(sender_id, message, step, state, interface):
|
||||
if step == 1:
|
||||
choice = message
|
||||
if choice == '2':
|
||||
handle_help_command(sender_id, interface)
|
||||
return
|
||||
elif choice == '0':
|
||||
channels = get_channels()
|
||||
if channels:
|
||||
response = "Select a channel number to view:\n" + "\n".join(
|
||||
[f"[{i}] {channel[0]}" for i, channel in enumerate(channels)])
|
||||
send_message(response, sender_id, interface)
|
||||
update_user_state(sender_id, {'command': 'CHANNEL_DIRECTORY', 'step': 2})
|
||||
else:
|
||||
send_message("No channels available in the directory.", sender_id, interface)
|
||||
handle_channel_directory_command(sender_id, interface)
|
||||
elif choice == '1':
|
||||
send_message("Name your channel for the directory:", sender_id, interface)
|
||||
update_user_state(sender_id, {'command': 'CHANNEL_DIRECTORY', 'step': 3})
|
||||
|
||||
elif step == 2:
|
||||
channel_index = int(message)
|
||||
channels = get_channels()
|
||||
if 0 <= channel_index < len(channels):
|
||||
channel_name, channel_url = channels[channel_index]
|
||||
send_message(f"Channel Name: {channel_name}\nChannel URL:\n{channel_url}", sender_id, interface)
|
||||
handle_channel_directory_command(sender_id, interface)
|
||||
|
||||
elif step == 3:
|
||||
channel_name = message
|
||||
send_message("Send a message with your channel URL:", sender_id, interface)
|
||||
update_user_state(sender_id, {'command': 'CHANNEL_DIRECTORY', 'step': 4, 'channel_name': channel_name})
|
||||
|
||||
elif step == 4:
|
||||
channel_url = message
|
||||
channel_name = state['channel_name']
|
||||
add_channel(channel_name, channel_url)
|
||||
send_message(f"Your channel '{channel_name}' has been added to the directory.", sender_id, interface)
|
||||
handle_channel_directory_command(sender_id, interface)
|
34
config.ini
Normal file
34
config.ini
Normal file
|
@ -0,0 +1,34 @@
|
|||
###############################
|
||||
#### Select Interface type ####
|
||||
###############################
|
||||
# [type = serial] for USB connected devices
|
||||
#If there are multiple serial devices connected, be sure to use the "port" option and specify a port
|
||||
# Linux Example:
|
||||
# port = /dev/ttyUSB0
|
||||
#
|
||||
# Windows Example:
|
||||
# port = COM3
|
||||
# [type = tcp] for network connected devices (ESP32 devices only - this does not work for WisBlock)
|
||||
# If using tcp, remove the # from the beginning and replace 192.168.x.x with the IP address of your device
|
||||
# Example:
|
||||
# [interface]
|
||||
# type = tcp
|
||||
# hostname = 192.168.1.100
|
||||
|
||||
[interface]
|
||||
type = serial
|
||||
port = /dev/ttyUSB0
|
||||
# hostname = 192.168.x.x
|
||||
|
||||
|
||||
############################
|
||||
#### BBS NODE SYNC LIST ####
|
||||
############################
|
||||
# Provide a list of other nodes running TC²-BBS to sync mail messages and bulletins with
|
||||
# Enter in a list of other BBS Nodes by their nodeID separated by commas (no spaces)
|
||||
# Example:
|
||||
# [sync]
|
||||
# bbs_nodes = !17d7e4b7,!18e9f5a3,!1a2b3c4d
|
||||
|
||||
# [sync]
|
||||
# bbs_nodes = !17d7e4b7
|
39
config_init.py
Normal file
39
config_init.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
import configparser
|
||||
import time
|
||||
import meshtastic.serial_interface
|
||||
import meshtastic.tcp_interface
|
||||
import serial.tools.list_ports
|
||||
|
||||
def initialize_config():
|
||||
config = configparser.ConfigParser()
|
||||
config.read('config.ini')
|
||||
interface_type = config['interface']['type']
|
||||
hostname = config['interface'].get('hostname', None)
|
||||
port = config['interface'].get('port', None)
|
||||
bbs_nodes = config['sync']['bbs_nodes'].split(',')
|
||||
return config, interface_type, hostname, port, bbs_nodes
|
||||
|
||||
def get_interface(interface_type, hostname=None, port=None):
|
||||
while True:
|
||||
try:
|
||||
if interface_type == 'serial':
|
||||
if port:
|
||||
return meshtastic.serial_interface.SerialInterface(port)
|
||||
else:
|
||||
ports = list(serial.tools.list_ports.comports())
|
||||
if len(ports) == 1:
|
||||
return meshtastic.serial_interface.SerialInterface(ports[0].device)
|
||||
elif len(ports) > 1:
|
||||
port_list = ', '.join([p.device for p in ports])
|
||||
raise ValueError(f"Multiple serial ports detected: {port_list}. Specify one with the 'port' argument.")
|
||||
else:
|
||||
raise ValueError("No serial ports detected.")
|
||||
elif interface_type == 'tcp':
|
||||
if not hostname:
|
||||
raise ValueError("Hostname must be specified for TCP interface")
|
||||
return meshtastic.tcp_interface.TCPInterface(hostname=hostname)
|
||||
else:
|
||||
raise ValueError("Invalid interface type specified in config file")
|
||||
except PermissionError as e:
|
||||
print(f"PermissionError: {e}. Retrying in 5 seconds...")
|
||||
time.sleep(5)
|
149
db_operations.py
Normal file
149
db_operations.py
Normal file
|
@ -0,0 +1,149 @@
|
|||
import logging
|
||||
import sqlite3
|
||||
import threading
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from utils import (
|
||||
send_bulletin_to_bbs_nodes,
|
||||
send_delete_bulletin_to_bbs_nodes,
|
||||
send_delete_mail_to_bbs_nodes,
|
||||
send_mail_to_bbs_nodes, send_message
|
||||
)
|
||||
|
||||
|
||||
thread_local = threading.local()
|
||||
|
||||
def get_db_connection():
|
||||
if not hasattr(thread_local, 'connection'):
|
||||
thread_local.connection = sqlite3.connect('bulletins.db')
|
||||
return thread_local.connection
|
||||
|
||||
def initialize_database():
|
||||
conn = get_db_connection()
|
||||
c = conn.cursor()
|
||||
c.execute('''CREATE TABLE IF NOT EXISTS bulletins (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
board TEXT NOT NULL,
|
||||
sender_short_name TEXT NOT NULL,
|
||||
date TEXT NOT NULL,
|
||||
subject TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
unique_id TEXT NOT NULL
|
||||
)''')
|
||||
c.execute('''CREATE TABLE IF NOT EXISTS mail (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
sender TEXT NOT NULL,
|
||||
sender_short_name TEXT NOT NULL,
|
||||
recipient TEXT NOT NULL,
|
||||
date TEXT NOT NULL,
|
||||
subject TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
unique_id TEXT NOT NULL
|
||||
);''')
|
||||
c.execute('''CREATE TABLE IF NOT EXISTS channels (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
url TEXT NOT NULL
|
||||
);''')
|
||||
conn.commit()
|
||||
print("Database schema initialized.")
|
||||
|
||||
def add_channel(name, url):
|
||||
conn = get_db_connection()
|
||||
c = conn.cursor()
|
||||
c.execute("INSERT INTO channels (name, url) VALUES (?, ?)", (name, url))
|
||||
conn.commit()
|
||||
|
||||
def get_channels():
|
||||
conn = get_db_connection()
|
||||
c = conn.cursor()
|
||||
c.execute("SELECT name, url FROM channels")
|
||||
return c.fetchall()
|
||||
|
||||
|
||||
|
||||
def add_bulletin(board, sender_short_name, subject, content, bbs_nodes, interface, unique_id=None):
|
||||
conn = get_db_connection()
|
||||
c = conn.cursor()
|
||||
date = datetime.now().strftime('%m/%d/%Y %H:%M')
|
||||
if not unique_id:
|
||||
unique_id = str(uuid.uuid4())
|
||||
c.execute(
|
||||
"INSERT INTO bulletins (board, sender_short_name, date, subject, content, unique_id) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
(board, sender_short_name, date, subject, content, unique_id))
|
||||
conn.commit()
|
||||
if bbs_nodes and interface:
|
||||
send_bulletin_to_bbs_nodes(board, sender_short_name, subject, content, unique_id, bbs_nodes, interface)
|
||||
|
||||
# New logic to send group chat notification for urgent bulletins
|
||||
if board.lower() == "urgent":
|
||||
group_chat_id = 4294967295 # Default group chat ID (0xFFFFFFFF)
|
||||
notification_message = f"💥NEW URGENT BULLETIN💥\nFrom: {sender_short_name}\nTitle: {subject}"
|
||||
send_message(notification_message, group_chat_id, interface)
|
||||
|
||||
return unique_id
|
||||
|
||||
def get_bulletins(board):
|
||||
conn = get_db_connection()
|
||||
c = conn.cursor()
|
||||
c.execute("SELECT id, subject, sender_short_name, date, unique_id FROM bulletins WHERE board = ?", (board,))
|
||||
return c.fetchall()
|
||||
|
||||
def get_bulletin_content(bulletin_id):
|
||||
conn = get_db_connection()
|
||||
c = conn.cursor()
|
||||
c.execute("SELECT sender_short_name, date, subject, content, unique_id FROM bulletins WHERE id = ?", (bulletin_id,))
|
||||
return c.fetchone()
|
||||
|
||||
|
||||
def delete_bulletin(bulletin_id, bbs_nodes, interface):
|
||||
conn = get_db_connection()
|
||||
c = conn.cursor()
|
||||
c.execute("DELETE FROM bulletins WHERE id = ?", (bulletin_id,))
|
||||
conn.commit()
|
||||
send_delete_bulletin_to_bbs_nodes(bulletin_id, bbs_nodes, interface)
|
||||
|
||||
def add_mail(sender_id, sender_short_name, recipient_id, subject, content, bbs_nodes, interface, unique_id=None):
|
||||
conn = get_db_connection()
|
||||
c = conn.cursor()
|
||||
date = datetime.now().strftime('%m/%d/%Y %H:%M')
|
||||
if not unique_id:
|
||||
unique_id = str(uuid.uuid4())
|
||||
c.execute("INSERT INTO mail (sender, sender_short_name, recipient, date, subject, content, unique_id) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||
(sender_id, sender_short_name, recipient_id, date, subject, content, unique_id))
|
||||
conn.commit()
|
||||
if bbs_nodes and interface:
|
||||
send_mail_to_bbs_nodes(sender_id, sender_short_name, recipient_id, subject, content, unique_id, bbs_nodes, interface)
|
||||
return unique_id
|
||||
|
||||
def get_mail(recipient_id):
|
||||
conn = get_db_connection()
|
||||
c = conn.cursor()
|
||||
c.execute("SELECT id, sender_short_name, subject, date, unique_id FROM mail WHERE recipient = ?", (recipient_id,))
|
||||
return c.fetchall()
|
||||
|
||||
def get_mail_content(mail_id):
|
||||
conn = get_db_connection()
|
||||
c = conn.cursor()
|
||||
c.execute("SELECT sender_short_name, date, subject, content, unique_id FROM mail WHERE id = ?", (mail_id,))
|
||||
return c.fetchone()
|
||||
|
||||
def delete_mail(unique_id, bbs_nodes, interface):
|
||||
logging.info(f"Attempting to delete mail with unique_id: {unique_id}")
|
||||
conn = get_db_connection()
|
||||
c = conn.cursor()
|
||||
try:
|
||||
c.execute("SELECT unique_id FROM mail WHERE unique_id = ?", (unique_id,))
|
||||
result = c.fetchone()
|
||||
logging.debug(f"Fetch result for unique_id {unique_id}: {result}")
|
||||
if result is None:
|
||||
logging.error(f"No mail found with unique_id: {unique_id}")
|
||||
return # Early exit if no matching mail found
|
||||
c.execute("DELETE FROM mail WHERE unique_id = ?", (unique_id,))
|
||||
conn.commit()
|
||||
send_delete_mail_to_bbs_nodes(unique_id, bbs_nodes, interface)
|
||||
logging.info(f"Mail with unique_id: {unique_id} deleted and sync message sent.")
|
||||
except Exception as e:
|
||||
logging.error(f"Error deleting mail with unique_id {unique_id}: {e}")
|
||||
raise
|
100
fortunes.txt
Normal file
100
fortunes.txt
Normal file
|
@ -0,0 +1,100 @@
|
|||
Nothing is impossible. Except Monday mornings.
|
||||
You are not illiterate.
|
||||
A comfort zone is a magical place where nothing ever grows.
|
||||
Give yourself a break before you breakdown.
|
||||
If a true sense of value is to be yours it must come through service.
|
||||
Happiness is enjoying what you got. Never from what you want.
|
||||
Go confidently in the direction of your dreams.
|
||||
Genius is eternal patience.
|
||||
Follow the advice of your heart.
|
||||
Follow the middle path. Neither extreme will make you happy.
|
||||
Fight for it. You will come out on the top.
|
||||
Failure is the tuition you pay for success.
|
||||
Expect a change for the better.
|
||||
Examine the situation before you act impulsively.
|
||||
Everything you are against weakens you. Everything you are for empowers you.
|
||||
Curiosity kills boredom. Nothing can kill curiosity.
|
||||
Common sense is instinct. Enough of it is genius.
|
||||
Call an old friend today.
|
||||
Better ask twice than lose yourself once.
|
||||
Behavior is a mirror in which everyone shows his own image.
|
||||
Balance life with a little sweet & sour.
|
||||
Attitude is a little thing that makes a BIG difference.
|
||||
An unexpected check or fortune will arrive today.
|
||||
An unexpected event will bring you riches.
|
||||
An upset is an opportunity to see the truth.
|
||||
Any rough times are behind you.
|
||||
An important person will offer you support.
|
||||
An investment in knowledge always pays the best interest.
|
||||
An investment in yourself will pay dividends for the rest of your life.
|
||||
An old wish will come true.
|
||||
All your hard work will soon pay off.
|
||||
All your sorrows will vanish.
|
||||
All the little things will add to a happy journey.
|
||||
All that we are is a result of what we have thought.
|
||||
All the effort you are making will ultimately pay off.
|
||||
Act boldly and unseen forces will come to your aid.
|
||||
6 out of every 10 people refuse to be a statistic.
|
||||
a belief is just a thought you keep having
|
||||
A bit of fragrance clings to the hand that gives flowers.
|
||||
Absolutes are absolutely wrong
|
||||
abstraction is a type of decadence
|
||||
Abuse of authority comes as no surprise.
|
||||
Abuse of power comes as no surprise
|
||||
Acting without thinking can be awfully entertaining.
|
||||
Actions speak louder than words... but nothing speaks louder than they who take no action
|
||||
Adapt or die.
|
||||
Ad astra per aspera.
|
||||
A fine will be charged for wounding the library books.
|
||||
A good man has few enemies. A ruthless man has none.
|
||||
A lack of leadership is no substitute for inaction.
|
||||
Alarm clocks kill dreams.
|
||||
A little knowledge can go a long way
|
||||
A little madness now and then is relished by the wisest men.
|
||||
A little rudeness and disrespect can elevate a meaningless interaction to a battle of wills and add drama to an otherwise dull day.
|
||||
All employees are forbidden.
|
||||
All this foliage is obstructing my scenic view.
|
||||
Almost half of them are BELOW AVERAGE
|
||||
Always be ready to walk away from a bad idea.
|
||||
Always store beer in a dark place.
|
||||
Annihilate the adversaries with laughter.
|
||||
An object at rest cannot be stopped
|
||||
Are You Mad At Yourself for Not Exiting
|
||||
a sense of timing is the mark of genius
|
||||
Asking questions is the only way to get answers.
|
||||
A suspicious mind is a healthy mind
|
||||
Frogs are my favorite vegetable.
|
||||
GET OFF THE INTERNET AND DO SOMETHING
|
||||
GET OVER IT
|
||||
Get paranoid to act fast.
|
||||
Get ready for this.
|
||||
Good luck on your exam.
|
||||
Go places.
|
||||
Go talk to that old weird guy at Dennys.
|
||||
Government Complaints Box 📥
|
||||
Happiness is a new idea.
|
||||
Has to happen to somebody.
|
||||
The poors are at it again.
|
||||
Have a nice day. Or else
|
||||
History is written by the winners
|
||||
I can see you
|
||||
ice cream is never the wrong decision
|
||||
I found Nirvana. It was in my album collection.
|
||||
If seeing is believing you have to be blind.
|
||||
I saw what you did just now
|
||||
It is forbidden to forbid.
|
||||
It is happening again.
|
||||
Knock yourself out.
|
||||
Knowing is hard.
|
||||
knowing yourself lets you understand others
|
||||
knowledge should be advanced at all costs
|
||||
Look for people picking their nose in their car.
|
||||
Looks like someone divided by zero.
|
||||
Look up.
|
||||
MAKE IT STOP
|
||||
Make weirdness normal.
|
||||
Mashed potatoes can be your friend.
|
||||
Never wrestle a pig. You both get dirty and the pig likes it.
|
||||
Nice one. You got away with it.
|
||||
Roll again.
|
||||
SHHHHHH. listen.
|
96
message_processing.py
Normal file
96
message_processing.py
Normal file
|
@ -0,0 +1,96 @@
|
|||
import logging
|
||||
|
||||
from command_handlers import (
|
||||
handle_mail_command, handle_bulletin_command, handle_exit_command,
|
||||
handle_help_command, handle_stats_command, handle_fortune_command,
|
||||
handle_bb_steps, handle_mail_steps, handle_stats_steps, handle_wall_of_shame_command,
|
||||
handle_channel_directory_command, handle_channel_directory_steps
|
||||
)
|
||||
|
||||
from db_operations import add_bulletin, add_mail, delete_bulletin, delete_mail
|
||||
from utils import get_user_state, get_node_short_name, get_node_id_from_num, send_message
|
||||
|
||||
command_handlers = {
|
||||
"m": handle_mail_command,
|
||||
"b": handle_bulletin_command,
|
||||
"s": handle_stats_command,
|
||||
"f": handle_fortune_command,
|
||||
"w": handle_wall_of_shame_command,
|
||||
"exit": handle_exit_command,
|
||||
"h": handle_help_command,
|
||||
"c": handle_channel_directory_command
|
||||
}
|
||||
|
||||
def process_message(sender_id, message, interface, is_sync_message=False):
|
||||
state = get_user_state(sender_id)
|
||||
message_lower = message.lower()
|
||||
bbs_nodes = interface.bbs_nodes
|
||||
|
||||
if is_sync_message:
|
||||
if message.startswith("BULLETIN|"):
|
||||
parts = message.split("|")
|
||||
board, sender_short_name, subject, content, unique_id = parts[1], parts[2], parts[3], parts[4], parts[5]
|
||||
add_bulletin(board, sender_short_name, subject, content, [], interface, unique_id=unique_id)
|
||||
|
||||
if board.lower() == "urgent":
|
||||
group_chat_id = 4294967295
|
||||
notification_message = f"💥NEW URGENT BULLETIN💥\nFrom: {sender_short_name}\nTitle: {subject}"
|
||||
send_message(notification_message, group_chat_id, interface)
|
||||
elif message.startswith("MAIL|"):
|
||||
parts = message.split("|")
|
||||
sender_id, sender_short_name, recipient_id, subject, content, unique_id = parts[1], parts[2], parts[3], parts[4], parts[5], parts[6]
|
||||
add_mail(sender_id, sender_short_name, recipient_id, subject, content, [], interface, unique_id=unique_id)
|
||||
elif message.startswith("DELETE_BULLETIN|"):
|
||||
unique_id = message.split("|")[1]
|
||||
delete_bulletin(unique_id, [], interface)
|
||||
elif message.startswith("DELETE_MAIL|"):
|
||||
unique_id = message.split("|")[1]
|
||||
logging.info(f"Processing delete mail with unique_id: {unique_id}")
|
||||
delete_mail(unique_id, [], interface)
|
||||
else:
|
||||
if message_lower in command_handlers:
|
||||
command_handlers[message_lower](sender_id, interface)
|
||||
elif state:
|
||||
command = state['command']
|
||||
step = state['step']
|
||||
|
||||
if command == 'MAIL':
|
||||
handle_mail_steps(sender_id, message, step, state, interface, bbs_nodes)
|
||||
elif command == 'BULLETIN':
|
||||
handle_bb_steps(sender_id, message, step, state, interface, bbs_nodes)
|
||||
elif command == 'STATS':
|
||||
handle_stats_steps(sender_id, message, step, interface, bbs_nodes)
|
||||
elif command == 'CHANNEL_DIRECTORY':
|
||||
handle_channel_directory_steps(sender_id, message, step, state, 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':
|
||||
message_bytes = packet['decoded']['payload']
|
||||
message_string = message_bytes.decode('utf-8')
|
||||
sender_id = packet['from']
|
||||
to_id = packet.get('to')
|
||||
sender_node_id = packet['fromId']
|
||||
|
||||
sender_short_name = get_node_short_name(sender_node_id, interface)
|
||||
receiver_short_name = get_node_short_name(get_node_id_from_num(to_id, interface),
|
||||
interface) if to_id else "Group Chat"
|
||||
logging.info(f"Received message from user '{sender_short_name}' to {receiver_short_name}: {message_string}")
|
||||
|
||||
bbs_nodes = interface.bbs_nodes
|
||||
is_sync_message = any(message_string.startswith(prefix) for prefix in
|
||||
["BULLETIN|", "MAIL|", "DELETE_BULLETIN|", "DELETE_MAIL|"])
|
||||
|
||||
if sender_node_id in bbs_nodes:
|
||||
if is_sync_message:
|
||||
process_message(sender_id, message_string, interface, is_sync_message=True)
|
||||
else:
|
||||
logging.info("Ignoring non-sync message from known BBS node")
|
||||
elif to_id is not None and to_id != 0 and to_id != 255 and to_id == interface.myInfo.my_node_num:
|
||||
process_message(sender_id, message_string, interface, is_sync_message=False)
|
||||
else:
|
||||
logging.info("Ignoring message sent to group chat or from unknown node")
|
||||
except KeyError as e:
|
||||
logging.error(f"Error processing packet: {e}")
|
2
requirements.txt
Normal file
2
requirements.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
meshtastic
|
||||
pypubsub
|
60
server.py
Normal file
60
server.py
Normal file
|
@ -0,0 +1,60 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
TC²-BBS Server for Meshtastic by TheCommsChannel (TC²)
|
||||
Date: 06/25/2024
|
||||
Version: 0.1.0
|
||||
|
||||
Description:
|
||||
The system allows for mail message handling, bulletin boards, and a channel
|
||||
directory. It uses a configuration file for setup details and an SQLite3
|
||||
database for data storage. Mail messages and bulletins are synced with
|
||||
other BBS servers listed in the config.ini file.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from config_init import initialize_config, get_interface
|
||||
from db_operations import initialize_database
|
||||
from message_processing import on_receive
|
||||
from pubsub import pub
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
|
||||
def display_banner():
|
||||
banner = """
|
||||
████████╗ ██████╗██████╗ ██████╗ ██████╗ ███████╗
|
||||
╚══██╔══╝██╔════╝╚════██╗ ██╔══██╗██╔══██╗██╔════╝
|
||||
██║ ██║ █████╔╝█████╗██████╔╝██████╔╝███████╗
|
||||
██║ ██║ ██╔═══╝ ╚════╝██╔══██╗██╔══██╗╚════██║
|
||||
██║ ╚██████╗███████╗ ██████╔╝██████╔╝███████║
|
||||
╚═╝ ╚═════╝╚══════╝ ╚═════╝ ╚═════╝ ╚══════╝
|
||||
Meshtastic Version
|
||||
"""
|
||||
print(banner)
|
||||
|
||||
def main():
|
||||
display_banner()
|
||||
config, interface_type, hostname, port, bbs_nodes = initialize_config()
|
||||
interface = get_interface(interface_type, hostname, port)
|
||||
interface.bbs_nodes = bbs_nodes
|
||||
|
||||
logging.info(f"TC²-BBS is running on {interface_type} interface...")
|
||||
|
||||
initialize_database()
|
||||
|
||||
def receive_packet(packet):
|
||||
on_receive(packet, interface)
|
||||
|
||||
pub.subscribe(receive_packet, 'meshtastic.receive')
|
||||
|
||||
try:
|
||||
while True:
|
||||
pass
|
||||
except KeyboardInterrupt:
|
||||
logging.info("Shutting down the server...")
|
||||
interface.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
73
utils.py
Normal file
73
utils.py
Normal file
|
@ -0,0 +1,73 @@
|
|||
import logging
|
||||
import time
|
||||
|
||||
user_states = {}
|
||||
|
||||
|
||||
def update_user_state(user_id, state):
|
||||
user_states[user_id] = state
|
||||
|
||||
|
||||
def get_user_state(user_id):
|
||||
return user_states.get(user_id, None)
|
||||
|
||||
|
||||
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
|
||||
)
|
||||
time.sleep(2)
|
||||
|
||||
|
||||
def get_node_info(interface, short_name):
|
||||
nodes = [{'num': node_id, 'shortName': node['user']['shortName'], 'longName': node['user']['longName']}
|
||||
for node_id, node in interface.nodes.items()
|
||||
if node['user']['shortName'] == short_name]
|
||||
return nodes
|
||||
|
||||
|
||||
def get_node_id_from_num(node_num, interface):
|
||||
for node_id, node in interface.nodes.items():
|
||||
if node['num'] == node_num:
|
||||
return node_id
|
||||
return None
|
||||
|
||||
|
||||
def get_node_short_name(node_id, interface):
|
||||
node_info = interface.nodes.get(node_id)
|
||||
if node_info:
|
||||
return node_info['user']['shortName']
|
||||
return None
|
||||
|
||||
|
||||
def send_bulletin_to_bbs_nodes(board, sender_short_name, subject, content, unique_id, bbs_nodes, interface):
|
||||
message = f"BULLETIN|{board}|{sender_short_name}|{subject}|{content}|{unique_id}"
|
||||
for node_id in bbs_nodes:
|
||||
send_message(message, node_id, interface)
|
||||
|
||||
|
||||
def send_mail_to_bbs_nodes(sender_id, sender_short_name, recipient_id, subject, content, unique_id, bbs_nodes,
|
||||
interface):
|
||||
message = f"MAIL|{sender_id}|{sender_short_name}|{recipient_id}|{subject}|{content}|{unique_id}"
|
||||
logging.info(f"SERVER SYNC: Syncing new mail message {subject} sent from {sender_short_name} to other BBS systems.")
|
||||
for node_id in bbs_nodes:
|
||||
send_message(message, node_id, interface)
|
||||
|
||||
|
||||
def send_delete_bulletin_to_bbs_nodes(bulletin_id, bbs_nodes, interface):
|
||||
message = f"DELETE_BULLETIN|{bulletin_id}"
|
||||
for node_id in bbs_nodes:
|
||||
send_message(message, node_id, interface)
|
||||
|
||||
|
||||
def send_delete_mail_to_bbs_nodes(unique_id, bbs_nodes, interface):
|
||||
message = f"DELETE_MAIL|{unique_id}"
|
||||
logging.info(f"SERVER SYNC: Sending delete mail sync message with unique_id: {unique_id}")
|
||||
for node_id in bbs_nodes:
|
||||
send_message(message, node_id, interface)
|
Loading…
Reference in a new issue