messageboard.py
1 # 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. 2 # https://github.com/chengtripp/lxmf_messageboard 3 4 import RNS 5 import LXMF 6 import os, time 7 from queue import Queue 8 import RNS.vendor.umsgpack as msgpack 9 10 display_name = "NomadNet Message Board" 11 max_messages = 20 12 13 def setup_lxmf(): 14 if os.path.isfile(identitypath): 15 identity = RNS.Identity.from_file(identitypath) 16 RNS.log('Loaded identity from file', RNS.LOG_INFO) 17 else: 18 RNS.log('No Primary Identity file found, creating new...', RNS.LOG_INFO) 19 identity = RNS.Identity() 20 identity.to_file(identitypath) 21 22 return identity 23 24 def lxmf_delivery(message): 25 # Do something here with a received message 26 RNS.log("A message was received: "+str(message.content.decode('utf-8'))) 27 28 message_content = message.content.decode('utf-8') 29 source_hash_text = RNS.hexrep(message.source_hash, delimit=False) 30 31 #Create username (just first 5 char of your addr) 32 username = source_hash_text[0:5] 33 34 RNS.log('Username: {}'.format(username), RNS.LOG_INFO) 35 36 time_string = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(message.timestamp)) 37 new_message = '{} {}: {}\n'.format(time_string, username, message_content) 38 39 # Push message to board 40 # First read message board (if it exists 41 if os.path.isfile(boardpath): 42 f = open(boardpath, "rb") 43 message_board = msgpack.unpack(f) 44 f.close() 45 else: 46 message_board = [] 47 48 #Check we aren't doubling up (this can sometimes happen if there is an error initially and it then gets fixed) 49 if new_message not in message_board: 50 # Append our new message to the list 51 message_board.append(new_message) 52 53 # Prune the message board if needed 54 while len(message_board) > max_messages: 55 RNS.log('Pruning Message Board') 56 message_board.pop(0) 57 58 # Now open the board and write the updated list 59 f = open(boardpath, "wb") 60 msgpack.pack(message_board, f) 61 f.close() 62 63 # Send reply 64 message_reply = '{}_{}_Your message has been added to the messageboard'.format(source_hash_text, time.time()) 65 q.put(message_reply) 66 67 def announce_now(lxmf_destination): 68 lxmf_destination.announce() 69 70 def send_message(destination_hash, message_content): 71 try: 72 # Make a binary destination hash from a hexadecimal string 73 destination_hash = bytes.fromhex(destination_hash) 74 75 except Exception as e: 76 RNS.log("Invalid destination hash", RNS.LOG_ERROR) 77 return 78 79 # Check that size is correct 80 if not len(destination_hash) == RNS.Reticulum.TRUNCATED_HASHLENGTH//8: 81 RNS.log("Invalid destination hash length", RNS.LOG_ERROR) 82 83 else: 84 # Length of address was correct, let's try to recall the 85 # corresponding Identity 86 destination_identity = RNS.Identity.recall(destination_hash) 87 88 if destination_identity == None: 89 # No path/identity known, we'll have to abort or request one 90 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) 91 RNS.Transport.request_path(destination_hash) 92 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) 93 94 else: 95 # We know the identity for the destination hash, let's 96 # reconstruct a destination object. 97 lxmf_destination = RNS.Destination(destination_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "lxmf", "delivery") 98 99 # Create a new message object 100 lxm = LXMF.LXMessage(lxmf_destination, local_lxmf_destination, message_content, title="Reply", desired_method=LXMF.LXMessage.DIRECT) 101 102 # You can optionally tell LXMF to try to send the message 103 # as a propagated message if a direct link fails 104 lxm.try_propagation_on_fail = True 105 106 # Send it 107 message_router.handle_outbound(lxm) 108 109 def announce_check(): 110 if os.path.isfile(announcepath): 111 f = open(announcepath, "r") 112 announce = int(f.readline()) 113 f.close() 114 else: 115 RNS.log('failed to open announcepath', RNS.LOG_DEBUG) 116 announce = 1 117 118 if announce > int(time.time()): 119 RNS.log('Recent announcement', RNS.LOG_DEBUG) 120 else: 121 f = open(announcepath, "w") 122 next_announce = int(time.time()) + 1800 123 f.write(str(next_announce)) 124 f.close() 125 announce_now(local_lxmf_destination) 126 RNS.log('Announcement sent, expr set 1800 seconds', RNS.LOG_INFO) 127 128 #Setup Paths and Config Files 129 userdir = os.path.expanduser("~") 130 131 if os.path.isdir("/etc/nomadmb") and os.path.isfile("/etc/nomadmb/config"): 132 configdir = "/etc/nomadmb" 133 elif os.path.isdir(userdir+"/.config/nomadmb") and os.path.isfile(userdir+"/.config/nomadmb/config"): 134 configdir = userdir+"/.config/nomadmb" 135 else: 136 configdir = userdir+"/.nomadmb" 137 138 storagepath = configdir+"/storage" 139 if not os.path.isdir(storagepath): 140 os.makedirs(storagepath) 141 142 identitypath = configdir+"/storage/identity" 143 announcepath = configdir+"/storage/announce" 144 boardpath = configdir+"/storage/board" 145 146 # Message Queue 147 q = Queue(maxsize = 5) 148 149 # Start Reticulum and print out all the debug messages 150 reticulum = RNS.Reticulum(loglevel=RNS.LOG_VERBOSE) 151 152 # Create a Identity. 153 current_identity = setup_lxmf() 154 155 # Init the LXMF router 156 message_router = LXMF.LXMRouter(identity = current_identity, storagepath = configdir) 157 158 # Register a delivery destination (for yourself) 159 # In this example we use the same Identity as we used 160 # to instantiate the LXMF router. It could be a different one, 161 # but it can also just be the same, depending on what you want. 162 local_lxmf_destination = message_router.register_delivery_identity(current_identity, display_name=display_name) 163 164 # Set a callback for when a message is received 165 message_router.register_delivery_callback(lxmf_delivery) 166 167 # Announce node properties 168 169 RNS.log('LXMF Router ready to receive on: {}'.format(RNS.prettyhexrep(local_lxmf_destination.hash)), RNS.LOG_INFO) 170 announce_check() 171 172 while True: 173 174 # Work through internal message queue 175 for i in list(q.queue): 176 message_id = q.get() 177 split_message = message_id.split('_') 178 destination_hash = split_message[0] 179 message = split_message[2] 180 RNS.log('{} {}'.format(destination_hash, message), RNS.LOG_INFO) 181 send_message(destination_hash, message) 182 183 # Check whether we need to make another announcement 184 announce_check() 185 186 #Sleep 187 time.sleep(10)