mirror of
https://github.com/SpudGunMan/meshing-around.git
synced 2026-03-28 17:32:36 +01:00
This fixes an issue with how the bbslink sync process was handling incoming posts. It was not removing the @fromNode from the end of the body, which was causing it to get appended again during the push process. This would compound with more nodes involved in the sync process alt to https://github.com/SpudGunMan/meshing-around/pull/296 Co-Authored-By: Amy Nagle <1270500+kabili207@users.noreply.github.com>
311 lines
12 KiB
Python
311 lines
12 KiB
Python
# helper functions for various BBS messaging tasks
|
|
# K7MHI Kelly Keeton 2024
|
|
|
|
import pickle # pip install pickle
|
|
from modules.log import logger
|
|
from modules.settings import bbs_admin_list, bbs_ban_list, MESSAGE_CHUNK_SIZE, bbs_link_enabled, bbs_link_whitelist, responseDelay
|
|
import time
|
|
from datetime import datetime
|
|
|
|
useSynchCompression = False
|
|
|
|
if useSynchCompression:
|
|
import zlib
|
|
from modules.system import send_raw_bytes
|
|
|
|
trap_list_bbs = ("bbslist", "bbspost", "bbsread", "bbsdelete", "bbshelp", "bbsinfo", "bbslink", "bbsack")
|
|
|
|
# global message list, later we will use a pickle on disk
|
|
bbs_messages = []
|
|
bbs_dm = []
|
|
|
|
|
|
def load_bbsdb():
|
|
global bbs_messages
|
|
try:
|
|
with open('data/bbsdb.pkl', 'rb') as f:
|
|
new_bbs_messages = pickle.load(f)
|
|
if isinstance(new_bbs_messages, list):
|
|
for msg in new_bbs_messages:
|
|
msgHash = hash(tuple(msg[1:3]))
|
|
if all(hash(tuple(existing_msg[1:3])) != msgHash for existing_msg in bbs_messages):
|
|
new_id = len(bbs_messages) + 1
|
|
bbs_messages.append([new_id, msg[1], msg[2], msg[3]])
|
|
return True # Loaded successfully, regardless of whether new messages were added
|
|
return False # File existed but did not contain a valid list of messages (possibly corrupted)
|
|
except FileNotFoundError:
|
|
# create a new bbsdb.pkl with a welcome message
|
|
# template ([messageID, subject, message, fromNode, now, thread, replyto])
|
|
bbs_messages = [[1, "Welcome to meshBBS", "Welcome to the BBS, please post a message!",0,time.strftime('%Y-%m-%d %H:%M:%S'),0,0]]
|
|
logger.debug("System: bbsdb.pkl not found, creating new one")
|
|
try:
|
|
with open('data/bbsdb.pkl', 'wb') as f:
|
|
pickle.dump(bbs_messages, f)
|
|
return True
|
|
except Exception as e:
|
|
logger.error(f"System: Error creating bbsdb.pkl: {e}")
|
|
return False
|
|
except Exception as e:
|
|
logger.error(f"System: Error loading bbsdb.pkl: {e}")
|
|
return False
|
|
|
|
def save_bbsdb():
|
|
global bbs_messages
|
|
# save the bbs messages to the database file
|
|
try:
|
|
logger.debug("System: Saving data/bbsdb.pkl")
|
|
with open('data/bbsdb.pkl', 'wb') as f:
|
|
pickle.dump(bbs_messages, f)
|
|
except Exception as e:
|
|
logger.error(f"System: Error saving bbsdb: {e}")
|
|
|
|
def bbs_help():
|
|
# help message
|
|
return "BBS Commands:\n'bbslist'\n'bbspost $subject #message'\n'bbsread #'\n'bbsdelete #'\n'cmd'"
|
|
|
|
def bbs_list_messages():
|
|
#print (f"System: raw bbs_messages: {bbs_messages}")
|
|
# return a string with new line for each message subject in the list bbs_messages
|
|
message_list = ""
|
|
for message in bbs_messages:
|
|
# message[0] is the messageID, message[1] is the subject
|
|
message_list += "[#" + str(message[0]) + "] " + message[1] + "\n"
|
|
|
|
# last newline removed
|
|
message_list = message_list[:-1]
|
|
return message_list
|
|
|
|
def bbs_delete_message(messageID = 0, fromNode = 0):
|
|
#if messageID out of range ignore
|
|
if (messageID - 1) >= len(bbs_messages):
|
|
return "Message not found."
|
|
|
|
# delete a message from the bbsdb
|
|
if messageID > 0:
|
|
# if same user wrote message they can delete it
|
|
if fromNode == bbs_messages[messageID - 1][3] or str(fromNode) in bbs_admin_list:
|
|
bbs_messages.pop(messageID - 1)
|
|
# reset the messageID
|
|
for i in range(len(bbs_messages)):
|
|
bbs_messages[i][0] = i + 1
|
|
|
|
# save the bbsdb
|
|
save_bbsdb()
|
|
|
|
return "Msg #" + str(messageID) + " deleted."
|
|
else:
|
|
logger.warning(f"System: node {fromNode}, tried to delete a message: {bbs_messages[messageID - 1]} and was dropped.")
|
|
return "You are not authorized to delete this message."
|
|
else:
|
|
return "Please specify a message number to delete."
|
|
|
|
def bbs_post_message(subject, message, fromNode, threadID=0, replytoID=0):
|
|
# post a message to the bbsdb
|
|
now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|
thread = threadID
|
|
replyto = replytoID
|
|
# post a message to the bbsdb and assign a messageID
|
|
messageID = len(bbs_messages) + 1
|
|
|
|
# Check the BAN list for naughty nodes and silently drop the message
|
|
if str(fromNode) in bbs_ban_list:
|
|
logger.warning(f"System: Naughty node {fromNode}, tried to post a message: {subject}, {message} and was dropped.")
|
|
return "Message posted. ID is: " + str(messageID)
|
|
# validate message length isnt three times the MESSAGE_CHUNK_SIZE
|
|
if len(message) > (3 * MESSAGE_CHUNK_SIZE):
|
|
return "Message too long, max length is " + str(3 * MESSAGE_CHUNK_SIZE) + " characters."
|
|
# validate not a duplicate message
|
|
for msg in bbs_messages:
|
|
if msg[1].strip().lower() == subject.strip().lower() and msg[2].strip().lower() == message.strip().lower():
|
|
messageID = msg[0]
|
|
return "Message posted. ID is: " + str(messageID)
|
|
# validate its not overlength by keeping in chunker limit
|
|
# append the message to the list
|
|
bbs_messages.append([messageID, subject, message, fromNode, now, thread, replyto])
|
|
logger.info(f"System: NEW Message Posted, subject: {subject}, message: {message} from {fromNode}")
|
|
|
|
# save the bbsdb
|
|
save_bbsdb()
|
|
|
|
return "Message posted. ID is: " + str(messageID)
|
|
|
|
def bbs_read_message(messageID = 0):
|
|
#if messageID out of range ignore
|
|
if (messageID - 1) >= len(bbs_messages):
|
|
return "Message not found."
|
|
if messageID > 0:
|
|
fromNode = bbs_messages[messageID - 1][3]
|
|
fromNodeHex = hex(fromNode)[-4:]
|
|
message = bbs_messages[messageID - 1]
|
|
return f"Msg #{message[0]}\nFrom:{fromNodeHex}\n{message[2]}"
|
|
else:
|
|
return "Please specify a message number to read."
|
|
|
|
def save_bbsdm():
|
|
global bbs_dm
|
|
# save the bbs messages to the database file
|
|
logger.debug("System: Saving Updated BBS Direct Messages data/bbsdm.pkl")
|
|
with open('data/bbsdm.pkl', 'wb') as f:
|
|
pickle.dump(bbs_dm, f)
|
|
|
|
def load_bbsdm():
|
|
global bbs_dm
|
|
# load the bbs messages from the database file
|
|
try:
|
|
with open('data/bbsdm.pkl', 'rb') as f:
|
|
new_bbs_dm = pickle.load(f)
|
|
if isinstance(new_bbs_dm, list):
|
|
for msg in new_bbs_dm:
|
|
if msg not in bbs_dm:
|
|
bbs_dm.append(msg)
|
|
except:
|
|
bbs_dm = [[1234567890, "Message", 1234567890]]
|
|
logger.debug("System: Creating new data/bbsdm.pkl")
|
|
with open('data/bbsdm.pkl', 'wb') as f:
|
|
pickle.dump(bbs_dm, f)
|
|
|
|
def bbs_post_dm(toNode, message, fromNode):
|
|
global bbs_dm
|
|
# Check the BAN list for naughty nodes and silently drop the message
|
|
if str(fromNode) in bbs_ban_list:
|
|
logger.warning(f"System: Naughty node {fromNode}, tried to post a message: {message} and was dropped.")
|
|
return "DM Posted for node " + str(toNode)
|
|
|
|
# validate message length isnt three times the MESSAGE_CHUNK_SIZE
|
|
if len(message) > (3 * MESSAGE_CHUNK_SIZE):
|
|
return "Message too long, max length is " + str(3 * MESSAGE_CHUNK_SIZE) + " characters."
|
|
# validate not a duplicate message
|
|
for msg in bbs_dm:
|
|
if msg[0] == int(toNode) and msg[1].strip().lower() == message.strip().lower():
|
|
return "DM Posted for node " + str(toNode)
|
|
|
|
# append the message to the list
|
|
bbs_dm.append([int(toNode), message, int(fromNode)])
|
|
|
|
# save the bbsdb
|
|
save_bbsdm()
|
|
return "BBS DM Posted for node " + str(toNode)
|
|
|
|
def get_bbs_stats():
|
|
global bbs_messages, bbs_dm
|
|
# Return some stats on the bbs pending messages and total posted messages
|
|
return f"📡BBSdb has {len(bbs_messages)} messages.\nDirect ✉️ Messages waiting: {(len(bbs_dm) - 1)}"
|
|
|
|
def bbs_check_dm(toNode):
|
|
global bbs_dm
|
|
# Check for any messages for toNode
|
|
for msg in bbs_dm:
|
|
if msg[0] == toNode:
|
|
return msg
|
|
return False
|
|
|
|
def bbs_delete_dm(toNode, message):
|
|
global bbs_dm
|
|
# delete a message from the bbsdm
|
|
for msg in bbs_dm:
|
|
if msg[0] == toNode:
|
|
# check if the message matches
|
|
if msg[1] == message:
|
|
bbs_dm.remove(msg)
|
|
# save the bbsdb
|
|
save_bbsdm()
|
|
return "System: cleared mail for" + str(toNode)
|
|
return "System: No DM found for node " + str(toNode)
|
|
|
|
def compress_data(data_to_compress):
|
|
# Prepare message as bytes
|
|
compressed = zlib.compress(data_to_compress.encode('utf-8'))
|
|
return compressed
|
|
|
|
def decompress_data(data_bytes):
|
|
try:
|
|
decompressed = zlib.decompress(data_bytes)
|
|
msg = decompressed.decode('utf-8')
|
|
return msg
|
|
except Exception as e:
|
|
logger.warning(f"Error decompressing data: {e}")
|
|
return False
|
|
|
|
def bbs_receive_compressed(data_bytes, fromNode, RxNode):
|
|
try:
|
|
decompressed = zlib.decompress(data_bytes)
|
|
msg = decompressed.decode('utf-8')
|
|
|
|
bbs_sync_posts(msg, fromNode, RxNode)
|
|
|
|
return msg
|
|
except Exception as e:
|
|
logger.error(f"Error decompressing BBS message: {e}")
|
|
return None
|
|
|
|
def bbs_sync_posts(input, peerNode, RxNode):
|
|
messageID = 0
|
|
|
|
# check if the bbs link is enabled
|
|
if bbs_link_whitelist != ['']:
|
|
if str(peerNode) not in bbs_link_whitelist:
|
|
logger.warning(f"System: BBS Link is disabled for node {peerNode}.")
|
|
return "System: BBS Link is disabled for your node."
|
|
if bbs_link_enabled == False:
|
|
return "System: BBS Link is disabled."
|
|
|
|
# respond when another bot asks for the bbs posts to sync
|
|
if "bbslink" in input.lower():
|
|
if "$" in input and "#" in input:
|
|
#store the message
|
|
subject = input.split("$")[1].split("#")[0]
|
|
body = input.split("#")[1]
|
|
fromNodeHex = body.split("@")[1]
|
|
#validate the fromNodeHex is a valid hex number
|
|
try:
|
|
int(fromNodeHex, 16)
|
|
except ValueError:
|
|
logger.error(f"System: Invalid fromNodeHex in bbslink from node {peerNode}: {input}")
|
|
fromNodeHex = hex(peerNode)
|
|
#validate the subject and body are not empty
|
|
if subject.strip() == "" or body.strip() == "":
|
|
logger.error(f"System: Empty subject or body in bbslink from node {peerNode}: {input}")
|
|
return "System: Invalid bbslink format."
|
|
|
|
#store the message in the bbsdb
|
|
try:
|
|
bbs_post_message(subject, body, int(fromNodeHex, 16))
|
|
except:
|
|
logger.error(f"System: Error parsing bbslink from node {peerNode}: {input}")
|
|
fromNodeHex = hex(peerNode)
|
|
messageID = input.split(" ")[1]
|
|
return f"bbsack {messageID}"
|
|
elif "bbsack" in input.lower():
|
|
# increment the messageID
|
|
if len(input.split(" ")) > 1:
|
|
try:
|
|
messageID = int(input.split(" ")[1]) + 1
|
|
except:
|
|
return "link error"
|
|
else:
|
|
return "link error"
|
|
|
|
# send message with delay to keep chutil happy
|
|
if messageID < len(bbs_messages):
|
|
logger.debug(f"System: wait to bbslink with peer " + str(peerNode))
|
|
fromNodeHex = hex(bbs_messages[messageID][3])
|
|
time.sleep(5 + responseDelay)
|
|
# every 5 messages add extra delay
|
|
if messageID % 5 == 0:
|
|
time.sleep(10 + responseDelay)
|
|
logger.debug(f"System: Sending bbslink message {messageID} of {len(bbs_messages)} to peer " + str(peerNode))
|
|
msg = f"bbslink {messageID} ${bbs_messages[messageID][1]} #{bbs_messages[messageID][2]} @{fromNodeHex}"
|
|
if useSynchCompression:
|
|
compressed = compress_data(msg)
|
|
send_raw_bytes(peerNode, compressed)
|
|
logger.debug("System: Sent compressed bbslink message to peer " + str(peerNode))
|
|
else:
|
|
return msg
|
|
else:
|
|
logger.debug("System: bbslink sync complete with peer " + str(peerNode))
|
|
|
|
|
|
#initialize the bbsdb's
|
|
load_bbsdb()
|
|
load_bbsdm()
|