import os from datetime import datetime, timezone import json import time from libs.fspn.utils.wrapper_util import threaded from libs.app.common.logging import get_logger from libs.fspn.utils.sha256_util import hash_file, hash_bytes, hash_string from libs.noSys.noSysModule import NoSysModule from libs.noSys.events import Events as nosys_events, DynamicEvents as nosys_dynamic_events from libs.noSys.peers import Peer from libs.p2post.p2post import P2post from .networks import Networks from .dataManager import DataManager from .p2privateApiBlueprint import Blueprint logger = get_logger() class P2private(NoSysModule): def __init__(self, noSys): super().__init__(noSys) self.nosys_core.subscribe_event(nosys_events.USER_ADDED, self.on_user_added) self.data = DataManager() self.p2post:P2post = None self.networks_module:Networks = None self.requested_signatures = {} self.friends_state = {} def setup(self): self.p2post = self.nosys_core.modules.get("p2post", "p2post") self.networks_module = self.nosys_core.modules.get("p2private", "networks") self.nosys_core.modules.api.register_blueprint(Blueprint(self).blueprint) def on_nosys_ready(self, event): pass def add_friend(self, pubkey, relays): self.data.add_friend(pubkey, relays) self.set_friend_state(pubkey) def on_user_added(self, event): user_id:str = event.user_id self.manage_friends(user_id) @threaded def manage_friends(self, my_user): for friend in self.data.list_friends(): self.set_friend_state(friend["pubkey"]) while True: for friend in self.data.list_friends(): self.manage_friend(friend) time.sleep(10) def set_friend_state(self, friend_pubkey): # TODO Add friends inside users object data self.friends_state[friend_pubkey] = { "id": friend_pubkey, "status": "dis", # TODO Enum } def manage_friend(self, user): friend_id = user["pubkey"] state = self.friends_state[friend_id] if state["status"] == "dis": print("FRIEND IS DISCONNECTED") for relay in user["relays"]: if self.networks_module.network_states.get(relay): print(f"Connected to relay {relay}") # TODO Check if friend is on in the network and send message to connect to a rendezvous elif state["status"] == "con": print("FRIEND IS CONNECTED") def on_module_message(self, event): handler_action = getattr(self, 'on_'+event.data.get("action")) handler_action(event) def create_message(self, from_user, to_user, content, medias): medias_data = [] for media in medias: if media["type"] == "local": file_hash = os.path.splitext(os.path.basename(media["file_path"]))[0] medias_data.append({"type":"local", "hash":file_hash}) self.p2post.data.add_media(file_hash, media["file_path"]) else: medias_data.append({"type":media["type"], "url":media["url"]}) current_utc_datetime = datetime.now(timezone.utc) utc_timestamp = current_utc_datetime.timestamp() message = { "timestamp": utc_timestamp, "from": from_user, "to": to_user, "content": content, #TODO Hash it "medias": medias_data } message_serialized = json.dumps(message, sort_keys=True, separators=(",", ":")) message["hash"] = hash_bytes(message_serialized.encode('utf-8')) request_id = self.nosys_core.modules.pmc.sign(message["hash"], from_user, self.signature_callback, f"New Message {message}") self.requested_signatures[request_id] = message logger.debug(f"Message waiting signature {request_id}: {message}") return message["hash"] def signature_callback(self, request_id, signature): message = self.requested_signatures.get(request_id) if signature and message: message["signature"] = signature self.send_message(message) def send_message(self, message): self.data.add_message(message) direct_connections = self.nosys_core.peers.get_by_peer_user_id(message["to"]) if direct_connections: logger.debug(f"Friend {message['to']} direct connected") for peer in direct_connections: # TODO Maybe check 'from user', peers can set to just receive message from the user in connection and networks self.send_private_message(message, peer.id) else: logger.debug(f"Friend {message['to']} not direct connected. Posting message to user relays.") to_user = message["to"] relay_networks = self.get_friend_relay_networks(to_user) # TODO get from store if not relay_networks: logger.error(f"Not found a network relay to friend {to_user}") # TODO Update message status else: self.networks_module.post_network_message(message, relay_networks) def send_private_message(self, message, peer_id): payload = {'action':'private_message','message':message} self.nosys_core.dispatcher.send_message(payload, peer_id, self.id) def on_private_message(self, event): data = event.data message = data["message"] if not self.data.get_message(message["hash"]): self.data.add_message(message) logger.debug(f"New message received {message}") else: logger.debug("Message already exists") # Read receipts ???? Talk to friends about it def get_friend_relay_networks(self, user_id): user = self.data.get_friend(user_id) if user: return user["relays"] return [] def on_module_connection(self, event): peer:Peer = event.peer peer_user = peer.connection.security.peer_user state = self.friends_state.get(peer_user) if state: state["status"] = "con"