commit 983d4a37909a6e5c01d3804a373520c0d91d3be3 Author: liamcottle Date: Mon Apr 29 01:09:06 2024 +1200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..34ff768 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +storage/ diff --git a/index.html b/index.html new file mode 100644 index 0000000..1cf3441 --- /dev/null +++ b/index.html @@ -0,0 +1,266 @@ + + + + + + + Reticulum WebChat + + + + + + + + + + + +
+ + +
+
+
+
+ +
+
+
+
Reticulum WebChat
+
Developed by Liam Cottle
+
+ +
+
+ + +
+
+
+
+
+
+ + +
+ + + +
+ + +
+ + + +
+ +
+
+
+ You + Error + @<{{ message.source_hash }}> +
+
{{ message.text }}
+
+
+
+
+
+
+ + +
+
+ + +
+ +
+ +
+
+ +
+
+ +
+ + + \ No newline at end of file diff --git a/web.py b/web.py new file mode 100644 index 0000000..915c9e7 --- /dev/null +++ b/web.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python + +import json + +import RNS +import LXMF +import asyncio +import websockets +import base64 + +# init reticulum +reticulum = RNS.Reticulum(None) + +# create a new identity and log as base64 +identity = RNS.Identity() +print(base64.b64encode(identity.get_private_key())) + +# init lxmf router +message_router = LXMF.LXMRouter(identity=identity, storagepath="storage/lxmf") + +# register lxmf identity +local_lxmf_destination = message_router.register_delivery_identity(identity, display_name="ReticulumWebChat") + +# global reference to all connected websocket clients +websocket_clients = [] + + +async def main(): + + # set a callback for when an lxmf message is received + message_router.register_delivery_callback(lxmf_delivery) + + # start websocket server + async with websockets.serve(on_websocket_client_connected, "", 8000): + await asyncio.Future() # run forever + + +# handle websocket messages +async def on_websocket_client_connected(client): + + # add client to connected clients list + websocket_clients.append(client) + + # handle client messages until disconnected + while True: + try: + message = await client.recv() + data = json.loads(message) + await on_data(client, data) + except websockets.ConnectionClosedOK: + # client disconnected, we can stop looping + break + except Exception as e: + # ignore errors while handling message + print("failed to process client message") + print(e) + + # loop finished, client is no longer connected + websocket_clients.remove(client) + + +async def on_data(client, data): + + # get type from client data + _type = data["type"] + + # handle sending an lxmf message + if _type == "lxmf.delivery": + + # send lxmf message to destination + destination_hash = data["destination_hash"] + message = data["message"] + send_message(destination_hash, message) + + # # TODO: send response to client when marked as delivered? + # await client.send(json.dumps({ + # "type": "lxmf.sent", + # })) + + # handle sending an announce + elif _type == "announce": + + # send announce for lxmf + local_lxmf_destination.announce() + + # unhandled type + else: + print("unhandled client message type: " + _type) + + +def websocket_broadcast(data): + # broadcast provided data to all connected websocket clients + for websocket_client in websocket_clients: + asyncio.run(websocket_client.send(data)) + + +def lxmf_delivery(message): + try: + + # get message data + message_content = message.content.decode('utf-8') + source_hash_text = RNS.hexrep(message.source_hash, delimit=False) + + # send received lxmf message data to all websocket clients + websocket_broadcast(json.dumps({ + "type": "lxmf.delivery", + "source_hash": source_hash_text, + "message": message_content, + })) + + except Exception as e: + # do nothing on error + print(e) + + +def send_message(destination_hash, message_content): + + try: + + # convert destination hash to bytes + destination_hash = bytes.fromhex(destination_hash) + + # find destination identity from hash + destination_identity = RNS.Identity.recall(destination_hash) + if destination_identity is None: + + # we don't know the path/identity for this destination hash, we will request it + RNS.Transport.request_path(destination_hash) + + # we have to bail out of sending, since we don't have the path yet + return + + # create destination for recipients lxmf delivery address + lxmf_destination = RNS.Destination(destination_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "lxmf", "delivery") + + # create lxmf message + lxm = LXMF.LXMessage(lxmf_destination, local_lxmf_destination, message_content, desired_method=LXMF.LXMessage.DIRECT) + lxm.try_propagation_on_fail = True + + # send lxmf message to be routed to destination + message_router.handle_outbound(lxm) + + except: + # FIXME send error to websocket? + print("failed to send lxmf message") + + +if __name__ == "__main__": + asyncio.run(main())