import os
import asyncio
from pythemis.skeygen import KEY_PAIR_TYPE, GenerateKeyPair
from pythemis.smessage import SMessage, ssign, sverify
from pythemis.exception import ThemisError
from pythemis.scell import SCellSeal

from base64 import b64decode,b64encode
# from kademlia.network import Server
import os,time,sys,logging
from pathlib import Path
import requests

sys.path.append('../p2p')
BSEP=b'||||||||||'
BSEP2=b'@@@@@@@@@@'
BSEP3=b'##########'
NODE_SLEEP_FOR=1

P2P_PREFIX=b'/persona/'
P2P_PREFIX_POST=b'/msg/'
P2P_PREFIX_INBOX=b'/inbox/'
P2P_PREFIX_OUTBOX=b'/outbox/'

WORLD_PUB_KEY = b'VUVDMgAAAC1z53KeApQY4RICK5k0nXnnS+K17veIFMPlFKo7mqnRhTZDhAmG'
WORLD_PRIV_KEY = b'UkVDMgAAAC26HXeGACxZUoKYKlZ7sDmVoLwffNj3CrdqoPrE94+2ysfhufmP'

KOMRADE_PUB_KEY = b'VUVDMgAAAC09uo+wAgu/V9xyvMkMDbOQEk1ssOrFADaiyTzfwVjE6o8FHoil'
KOMRADE_PRIV_KEY = b'UkVDMgAAAC33fFiaAIpmQewjkYndzMcMkj1mLy/lE4RXJQzIlUN94tyC5g29'

DEBUG = True
UPLOAD_DIR = 'uploads/'
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}

PORT_LISTEN = 5639
NODE_SLEEP_FOR=1
NODES_PRIME = [("128.232.229.63",8467)] 
KEYSERVER_ADDR = 'komrade.app' #'128.232.229.63'
KEYSERVER_PORT = 5566


KEY_PATH = os.path.join(os.path.expanduser('~'),'.komrade')
KEY_PATH_PUB = os.path.join(KEY_PATH,'.locs')
KEY_PATH_PRIV = os.path.join(KEY_PATH,'.keys')

for x in [KEY_PATH,KEY_PATH_PUB,KEY_PATH_PRIV]:
    if not os.path.exists(x): os.makedirs(x)

WORLD_PRIV_KEY_FN = os.path.join(KEY_PATH_PRIV,'.world.key')
WORLD_PUB_KEY_FN = os.path.join(KEY_PATH_PUB,'.world.loc')
KOMRADE_PRIV_KEY_FN = os.path.join(KEY_PATH_PRIV,'.komrade.key')
KOMRADE_PUB_KEY_FN = os.path.join(KEY_PATH_PUB,'.komrade.loc')


def check_world_keys():
    if not os.path.exists(WORLD_PRIV_KEY_FN):
        with open(WORLD_PRIV_KEY_FN,'wb') as of:
            of.write(WORLD_PRIV_KEY)
    
    if not os.path.exists(WORLD_PUB_KEY_FN):
        with open(WORLD_PUB_KEY_FN,'wb') as of:
            of.write(WORLD_PUB_KEY)

    if not os.path.exists(KOMRADE_PRIV_KEY_FN):
        with open(KOMRADE_PRIV_KEY_FN,'wb') as of:
            of.write(KOMRADE_PRIV_KEY)
    
    if not os.path.exists(KOMRADE_PUB_KEY_FN):
        with open(KOMRADE_PUB_KEY_FN,'wb') as of:
            of.write(KOMRADE_PUB_KEY)
    
# check_world_keys()


## CONNECTING




## utils

def get_random_id():
    import uuid
    return uuid.uuid4().hex

def get_random_binary_id():
    import base64
    idstr = get_random_id()
    return base64.b64encode(idstr.encode())


def logger():
    import logging
    handler = logging.StreamHandler()
    formatter = logging.Formatter('[%(asctime)s]\n%(message)s\n')
    handler.setFormatter(formatter)
    logger = logging.getLogger(__file__)
    logger.addHandler(handler)
    logger.setLevel(logging.DEBUG)
    return logger

LOG = None

def log(*x):
    global LOG
    if not LOG: LOG=logger().debug

    tolog=' '.join(str(_) for _ in x)
    LOG(tolog)



class NetworkStillConnectingError(OSError): pass







class Persona(object):

    def __init__(self,name,api=None,node=None,create_if_missing=True):
        self.name=name
        self.log=log
        self.privkey=None
        self.pubkey=None
        self.api=api

        self.can_receive = False
        self.can_send = False
        # self.can_login = False
        self.external_keys_loaded = False


        self.pubkey_enc = None
        self.pubkey_decr = None

        # self._node=node
        self.create_if_missing=create_if_missing
        # self.log(f'>> Persona.__init__(name={name},create_if_missing={create_if_missing})')

        # load at least any local keys (non-async)
        self.find_keys_local()
        # self.login_or_register()

    

    def __repr__(self):
        #pkeystr = '+loc' if self.has_public_key() else ''
        #privkeystr = ' +key' if self.has_private_key() else ''
        # ptypestr='acccount' if self.has_private_key() else 'contact'
        ptypestr='loc' if not self.has_private_key() else 'keyloc'

        return f'{self.name} ({ptypestr})'

    def get_keyserver_pubkey(self):
        Q=f'http://{KEYSERVER_ADDR}:{KEYSERVER_PORT}/pub'
        
        r = self.api.request(Q)
        return r.content
        # self.log('r =',r,b64encode(r.content))
        # pubkey_b64 = b64encode(r.content)
        # return pubkey_b64

    def get_externally_signed_pubkey(self):
        Q=f'http://{KEYSERVER_ADDR}:{KEYSERVER_PORT}/get/{self.name}'
        self.log('Q:',Q)
        r = self.api.request(Q)
        package_b64 = r.content
        package = b64decode(package_b64)
        self.log('package <--',package)
        if not package: return (b'',b'',b'')
        return package.split(BSEP)
        # pubkey_b64, signed_pubkey_b64, server_signed_pubkey_b64 = package.split(BSEP)
        
        # signed_pubkey = b64encode(r.content)
        # return (b64encode(pubkey), b64encode(signed_pubkey))

    def set_externally_signed_pubkey(self):
        import requests
        Q=f'http://{KEYSERVER_ADDR}:{KEYSERVER_PORT}/add/{self.name}' #/{name}/{key}
        
        package = self.pubkey_b64 + BSEP + self.signed_pubkey_b64
        self.log('set_externally_signed_pubkey package -->',package)

        r = requests.post(Q, data=package) #{'name':self.name,'key':self.pubkey_b64})
        return r
    
    def has_private_key(self):
        return self.privkey is not None
    
    def has_public_key(self):
        return self.pubkey is not None
    

    @property
    async def node(self):
        node = await self.api.node
        return node


    def load_keyserver_pubkey(self):
        self.keyserver_pubkey_b64 = self.get_keyserver_pubkey()
        self.keyserver_pubkey = b64decode(self.keyserver_pubkey_b64)
        self.log('keyserver_pubkey =',self.keyserver_pubkey)
        return self.keyserver_pubkey

    def load_external_keys(self):
        if self.external_keys_loaded: return

        self.pubkey_ext_b64, self.signed_pubkey_ext_b64, self.server_signed_pubkey_ext_b64 = self.get_externally_signed_pubkey()
        self.log('pubkey_ext_b64 =',self.pubkey_ext_b64)
        self.log('signed_pubkey_ext_b64 =',self.signed_pubkey_ext_b64)
        self.log('server_signed_pubkey_ext_b64 =',self.server_signed_pubkey_ext_b64)
        self.external_keys_loaded = True

    def keyserver_name_exists(self):
        user_missing = (self.pubkey_ext_b64 is b'' or self.signed_pubkey_ext_b64 is b'' or self.server_signed_pubkey_ext_b64 is b'')
        return not user_missing


    def meet(self,save_loc=True):
        pubkey_ext = b64decode(self.pubkey_keyserver_verified)
        self.log('pubkey_ext',pubkey_ext)
        self.pubkey=pubkey_ext
        self.log('setting self.pubkey to external value:',self.pubkey)
        self.log('self.pubkey_64',self.pubkey_b64)
        self.log('keyserver_verified',self.pubkey_keyserver_verified)
        
        with open(self.key_path_pub,'wb') as of:
            of.write(self.pubkey_keyserver_verified)
        
        return {'success':'Met person as acquaintance'}

    def register(self):
        # register
        if self.create_if_missing:
            self.gen_keys()
            res = self.set_externally_signed_pubkey()
            self.log('set_externally_signed_pubkey res =',res)
            if res is None:
                return {'error':'Could not set externally signed pubkey'}
            else:
                return {'success':'Created new pubkey'}
        else:
            return {'error':'No public key externally, but create_if_missing==False'}


    # login, meet, or register
    def boot(self):
        # keyserver active?
        keyserver_pubkey = self.keyserver_pubkey = self.load_keyserver_pubkey()
        if keyserver_pubkey is None:
            return {'error':'Cannot conntact keyserver'}
        
        # load external keys
        self.load_external_keys()

        # user exists...
        if self.keyserver_name_exists():

            # if I claim to be this person
            if self.privkey and self.pubkey_decr:
                attempt = self.login()
                self.log('login attempt ->',attempt)
                return attempt

            # otherwise just meet them
            attempt = self.meet()
            self.log('meet attempt ->',attempt)
            return attempt

        # user does not exist
        attempt = self.register()
        self.log('register attempt -->',attempt)
        return attempt
    
    @property
    def key_path_pub(self):
        return os.path.join(KEY_PATH_PUB,'.'+self.name+'.loc')

    @property
    def key_path_pub_enc(self):
        return os.path.join(KEY_PATH_PUB,'.'+self.name+'.loc.box')


    @property
    def key_path_priv(self):
        return os.path.join(KEY_PATH_PRIV,'.'+self.name+'.key')
    
    @property
    def name_b64(self):
        return b64encode(self.name.encode())

        
    @property
    def privkey_b64(self):
        return b64encode(self.privkey)

    @property
    def pubkey_b64(self):
        return b64encode(self.pubkey) if self.pubkey else b''
    
    @property
    def signed_pubkey_b64(self):
        return self.sign(self.pubkey_b64)
        # return self.encrypt(self.pubkey_b64, self.pubkey_b64)
    
    ## genearating keys
    
    # def gen_keys1(self):
    #     self.log('gen_keys()')
    #     keypair = GenerateKeyPair(KEY_PAIR_TYPE.EC)
    #     self.privkey = keypair.export_private_key()
    #     self.pubkey = keypair.export_public_key()

    #     self.log(f'priv_key saved to {self.key_path_priv}')
    #     with open(self.key_path_priv, "wb") as private_key_file:
    #         private_key_file.write(self.privkey_b64)

    #     with open(self.key_path_pub, "wb") as public_key_file:
    #         # save SIGNED public key
    #         public_key_file.write(self.pubkey_b64)
        
    #     with open(self.key_path_pub_enc,'wb') as signed_public_key_file:
    #         # self.log('encrypted_pubkey_b64 -->',self.encrypted_pubkey_b64)
    #         pubkey_b64 = b64encode(self.pubkey)
    #         self.log('pubkey',self.pubkey)
    #         self.log('pubkey_b64',pubkey_b64)
            
    #         encrypted_pubkey_b64 = self.encrypt(pubkey_b64, pubkey_b64, KOMRADE_PRIV_KEY)
    #         self.log('encrypted_pubkey_b64 -->',encrypted_pubkey_b64)
            
    #         signed_public_key_file.write(encrypted_pubkey_b64)

    def gen_keys(self,passphrase=DEBUG_DEFAULT_PASSPHRASE):
        """
        Generate private/public key pair
        Secure that with passphrase
        """

        ## Generate key pair
        self.log('gen_keys()')
        keypair = GenerateKeyPair(KEY_PAIR_TYPE.EC)
        self.privkey = keypair.export_private_key()
        self.pubkey = keypair.export_public_key()

        ## Secure with passphrase
        if passphrase is None: passphrase=input('Please protect account with passphrase:\n')
        cell = SCellSeal(passphrase=passphrase)
        keypair_b = self.privkey + BSEP + self.pubkey
        
        keypair_b_encr = cell.encrypt(keypair_b)


        self.log(f'priv_key saved to {self.key_path_priv}')
        with open(self.key_path_priv, "wb") as private_key_file:
            private_key_file.write(self.privkey_b64)

        with open(self.key_path_pub, "wb") as public_key_file:
            # save SIGNED public key
            public_key_file.write(self.pubkey_b64)
        
        with open(self.key_path_pub_enc,'wb') as signed_public_key_file:
            # self.log('encrypted_pubkey_b64 -->',self.encrypted_pubkey_b64)
            pubkey_b64 = b64encode(self.pubkey)
            self.log('pubkey',self.pubkey)
            self.log('pubkey_b64',pubkey_b64)
            
            encrypted_pubkey_b64 = self.encrypt(pubkey_b64, pubkey_b64, KOMRADE_PRIV_KEY)
            self.log('encrypted_pubkey_b64 -->',encrypted_pubkey_b64)
            
            signed_public_key_file.write(encrypted_pubkey_b64)
        

    ## loading keys from disk

    def find_keys_local(self):
        self.log(f'find_keys_local(path_pub={self.key_path_pub}, path_priv={self.key_path_priv})')

        if os.path.exists(self.key_path_priv):
            with open(self.key_path_priv) as priv_f:
                self.privkey=b64decode(priv_f.read())

        # Load from encrypted?
        if os.path.exists(self.key_path_pub_enc) and self.privkey:
            with open(self.key_path_pub_enc) as pub_f:
                self.pubkey_enc=pub_f.read()
                self.pubkey_decr=self.decrypt(self.pubkey_enc, KOMRADE_PUB_KEY)
                # self.pubkey=self.pubkey_decr
                self.log('loaded self.pubkey from enc',self.pubkey)
                self.can_receive = True
                self.can_send = True
        
        # load from nonencrypted pubkey?
        if os.path.exists(self.key_path_pub):
            with open(self.key_path_pub) as pub_f:
                self.pubkey=b64decode(pub_f.read())
                self.log('loaded self.pubkey from UNenc',self.pubkey)
                self.can_receive = True

    @property
    def pubkey_keyserver_verified(self):
        # test if remote data agrees
        if not hasattr(self,'_keyserver_verified'):
            self._keyserver_verified = self.verify(self.server_signed_pubkey_ext_b64, self.keyserver_pubkey_b64)
        return self._keyserver_verified
            
    def login(self):
        # test if local data present
        if not self.pubkey or not self.pubkey_decr:
            return {'error':'Public keys not present'}

        # test if local data agrees
        self.log('self.pubkey',self.pubkey_b64)
        self.log('self.pubkey_decr',self.pubkey_decr)
        if self.pubkey_b64 != self.pubkey_decr:
            return {'error':'Public keys do not match'}

        # test if remote data agrees
        keyserver_verified = self.pubkey_keyserver_verified
        if keyserver_verified is None: 
            return {'error':'Keyserver verification failed'}

        # test if pubkey match
        enc_match = self.pubkey_decr == keyserver_verified
        if enc_match:
            return {'success':'Keys matched'}
        return {'error':'Keys did not match'}

        
    



    ## E/D/S/V

    def encrypt(self,msg_b64,for_pubkey_b64, privkey_b64=None):
        self.log('encrypt()',msg_b64,for_pubkey_b64,privkey_b64)
        
        privkey = b64decode(privkey_b64) if privkey_b64 else self.privkey
        # handle verification failure
        for_pubkey = b64decode(for_pubkey_b64)
        encrypted_msg = SMessage(privkey, for_pubkey).wrap(msg_b64)
        return b64encode(encrypted_msg)

    def decrypt(self,encrypted_msg_b64,from_pubkey_b64, privkey_b64=None):
        privkey = b64decode(privkey_b64) if privkey_b64 else self.privkey

        # handle verification failure
        from_pubkey = b64decode(from_pubkey_b64)
        encrypted_msg = b64decode(encrypted_msg_b64)
        decrypted_msg = SMessage(privkey, from_pubkey).unwrap(encrypted_msg)
        return decrypted_msg

    def sign(self,msg_b64, privkey=None):
        if not privkey: privkey=self.privkey
        signed_msg = b64encode(ssign(privkey, msg_b64))
        return signed_msg

    def verify(self,signed_msg_b64,pubkey_b64=None):
        if pubkey_b64 is None: pubkey_b64=self.pubkey_b64
        
        self.log('verify() signed_msg_b64 =',signed_msg_b64)
        self.log('verify() pubkey_b64 =',pubkey_b64)
        signed_msg = b64decode(signed_msg_b64)
        public_key = b64decode(pubkey_b64)

        self.log('verify() signed_msg =',signed_msg)
        self.log('verify() public_key =',public_key)

        try:
            verified_msg = sverify(public_key, signed_msg)
            return verified_msg
        except ThemisError as e:
            print('!!',e)
            return None
    



    @property
    def uri_inbox(self):
        return P2P_PREFIX_INBOX+self.name.encode()

    @property
    def uri_outbox(self):
        return P2P_PREFIX_OUTBOX+self.name.encode()

    @property
    def app_pubkey_b64(self):
        return KOMRADE_PUB_KEY




    ## POSTING/SENDING MSGS

    async def post(self,encrypted_payload_b64,to_person):
        # double wrap
        double_encrypted_payload = self.encrypt(encrypted_payload_b64, to_person.pubkey_b64, KOMRADE_PRIV_KEY) 
        self.log('double_encrypted_payload =',double_encrypted_payload)

        post_id = get_random_binary_id() #get_random_id().encode()
        node = await self.node

        uri_post = P2P_PREFIX_POST + post_id
        res = await node.set(uri_post, double_encrypted_payload)
        self.log('result of post() =',res)

        return uri_post

    async def send(self,msg_b,to,from_person=None):
        """
        1) [Encrypted payload:]
            1) Timestamp
            2) Public key of sender
            3) Public key of recipient
            4) AES-encrypted Value
        2) [Decryption tools]
            1) AES-decryption key
            2) AES decryption IV value
        5) Signature of value by author
        """
        
        if type(msg_b)==str: msg_b=msg_b.encode()
        msg_b64=b64encode(msg_b)

        # encrypt and sign
        to_person = to
        if from_person is None: from_person = self
        encrypted_payload = from_person.encrypt(msg_b64, to_person.pubkey_b64)
        signed_encrypted_payload = from_person.sign(encrypted_payload)

        # package
        time_b64 = b64encode(str(time.time()).encode())
        WDV_b64 = b64encode(BSEP.join([
            signed_encrypted_payload,
            from_person.pubkey_b64,
            from_person.name_b64,
            time_b64]))
        self.log('WDV_b64 =',WDV_b64)

        # post
        post_id = await self.post(WDV_b64, to_person)
        self.log('post_id <-',post_id)

        # add to inbox
        res = await to_person.add_to_inbox(post_id)
        self.log('add_to_inbox <-',res)

        # add to outbox?
        # pass










    async def load_inbox(self,decrypt_msg_uri=False,last=None):
        node = await self.node
        encrypted_inbox_idstr_b64 = await node.get(self.uri_inbox)
        self.log('encrypted_inbox_idstr_b64 =',encrypted_inbox_idstr_b64)
        if encrypted_inbox_idstr_b64 is None: return []
        
        # inbox_idstr = self.decrypt(encrypted_inbox_idstr_b64, self.app_pubkey_b64)
        # self.log('decrypted inbox_idstr =',inbox_idstr)
        # decrypt!

        encrypted_inbox_idstr = b64decode(encrypted_inbox_idstr_b64)
        self.log('encrypted_inbox_idstr =',encrypted_inbox_idstr)

        inbox_ids = encrypted_inbox_idstr.split(BSEP) if encrypted_inbox_idstr is not None else []
        self.log('inbox_ids =',inbox_ids)
        
        if decrypt_msg_uri:
            inbox_ids = [self.decrypt(enc_msg_id_b64,KOMRADE_PUB_KEY) for enc_msg_id_b64 in inbox_ids]
            self.log('inbox_ids decrypted =',inbox_ids)

        return inbox_ids[:last]

    async def add_to_inbox(self,msg_uri,inbox_sofar=None):
        # encrypt msg id so only inbox owner can resolve the pointer
        self.log('unencrypted msg uri:',msg_uri)
        encrypted_msg_uri = self.encrypt(msg_uri, self.pubkey_b64, KOMRADE_PRIV_KEY)
        self.log('encrypted msg uri:',encrypted_msg_uri)

        # get current inbox
        if inbox_sofar is None: inbox_sofar=await self.load_inbox()
        self.log('inbox_sofar:',inbox_sofar)

        # add new value
        new_inbox = inbox_sofar + [encrypted_msg_uri]
        new_inbox_b = BSEP.join(new_inbox)
        self.log('new_inbox_b:',new_inbox_b)

        new_inbox_b64 = b64encode(new_inbox_b)

        self.log('new_inbox_b64:',new_inbox_b64)

        # set on net
        node = await self.node
        await node.set(self.uri_inbox,new_inbox_b64)

        new_length = len(new_inbox)
        return {'success':'Inbox length increased to %s' % new_length}
        #return {'error':'Could not append data'}


    async def add_to_outbox(self):
        """
        Do not store on server!
        """
        pass

    async def read_inbox(self,uri_inbox=None):
        if uri_inbox is None: uri_inbox = P2P_PREFIX_INBOX+self.name.encode()
        node = await self.node
        inbox_ids = await node.get(uri_inbox)
        if inbox_ids is not None:
            inbox_ids = inbox_ids.split(BSEP)
            self.log('found inbox IDs:',inbox_ids)

            msgs_toread = [self.read_msg(msg_id) for msg_id in inbox_ids]
            msgs =  await asyncio.gather(*msgs_toread)
            self.log('read_inbox() msgs = ',msgs)
            return msgs
        return []

    async def read_outbox(self,uri_outbox=None):
        if uri_outbox is None: uri_outbox = P2P_PREFIX_OUTBOX+self.name.encode()
        return await self.read_inbox(uri_outbox)


    async def read_msg(self,msg_id):
        self.log(f'Persona.read_msg({msg_id}) ?')
        uri_msg=P2P_PREFIX_POST+msg_id
        node = await self.node
            
        res = await node.get(uri_msg)
        self.log('res = ',res)
        if res is not None:
            double_encrypted_payload_b64 = res
            single_encrypted_payload = self.decrypt(double_encrypted_payload_b64, KOMRADE_PUB_KEY)
            self.log('GOT ENRYPTED PAYLOAD:',single_encrypted_payload)

            signed_encrypted_payload_b64,from_pubkey_b64,name_b64,time_b64 = single_encrypted_payload.split(BSEP)
            self.log('signed_encrypted_payload =',signed_encrypted_payload_b64)
            self.log('from_pubkey_b64 =',from_pubkey_b64)
            self.log('time_b64 =',time_b64)
            
            from_name = b64decode(name_b64).decode()
            self.log('from_name =',from_name)
            timestamp = b64decode(time_b64).decode()
            tmpP = Persona(from_name)
            await tmpP.boot()
            from_pubkey_b64_acc_to_name = tmpP.pubkey_b64
            assert from_pubkey_b64==from_pubkey_b64_acc_to_name

            encrypted_payload_b64 = self.verify(signed_encrypted_payload_b64, from_pubkey_b64)
            self.log('encrypted_payload_b64 =',encrypted_payload_b64)
            
            payload = self.decrypt(encrypted_payload_b64, from_pubkey_b64)
            self.log('payload =',payload)
            return {
                'success':True,
                'content':payload,
                'from_name':from_name,
                'from_pubkey_b64':from_pubkey_b64,
                'timestamp':timestamp
            }
        return {'error':'Unknown'}
            


def run_multiple_tasks(tasks):
    async def _go(tasks):
        res = await asyncio.gather(*tasks, return_exceptions=True)
        return res
    return asyncio.get_event_loop().run_until_complete(_go(tasks))

async def main():

    # start node
    from kademlia.network import Server
    #from p2p_api import 
    PORT_LISTEN = 5969

    # NODES_PRIME = [("128.232.229.63",8467), ("68.66.241.111",8467)] 
    NODES_PRIME = [("128.232.229.63",8467)] 

    node = Server(log=log)
    await node.listen(PORT_LISTEN)
    await node.bootstrap(NODES_PRIME)
    
    marx = Persona('marx',node=node)
    elon = Persona('elon2',node=node)
    world = Persona('world',node=node)
    await world.boot()


    await marx.boot()
    await elon.boot()

    # await marx.send(b'Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! ',to=elon)


    # await elon.read_inbox()

    await marx.send(b'A specter is haunting the internet',to=world)
    await elon.send(b'My rockets explode and so will your mind',to=world)
    await elon.send(b'My rockets explode and so will your mind',to=world)

    await world.read_inbox()

    return True
    

if __name__=='__main__':
    asyncio.run(main())














