/ nomadnet / examples / messageboard / messageboard.py
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)