From f37ce1482242f785104462bfaff128304264f798 Mon Sep 17 00:00:00 2001 From: chengtripp <124098378+chengtripp@users.noreply.github.com> Date: Sat, 4 Feb 2023 14:16:06 +0000 Subject: [PATCH 1/3] Create messageboard.py --- nomadnet/apps/messageboard/messageboard.py | 187 +++++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 nomadnet/apps/messageboard/messageboard.py diff --git a/nomadnet/apps/messageboard/messageboard.py b/nomadnet/apps/messageboard/messageboard.py new file mode 100644 index 0000000..8a71cd1 --- /dev/null +++ b/nomadnet/apps/messageboard/messageboard.py @@ -0,0 +1,187 @@ +# Simple message board that can be hosted on a NomadNet node, messages can be posted by 'conversing' with a unique peer, all messages are then forwarded to the message board. +# https://github.com/chengtripp/lxmf_messageboard + +import RNS +import LXMF +import os, time +from queue import Queue +import RNS.vendor.umsgpack as msgpack + +display_name = "NomadNet Message Board" +max_messages = 20 + +def setup_lxmf(): + if os.path.isfile(identitypath): + identity = RNS.Identity.from_file(identitypath) + RNS.log('Loaded identity from file', RNS.LOG_INFO) + else: + RNS.log('No Primary Identity file found, creating new...', RNS.LOG_INFO) + identity = RNS.Identity() + identity.to_file(identitypath) + + return identity + +def lxmf_delivery(message): + # Do something here with a received message + RNS.log("A message was received: "+str(message.content.decode('utf-8'))) + + message_content = message.content.decode('utf-8') + source_hash_text = RNS.hexrep(message.source_hash, delimit=False) + + #Create username (just first 5 char of your addr) + username = source_hash_text[0:5] + + RNS.log('Username: {}'.format(username), RNS.LOG_INFO) + + time_string = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(message.timestamp)) + new_message = '{} {}: {}\n'.format(time_string, username, message_content) + + # Push message to board + # First read message board (if it exists + if os.path.isfile(boardpath): + f = open(boardpath, "rb") + message_board = msgpack.unpack(f) + f.close() + else: + message_board = [] + + #Check we aren't doubling up (this can sometimes happen if there is an error initially and it then gets fixed) + if new_message not in message_board: + # Append our new message to the list + message_board.append(new_message) + + # Prune the message board if needed + while len(message_board) > max_messages: + RNS.log('Pruning Message Board') + message_board.pop(0) + + # Now open the board and write the updated list + f = open(boardpath, "wb") + msgpack.pack(message_board, f) + f.close() + + # Send reply + message_reply = '{}_{}_Your message has been added to the messageboard'.format(source_hash_text, time.time()) + q.put(message_reply) + +def announce_now(lxmf_destination): + lxmf_destination.announce() + +def send_message(destination_hash, message_content): + try: + # Make a binary destination hash from a hexadecimal string + destination_hash = bytes.fromhex(destination_hash) + + except Exception as e: + RNS.log("Invalid destination hash", RNS.LOG_ERROR) + return + + # Check that size is correct + if not len(destination_hash) == RNS.Reticulum.TRUNCATED_HASHLENGTH//8: + RNS.log("Invalid destination hash length", RNS.LOG_ERROR) + + else: + # Length of address was correct, let's try to recall the + # corresponding Identity + destination_identity = RNS.Identity.recall(destination_hash) + + if destination_identity == None: + # No path/identity known, we'll have to abort or request one + RNS.log("Could not recall an Identity for the requested address. You have probably never received an announce from it. Try requesting a path from the network first. In fact, let's do this now :)", RNS.LOG_ERROR) + RNS.Transport.request_path(destination_hash) + RNS.log("OK, a path was requested. If the network knows a path, you will receive an announce with the Identity data shortly.", RNS.LOG_INFO) + + else: + # We know the identity for the destination hash, let's + # reconstruct a destination object. + lxmf_destination = RNS.Destination(destination_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "lxmf", "delivery") + + # Create a new message object + lxm = LXMF.LXMessage(lxmf_destination, local_lxmf_destination, message_content, title="Reply", desired_method=LXMF.LXMessage.DIRECT) + + # You can optionally tell LXMF to try to send the message + # as a propagated message if a direct link fails + lxm.try_propagation_on_fail = True + + # Send it + message_router.handle_outbound(lxm) + +def announce_check(): + if os.path.isfile(announcepath): + f = open(announcepath, "r") + announce = int(f.readline()) + f.close() + else: + RNS.log('failed to open announcepath', RNS.LOG_DEBUG) + announce = 1 + + if announce > int(time.time()): + RNS.log('Recent announcement', RNS.LOG_DEBUG) + else: + f = open(announcepath, "w") + next_announce = int(time.time()) + 1800 + f.write(str(next_announce)) + f.close() + announce_now(local_lxmf_destination) + RNS.log('Announcement sent, expr set 1800 seconds', RNS.LOG_INFO) + +#Setup Paths and Config Files +userdir = os.path.expanduser("~") + +if os.path.isdir("/etc/nomadmb") and os.path.isfile("/etc/nomadmb/config"): + configdir = "/etc/nomadmb" +elif os.path.isdir(userdir+"/.config/nomadmb") and os.path.isfile(userdir+"/.config/nomadmb/config"): + configdir = userdir+"/.config/nomadmb" +else: + configdir = userdir+"/.nomadmb" + +storagepath = configdir+"/storage" +if not os.path.isdir(storagepath): + os.makedirs(storagepath) + +identitypath = configdir+"/storage/identity" +announcepath = configdir+"/storage/announce" +boardpath = configdir+"/storage/board" + +# Message Queue +q = Queue(maxsize = 5) + +# Start Reticulum and print out all the debug messages +reticulum = RNS.Reticulum(loglevel=RNS.LOG_VERBOSE) + +# Create a Identity. +current_identity = setup_lxmf() + +# Init the LXMF router +message_router = LXMF.LXMRouter(identity = current_identity, storagepath = configdir) + +# Register a delivery destination (for yourself) +# In this example we use the same Identity as we used +# to instantiate the LXMF router. It could be a different one, +# but it can also just be the same, depending on what you want. +local_lxmf_destination = message_router.register_delivery_identity(current_identity, display_name=display_name) + +# Set a callback for when a message is received +message_router.register_delivery_callback(lxmf_delivery) + +# Announce node properties + +RNS.log('LXMF Router ready to receive on: {}'.format(RNS.prettyhexrep(local_lxmf_destination.hash)), RNS.LOG_INFO) +announce_check() + +while True: + + # Work through internal message queue + for i in list(q.queue): + message_id = q.get() + split_message = message_id.split('_') + destination_hash = split_message[0] + message = split_message[2] + RNS.log('{} {}'.format(destination_hash, message), RNS.LOG_INFO) + send_message(destination_hash, message) + + # Check whether we need to make another announcement + announce_check() + + #Sleep + time.sleep(10) From 570b99d814b2a618de74e3310173063eb01320d3 Mon Sep 17 00:00:00 2001 From: chengtripp <124098378+chengtripp@users.noreply.github.com> Date: Sat, 4 Feb 2023 14:17:38 +0000 Subject: [PATCH 2/3] Create messageboard.mu --- nomadnet/apps/messageboard/messageboard.mu | 41 ++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 nomadnet/apps/messageboard/messageboard.mu diff --git a/nomadnet/apps/messageboard/messageboard.mu b/nomadnet/apps/messageboard/messageboard.mu new file mode 100644 index 0000000..24993d0 --- /dev/null +++ b/nomadnet/apps/messageboard/messageboard.mu @@ -0,0 +1,41 @@ +#!/bin/python3 +import time +import os +import RNS.vendor.umsgpack as msgpack + +message_board_peer = 'please_replace' +userdir = os.path.expanduser("~") + +if os.path.isdir("/etc/nomadmb") and os.path.isfile("/etc/nomadmb/config"): + configdir = "/etc/nomadmb" +elif os.path.isdir(userdir+"/.config/nomadmb") and os.path.isfile(userdir+"/.config/nomadmb/config"): + configdir = userdir+"/.config/nomadmb" +else: + configdir = userdir+"/.nomadmb" + +storagepath = configdir+"/storage" +if not os.path.isdir(storagepath): + os.makedirs(storagepath) + +boardpath = configdir+"/storage/board" + +print('`!`F222`Bddd`cNomadNet Message Board') + +print('-') +print('`a`b`f') +print("") +print("To add a message to the board just converse with the NomadNet Message Board at `[lxmf@{}]".format(message_board_peer)) +time_string = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())) +print("Last Updated: {}".format(time_string)) +print("") +print('>Messages') +print(" Date Time Username Message") +f = open(boardpath, "rb") +board_contents = msgpack.unpack(f) +board_contents.reverse() + +for content in board_contents: + print("`a{}".format(content.rstrip())) + print("") + +f.close() From 02a15f1503c1e9191c5d61f38674b09dcae2c4ec Mon Sep 17 00:00:00 2001 From: chengtripp <124098378+chengtripp@users.noreply.github.com> Date: Sat, 4 Feb 2023 14:20:42 +0000 Subject: [PATCH 3/3] Create README.md --- nomadnet/apps/messageboard/README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 nomadnet/apps/messageboard/README.md diff --git a/nomadnet/apps/messageboard/README.md b/nomadnet/apps/messageboard/README.md new file mode 100644 index 0000000..53c637b --- /dev/null +++ b/nomadnet/apps/messageboard/README.md @@ -0,0 +1,18 @@ +# lxmf_messageboard +Simple message board that can be hosted on a NomadNet node, messages can be posted by 'conversing' with a unique peer, all messages are then forwarded to the message board. + +## How Do I Use It? +A user can submit messages to the message board by initiating a chat with the message board peer, they are assigned a username (based on the first 5 characters of their address) and their messages are added directly to the message board. The message board can be viewed on a page hosted by a NomadNet node. + +An example message board can be found on the reticulum testnet hosted on the SolarExpress Node `` and the message board peer `` + +## How Does It Work? +The message board page itself is hosted on a NomadNet node, you can place the message_board.mu into the pages directory. You can then run the message_board.py script which provides the peer that the users can send messages to. The two parts are joined together using umsgpack and a flat file system similar to NomadNet and Reticulum and runs in the background. + +## How Do I Set It Up? +* Turn on node hosting in NomadNet +* Put the `message_board.mu` file into `pages` directory in the config file for `NomadNet`. Edit the file to customise from the default page. +* Run the `message_board.py` script (`python3 message_board.py` either in a `screen` or as a system service), this script uses `NomadNet` and `RNS` libraries and has no additional libraries that need to be installed. Take a note of the message boards address, it is printed on starting the board, you can then place this address in `message_board.mu` file to make it easier for users to interact the board. + +## Credits +* The send and receive functions in message_board.py are based on examples posted on the Reticulum Matrix channel by [Mark](https://github.com/markqvist)