Exemplo n.º 1
0
class RichPressence:
    def __init__(self, client):
        self.rpc = Presence(client)  # Initialize the client class
        self.rpc.connect()  # Start the handshake loop

    def set_presence(self, pid, state, detail, large_image=None, starttime=None, limit=5):
        try:
            # while  True:
            if large_image and starttime:
                self.rpc.update(state=state, large_image=large_image, start=starttime, details=detail, pid=pid)  # Set the presence
            elif large_image:
                self.rpc.update(state=state, large_image=large_image, details=detail, pid=pid)  # Set the presence
            elif starttime:
                self.rpc.update(state=state, start=starttime, details=detail, pid=pid)  # Set the presence
            else:
                self.rpc.update(state=state, details=detail, pid=pid)  # Set the presence
                
                # time.sleep(limit)
        except Exception as e:
            print(e)
            self.rpc.close()  # close connection
            exit()

    def clear(self, pid):
        try:
            self.rpc.clear(pid)  # close connection
        except Exception as e:
            print(e)
            pass
class gui():
    def __init__(self):
        config = loadConfig("config.ini", config=CONFIGDEFAULT)
        self.rungame = config["rungame.run"], config["rungame.path"], config[
            "rungame.args"], config["rungame.wait"]
        self.logger = log()
        self.watch = watch(config)
        self.RPC = Presence(535809971867222036)
        self.RPC.connect()
        self.main()

    def main(self):
        self.logger.debug("Start Main Loop")
        self.main_thread = True
        thread = threading.Thread(target=self.background, args=())
        thread.start()
        menu_def = ["BLANK", "Exit"]
        tray = sg.SystemTray(menu=menu_def,
                             filename='./elite-dangerous-clean.ico',
                             tooltip="Elite Dangerous Rich Presence")
        while self.main_thread is True:  # The event loop
            menu_item = tray.Read(timeout=15000)
            if menu_item == 'Exit':
                self.watch.mainThreadStopped()
                break
            data = self.watch.presenceUpdate()
            response = self.RPC.update(state=data["state"],
                                       details=data["details"],
                                       start=data["start"],
                                       large_text=data["large_text"],
                                       large_image=data["large_image"],
                                       party_size=data["party_size"])
            self.logger.debug(response)
        self.RPC.close()
        self.logger.debug("Main Loop Complete")

    def background(self):
        self.logger.debug("Check if the Game should be started")
        if self.watch.getGame() is False and self.watch.getLauncher(
        ) is False and self.rungame[0] is True:
            if os.path.exists(self.rungame[1]):
                self.logger.debug("Game Path exists")
                try:
                    self.logger.debug("Try to start game")
                    game = self.rungame[1] + " " + self.rungame[2]
                    subprocess.Popen(game)
                    time.sleep(int(self.rungame[3]))
                except Exception as e:
                    self.logger.error("Could not start Game: " + str(e))
            else:
                self.logger.warning("Game Path doesn't exists")
        self.logger.debug("Start Background Loop")
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        loop.run_until_complete(self.watch.main())
        loop.close()
        self.main_thread = False
        self.logger.debug("Background Loop Complete")
Exemplo n.º 3
0
def main():

    VERSION = "1.1.0";

    config = GetConfig();

    mpdc = MPDClient();
    mpdc.timeout = int(config["General"]["MPDtimeout"]);
    mpdc.idletimeout = int(config["General"]["MPDtimeout_idle"]);
    try:
        mpdc.connect(config["General"]["MPDip"], port=int(config["General"]["MPDport"]));
    except ConnectionRefusedError:
        print(f"{TermColors.ERROR}ERROR!!! Either mpd isn't running or you have a mistake in the config. Fix ASAP! Exiting!{TermColors.END}");
        exit(-1);

    RPC = Presence(710956455867580427);
    RPC.connect();

    if (psys() == "Windows"):
        ossys("cls");
    else:
        ossys("clear");

    print(f"{TermColors.WORKS}MPDDRP v.{VERSION} - https://github.com/AKurushimi/mpddrp{TermColors.END}");

    try:
        while True:

            statusout = mpdc.status();
            csout = mpdc.currentsong();

            if (statusout["state"] != "stop"):
                title = csout["title"];
                artist = csout["artist"];
                album = csout["album"];
                timevar = statusout["time"].split(":");
                timenow = str(timedelta(seconds=int(timevar[0])));
                timeall = str(timedelta(seconds=int(timevar[1])));

            if (statusout["state"] == "pause"):
                RPC.update(details=title + " | " + album, state="Paused | " + artist, large_image="mpdlogo", small_image="pause", small_text="Paused");
            elif (statusout["state"] == "stop"):
                RPC.update(details="Stopped | MPD", state="   ", large_image="mpdlogo", small_image="stop", small_text="Stopped");
            elif (statusout["state"] == "play"):
                RPC.update(details=title + " | " + album, state=timenow + "/" + timeall + " | " + artist, large_image="mpdlogo", small_image="play", small_text="Playing");
                sleep(1);

    except (RuntimeError):
        pass;
    except (KeyboardInterrupt, SystemExit):
        RPC.clear();
        RPC.close();
        exit(0);
Exemplo n.º 4
0
def main():
    username = input("ROBLOX Username: "******"Your userid is {}".format(userId))
        print("To connect this to Roblox Studio, follow the README.md instructions!")
        rpc = Presence(client_id,pipe=0)
        rpc.connect()
        start = int(time.time())
        rpc.update(state="Idle",start=start,large_image="logo")
        previously = None
        while True:
            running = checkProcess("RobloxStudioBeta")
            if previously == None:
                previously = running
            if running == True:
                if previously == False:
                    rpc.connect()
                data = getData(userId)
                editing = data["editing"]
                if editing == "false":
                    rpc.update(state="Idle",large_image="logo",large_text="Made by Jesse#0877",start=start)
                else:
                    #scriptName = data[2]
                    #path = data[3]
                    #game = data[5]
                    #lines = data[6]
                    scriptName = data["scriptName"]
                    path = data["path"]
                    game = data["game"]
                    lines = data["lines"]
                    scriptType = data["script"]

                    small_image = "lua"
                    small_text = "lua"

                    if scriptType == "script":
                        small_image = "script"
                        small_text = "Script"
                    elif scriptType == "modulescript":
                        small_image = "modulescript"
                        small_text = "ModuleScript"
                    elif scriptType == "localscript":
                        small_image = "localscript"
                        small_text = "LocalScript"
                    rpc.update(state="Editing Script: {}".format(scriptName) + ", {} Lines.".format(lines),details=game,large_image="logo",large_text="Made by Jesse#0877",small_image=small_image,small_text=small_text,start=start)
            else:
                rpc.close()
            previously = running
            time.sleep(15)
    except Exception as e:
        print("An error occured: {}\nPlease send this error to Jesse#0877 on discord!".format(e))
        os.system('pause')
Exemplo n.º 5
0
    def start_overwatch(self, gui):
        RPC = Presence('767085094875168778', loop=asyncio.new_event_loop())
        RPC.connect()

        platform = Platforms(self.config.platform)

        requester = Requests(self.data, Errors())

        while True:
            try:
                if not self.run:
                    RPC.close()
                    return

                last_player_char = self.get_last_played_id(
                    platform.platform, self.config.membership_id, requester)

                activity_data = requester.get(
                    ACTIVITY_LOOKUP.format(platform.platform,
                                           self.config.membership_id,
                                           last_played_char))
                activity_hash = activity_data["Response"]["activities"][
                    "data"]["currentActivityHash"]
                activity_decoded = self.manifest.decode_hash(
                    activity_hash, "DestinyActivityDefinition", self.language)
                activity_decoded_en = self.manifest.decode_hash(
                    activity_hash, "DestinyActivityDefinition", "en")

                mode_hash = activity_data["Response"]["activities"]["data"][
                    "currentActivityHash"]
                mode_data = self.manifest.decode_hash(
                    activity_hash, "DestinyActivityDefinition", "en")

                orbit_translation = Orbit(self.language).orbit_text
                details, state = orbit_translation, orbit_translation
                picture, timer = "in_orbit", time.time()

                if mode_date != None:
                    print(mode_data)

                time.sleep(30)

                self.run = False
            except Exception as e:
                print(traceback.format_exc())
                return
Exemplo n.º 6
0
class Application(tk.Frame):
    def __init__(self, master=tk.Tk()):
        super().__init__(master)
        self.master = master
        self.master.protocol("WM_DELETE_WINDOW", self.on_exit)

        self.rpc = Presence(CLIENT_ID, pipe=0)

        self.rpc.connect()
        self.detail, self.state = "Custom Presence", "Launching!"

        self.initial()
        self.pack()
        self.loop_rpc()

    def initial(self):
        tk.Label(self, text="State: ").grid(row=1)
        self.states = tk.Entry(self)
        tk.Label(self, text="Details: ").grid(row=2)
        self.details = tk.Entry(self)

        self.update_ = tk.Button(self, text="Update Presence", command=self.update_)

        self.states.grid(row=1, column=1)
        self.details.grid(row=2, column=1)
        self.update_.grid(row=3, column=2)

    def loop_rpc(self):
        self.rpc.update(details=self.detail, state=self.state, large_image="futabalargeimagekey")
        self.master.after(10, self.loop_rpc)

    def update_(self):
        try:
            self.error.pack_forget()
            self.correct.pack_forget()
        except: pass

        if not self.details.get() or not self.states.get():
            self.error = tk.Label(text="Details and States cannot be empty!")
            return

        self.detail, self.state = self.details.get(), self.states.get()

    def on_exit(self):
        self.rpc.close()
        self.master.destroy()
Exemplo n.º 7
0
class RPC:
    def __init__(self, client_id, pid):
        self.pid = pid
        self.id = client_id
        self.RPC = Presence(self.id, pipe=0)
        self.timeout = 5
        self.thread = None

    @staticmethod
    def _get_name(gms_proc):
        if gms_proc.hwnd is None:
            name = "Loading..."
            gms_proc.get_gms_hwnd()
        else:
            name = str(win32gui.GetWindowText(gms_proc.hwnd)).replace(
                " - GameMaker Studio 2*", "")
            name = name.replace(" - GameMaker Studio 2", "")
        if len(name) <= 2:
            name = "InvalidName"
        return name

    def _run_background(self):
        gms_proc = Process(self.pid)
        self.RPC.connect()
        epoch_time = int(time.time())
        while is_running(self.pid):

            name = self._get_name(gms_proc)

            self.RPC.update(pid=self.pid,
                            large_image="logo",
                            details=name,
                            start=epoch_time)
            time.sleep(self.timeout)
        self.RPC.close()

    def run(self):
        self.thread = threading.Thread(target=self._run_background())
        self.thread.run()
Exemplo n.º 8
0
def update_rp():
    global details, state, image_name, connected, old_client_id, client_id, rpc

    # Check if clientID is valid
    if not client_id.isnumeric():
        display_message(app_resource_manager.get_string("invalid_client_id"))
        return

    # Connect with discord
    try:
        if not connected:
            # Not connected
            rpc = Presence(client_id)
            rpc.connect()
            connected = True
        elif client_id is not old_client_id:
            # Connected, but clientID was changed
            rpc.close()
            rpc = Presence(client_id)
            rpc.connect()
        old_client_id = client_id
    except:
        display_message(app_resource_manager.get_string("could_not_connect_to_discord"))
        return

    if not use_apple_music:
        # Check if the buttons information were correctly entered
        buttons_list = []
        if buttons_information[0] and buttons_information[0] != app_resource_manager.get_string("text_1"):
            if buttons_information[1] and validators.url(buttons_information[1]):
                if buttons_information[2] and buttons_information[2] != app_resource_manager.get_string("text_2"):
                    if buttons_information[3] and validators.url(buttons_information[3]):
                        buttons_list = [{"label": buttons_information[0], "url": buttons_information[1]}, {"label": buttons_information[2], "url": buttons_information[3]}]
                    else:
                        display_message(app_resource_manager.get_string("invalid_second_link"))
                        return
                else:
                    buttons_list = [{"label": buttons_information[0], "url": buttons_information[1]}]
            else:
                display_message(app_resource_manager.get_string("invalid_first_link"))
                return

        # Check if some data was not entered
        if not details or len(details) < 2:
            details = "  "
        if not state or len(state) < 2:
            state = "  "
        if not image_name:
            image_name = "  "

        # Update presence (with or without buttons)
        try:
            if buttons_information[0] and len(buttons_list) > 0:
                rpc.update(details=details, state=state, large_image=image_name, start=int(time.time()), buttons=buttons_list)
            else:
                rpc.update(details=details, state=state, large_image=image_name, start=int(time.time()))
        except:
            display_message(app_resource_manager.get_string("could_not_update"))

    else:
        # Update presence (apple music)
        try:
            rpc.update(details=details, state=state, large_image="icon", end=(time.time() + song_duration) if not details.startswith(app_resource_manager.get_string("paused")) else None)
        except:
            display_message(app_resource_manager.get_string("could_not_update"))
Exemplo n.º 9
0
            page_title = window.window_text()
            page_title = page_title[:-8]
            opera_windows.append(page_title)
            multiple_windows_open = False
            if len(opera_windows) > 1:
                multiple_windows_open = True
            else:
                found = False
                for site in sites:
                    if str.lower(site) in str.lower(page_title):
                        rpc.update(details="Browing the web",
                                   state=f"Using {site}",
                                   large_image="epiklogo")
                        found = True
                    else:
                        pass
                if not found:
                    rpc.update(details="Browing the web",
                               state=None,
                               large_image="epiklogo")
                if multiple_windows_open:
                    rpc.update(details="Browing the web",
                               state=None,
                               large_image="epiklogo")
    if not found_opera:
        if not rpc_closed:
            rpc.close()
            log("RPC Closed!")
            rpc_closed = True
    time.sleep(1)
Exemplo n.º 10
0
if __name__ == "__main__":
    path = sys.argv[1]
    if not path:
        print("Log path not given, exitting.")
        exit(1)

    begin_time = time.time()
    presence = Presence("655060628582432768")
    observer = Observer()
    event_handler = FileEventHandler()

    observer.schedule(event_handler, path)
    observer.start()
    print("Started file watcher")

    presence.connect()
    print("Started Discord Presence")
    print("=" * 20, end="\n\n")

    # Default presence
    set_presence("Main Menu")

    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        observer.stop()
        presence.close()

    observer.join()
def main(debug_mode=False):
    """
    Main entrypoint (Should only be run when used as a script, and not when imported).
    """
    # Global Definitions
    global process_hwnd
    # Primary Variables
    event_key = "$RPCEvent$"
    event_length = 11
    array_split_key = "=="
    array_separator_key = "|"
    process_hwnd = None

    # RPC Data
    rpc_obj = None
    last_decoded = [None] * event_length
    last_activity = {}

    # Initial Welcome Messages
    root_logger.info("========== DiscordRichPresence Service - " +
                     process_version + " ==========")
    root_logger.info("System Info: \"" + sys.version + "\"")
    root_logger.info("Started DiscordRichPresence Service for \"" +
                     config["process_name"] + "\"")
    root_logger.info(
        "Note: Please keep this script open while logging and sending Rich Presence updates."
    )
    root_logger.info(
        "==========================================================")

    while True:
        process_hwnd = None
        if is_windows:
            win32gui.EnumWindows(callback, None)
        else:
            process_hwnd = is_running(config["process_name"])

        if debug_mode:
            # if in DEBUG mode, squares are read, the image with the dot matrix is
            # shown and then the script quits.
            if process_hwnd:
                root_logger.debug(
                    'DEBUG: Reading squares. Please check result image for verification...'
                )
                read_squares(process_hwnd, event_length, event_key,
                             array_separator_key, debug_mode)
            else:
                root_logger.debug("DEBUG: Unable to locate target process.")
            input("Press Enter to continue...")
            break
        elif process_hwnd:
            lines = read_squares(process_hwnd, event_length, event_key,
                                 array_separator_key, debug_mode)

            if not lines:
                time.sleep(config["scan_rate"])
                continue

            has_id_changed = lines[0] != last_decoded[0]

            if has_id_changed or lines != last_decoded:
                if not rpc_obj or has_id_changed:
                    if rpc_obj:
                        root_logger.info(
                            "The client id has been changed, reconnecting with new ID %s..."
                            % lines[0])
                        rpc_obj.close()
                    else:
                        root_logger.info(
                            "Not connected to Discord, connecting to ID %s..."
                            % lines[0])

                    while True:
                        try:
                            rpc_obj = Presence(client_id=lines[0])
                            rpc_obj.connect()
                        except Exception as exc:
                            root_logger.error(
                                "Unable to connect to Discord (%s). It's "
                                'probably not running. I will try again in %s '
                                'sec.' % (str(exc), config["refresh_rate"]))
                            time.sleep(config["refresh_rate"])
                        else:
                            break

                timer_data = {}
                asset_data = {}
                button_info = []
                activity = {}
                # Asset Data Sync
                if not null_or_empty(lines[1]):
                    asset_data["large_image"] = lines[1]
                    if not null_or_empty(lines[2]):
                        asset_data["large_text"] = lines[2]
                if not null_or_empty(lines[3]):
                    asset_data["small_image"] = lines[3]
                    if not null_or_empty(lines[4]):
                        asset_data["small_text"] = lines[4]
                # Start Timer Data Setup
                if "generated" in lines[7]:
                    lines[7] = round(time.time())
                elif "last" in lines[7]:
                    lines[7] = last_decoded[7] or round(time.time())
                # End Timer Data Setup
                if "generated" in lines[8]:
                    lines[8] = round(time.time())
                elif "last" in lines[8]:
                    lines[8] = last_decoded[8] or round(time.time())
                # Timer Data Sync
                if not null_or_empty(lines[7]):
                    timer_data["start"] = lines[7]
                    if not null_or_empty(lines[8]):
                        timer_data["end"] = lines[8]
                # Buttons Data Sync
                if not null_or_empty(lines[9]):
                    button_data = parse_button_data(lines[9], array_split_key)
                    if len(button_data) == 2:
                        button_label = button_data[0] if (
                            len(button_data[0]) <= 32) else button_data[1]
                        button_url = button_data[1] if (
                            button_data[1] != button_label) else button_data[0]
                        button_info.append({
                            "label": button_label,
                            "url": button_url
                        })
                if not null_or_empty(lines[10]):
                    button_data = parse_button_data(lines[10], array_split_key)
                    if len(button_data) == 2:
                        button_label = button_data[0] if not (is_valid_url(
                            button_data[0])) else button_data[1]
                        button_url = button_data[1] if (
                            button_data[1] != button_label) else button_data[0]
                        button_info.append({
                            "label": button_label,
                            "url": button_url
                        })
                # Activity Data Sync
                if not null_or_empty(lines[5]):
                    activity["details"] = lines[5]
                if not null_or_empty(lines[6]):
                    activity["state"] = lines[6]

                activity["assets"] = asset_data
                activity["timestamps"] = timer_data
                activity["buttons"] = button_info

                if activity != last_activity:
                    root_logger.info("Setting new activity: %s" % activity)

                    try:
                        rpc_obj.update(
                            state=activity.get("state") or None,
                            details=activity.get("details") or None,
                            start=timer_data.get("start") or None,
                            end=timer_data.get("end") or None,
                            large_image=asset_data.get("large_image") or None,
                            large_text=asset_data.get("large_text") or None,
                            small_image=asset_data.get("small_image") or None,
                            small_text=asset_data.get("small_text") or None,
                            buttons=activity.get("buttons") or None)
                        last_activity = activity
                        last_decoded = lines
                    except Exception as exc:
                        root_logger.error(
                            'Looks like the connection to Discord was broken (%s). '
                            'I will try to connect again in %s sec.' %
                            (str(exc), config["refresh_rate"]))
                        last_decoded = [None] * event_length
                        last_activity = {}
                        rpc_obj = None
        elif not process_hwnd and rpc_obj:
            root_logger.info(
                'Target process is no longer active, disconnecting')
            rpc_obj.close()
            rpc_obj = None
            # clear these so it gets re-read and resubmitted upon reconnection
            last_decoded = [None] * event_length
            last_activity = {}
        time.sleep(config["refresh_rate"])
Exemplo n.º 12
0
def main():

    try:
        RPC = Presence(client_id="772841390467711041")
        RPC.connect()
    except Exception:
        pass
    version = "Valbot v1.8.0"

    versionstripped = version.replace("Valbot ", "")

    activeactivity = "In Launcher"
    os.system('mode con: cols=39 lines=25')
    title = "title " + version
    os.system(title)
    try:

        RPC.update(state=("Running " + version.replace("Valbot ", "")),
                   start=time.time(),
                   large_image="valbotnew",
                   large_text=version,
                   details=activeactivity)

    except Exception:
        pass

    print(
        Fore.RED + """
    ╔╗  ╔╗╔═══╗╔╗   ╔══╗ ╔═══╗╔════╗
    ║╚╗╔╝║║╔═╗║║║   ║╔╗║ ║╔═╗║║╔╗╔╗║
    ╚╗║║╔╝║║ ║║║║   ║╚╝╚╗║║ ║║╚╝║║╚╝
     ║╚╝║ ║╚═╝║║║ ╔╗║╔═╗║║║ ║║  ║║  
     ╚╗╔╝ ║╔═╗║║╚═╝║║╚═╝║║╚═╝║  ║║  
      ╚╝  ╚╝ ╚╝╚═══╝╚═══╝╚═══╝  ╚╝
                             """ + Style.NORMAL + Fore.RED,
        versionstripped + Style.RESET_ALL)

    print(Style.RESET_ALL)
    print(Style.RESET_ALL + Fore.RED + "          By Fums & WolfAnto")
    print(Style.RESET_ALL)
    print(Style.RESET_ALL + Fore.RED +
          "———————————————————————————————————————")

    print(Style.RESET_ALL)
    print(Fore.RED + " [1]", Style.BRIGHT + "Start Bot")
    print(Style.RESET_ALL)
    print(Fore.RED + " [2]", Style.BRIGHT + "Information")
    print(Style.RESET_ALL)
    print(Fore.RED + " [3]", Style.BRIGHT + "Manage Discord Webhook")
    print(Style.RESET_ALL)
    print(Fore.RED + " [4]", Style.BRIGHT + "XP Calculator")
    print(Style.RESET_ALL)
    print(Fore.RED + " [5]", Style.BRIGHT + "Exit Bot")
    print(Style.RESET_ALL)

    print(Fore.YELLOW + "")
    menu = int(input(" > "))

    try:
        if menu == 1:

            print(Style.RESET_ALL)
            print(Fore.YELLOW, "Make sure you have the")
            print(Style.BRIGHT, "Valorant Card",
                  Style.NORMAL + "Player Card equipped")
            print(Style.RESET_ALL)
            print(Fore.YELLOW, "Valorant must be" + Style.BRIGHT, "FULLSCREEN")
            print(Style.NORMAL, "for the bot to function")
            response = requests.get(
                "https://api.github.com/repos/MrFums/Valbot/releases/latest")
            latest2 = response.json()["name"]
            changelog = response.json()["body"]

            latest3 = latest2.replace("Valbot v", "")
            latest4 = latest2.replace("Valbot ", "")
            latest = latest3.replace(".", "")
            latest = int(latest)

            version = version.replace("Valbot v", "")
            version = version.replace(".", "")
            version = int(version)

            if version < latest:
                print(Style.RESET_ALL)
                print(Fore.RED,
                      "Version (" + str(versionstripped) + ") is outdated")
                print(Style.RESET_ALL)
                print(Fore.RED,
                      "Download latest version (" + str(latest4) + ") now")
                print(Style.RESET_ALL)
                time.sleep(5)

            print(Style.RESET_ALL)
            root = str(pathlib.Path(__file__).parent.absolute())
            fullpath = root + "\Valorant.lnk"
            vallnk = Path(fullpath)
            if vallnk.is_file():
                # file exists
                try:
                    RPC.close()
                except Exception:
                    pass
                time.sleep(2)
                os.startfile("bot.py")
                time.sleep(.5)
                quit()
            else:
                print(Fore.RED, 'You do not have a Valorant shortcut')
                print(' named "Valorant" in the directory')
                print(Fore.RED, 'Add one now to continue')
                time.sleep(5)
                print(Style.RESET_ALL)
                if os.path.exists("runtime_values"):
                    os.remove("runtime_values")
                time.sleep(5)
                main()

        elif menu == 2:
            print(Style.RESET_ALL + "")
            print(Fore.RED + " IMPORTANT INFORMATION")
            print("")
            print(Fore.WHITE + " 1)",
                  Fore.CYAN + "Setup your Valorant shortcut")
            print("    and discord webhook (optional)", Style.RESET_ALL)
            print("")
            print(Fore.WHITE + " 2)",
                  Fore.CYAN + "Change your player card to the")
            print("    Valorant Card Player Card")
            print("")
            print(Fore.WHITE + " 3)",
                  Fore.CYAN + "Valorant must be fullscreen")
            print("    and focused")
            print("")
            print(Fore.BLUE + " Discord :", Fore.YELLOW + "Fums#0888")
            print(Fore.CYAN, Fore.BLUE + "Github :",
                  Fore.YELLOW + "https://github.com/MrFums", Style.RESET_ALL)
            print(Style.RESET_ALL)
            print(Style.BRIGHT, Fore.RED)
            print("")
            print(" Input anything to return...")
            print(Fore.YELLOW, Style.NORMAL + "")
            input(" > ")
            main()

        elif menu == 3:
            print(Style.RESET_ALL)
            print(Fore.RED + " WEBHOOK MANAGER")
            print(Style.RESET_ALL)
            print(Fore.WHITE + " 1)",
                  Fore.CYAN + "Open Discord, go to a server where")
            print("    you have permission to create")
            print("    and manage a webhook")
            print("")
            print(Fore.WHITE + " 2)",
                  Fore.CYAN + "Either create and / or edit an ")
            print('    existing channel and go to')
            print('    "Integrations"')
            print("")
            print(Fore.WHITE + " 3)",
                  Fore.CYAN + "Click webhooks and then click")
            print('    "Create Webhook", select the right')
            print('    channel, and then click')
            print('    "Copy Webhook URL"')
            print("")
            print(Fore.WHITE + " 4)", Fore.CYAN + "Paste your webhook below")
            print(Style.RESET_ALL)
            print(Style.BRIGHT, Fore.RED)
            print(" Paste a webhook to save or anything")
            print(" else to cancel")
            print(Style.RESET_ALL)
            print(Style.RESET_ALL)

            urlcheck = "https://discord.com/api/webhooks/"
            print(Fore.YELLOW + "")
            inputwebhook = input(" > ")
            inputwebhook = inputwebhook.replace(
                "https://discordapp.com/api/webhooks/",
                "https://discord.com/api/webhooks/")

            if urlcheck in inputwebhook:
                print(Style.RESET_ALL)

                if os.path.exists("webhook.config"):
                    os.remove("webhook.config")

                f = open("webhook.config", "a+")
                f.write(inputwebhook)
                f.close()
                print(Style.RESET_ALL)
                print(Fore.GREEN + " [√] Webhook added")
                print(Style.RESET_ALL)
                time.sleep(3)
                main()

            else:
                print(Style.RESET_ALL)
                print(Fore.RED +
                      " The input was not a webhook, returning to menu...")
                time.sleep(3)
                main()

        elif menu == 4:
            print(Style.RESET_ALL)
            print(Style.RESET_ALL, Fore.YELLOW + Style.BRIGHT,
                  "How much XP do you need to earn?")
            try:
                print(Style.RESET_ALL)
                print(Fore.YELLOW + "")
                xpai = int(input(" > "))
            except ValueError:
                print(" Error 2: You must enter an integer!")
                time.sleep(2)
                main()

            xpa = xpai / 900
            xpau = (math.ceil(xpa))
            print(Style.RESET_ALL)
            print(Fore.BLUE, "Required Games: " + Style.BRIGHT + str(xpau),
                  Fore.BLUE + "games")
            print(Style.RESET_ALL)
            print(Fore.BLUE, "ETA:", Style.BRIGHT + str(xpau / 4),
                  Fore.BLUE + "hours")

            time.sleep(7)
            print(Style.RESET_ALL)
            main()

        elif menu == 5:
            print(Style.BRIGHT + Fore.RED + " Quitting...")
            time.sleep(1)
            quit()

        else:
            print(" Error 1: Enter a valid integer within the range 1 - 5!")
            time.sleep(2)
            main()

    except ValueError:
        print(" Error 2: You must enter an integer!")
        time.sleep(2)
        main()
Exemplo n.º 13
0
class DiscordPresence(object):
    """Provide rich presence integration with Discord for games"""

    def __init__(self):
        self.available = bool(PyPresence)
        self.game_name = ""
        self.runner_name = ""
        self.last_rpc = 0
        self.rpc_interval = 60
        self.presence_connected = False
        self.rpc_client = None
        self.client_id = None
        self.custom_game_name = ''
        self.show_runner = True
        self.custom_runner_name = ''
        self.rpc_enabled = True

    def connect(self):
        """Make sure we are actually connected before trying to send requests"""
        logger.debug("Ensuring connected.")
        if self.presence_connected:
            logger.debug("Already connected!")
        else:
            logger.debug("Creating Presence object.")
            self.rpc_client = PyPresence(self.client_id)
            try:
                logger.debug("Attempting to connect.")
                self.rpc_client.connect()
                self.presence_connected = True
            except (ConnectionError, FileNotFoundError):
                logger.error("Could not connect to Discord")
        return self.presence_connected

    def disconnect(self):
        """Ensure we are definitely disconnected and fix broken event loop from pypresence
        That method is a huge mess of non-deterministic bs and should be nuked from orbit.
        """
        logger.debug("Disconnecting from Discord")
        if self.rpc_client:
            try:
                self.rpc_client.close()
            except Exception as e:
                logger.exception("Unable to close Discord RPC connection: %s", e)
            if self.rpc_client.sock_writer is not None:
                try:
                    logger.debug("Forcefully closing sock writer.")
                    self.rpc_client.sock_writer.close()
                except Exception:
                    logger.exception("Sock writer could not be closed.")
            try:
                logger.debug("Forcefully closing event loop.")
                self.rpc_client.loop.close()
            except Exception:
                logger.debug("Could not close event loop.")
            try:
                logger.debug("Forcefully replacing event loop.")
                self.rpc_client.loop = None
                asyncio.set_event_loop(asyncio.new_event_loop())
            except Exception as e:
                logger.exception("Could not replace event loop: %s", e)
            try:
                logger.debug("Forcefully deleting RPC client.")
                self.rpc_client = None
            except Exception as ex:
                logger.exception(ex)
        self.rpc_client = None
        self.presence_connected = False

    def update_discord_rich_presence(self):
        """Dispatch a request to Discord to update presence"""
        if int(time.time()) - self.rpc_interval < self.last_rpc:
            logger.debug("Not enough time since last RPC")
            return
        if self.rpc_enabled:
            self.last_rpc = int(time.time())
            if not self.connect():
                return
            try:
                state_text = "via %s" % self.runner_name if self.show_runner else "  "
                logger.info("Attempting to update Discord status: %s, %s",
                            self.game_name, state_text)
                self.rpc_client.update(details="Playing %s" % self.game_name, state=state_text)
            except PyPresenceException as ex:
                logger.error("Unable to update Discord: %s", ex)

    def clear_discord_rich_presence(self):
        """Dispatch a request to Discord to clear presence"""
        if self.rpc_enabled:
            if self.connect():
                try:
                    logger.info('Attempting to clear Discord status.')
                    self.rpc_client.clear()
                except PyPresenceException as ex:
                    logger.error("Unable to clear Discord: %s", ex)
                    self.disconnect()
Exemplo n.º 14
0
class Presence(object):
    """This class provides rich presence integration
       with Discord for games.
    """

    def __init__(self):
        if PyPresence is not None:
            logger.debug('Discord Rich Presence not available due to lack of pypresence')
            self.rich_presence_available = True
        else:
            logger.debug('Discord Rich Presence enabled')
            self.rich_presence_available = False
        self.last_rpc = 0
        self.rpc_interval = 60
        self.presence_connected = False
        self.rpc_client = None
        self.client_id = None
        self.custom_game_name = ''
        self.show_runner = True
        self.custom_runner_name = ''
        self.rpc_enabled = True

    def available(self):
        """Confirm that we can run Rich Presence functions"""
        return self.rich_presence_available

    def ensure_discord_connected(self):
        """Make sure we are actually connected before trying to send requests"""
        if not self.available():
            logger.debug("Discord Rich Presence not available due to lack of pypresence")
            return
        logger.debug("Ensuring connected.")
        if self.presence_connected:
            logger.debug("Already connected!")
        else:
            logger.debug("Creating Presence object.")
            self.rpc_client = PyPresence(self.client_id)
            try:
                logger.debug("Attempting to connect.")
                self.rpc_client.connect()
                self.presence_connected = True
            except Exception as e:
                logger.error("Unable to reach Discord.  Skipping update: %s", e)
                self.ensure_discord_disconnected()
        return self.presence_connected

    def ensure_discord_disconnected(self):
        """Ensure we are definitely disconnected and fix broken event loop from pypresence"""
        if not self.available():
            logger.debug("Discord Rich Presence not available due to lack of pypresence")
            return
        logger.debug("Ensuring disconnected.")
        if self.rpc_client is not None:
            try:
                self.rpc_client.close()
            except Exception as e:
                logger.error("Unable to close Discord RPC connection: %s", e)
            if self.rpc_client.sock_writer is not None:
                try:
                    logger.debug("Forcefully closing sock writer.")
                    self.rpc_client.sock_writer.close()
                except Exception:
                    logger.debug("Sock writer could not be closed.")
            try:
                logger.debug("Forcefully closing event loop.")
                self.rpc_client.loop.close()
            except Exception:
                logger.debug("Could not close event loop.")
            try:
                logger.debug("Forcefully replacing event loop.")
                self.rpc_client.loop = None
                asyncio.set_event_loop(asyncio.new_event_loop())
            except Exception as e:
                logger.debug("Could not replace event loop: %s", e)
            try:
                logger.debug("Forcefully deleting RPC client.")
                del self.rpc_client
            except Exception:
                pass
        self.rpc_client = None
        self.presence_connected = False

    def update_discord_rich_presence(self):
        """Dispatch a request to Discord to update presence"""
        if not self.available():
            logger.debug("Discord Rich Presence not available due to lack of pypresence")
            return
        if int(time.time()) - self.rpc_interval < self.last_rpc:
            logger.debug("Not enough time since last RPC")
            return
        if self.rpc_enabled:
            logger.debug("RPC is enabled")
            self.last_rpc = int(time.time())
            connected = self.ensure_discord_connected()
            if not connected:
                return
            try:
                state_text = "via {}".format(self.runner_name) if self.show_runner else "  "
                logger.info("Attempting to update Discord status: %s, %s", self.game_name, state_text)
                self.rpc_client.update(details="Playing {}".format(self.game_name), state=state_text)
            except PyPresenceException as e:
                logger.error("Unable to update Discord: %s", e)
        else:
            logger.debug("RPC disabled")

    def clear_discord_rich_presence(self):
        """Dispatch a request to Discord to clear presence"""
        if not self.available():
            logger.debug("Discord Rich Presence not available due to lack of pypresence")
            return
        if self.rpc_enabled:
            connected = self.ensure_discord_connected()
            if connected:
                try:
                    logger.info('Attempting to clear Discord status.')
                    self.rpc_client.clear()
                except PyPresenceException as e:
                    logger.error("Unable to clear Discord: %s", e)
                    self.ensure_discord_disconnected()
Exemplo n.º 15
0
class UserInterface(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super(UserInterface, self).__init__()

        print("Setting up the window...")
        self.setupUi(self)

        print("Loading settings window...")
        self.settingsWindow = Settings_UserInterface()
        self.settings_button.clicked.connect(self.openSettings)
        self.settingsWindow.onClose.connect(self.onSettingsClosed)

        print("Loading configuration...")
        json_file = open("data/config.json", "r")
        self.config_json = json.load(json_file)
        json_file.close()

        print("Loading languages...")
        json_file = open("data/translation.json", "r", encoding="UTF-8")
        self.translation = json.load(json_file)
        json_file.close()

        print("Initializing the presence...")
        client_id = self.config_json["App_ID"]
        self.RPC = Presence(client_id)
        print("Connecting...")
        self.RPC.connect()
        print("Ready")

        self.language = self.config_json["user_language"]
        self.l_format = self.translation[self.language]["format"]
        self.isRunning = False
        self.infos = {}

        self.confirm_button.clicked.connect(self.on_confirm_button_clicked)
        self.confirm_button.setText(self.translation[self.language]["confirm"])

        self.title_label.setText(self.translation[self.language]["enter url"])

        self.settings_button.setText(
            self.translation[self.language]["settings"])

        self.scrollView = AnimeScrollView()
        self.setFocusPolicy(Qt.StrongFocus)
        self.scrollView.clicked.connect(self.onLabelClick)
        self.urlLayout.addWidget(self.scrollView)
        self.url_entry.focusOutEvent = lambda event: self.handleFocus("exit")
        self.url_entry.setPlaceholderText(
            self.translation[self.language]["enter url"])
        self.url_entry.focusInEvent = lambda event: self.handleFocus("enter")
        self.url_entry.textEdited.connect(self.onEdit)
        self.scrollView.hide()
        self.fetcher = None

        self.choice = WebsiteComboBox(self.translation[self.language])
        self.urlLayout.addWidget(self.choice)
        self.choice.hide()

    def on_confirm_button_clicked(self):
        texte = self.url_entry.text()
        if texte:
            if texte.startswith("http") or texte.startswith("www"):
                self.update_presence(texte, "url")
                self.result_label.setStyleSheet("QLabel{color: #26bc1a;}")
            else:
                self.update_presence(texte, "name")
            self.result_label.setText(
                self.translation[self.language]["updated presence"])
            """else:
                self.result_label.setStyleSheet("QLabel{color: #bc1a26;}")
                self.result_label.setText("Invalid url")"""

            # self.url_entry.clear()

    def onLabelClick(self, text):
        self.url_entry.setText(text)
        self.scrollView.hide()
        self.choice.show()
        self.confirm_button.show()

    def handleFocus(self, event):
        if event == "enter":
            self.choice.hide()
            if self.scrollView.animeLabels:
                self.scrollView.show()
                self.confirm_button.hide()
        else:
            if not self.scrollView.hasFocus():
                self.scrollView.hide()
                self.confirm_button.show()
            if not (self.url_entry.text().startswith("http")
                    or self.url_entry.text().startswith("www")
                    ) and not self.scrollView.isVisible():
                self.choice.show()

    def onEdit(self, query):
        self.choice.hide()
        if not (query.startswith("http") or query.startswith("www")):
            if not self.isRunning:
                self.isRunning = True
                self.fetcher = Fetcher(query)
                self.fetcher.finished.connect(self.f)
                self.fetcher.finished.connect(self.scrollView.fill)
                self.fetcher.start()
            else:
                self.fetcher.terminate()
                self.isRunning = False
                return self.onEdit(query)
        else:
            self.confirm_button.show()
            self.scrollView.hide()

    def f(self, x):
        self.isRunning = False

    def generate_state(self, lFormat, infos):
        nStr = lFormat
        nStr = nStr.replace("anime_name", infos["anime_name"])
        if infos["ep_nb"] == "0":
            nStr = nStr[:len(infos["anime_name"])]
            return nStr
        if infos["s_nb"] == "0":
            nStr = nStr.replace("ep_nb", infos["ep_nb"])
            nStr = nStr.replace(" saison s_nb", "")
            return nStr
        nStr = nStr.replace("s_nb",
                            infos["s_nb"]).replace("ep_nb", infos["ep_nb"])
        return nStr

    def openSettings(self):
        self.hide()
        self.settingsWindow.show()
        self.settingsWindow.closeBySave = False

    def onSettingsClosed(self, var):
        if var:
            self.show()
        else:
            self.close()

    def update_presence(self, text, type):
        if type == "url":
            infos = getAnimeInfos(text)
        else:
            website = ["", "adn", "crunchyroll", "wakanim",
                       ""][self.choice.currentIndex()]
            infos = {
                "ep_nb": "0",
                "s_nb": "0",
                "anime_name": text,
                "website": "",
                "image": "",
                "small_image": ""
            }
            if website:
                infos["image"] = website + "_logo"
        state = self.generate_state(self.l_format, infos)
        self.actual_epoch = time()
        if infos["small_image"] and infos["image"]:
            self.RPC.update(
                details=self.translation[self.language]["watching an anime"],
                state=state,
                large_image=infos["image"],
                small_image=infos["small_image"],
                start=self.actual_epoch)
        elif infos["image"]:
            self.RPC.update(
                details=self.translation[self.language]["watching an anime"],
                state=state,
                large_image=infos["image"],
                start=self.actual_epoch)
        else:
            self.RPC.update(
                details=self.translation[self.language]["watching an anime"],
                state=state,
                start=self.actual_epoch)

    @pyqtSlot()
    def closeEvent(self, event):
        self.RPC.close()
        event.accept()
Exemplo n.º 16
0
class HistoryViewModel(QtCore.QObject):
    # Constants
    __INITIAL_SCROBBLE_HISTORY_COUNT = int(
        os.environ.get('INITIAL_HISTORY_ITEMS',
                       30))  # 30 is the default but can be configured
    __MEDIA_PLAYER_POLLING_INTERVAL = 100 if os.environ.get('MOCK') else 1000
    __CURRENT_SCROBBLE_INDEX = -1
    __NO_SELECTION_INDEX = -2
    __DISCORD_RPC_MAX_RETRIES = 5

    # Qt Property changed signals
    is_enabled_changed = QtCore.Signal()
    current_scrobble_data_changed = QtCore.Signal()
    scrobble_percentage_changed = QtCore.Signal()
    is_player_paused_changed = QtCore.Signal()
    is_spotify_plugin_available_changed = QtCore.Signal()
    is_using_mock_player_changed = QtCore.Signal()
    is_discord_rich_presence_enabled_changed = QtCore.Signal()
    selected_scrobble_changed = QtCore.Signal()
    selected_scrobble_index_changed = QtCore.Signal()
    is_loading_changed = QtCore.Signal()
    media_player_name_changed = QtCore.Signal()

    # Scrobble history list model signals
    pre_append_scrobble = QtCore.Signal()
    post_append_scrobble = QtCore.Signal()
    scrobble_album_image_changed = QtCore.Signal(int)
    scrobble_lastfm_is_loved_changed = QtCore.Signal(int)
    begin_refresh_history = QtCore.Signal()
    end_refresh_history = QtCore.Signal()

    # Signals handled from QML
    preloadProfileAndFriends = QtCore.Signal()

    def __init__(self) -> None:
        QtCore.QObject.__init__(self)

        # TODO: Figure out which things should be in reset_state
        self.__application_reference: ApplicationViewModel = None
        self.__is_enabled: bool = False

        self.__media_player: MediaPlayerPlugin = None
        self.__is_spotify_plugin_available = False  # This is set to true if Spotify is installed

        # Set up Discord presence
        self.__is_discord_rpc_enabled = False
        self.__is_discord_rpc_connected = False
        self.__discord_rpc = Presence('799678908819439646')

        # Settings
        # TODO: Move these properties to an App view model
        self.__is_submission_enabled: bool = None
        self.is_player_paused = False
        self.__was_last_player_event_paused: bool = False

        if os.environ.get('MOCK'):
            self.__media_player = MockPlayerPlugin()
            self.__connect_media_player_signals()
        else:
            # Initialize media player plugins
            self.__spotify_plugin = SpotifyPlugin()
            self.__music_app_plugin = MusicAppPlugin()

            use_spotify = False
            spotify_app = SBApplication.applicationWithBundleIdentifier_(
                'com.spotify.client')

            # Use Music app plugin by default since every Mac has it (this will be changed when the user preference is loaded from the database)
            # TODO: Make it possible for no media player to be set during onboarding so we don't need to have this temporary value?
            self.switchToMediaPlugin('musicApp',
                                     should_update_in_database=False)

        self.media_player_name_changed.emit()

        # Start polling interval to check for new media player position
        self.__timer = QtCore.QTimer(self)
        self.__timer.timeout.connect(self.__fetch_new_media_player_position)

        self.reset_state()

    def reset_state(self) -> None:
        # Store Scrobble objects that have been submitted
        self.scrobble_history: List[Scrobble] = []

        # Keep track of whether the history view is loading data
        self.__is_loading = False

        # Hold a Scrobble object for currently playing track (will later be submitted)
        self.__current_scrobble: Scrobble = None

        # Hold the index of the selected scrobble in the sidebar
        self.__selected_scrobble_index: int = None

        # Hold the Scrobble object at the __selected_scrobble_index
        # This can either be a copy of the current scrobble or one in the history
        self.selected_scrobble: Scrobble = None

        # Keep track of whether the current scrobble has hit the threshold for submission
        self.__current_scrobble_percentage: float = None
        self.__should_submit_current_scrobble = False

        # Keep track of furthest position reached in a song
        self.__furthest_player_position_reached: float = None

        # Keep track of how many poll ticks have passed since the playback position changed
        self.__cached_playback_position: float = None
        self.__ticks_since_position_change: int = None

        # Store the current track's crop values since they aren't part of the scrobble object
        self.__current_track_crop: TrackCrop = None

        if (self.__is_enabled):
            self.__is_spotify_plugin_available = False

            # Load preferences from database
            media_player_preference = db_helper.get_preference('media_player')
            is_rich_presence_enabled = db_helper.get_preference(
                'rich_presence_enabled')

            # Check if Spotify is installed; show option to switch media player in status bar menu if it's installed
            spotify_app = SBApplication.applicationWithBundleIdentifier_(
                'com.spotify.client')

            if spotify_app:
                self.__is_spotify_plugin_available = True
                self.is_spotify_plugin_available_changed.emit()

            if media_player_preference == 'spotify':
                # If Spotify is not installed, reset media player preference back to Music.app
                # TODO: Use better method to figure out if Spotify is installed without logged error
                if self.__is_spotify_plugin_available:
                    self.switchToMediaPlugin('spotify',
                                             should_update_in_database=False)
                else:
                    logging.info(
                        'Spotify not detected on device; switching back to Music.app as media player'
                    )
                    db_helper.set_preference('media_player', 'musicApp')
                    media_player_preference = 'musicApp'

            if media_player_preference == 'musicApp':
                self.switchToMediaPlugin('musicApp',
                                         should_update_in_database=False)

            self.media_player_name_changed.emit()

            # Try connecting to Discord rich presence if enabled
            self.__is_discord_rpc_enabled = db_helper.get_preference(
                'rich_presence_enabled')
            self.is_discord_rich_presence_enabled_changed.emit()

            if self.__is_discord_rpc_enabled and helpers.is_discord_open():
                self.__connect_discord_rpc_if_open()

    # --- Qt Property Getters and Setters ---

    def set_is_discord_rich_presence_enabled(self, is_enabled):
        self.__is_discord_rpc_enabled = is_enabled

        if is_enabled:
            if self.__connect_discord_rpc_if_open():
                # If there is a track playing when rich presence is enabled, update the rich presence immediately
                # This is nested because we want to the __connect_discord_rpc_if_open logic to run regardless
                if self.__current_scrobble:
                    self.__update_discord_rpc(
                        self.__current_scrobble.track_title,
                        self.__current_scrobble.artist_name,
                        self.__current_scrobble.album_title,
                        self.__cached_playback_position)
        else:
            if self.__is_discord_rpc_connected:
                self.__is_discord_rpc_connected = False

                if helpers.is_discord_open():
                    self.__discord_rpc.close()

        self.is_discord_rich_presence_enabled_changed.emit()
        db_helper.set_preference('rich_presence_enabled', is_enabled)

    def set_application_reference(self,
                                  new_reference: ApplicationViewModel) -> None:
        if not new_reference:
            return

        self.__application_reference = new_reference
        self.__application_reference.is_logged_in_changed.connect(
            lambda: self.set_is_enabled(self.__application_reference.
                                        is_logged_in))

    def get_selected_scrobble_index(self):
        '''Make the private selected scrobble index variable available to the UI'''

        if self.__selected_scrobble_index is None:
            # NO_SELECTION_INDEX represents no selection because Qt doesn't understand Python's None value
            return HistoryViewModel.__NO_SELECTION_INDEX

        return self.__selected_scrobble_index

    def set_selected_scrobble_index(self, new_index: int) -> None:
        if not self.__is_enabled:
            return

        # Prevent setting an illegal index with keyboard shortcuts
        if (
                # Prevent navigating before current scrobble
                new_index < HistoryViewModel.__CURRENT_SCROBBLE_INDEX or
            (
                # Prevent navigating to current scrobble when there isn't one
                new_index == HistoryViewModel.__CURRENT_SCROBBLE_INDEX
                and self.__current_scrobble is None)

                # Prevent navigating past scrobble history
                or new_index == len(self.scrobble_history)):
            return

        self.__selected_scrobble_index = new_index
        self.selected_scrobble_index_changed.emit()

        if new_index == HistoryViewModel.__CURRENT_SCROBBLE_INDEX:
            self.selected_scrobble = self.__current_scrobble
        else:
            self.selected_scrobble = self.scrobble_history[new_index]

        # Update details view
        self.selected_scrobble_changed.emit()

    def set_is_enabled(self, is_enabled: bool) -> None:
        self.__is_enabled = is_enabled
        self.is_enabled_changed.emit()

        if is_enabled:
            # Reset view model
            self.reset_state()
            self.__timer.start(self.__MEDIA_PLAYER_POLLING_INTERVAL)

            # Check for network connection on open
            self.__application_reference.update_is_offline()

            if not self.__application_reference.is_offline:
                self.reloadHistory()

                # Don't preload profile and friends in mock mode since it sends off tons of requests
                if not os.environ.get('MOCK'):
                    self.preloadProfileAndFriends.emit()
        else:
            self.begin_refresh_history.emit()
            self.reset_state()
            self.end_refresh_history.emit()
            self.selected_scrobble_index_changed.emit()
            self.selected_scrobble_changed.emit(
            )  # This causes details pane to stop showing a scrobble
            self.current_scrobble_data_changed.emit()
            self.__timer.stop()

        self.is_loading_changed.emit()

    # --- Slots ---

    @QtCore.Slot()
    def reloadHistory(self):
        '''Reload recent scrobbles from Last.fm'''

        if (self.__INITIAL_SCROBBLE_HISTORY_COUNT == 0

                # Prevent reloading during onboarding
                or not self.__is_enabled

                # Prevent reloading while the view is already loading
                or self.__is_loading):
            return

        # Update loading indicator
        self.__is_loading = True
        self.is_loading_changed.emit()

        # Reset scrobble history list
        self.begin_refresh_history.emit()
        self.scrobble_history = []
        self.end_refresh_history.emit()

        # Fetch and load recent scrobbles
        fetch_recent_scrobbles_task = FetchRecentScrobbles(
            lastfm=self.__application_reference.lastfm,
            count=self.__INITIAL_SCROBBLE_HISTORY_COUNT)
        fetch_recent_scrobbles_task.finished.connect(
            self.__handle_recent_scrobbles_fetched)
        QtCore.QThreadPool.globalInstance().start(fetch_recent_scrobbles_task)

    @QtCore.Slot(int)
    def toggleLastfmIsLoved(self, scrobble_index: int) -> None:

        if not self.__is_enabled:
            return

        scrobble = None

        if scrobble_index == HistoryViewModel.__CURRENT_SCROBBLE_INDEX:
            scrobble = self.__current_scrobble
        else:
            scrobble = self.scrobble_history[scrobble_index]

        new_value = not scrobble.lastfm_track.is_loved
        scrobble.lastfm_track.is_loved = new_value

        # Update any matching scrobbles in history
        for history_scrobble in self.scrobble_history:
            if history_scrobble == scrobble:
                history_scrobble.lastfm_track.is_loved = new_value

        # Update UI to reflect changes
        self.__emit_scrobble_ui_update_signals(scrobble)

        # Submit new value to Last.fm
        QtCore.QThreadPool.globalInstance().start(
            UpdateTrackLoveOnLastfm(lastfm=self.__application_reference.lastfm,
                                    scrobble=scrobble,
                                    value=new_value))

    @QtCore.Slot(str)
    def switchToMediaPlugin(self,
                            media_plugin_name: str,
                            should_update_in_database: bool = True) -> None:
        # Fake stopped event to un-load the current scrobble
        self.__handle_media_player_stopped()

        # Reset scrobble percentage
        self.__current_scrobble_percentage = 0

        # Reset whether the last event was a pause
        self.__was_last_player_event_paused = False

        # Disconnect event signals
        if self.__media_player:
            self.__media_player.stopped.disconnect(
                self.__handle_media_player_stopped)
            self.__media_player.playing.disconnect(
                self.__handle_media_player_playing)
            self.__media_player.paused.disconnect(
                self.__handle_media_player_paused)

        if media_plugin_name == 'spotify':
            self.__media_player = self.__spotify_plugin
        elif media_plugin_name == 'musicApp':
            self.__media_player = self.__music_app_plugin

        if should_update_in_database:
            db_helper.set_preference('media_player', media_plugin_name)

        self.__set_is_scrobble_submission_enabled(
            self.__media_player.IS_SUBMISSION_ENABLED)
        logging.info(
            f'Switched media player to {self.__media_player.MEDIA_PLAYER_NAME}'
        )

        self.__connect_media_player_signals()
        self.__media_player.request_initial_state()

        # Update 'Listening on X' text in history view for current scrobble
        self.media_player_name_changed.emit()

    def __connect_media_player_signals(self):
        # Reconnect event signals
        self.__media_player.stopped.connect(self.__handle_media_player_stopped)
        self.__media_player.playing.connect(self.__handle_media_player_playing)
        self.__media_player.paused.connect(self.__handle_media_player_paused)
        self.__media_player.showNotification.connect(
            lambda title, content: self.__application_reference.
            showNotification.emit(title, content))

    @QtCore.Slot(str)
    def mock_event(self, event_name):
        '''Allow mock player events to be triggered from QML'''

        self.__media_player.mock_event(event_name)

    # --- Private Methods ---

    def __connect_discord_rpc_if_open(self) -> None:
        '''Check if Discord is open; if open, connect with a new client ID if not already connected. If closed, disconnect'''

        if helpers.is_discord_open():
            if not self.__is_discord_rpc_connected:
                self.__discord_rpc.connect()
                self.__is_discord_rpc_connected = True
                logging.info(f'Discord RPC connected')
        else:
            if self.__is_discord_rpc_connected:
                self.__is_discord_rpc_connected = False
                self.__discord_rpc.close()
                logging.info(f'Discord RPC disconnected')

        return self.__is_discord_rpc_connected

    def __run_discord_rpc_request(self, request):
        '''Runs "request" lambda if Discord is open. If InvalidID occurs due to Discord generating a new client ID on relaunch, retry the request'''

        # Don't run any request if Discord isn't open
        if self.__connect_discord_rpc_if_open():
            did_request_succeed = False
            number_of_retries = 0

            while not did_request_succeed and number_of_retries < self.__DISCORD_RPC_MAX_RETRIES:
                try:
                    request(self)
                    did_request_succeed = True
                except InvalidID:
                    self.__is_discord_rpc_connected = False

                    if self.__connect_discord_rpc_if_open():
                        logging.info(
                            f'Discord RPC reconnected to new client (at retry #{number_of_retries})'
                        )

                    number_of_retries += 1

            if number_of_retries == self.__DISCORD_RPC_MAX_RETRIES:
                logging.warning('Max retries exceeded on Discord RPC request')

    def __update_discord_rpc(self, track_title: str, artist_name: str,
                             album_title: str, player_position: float):
        '''Update Discord RPC with new track details'''

        self.__discord_rpc.update(
            details=track_title,
            state=artist_name,
            large_image='music-logo',
            large_text='Playing on Music',
            small_image='lastredux-logo',
            small_text='Scrobbling on LastRedux',
            start=(datetime.now() - timedelta(seconds=player_position)).
            timestamp(
            )  # Don't include track start to accurately reflect timestamp in uncropped track
        )

    def __handle_recent_scrobbles_fetched(
            self, recent_scrobbles: LastfmList[LastfmScrobble]):
        # Tell the history list model that we are going to change the data it relies on
        self.begin_refresh_history.emit()

        # User might not have any scrobbles (new account)
        if recent_scrobbles:
            # Convert scrobbles from history into scrobble objects
            for i, recent_scrobble in enumerate(recent_scrobbles.items):
                self.scrobble_history.append(
                    Scrobble.from_lastfm_scrobble(recent_scrobble))

                self.__load_external_scrobble_data(self.scrobble_history[i])

        self.end_refresh_history.emit()

        self.__is_loading = False
        self.is_loading_changed.emit()

    def __load_external_scrobble_data(self, scrobble: Scrobble) -> None:
        if self.__application_reference.is_offline:
            return

        load_lastfm_track_info = LoadLastfmTrackInfo(
            self.__application_reference.lastfm, scrobble)
        load_lastfm_track_info.finished.connect(
            self.__handle_piece_of_external_scrobble_data_loaded)
        QtCore.QThreadPool.globalInstance().start(load_lastfm_track_info)

        load_lastfm_artist_info = LoadLastfmArtistInfo(
            self.__application_reference.lastfm, scrobble)
        load_lastfm_artist_info.finished.connect(
            self.__handle_piece_of_external_scrobble_data_loaded)
        QtCore.QThreadPool.globalInstance().start(load_lastfm_artist_info)

        load_lastfm_album_info = LoadLastfmAlbumInfo(
            self.__application_reference.lastfm, scrobble)
        load_lastfm_album_info.finished.connect(
            self.__handle_piece_of_external_scrobble_data_loaded)
        QtCore.QThreadPool.globalInstance().start(load_lastfm_album_info)

        load_track_images = LoadTrackImages(
            self.__application_reference.lastfm,
            self.__application_reference.art_provider, scrobble)
        load_track_images.finished.connect(
            self.__handle_piece_of_external_scrobble_data_loaded)
        QtCore.QThreadPool.globalInstance().start(load_track_images)

    def __handle_piece_of_external_scrobble_data_loaded(
            self, scrobble: Scrobble):
        if not self.__is_enabled:
            return

        # TODO: Find a better way to do this that's less hacky
        if not getattr(scrobble, 'TEMP_things_loaded', False):
            scrobble.TEMP_things_loaded = 0

        scrobble.TEMP_things_loaded += 1

        if scrobble.TEMP_things_loaded == 4:
            scrobble.is_loading = False

        self.__emit_scrobble_ui_update_signals(scrobble)

    def __emit_scrobble_ui_update_signals(self, scrobble: Scrobble) -> None:
        if not self.__is_enabled:
            return

        # Update details view if needed (all external scrobble data)
        if scrobble == self.selected_scrobble:
            self.selected_scrobble_changed.emit()

        # Update current scrobble view if needed (album art, is_loved)
        if scrobble == self.__current_scrobble:
            self.current_scrobble_data_changed.emit()

        # Update loved status and album art for all applicable history items if they match
        for i, history_scrobble in enumerate(self.scrobble_history):
            if scrobble == history_scrobble:
                self.scrobble_album_image_changed.emit(i)
                self.scrobble_lastfm_is_loved_changed.emit(i)

    def __submit_scrobble(self, scrobble: Scrobble) -> None:
        '''Add a scrobble object to the history array and submit it to Last.fm'''

        if not self.__is_enabled:
            return

        # Tell scrobble history list model that a change will be made
        self.pre_append_scrobble.emit()

        # Prepend the new scrobble to the scrobble_history array in the view model
        self.scrobble_history.insert(0, scrobble)

        # Tell scrobble history list model that a change was made in the view model
        # The list model will call the data function in the background to get the new data
        self.post_append_scrobble.emit()

        if not self.__selected_scrobble_index is None:
            # Shift down the selected scrobble index if new scrobble has been added to the top
            # This is because if the user has a scrobble in the history selected and a new scrobble is
            # submitted, it will display the wrong data if the index isn't updated

            # Change __selected_scrobble_index instead of calling set___selected_scrobble_index because the
            # selected scrobble shouldn't be redundantly set to itself and still emit selected_scrobble_changed
            if self.__selected_scrobble_index > HistoryViewModel.__CURRENT_SCROBBLE_INDEX:
                # Shift down the selected scrobble index by 1
                self.__selected_scrobble_index += 1

                # Tell the UI that the selected index changed, so it can update the selection highlight
                self.selected_scrobble_index_changed.emit()

        # Submit scrobble to Last.fm
        if self.__is_submission_enabled:
            # Use end time to submit to play more nicely with apps like Marvis
            # TODO: Make this a setting
            self.__current_scrobble.timestamp = datetime.now()

            submit_scrobble_task = SubmitScrobble(
                lastfm=self.__application_reference.lastfm,
                scrobble=self.__current_scrobble)
            QtCore.QThreadPool.globalInstance().start(submit_scrobble_task)

        # Update playcounts for scrobbles (including the one just added to history)
        for history_scrobble in self.scrobble_history:
            if history_scrobble.lastfm_track and scrobble.lastfm_track:
                if history_scrobble.lastfm_track == scrobble.lastfm_track:
                    history_scrobble.lastfm_track.plays += 1

            if history_scrobble.lastfm_artist and scrobble.lastfm_artist:
                if history_scrobble.lastfm_artist == scrobble.lastfm_artist:
                    history_scrobble.lastfm_artist.plays += 1

        # Reset flag so new scrobble can later be submitted
        self.__should_submit_current_scrobble = False

    def __update_current_scrobble(
            self, media_player_state: MediaPlayerState) -> None:
        '''Replace the current scrobble, update cached track start/finish, update the UI'''

        if not self.__is_enabled:
            return

        # Initialize a new Scrobble object with the updated media player state
        self.__current_scrobble = Scrobble(
            artist_name=media_player_state.artist_name,
            track_title=media_player_state.track_title,
            album_title=media_player_state.album_title,
            album_artist_name=media_player_state.album_artist_name,
            timestamp=
            None  # TODO: Make this configurable, it should default to datetime.now() to match official app
        )

        # Update UI content in current scrobble sidebar item
        self.current_scrobble_data_changed.emit()

        # Reset player position to temporary value until a new value can be recieved from the media player
        self.__furthest_player_position_reached = 0

        # Refresh selected_scrobble with new __current_scrobble object if the current scrobble is selected
        if self.__selected_scrobble_index == HistoryViewModel.__CURRENT_SCROBBLE_INDEX:
            self.selected_scrobble = self.__current_scrobble

            # Update details pane view
            self.selected_scrobble_changed.emit()
        elif self.__selected_scrobble_index is None:
            self.__selected_scrobble_index = HistoryViewModel.__CURRENT_SCROBBLE_INDEX
            self.selected_scrobble = self.__current_scrobble

            # Update the current scrobble highlight and song details pane views
            self.selected_scrobble_index_changed.emit()

            # Update details pane view
            self.selected_scrobble_changed.emit()

        # Update cached media player track playback data
        self.__current_track_crop = media_player_state.track_crop

        # Load Last.fm data and album art
        self.__load_external_scrobble_data(self.__current_scrobble)

        # Reset scrobble meter
        self.__current_scrobble_percentage = 0
        self.scrobble_percentage_changed.emit()

    def __fetch_new_media_player_position(self) -> None:
        '''Fetch the current player position timestamp from the selected media player'''

        if (not self.__is_enabled

                # Skip fetching if there isn't a track playing
                or not self.__current_scrobble):
            return

        fetch_player_position_task = FetchPlayerPosition(self.__media_player)
        fetch_player_position_task.finished.connect(
            self.__handle_player_position_fetched)
        QtCore.QThreadPool.globalInstance().start(fetch_player_position_task)

    def __handle_player_position_fetched(self, player_position: float) -> None:
        '''Update furthest player position reached if needed based on player position data'''

        if not self.__is_enabled:
            return

        # Only update the furthest reached position if it's further than the last recorded furthest position
        # This is because if the user scrubs backward in the track, the scrobble progress bar will stop
        # moving until they reach the previous furthest point reached in the track
        # TODO: Add support for different scrobble submission styles such as counting seconds of playback
        if player_position >= self.__furthest_player_position_reached:
            self.__furthest_player_position_reached = player_position

        # Count how many ticks have passed since the playback position changed (used for Discord RPC)
        if player_position == self.__cached_playback_position:
            self.__ticks_since_position_change += 1

            # Clear discord status if paused for more than 60 seconds
            if self.__ticks_since_position_change == 60 and self.__is_discord_rpc_enabled:
                self.__run_discord_rpc_request(
                    lambda self: self.__discord_rpc.clear())
        else:
            self.__ticks_since_position_change = 0

        self.__cached_playback_position = player_position
        self.__current_scrobble_percentage = self.__determine_current_scrobble_percentage(
        )
        self.scrobble_percentage_changed.emit()

    def __determine_current_scrobble_percentage(self) -> int:
        '''Determine the percentage of the track that has played'''

        if not self.__is_enabled or not self.__current_scrobble:
            return 0

        # Skip calculations if the track has already reached the scrobble threshold
        if self.__should_submit_current_scrobble:
            return 1

        # Compensate for custom track start and end times
        # TODO: Only do this if the media player is the mac Music app
        relative_position = self.__furthest_player_position_reached - self.__current_track_crop.start
        relative_track_length = self.__current_track_crop.finish - self.__current_track_crop.start
        min_scrobble_length = relative_track_length * 0.75  # TODO: Grab the percentage from settings

        # Prevent scenarios where the relative position is negative
        relative_position = max(0, relative_position)
        scrobble_percentage = relative_position / min_scrobble_length

        # Prevent scenarios where the relative player position is greater than the relative track length
        # (don't let the percentage by greater than 1)
        scrobble_percentage = min(scrobble_percentage, 1)

        # Submit current scrobble if the progress towards the scrobble threshold is 100%
        if scrobble_percentage == 1:
            self.__should_submit_current_scrobble = True

        return scrobble_percentage

    def __set_is_scrobble_submission_enabled(self, value: bool) -> None:
        # Ignore any changes to is_submission_enabled if it's disabled globally
        if os.environ.get('DISABLE_SUBMISSION'):
            self.__is_submission_enabled = False
        else:
            self.__is_submission_enabled = value

        logging.info(
            f'Scrobble submission is set to {self.__is_submission_enabled}')

    def __handle_media_player_stopped(self) -> None:
        '''Handle media player stop event (no track is loaded)'''

        if (not self.__is_enabled

                # Don't do anything this case if there was nothing playing previously
                or not self.__current_scrobble):
            return

        if self.__is_discord_rpc_enabled:
            self.__run_discord_rpc_request(
                lambda self: self.__discord_rpc.clear())

        # Submit if the music player stops as well, not just when a new track starts
        if self.__should_submit_current_scrobble:
            self.__submit_scrobble(self.__current_scrobble)

        # Reset current scrobble
        self.__current_scrobble = None

        # Update the UI in current scrobble sidebar item
        self.current_scrobble_data_changed.emit()

        # If the current scrobble is selected, deselect it
        if self.__selected_scrobble_index == HistoryViewModel.__CURRENT_SCROBBLE_INDEX:
            self.__selected_scrobble_index = None
            self.selected_scrobble = None

            # Update the current scrobble highlight and song details pane views
            self.selected_scrobble_index_changed.emit()
            self.selected_scrobble_changed.emit()

    def __handle_media_player_playing(
            self, new_media_player_state: MediaPlayerState) -> None:
        '''Handle media player play event'''

        if not self.__is_enabled:
            return

        # Update now playing on Last.fm regardless of whether it's a new play
        if self.__is_submission_enabled:
            QtCore.QThreadPool.globalInstance().start(
                UpdateNowPlaying(
                    lastfm=self.__application_reference.lastfm,
                    artist_name=new_media_player_state.artist_name,
                    track_title=new_media_player_state.track_title,
                    album_title=new_media_player_state.album_title,
                    album_artist_name=new_media_player_state.album_artist_name,
                    duration=new_media_player_state.track_crop.finish -
                    new_media_player_state.track_crop.start))

        # Update Discord rich presence regardless of whether it's a new play
        if self.__is_discord_rpc_enabled:
            self.__run_discord_rpc_request(
                lambda self: self.__update_discord_rpc(
                    new_media_player_state.track_title, new_media_player_state.
                    artist_name, new_media_player_state.album_title,
                    new_media_player_state.position))

        # Update playback indicator
        self.is_player_paused = False
        self.is_player_paused_changed.emit()

        # If we just resumed from paused state, we don't need to continue with checking for new tracks
        # if self.__was_last_player_event_paused:
        #   self.__was_last_player_event_paused = False
        #   logging.debug(f'Ignoring resume event for {new_media_player_state.track_title}')
        #   return

        # Check if the track has changed or not
        is_same_track = None

        if not self.__current_scrobble:
            # This is the first track
            is_same_track = False
        else:
            is_same_track = (self.__current_scrobble.track_title
                             == new_media_player_state.track_title
                             and self.__current_scrobble.artist_name
                             == new_media_player_state.artist_name
                             and self.__current_scrobble.album_title
                             == new_media_player_state.album_title)

        # Submit the current scrobble if it hit the scrobbling threshold
        if not is_same_track:

            # # Make sure that the current track has finished playing if it's being looped
            # if is_same_track and not self.__was_last_player_event_paused:
            #   track_duration = self.__current_track_crop.finish - self.__current_track_crop.start
            #   margin_of_error = self.__MEDIA_PLAYER_POLLING_INTERVAL + 1

            #   if (track_duration - self.__furthest_player_position_reached) > margin_of_error:
            #     # Track didn't finish playing
            #     return

            # Submit current scrobble to Last.fm
            if self.__should_submit_current_scrobble:
                self.__submit_scrobble(self.__current_scrobble)

        # Load new track data into current scrobble
            self.__update_current_scrobble(new_media_player_state)

    def __handle_media_player_paused(self) -> None:
        '''Handle media player pause event'''

        # Update playback indicator
        self.is_player_paused = True
        self.is_player_paused_changed.emit()
        self.__was_last_player_event_paused = True

    # --- Qt Properties ---

    applicationReference = QtCore.Property(
        type=ApplicationViewModel,
        fget=lambda self: self.__application_reference,
        fset=set_application_reference)

    isEnabled = QtCore.Property(type=bool,
                                fget=lambda self: self.__is_enabled,
                                fset=set_is_enabled,
                                notify=is_enabled_changed)

    currentScrobble = QtCore.Property(
        type='QVariant',
        fget=lambda self: asdict(self.__current_scrobble)
        if self.__current_scrobble else None,
        notify=current_scrobble_data_changed)

    scrobblePercentage = QtCore.Property(
        type=float,
        fget=lambda self: self.__current_scrobble_percentage,
        notify=scrobble_percentage_changed)

    isSpotifyPluginAvailable = QtCore.Property(
        type=bool,
        fget=lambda self: self.__is_spotify_plugin_available,
        notify=is_spotify_plugin_available_changed)

    isUsingMockPlayer = QtCore.Property(
        type=bool,
        fget=lambda self: isinstance(self.__media_player, MockPlayerPlugin),
        notify=is_using_mock_player_changed)

    isDiscordRichPresenceEnabled = QtCore.Property(
        type=bool,
        fget=lambda self: self.__is_discord_rpc_enabled,
        fset=set_is_discord_rich_presence_enabled,
        notify=is_discord_rich_presence_enabled_changed)

    selectedScrobbleIndex = QtCore.Property(
        type=int,
        fget=get_selected_scrobble_index,
        fset=set_selected_scrobble_index,
        notify=selected_scrobble_index_changed)

    isLoading = QtCore.Property(type=bool,
                                fget=lambda self: self.__is_loading,
                                notify=is_loading_changed)

    mediaPlayerName = QtCore.Property(
        type=str,
        fget=lambda self: self.__media_player.MEDIA_PLAYER_NAME,
        notify=media_player_name_changed)
Exemplo n.º 17
0
class DiscordRPC(Thread):
    """
        Thread dédié au Discord Rich Presence
    """

    RPC = ""
    DRPCisEnabled = False
    isStopped = False
    startTime = int(time.time())
    timeBuffer = 5

    def run(self):
        try:
            self.RPC = Presence(
                int(
                    bytearray.fromhex(
                        '363233383936373835363035333631363634').decode()))
            self.RPC.connect()
        except (InvalidPipe):
            self.DRPCisEnabled = False
        else:
            self.DRPCisEnabled = True
            while (not self.isStopped):
                if True:
                    if (self.timeBuffer == 5):
                        nameList = ""
                        nbDof = 0
                        namePerso = self.__countWindows()
                        sizePerso = len(namePerso)
                        if sizePerso >= 1:
                            for i in range(sizePerso):
                                if not "Dofus" in namePerso[i][0]:
                                    nameList += namePerso[i][0]
                                    nbDof += 1
                                    if not i == sizePerso - 1:
                                        nameList += ", "
                                else:
                                    nameList = "Se connecte..."
                        else:
                            nameList = "Se connecte..."
                        self.timeBuffer = 0
                    else:
                        self.timeBuffer += 1

                    modifier = ""
                    if config["General"]["retro_mode"] == "True":
                        modifier = "retro"

                    message = "Joue avec 0 comptes :"
                    if nbDof > 1 or nbDof == 0:
                        message = "Joue avec {0} comptes {1} :".format(
                            nbDof, modifier)
                    else:
                        message = "Joue avec 1 compte {0} :".format(modifier)

                    self.RPC.update(details=message, \
                                    large_image="header", \
                                    small_image="dofuslogo", \
                                    small_text="Dofus", \
                                    state=nameList, \
                                    start=self.startTime)
                else:
                    # En cas de déconnexion
                    self.run()
                    self.DRPCisEnabled = False
                sleep(1)

    def __countWindows(self):
        namePerso = []
        top_windows = []
        win32gui.EnumWindows(windowEnumerationHandler, top_windows)
        for i in top_windows:
            if "Dofus" in i[1]:
                namePerso.append(i[1].split(' - '))
        return namePerso

    def stop(self):
        "Arrête le thread"
        if (self.DRPCisEnabled):
            self.RPC.close()
        self.isStopped = True
Exemplo n.º 18
0
class MainWindow(ThemedTk):
    """
    Child class of tk.Tk that creates the main windows of the parser.
    Creates all frames that are necessary for the various functions of
    the parser and provides exit-handling.
    """

    def __init__(self, raven: RavenClient):
        self.raven = variables.raven = raven
        self.width = 800 if sys.platform != "linux" else 825
        self.height = 425 if sys.platform != "linux" else 450
        # Initialize window
        ThemedTk.__init__(self)
        self.set_attributes()
        self.update_scaling()
        self.open_debug_window()
        self.finished = False
        variables.main_window = self
        self.style = ttk.Style()
        self.set_icon()
        self.set_variables()
        self.config_style()
        self.discord = DiscordClient()
        # Get the default path for CombatLogs and the Installation path
        self.default_path = variables.settings["parsing"]["path"]
        # Set window properties and create a splash screen from the splash_screen class
        self.withdraw()
        self.splash = BootSplash(self)
        self.splash.label_var.set("Building widgets...")
        self.protocol("WM_DELETE_WINDOW", self.exit)
        # Add a notebook widget with various tabs for the various functions
        self.notebook = ttk.Notebook(self, height=420, width=self.width)
        self.file_tab_frame = ttk.Frame(self.notebook)
        self.realtime_tab_frame = ttk.Frame(self.notebook)
        self.settings_tab_frame = ttk.Frame(self.notebook)
        self.characters_frame = CharactersFrame(self.notebook, self)
        self.file_select_frame = FileFrame(self.file_tab_frame, self)
        self.middle_frame = StatsFrame(self.file_tab_frame, self)
        self.ship_frame = ShipFrame(self.middle_frame.notebook)
        self.middle_frame.notebook.add(self.ship_frame, text="Ship")
        self.realtime_frame = RealTimeFrame(self.realtime_tab_frame, self)
        self.settings_frame = SettingsFrame(self.settings_tab_frame, self)
        self.builds_frame = BuildsFrame(self.notebook, self)
        self.toolsframe = ToolsFrame(self.notebook)
        self.strategies_frame = StrategiesFrame(self.notebook)
        self.chat_frame = ChatFrame(self.notebook, self)
        # Pack the frames and put their widgets into place
        self.grid_widgets()
        self.child_grid_widgets()
        # Add the frames to the Notebook
        self.setup_notebook()
        # Update the files in the file_select frame
        self.splash.label_var.set("Parsing files...")
        self.notebook.grid(column=0, row=0, padx=2, pady=2)
        self.file_select_frame.update_files()
        self.settings_frame.update_settings()
        # Check for updates
        self.splash.label_var.set("Checking for updates...")
        self.update()
        check_update()
        # Synchronize with Discord Bot Server
        if settings["sharing"]["enabled"] is True:
            self.splash.label_var.set("Synchronizing with Discord Bot Server...")
            self.update()
            self.discord.send_files(self)
        # Discord Rich Presence
        if settings["realtime"]["drp"] is True:
            self.rpc = Presence(436173115064713216)
            try:
                self.rpc.connect()
            except Exception:
                messagebox.showwarning("Error", "Discord Rich Presence failed to connect.")
                self.rpc = None
        else:
            self.rpc = None
        self.update_presence()
        # Give focus to the main window
        self.deiconify()
        self.finished = True
        self.splash.destroy()
        self.splash = None
        # Mainloop start (main.py)

    def update_presence(self):
        """Update to the basic GSF Parser presence"""
        if self.rpc is None:
            return
        assert isinstance(self.rpc, Presence)
        self.rpc.update(state="Not real-time results", large_image="logo_green_png")

    def grid_widgets(self):
        """Grid all widgets in the frames"""
        self.file_select_frame.grid(column=1, row=1, sticky="nswe")
        self.middle_frame.grid(column=2, row=1, sticky="nswe", padx=5, pady=5)
        self.realtime_frame.grid()
        self.settings_frame.grid()

    def child_grid_widgets(self):
        """Configure the child widgets of the Frames in grid geometry"""
        self.file_select_frame.grid_widgets()
        self.middle_frame.grid_widgets()
        self.realtime_frame.grid_widgets()
        self.ship_frame.grid_widgets()
        self.settings_frame.grid_widgets()
        self.builds_frame.grid_widgets()
        self.characters_frame.grid_widgets()
        self.toolsframe.grid_widgets()
        self.file_select_frame.clear_data_widgets()
        self.strategies_frame.grid_widgets()

    def setup_notebook(self):
        """Add all created frames to the notebook widget"""
        self.notebook.add(self.file_tab_frame, text="File parsing")
        self.notebook.add(self.realtime_tab_frame, text="Real-time parsing")
        self.notebook.add(self.chat_frame, text="Chat Logger")
        self.notebook.add(self.characters_frame, text="Characters")
        self.notebook.add(self.builds_frame, text="Builds")
        self.notebook.add(self.strategies_frame, text="Strategies")
        self.notebook.add(self.toolsframe, text="Tools")
        self.notebook.add(self.settings_tab_frame, text="Settings")

    def set_attributes(self):
        """
        Setup various window attributes:
        - Resizability
        - Window title
        - WM_DELETE_WINDOW redirect
        - DPI scaling
        - Screenshot functionality
        """
        self.resizable(width=False, height=False)
        self.wm_title("GSF Parser")
        self.protocol("WM_DELETE_WINDOW", self.exit)
        self.geometry("{}x{}".format(*self.get_window_size()))
        self.bind("<F10>", self.screenshot)

    @staticmethod
    def set_variables():
        """Set program global variables in the shared variables module"""
        variables.colors.set_scheme(variables.settings["gui"]["event_scheme"])

    def get_scaling_factor(self):
        """Return the DPI scaling factor (float)"""
        return self.winfo_pixels("1i") / 72.0

    def get_window_size(self):
        """Return the window size, taking scaling into account"""
        factor = self.winfo_pixels("1i") / 96.0
        size_x = int(self.width * factor)
        size_y = int(self.height * factor)
        return size_x, size_y

    def update_scaling(self):
        """Update the DPI scaling of the child widgets of the window"""
        self.tk.call('tk', 'scaling', '-displayof', '.', self.get_scaling_factor())

    def open_debug_window(self):
        """Open a DebugWindow instance if that setting is set to True"""
        if variables.settings["gui"]["debug"] is True:
            DebugWindow(self, title="GSF Parser Debug Window", stdout=True, stderr=True)
        return

    def config_style(self):
        """Configure the style: theme, font and foreground color"""
        # print("[MainWindow] PNG-based theme: {}".format(self.png_support))
        self.set_theme("arc")
        # print("[MainWindow] ThemedWidget Version: {}".format(getattr(self, "VERSION", None)))
        self.style.configure('.', font=("Calibri", 10))
        self.style.configure('TButton', anchor="w")
        self.style.configure('Toolbutton', anchor="w")
        self.style.configure('.', foreground=settings["gui"]["color"])
        self.setup_tk_toplevel_hooks()

    def setup_tk_toplevel_hooks(self):
        """Change the default background color of Tk and Toplevel"""
        color = self.style.lookup("TFrame", "background")
        self.config(background=color)
        __init__original = tk.Toplevel.__init__

        def __init__toplevel(*args, **kwargs):
            kwargs.setdefault("background", color)
            __init__original(*args, **kwargs)

        tk.Toplevel.__init__ = __init__toplevel

    def set_icon(self):
        """Changes the window's icon"""
        icon_path = os.path.join(get_assets_directory(), "logos", "icon_green.ico")
        icon = PhotoImage(Image.open(icon_path))
        self.tk.call("wm", "iconphoto", self._w, icon)

    def exit(self):
        """
        Function to cancel any running tasks and call any functions to
        save the data in use before actually closing the
        GSF Parser
        :return: SystemExit(0)
        """
        if self.destroy():
            sys.exit()

    def screenshot(self, *args):
        """Take a screenshot of the GSF Parser window and save"""
        x = self.winfo_x()
        y = self.winfo_y()
        result_box = (x, y, self.winfo_reqwidth() + x + 13, self.winfo_reqheight() + y + 15)
        file_name = os.path.join(
            get_temp_directory(),
            "screenshot_" + datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + ".png")
        with mss() as sct:
            image = sct.grab(result_box)
        image = Image.frombytes("RGB", image.size, image.rgb)
        image.save(file_name)

    def destroy(self):
        """
        Destroy the MainWindow after checking for running processes

        Destroying the MainWindow is not allowed while connected to a
        Strategy Server or running one, the user will have to exit the
        server first.
        """
        if self.strategies_frame.settings is not None:
            if self.strategies_frame.settings.server:
                messagebox.showerror("Error", "You cannot exit the GSF Parser while running a Strategy Server.")
                return False
            if self.strategies_frame.settings.client:
                messagebox.showerror("Error", "You cannot exit the GSF Parser while connected to a Strategy Server.")
                return False
            self.strategies_frame.settings.destroy()
        if self.realtime_frame.parser is not None:
            if self.realtime_frame.parser.is_alive():
                self.realtime_frame.stop_parsing()
        ThemedTk.destroy(self)
        if self.rpc is not None:
            self.rpc.update()
            self.rpc.close()
        return True

    def report_callback_exception(self, exc, val, tb):
        """Redirect Exceptions in the mainloop to RavenClient"""
        if not os.path.exists("development"):
            self.raven.captureException()
        ThemedTk.report_callback_exception(self, exc, val, tb)
Exemplo n.º 19
0
    "Authorization": "Bearer " + bearer,
    "User-Agent":
    "Mozilla/5.0 (Linux; Android 5.1.1; SM-G965N Build/R16NW.G965NKSU1ARC7; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.136 Mobile Safari/537.36",
    "Content-Type": "application/json",
    "Referer": "https://web.sd.lp1.acbaa.srv.nintendo.net/players/chat",
    "Accept-Encoding": "gzip, deflate",
    "Accept-Language": "en-US,en;q=0.9",
    "Cookie": cookies,
    "X-Requested-With": "com.nintendo.znca"
}

print(user)
while True:
    x = requests.post(url, data=data, headers=headers)

    if x.status_code == 403:
        print("you are offline")
        if dis:
            discorddiscord.close()
            dis = False
    if x.status_code == 201:
        print("you are online")
        if not dis:
            discorddiscord.connect()
            discorddiscord.update(
                state=user.json()["users"][0]["land"]["name"],
                details=user.json()["users"][0]["name"])
            dis = True
    print(x)
    sleep(10)
Exemplo n.º 20
0
class DiscordPresence(object):
    """This class provides rich presence integration
       with Discord for games.
    """

    def __init__(self):
        self.available = bool(PyPresence)
        self.game_name = ""
        self.runner_name = ""
        self.last_rpc = 0
        self.rpc_interval = 60
        self.presence_connected = False
        self.rpc_client = None
        self.client_id = None
        self.custom_game_name = ''
        self.show_runner = True
        self.custom_runner_name = ''
        self.rpc_enabled = True

    def load_from_config(self, config):
        """Loads
        """

    def ensure_discord_connected(self):
        """Make sure we are actually connected before trying to send requests"""
        logger.debug("Ensuring connected.")
        if self.presence_connected:
            logger.debug("Already connected!")
        else:
            logger.debug("Creating Presence object.")
            self.rpc_client = PyPresence(self.client_id)
            try:
                logger.debug("Attempting to connect.")
                self.rpc_client.connect()
                self.presence_connected = True
            except Exception as ex:
                logger.exception("Unable to reach Discord.  Skipping update: %s", ex)
                self.ensure_discord_disconnected()
        return self.presence_connected

    def ensure_discord_disconnected(self):
        """Ensure we are definitely disconnected and fix broken event loop from pypresence"""
        logger.debug("Ensuring disconnected.")
        if self.rpc_client is not None:
            try:
                self.rpc_client.close()
            except Exception as e:
                logger.exception("Unable to close Discord RPC connection: %s", e)
            if self.rpc_client.sock_writer is not None:
                try:
                    logger.debug("Forcefully closing sock writer.")
                    self.rpc_client.sock_writer.close()
                except Exception:
                    logger.exception("Sock writer could not be closed.")
            try:
                logger.debug("Forcefully closing event loop.")
                self.rpc_client.loop.close()
            except Exception:
                logger.debug("Could not close event loop.")
            try:
                logger.debug("Forcefully replacing event loop.")
                self.rpc_client.loop = None
                asyncio.set_event_loop(asyncio.new_event_loop())
            except Exception as e:
                logger.exception("Could not replace event loop: %s", e)
            try:
                logger.debug("Forcefully deleting RPC client.")
                self.rpc_client = None
            except Exception as ex:
                logger.exception(ex)
        self.rpc_client = None
        self.presence_connected = False

    def update_discord_rich_presence(self):
        """Dispatch a request to Discord to update presence"""
        if int(time.time()) - self.rpc_interval < self.last_rpc:
            logger.debug("Not enough time since last RPC")
            return
        if self.rpc_enabled:
            self.last_rpc = int(time.time())
            connected = self.ensure_discord_connected()
            if not connected:
                return
            try:
                state_text = "via %s" % self.runner_name if self.show_runner else "  "
                logger.info("Attempting to update Discord status: %s, %s",
                            self.game_name, state_text)
                self.rpc_client.update(details="Playing %s" % self.game_name, state=state_text)
            except PyPresenceException as ex:
                logger.error("Unable to update Discord: %s", ex)

    def clear_discord_rich_presence(self):
        """Dispatch a request to Discord to clear presence"""
        if self.rpc_enabled:
            connected = self.ensure_discord_connected()
            if connected:
                try:
                    logger.info('Attempting to clear Discord status.')
                    self.rpc_client.clear()
                except PyPresenceException as e:
                    logger.error("Unable to clear Discord: %s", e)
                    self.ensure_discord_disconnected()
Exemplo n.º 21
0
    def start_siva(self, interface):

        RPC = Presence('596381603522150421')
        RPC.connect()

        config = self.configurator.load()

        platform_enum_conversion_table = self.configurator.get_conversion_table(
            "platform")

        requests = Requests(config["api_token"], interface)
        decoder = Decoder(self.directory, requests.headers)
        if config["platform"].lower() == "battlenet":
            return interface.error("5")
        user_membership_type = platform_enum_conversion_table[
            config["platform"]]
        if config.get("id_search", None) == False:
            user_membership_data = requests.get(
                MEMBERSHIP_ID_LOOKUP.format(user_membership_type,
                                            config["username"]))["Response"]
            potential_users_list = []
            users_list = []
            for user_data in user_membership_data:
                if True:  #user_data["displayName"] == config["username"]: # Checking to see if its the same case, as of 9/9/19 Bungie API doesnt respect case-sensitivity.
                    potential_users_list.append(user_data)
                    users_list.append([
                        "{0} | {1} | {2}".format(user_data["displayName"],
                                                 config["platform"],
                                                 user_data["membershipId"]),
                        user_data["membershipId"]
                    ])

            self.user_membership_id = None

            if len(potential_users_list) == 0:
                return interface.error("2")
            elif len(potential_users_list) > 1:
                interface.create_pick_account_interface(users_list)
            elif len(potential_users_list) == 1:
                user_membership_data = potential_users_list[-1]
                self.user_membership_id = user_membership_data["membershipId"]

        if config.get("id_search", None) == True:
            self.user_membership_id = config["username"]

        while self.user_membership_id == None:
            time.sleep(1)

        while True:
            try:
                if not self.run:
                    RPC.close()
                    return

                last_played_character = get_last_played_id(
                    user_membership_type, self.user_membership_id, requests)
                image_conversion_table = self.configurator.get_conversion_table(
                    "image")
                state_conversion_table = self.configurator.get_conversion_table(
                    "state")
                details_conversion_table = self.configurator.get_conversion_table(
                    "details")

                activity_data = requests.get(
                    ACTIVITY_LOOKUP.format(user_membership_type,
                                           self.user_membership_id,
                                           last_played_character))
                activity_hash = activity_data["Response"]["activities"][
                    "data"]["currentActivityHash"]
                activity_data_decoded = decoder.decode_hash(
                    activity_hash, "DestinyActivityDefinition", self.language)
                activity_data_decoded_en = decoder.decode_hash(
                    activity_hash, "DestinyActivityDefinition", "en")

                mode_hash = activity_data["Response"]["activities"]["data"][
                    "currentActivityModeHash"]
                mode_data = decoder.decode_hash(
                    mode_hash, "DestinyActivityModeDefinition", self.language)

                # Default Arguments
                orbit_translation = self.configurator.get_conversion_table(
                    "orbit_translation")[self.language]
                details, state = orbit_translation, orbit_translation
                picture, timer = "in_orbit", time.time()

                if mode_data != None:
                    details = mode_data["displayProperties"]["name"]
                    state = activity_data_decoded["displayProperties"].get(
                        "name", "In Orbit")
                    picture = activity_data_decoded_en["displayProperties"][
                        "name"].lower().replace(" ", "_")
                    timer = convert_datestring_to_epoch(
                        activity_data["Response"]["activities"]["data"]
                        ["dateActivityStarted"])

                    for char in [",", "(", ")", ":"]:
                        picture = picture.replace(char, "")

                    if activity_data_decoded["isPvP"]:
                        details = "Crucible, " + mode_data[
                            "displayProperties"]["name"]
                        picture = "crucible"

                    if activity_data_decoded_en["displayProperties"][
                            "name"] == "Classified":
                        details = activity_data_decoded["displayProperties"][
                            "name"]
                        state = activity_data_decoded["displayProperties"][
                            "description"]
                        picture = "classified"

                RPC.update(
                    state=state_conversion_table.get(state, state),
                    details=details_conversion_table.get(details, details),
                    large_image=image_conversion_table.get(picture, picture),
                    small_image="destiny2_logo",
                    small_text="Destiny 2",
                    start=timer)
                time.sleep(30)
            except Exception as e:
                print(traceback.format_exc())
Exemplo n.º 22
0
class DiscordStatusMessage(EventPlugin):
    PLUGIN_ID = _("Discord status message")
    PLUGIN_NAME = _("Discord Status Message")
    PLUGIN_DESC = _("Change your Discord status message according to what "
                    "you're currently listening to.")
    VERSION = VERSION

    def __init__(self):
        self.song = None
        self.discordrp = None

    def update_discordrp(self, details, state=None):
        if not self.discordrp:
            try:
                self.discordrp = Presence(QL_DISCORD_RP_ID, pipe=0)
                self.discordrp.connect()
            except (DiscordNotFound, ConnectionRefusedError):
                self.discordrp = None

        if self.discordrp:
            try:
                self.discordrp.update(details=details,
                                      state=state,
                                      large_image=QL_LARGE_IMAGE)
            except InvalidID:
                # XXX Discord was closed?
                self.discordrp = None

    def handle_play(self):
        if self.song:
            details = Pattern(discord_status_config.rp_line1) % self.song
            state = Pattern(discord_status_config.rp_line2) % self.song

            # The details and state fields must be atleast 2 characters.
            if len(details) < 2:
                details = None

            if len(state) < 2:
                state = None

            self.update_discordrp(details, state)

    def handle_paused(self):
        self.update_discordrp(details=_("Paused"))

    def handle_unpaused(self):
        if not self.song:
            self.song = app.player.song
        self.handle_play()

    def plugin_on_song_started(self, song):
        self.song = song
        if not app.player.paused:
            self.handle_play()

    def plugin_on_paused(self):
        self.handle_paused()

    def plugin_on_unpaused(self):
        self.handle_unpaused()

    def enabled(self):
        if app.player.paused:
            self.handle_paused()
        else:
            self.handle_unpaused()

    def disabled(self):
        if self.discordrp:
            self.discordrp.clear()
            self.discordrp.close()
            self.discordrp = None
            self.song = None

    def PluginPreferences(self, parent):
        vb = Gtk.VBox(spacing=6)

        def rp_line1_changed(entry):
            discord_status_config.rp_line1 = entry.get_text()
            if not app.player.paused:
                self.handle_play()

        def rp_line2_changed(entry):
            discord_status_config.rp_line2 = entry.get_text()
            if not app.player.paused:
                self.handle_play()

        status_line1_box = Gtk.HBox(spacing=6)
        status_line1_box.set_border_width(3)

        status_line1 = Gtk.Entry()
        status_line1.set_text(discord_status_config.rp_line1)
        status_line1.connect('changed', rp_line1_changed)

        status_line1_box.pack_start(Gtk.Label(label=_("Status Line #1")),
                                    False, True, 0)
        status_line1_box.pack_start(status_line1, True, True, 0)

        status_line2_box = Gtk.HBox(spacing=3)
        status_line2_box.set_border_width(3)

        status_line2 = Gtk.Entry()
        status_line2.set_text(discord_status_config.rp_line2)
        status_line2.connect('changed', rp_line2_changed)

        status_line2_box.pack_start(Gtk.Label(label=_('Status Line #2')),
                                    False, True, 0)
        status_line2_box.pack_start(status_line2, True, True, 0)

        vb.pack_start(status_line1_box, True, True, 0)
        vb.pack_start(status_line2_box, True, True, 0)

        return vb
Exemplo n.º 23
0
            logging.info("Updated Discord RPC Data:")
            logging.info("Large Text: " + str(rpc_data['large_text']))
            logging.info("Small Text: " + str(rpc_data['small_text']))
            logging.info("State: " + str(rpc_data['state']))
            logging.debug("Raw input into Discord RPC: " + str(rpc_data))

            time.sleep(10)
    except InvalidID:
        # When script has a bad Client ID
        logging.error(
            "Unable to connect to Discord! Is your Client ID correct?")
        raise SystemExit(1)
    except:
        # When either Discord or a supported Adobe product is not detected...
        # Clear rpc_data
        rich_presence.clear()
        logging.exception(
            "Not detecting Adobe applications! Are you sure your running a compatible application?")
        # Update Discord
        time.sleep(1)
        update_loop()

if __name__ == "__main__":
    # Main instance
    try:
        logging.info("Started Adobe RPC!")
        connect_loop()
    except:
        rich_presence.close()
        logging.info("Stopped Adobe RPC!")
Exemplo n.º 24
0
def main():
    consoleargs = parser.parse_args()

    switch_ip = consoleargs.ip
    client_id = consoleargs.client_id

    if not checkIP(switch_ip):
        print('Invalid IP')
        exit()

    rpc = Presence(str(client_id))
    try:
        rpc.connect()
        rpc.clear()
    except:
        print('Unable to start RPC!')

    switch_server_address = (switch_ip, TCP_PORT)
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    try:
        sock.connect(switch_server_address)
        print('Successfully connected to %s' % switch_ip + ':' + str(TCP_PORT))
    except:
        print('Error connection to %s refused' % switch_ip + ':' + str(TCP_PORT))
        exit()

    lastProgramName = ''
    startTimer = 0

    while True:
        data = None
        try:
            data = sock.recv(628)
        except:
            print('Could not connect to Server! Retrying...')
            startTimer = 0
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            try:
                sock.connect(switch_server_address)
                print('Successfully reconnected to %s' %
                      repr(switch_server_address))
            except:
                print('Error reconnection to %s refused' %
                      repr(switch_server_address))
                exit()
        title = Title(data)
        if title.magic == PACKETMAGIC:
            if lastProgramName != title.name:
                startTimer = int(time.time())
            if consoleargs.ignore_home_screen and title.name == 'Home Menu':
                rpc.clear()
            else:
                smallimagetext = ''
                largeimagekey = ''
                details = ''
                largeimagetext = title.name
                if int(title.pid) != PACKETMAGIC:
                    smallimagetext = 'SwitchPresence-Rewritten'
                    if title.name not in switchOverrides:
                        largeimagekey = iconFromPid(title.pid)
                        details = 'Playing ' + str(title.name)
                    else:
                        orinfo = switchOverrides[title.name]
                        largeimagekey = orinfo['CustomKey'] or iconFromPid(title.pid)
                        details = orinfo['CustomPrefix'] or 'Playing'
                        details += ' ' + title.name
                else:
                    smallimagetext = 'QuestPresence'
                    if title.name not in questOverrides:
                        largeimagekey = title.name.lower().replace(' ', '')
                        details = 'Playing ' + title.name
                    else:
                        orinfo = questOverrides[title.name]
                        largeimagekey = orinfo['CustomKey'] or title.name.lower().replace(
                            ' ', '')
                        details = orinfo['CustomPrefix'] or 'Playing'
                        details += ' ' + title.name
                if not title.name:
                    title.name = ''
                lastProgramName = title.name
                rpc.update(details=details, start=startTimer, large_image=largeimagekey,
                        large_text=largeimagetext, small_text=smallimagetext)
            time.sleep(1)
        else:
            time.sleep(1)
            rpc.clear()
            rpc.close()
            sock.close()
            exit()
Exemplo n.º 25
0
class bot:
    def __init__(self):
        self.secondsuntilrestart = 3600  # this is how many seconds until the bot will restart. This is to stop the bot from crashing. Decrease this if you have crashes.
        self.xpamount = 0  # how much xp the bot has earnt during runtime
        self.restarted = 0  # how many times the bot has restarted during runtime
        self.gamesplayed = 0  # num of games played during runtime
        self.version = "Valbot v1.8.1"  # variable str to change valbot version name in outputs
        self.foundwebhook = False
        title = "title " + self.version
        os.system(title)
        os.system('mode con: cols=54 lines=18')
        if os.path.exists("webhook.config"):
            try:
                f = open('webhook.config', 'r')
                self.hookline = f.readline()
                f.close()
                self.foundwebhook = True
            except Exception:
                self.foundwebhook = False

        try:  # if cant connect to discord (if it isnt open for example), bot doesnt crash
            self.RPC = Presence(
                client_id="772841390467711041")  # discord rpc client id

            try:
                self.RPC.close()
            except Exception:
                pass
            self.RPC.connect()  # connects to rpc

        except Exception:
            pass

        print(
            Fore.RED + """
          ╔╗  ╔╗╔═══╗╔╗   ╔══╗ ╔═══╗╔════╗
          ║╚╗╔╝║║╔═╗║║║   ║╔╗║ ║╔═╗║║╔╗╔╗║
          ╚╗║║╔╝║║ ║║║║   ║╚╝╚╗║║ ║║╚╝║║╚╝
           ║╚╝║ ║╚═╝║║║ ╔╗║╔═╗║║║ ║║  ║║  
           ╚╗╔╝ ║╔═╗║║╚═╝║║╚═╝║║╚═╝║  ║║  
            ╚╝  ╚╝ ╚╝╚═══╝╚═══╝╚═══╝  ╚╝
                                   """ + Style.NORMAL + Fore.RED,
            self.version.replace("Valbot ", "") + Style.RESET_ALL)

        print(Style.RESET_ALL)
        print(Style.RESET_ALL + Fore.RED +
              "                 By Fums & WolfAnto")
        print(Style.RESET_ALL)
        print(Style.RESET_ALL + Fore.RED +
              "——————————————————————————————————————————————————————")

    def restartbot(self):  # restarts the bot after 2 hours
        print(Style.RESET_ALL)
        print(Fore.RED + " [!] BOT IS RESTARTING AFTER 2 HOURS")
        try:
            self.RPC.close()
        except Exception:
            pass
        if self.foundwebhook == True:
            try:
                webhook = DiscordWebhook(url=self.hookline, username="******")
                embed = DiscordEmbed(
                    color=0xFF0000,
                    title="Restart Notification",
                    description=
                    "Bot has been running for more than 2 hours\nBot will now be restarted\nThis is to prevent crashes"
                )

                embed.set_author(
                    name=self.version,
                    url="https://github.com/MrFums/Valbot",
                    icon_url=
                    "https://raw.githubusercontent.com/MrFums/ValbotAssets/main/jett.png",
                )
                embed.set_footer(text=self.version.replace("Valbot", ""))
                embed.set_timestamp()
                webhook.add_embed(embed)
                webhook.execute()
            except Exception:
                print(Fore.RED +
                      " [!] TRIED TO SEND A WEBHOOK BUT IT IS NOT SETUP")
        time.sleep(1)
        os.startfile("restart.py"
                     )  # starts the restart script which reopens this script
        time.sleep(3)
        quit()  # quits this runtime of the script

    def valorantrunning(self):
        found = False
        print(Fore.YELLOW, "[-] CHECKING IF VALORANT IS RUNNING")
        print(Style.RESET_ALL)
        time.sleep(2)

        for proc in psutil.process_iter():
            if proc.name() == "VALORANT-Win64-Shipping.exe":
                found = True
                break

        if not found:
            print(Fore.RED, "[!] VALORANT IS NOT RUNNING")
            print(Style.RESET_ALL)
            self.startvalorant()
        else:
            print(Fore.GREEN, "[√] VALORANT IS RUNNING")
            time.sleep(2)
            self.playbutton()

    def startvalorant(self):

        activeactivity = "Loading Valorant"

        if self.foundwebhook == True:
            try:
                webhook = DiscordWebhook(url=self.hookline, username="******")
                embed = DiscordEmbed(
                    color=0xFF0000,
                    title="Restart Notification",
                    description=
                    "Possible error with Valorant\nGame will now be restarted")

                embed.set_author(
                    name=self.version,
                    url="https://github.com/MrFums/Valbot",
                    icon_url=
                    "https://raw.githubusercontent.com/MrFums/ValbotAssets/main/jett.png",
                )
                embed.set_footer(text=self.version.replace("Valbot", ""))
                embed.set_timestamp()
                webhook.add_embed(embed)
                webhook.execute()
            except Exception:
                print(Fore.RED +
                      " [!] TRIED TO SEND A WEBHOOK BUT IT IS NOT SETUP")

        earned = "{:,}".format(self.xpamount)

        try:

            self.RPC.update(state=("Earned " + earned + " XP"),
                            start=time.time(),
                            large_image="valbotnew",
                            large_text=self.version,
                            details=activeactivity)

        except Exception:
            pass

        PROCNAME = "VALORANT-Win64-Shipping.exe"
        for proc in psutil.process_iter():
            try:
                if proc.name().lower() == PROCNAME.lower():
                    proc.kill()
                    print(Fore.YELLOW, "[-] KILLING THE VALORANT PROCESS")
                    time.sleep(10)
                    return True
            except (psutil.NoSuchProcess, psutil.AccessDenied,
                    psutil.ZombieProcess):
                return False
                print(Fore.RED, "[!] COULD NOT KILL THE VALORANT PROCESS")

        print(Style.RESET_ALL + Fore.YELLOW, "[-] STARTING VALORANT")
        print(Style.RESET_ALL)
        root = str(pathlib.Path(__file__).parent.absolute())
        fullpath = root + "\Valorant.lnk"
        vallnk = Path(fullpath)
        if vallnk.is_file():
            # file exists
            time.sleep(5)
            os.startfile("Valorant.lnk")

        else:
            print(Style.RESET_ALL,
                  Fore.RED + "[!] COULD NOT FIND A VALORANT SHORTCUT")
            print(Style.RESET_ALL)
        time.sleep(8)
        self.restarted += 1
        self.valorantrunning()

    def playbutton(self):

        now = time.time()

        future = now + 720

        activeactivity = "At Menu"

        earned = "{:,}".format(self.xpamount)

        try:

            self.RPC.update(state=("Earned " + earned + " XP"),
                            start=time.time(),
                            large_image="valbotnew",
                            large_text=self.version,
                            details=activeactivity)

        except Exception:
            pass
        print(Style.RESET_ALL)
        print(Fore.YELLOW + " [-] SEARCHING FOR PLAY BUTTON")
        time.sleep(.5)
        while True:
            if keyboard.is_pressed('f3'):
                self.pause()
            if time.time() > future:
                # detects possible issue with valorant and restarts the game
                print(Style.RESET_ALL)
                print(Fore.RED + " [!] FOUND A POSSIBLE ERROR WITH VALORANT")
                self.startvalorant()
                break

            play = pyautogui.locateOnScreen("images/play.png", grayscale=True)
            play2 = pyautogui.locateOnScreen("images/play.png",
                                             confidence=0.6,
                                             grayscale=True)

            if play is not None or play2 is not None:
                print(Style.RESET_ALL)
                print(Fore.GREEN + " [√] DETECTED PLAY BUTTON")

                if play is not None:
                    time.sleep(1)
                    pyautogui.moveTo(play)
                    pyautogui.click(play)
                    time.sleep(2)
                    self.playbuttonclicked()

                if play2 is not None:
                    time.sleep(1)
                    pyautogui.moveTo(play2)
                    pyautogui.click(play2)
                    time.sleep(2)
                    self.playbuttonclicked()

    def deathmatchbutton(self):

        print(Style.RESET_ALL)
        print(Fore.YELLOW, "[-] SEARCHING FOR DEATHMATCH BUTTON")

        time.sleep(1)
        now = time.time()

        future = now + 45

        while True:
            if keyboard.is_pressed('f3'):
                self.pause()
            if time.time() > future:
                # detects possible issue with valorant and restarts the game
                print(Style.RESET_ALL)
                print(Fore.RED + " [!] FOUND A POSSIBLE ERROR WITH VALORANT")
                self.startvalorant()
                break

            deathmatch = pyautogui.locateOnScreen("images/deathmatch.png",
                                                  grayscale=True)
            deathmatch2 = pyautogui.locateOnScreen("images/deathmatch.png",
                                                   confidence=0.6,
                                                   grayscale=True)

            if deathmatch is not None or deathmatch2 is not None:
                print(Style.RESET_ALL)
                print(Fore.GREEN + " [√] DETECTED DEATHMATCH BUTTON")

                if deathmatch is not None:
                    pyautogui.moveTo(deathmatch)
                    pyautogui.click(deathmatch)
                    time.sleep(.5)
                    pyautogui.click(x=960, y=540)
                    self.deathmatchbuttonclicked()

                if deathmatch2 is not None:
                    pyautogui.moveTo(deathmatch2)
                    pyautogui.click(deathmatch2)
                    time.sleep(.5)
                    pyautogui.click(x=960, y=540)
                    self.deathmatchbuttonclicked()

                time.sleep(5)

    def deathmatchbuttonclicked(self):

        print(Style.RESET_ALL)
        print(Fore.YELLOW + " [-] DETECTING IF DEATHMATCH IS DETECTED")
        time.sleep(1)
        now = time.time()

        future = now + 120

        while True:
            if keyboard.is_pressed('f3'):
                self.pause()
            if time.time() > future:
                # detects possible issue with valorant and restarts the game
                print(Style.RESET_ALL)
                print(Fore.RED + " [!] FOUND A POSSIBLE ERROR WITH VALORANT")
                self.startvalorant()
                break

            ondeathmatch = pyautogui.locateOnScreen("images/ondeathmatch.png",
                                                    grayscale=True)
            ondeathmatch2 = pyautogui.locateOnScreen("images/ondeathmatch.png",
                                                     grayscale=True,
                                                     confidence=0.5)

            if ondeathmatch is not None or ondeathmatch2 is not None:

                if ondeathmatch is not None:
                    print(Style.RESET_ALL)
                    print(Fore.GREEN +
                          " [√] DETECTED THAT DEATHMATCH IS SELECTED")
                    time.sleep(1)
                    self.searchforgame()

                if ondeathmatch2 is not None:
                    print(Style.RESET_ALL)
                    print(Fore.GREEN +
                          " [√] DETECTED THAT DEATHMATCH IS SELECTED")
                    time.sleep(1)
                    self.searchforgame()

            if ondeathmatch is None or ondeathmatch2 is None:
                print(Style.RESET_ALL)
                print(Fore.RED +
                      " [!] DETECTED THAT DEATHMATCH IS NOT SELECTED")
                time.sleep(1)
                self.deathmatchbutton()

    def playbuttonclicked(self):

        print(Style.RESET_ALL)
        print(Fore.YELLOW + " [-] DETECTING IF PLAY BUTTON IS SELECTED")
        time.sleep(1)
        now = time.time()

        future = now + 120

        while True:
            if keyboard.is_pressed('f3'):
                self.pause()
            if time.time() > future:
                # detects possible issue with valorant and restarts the game
                print(Style.RESET_ALL)
                print(Fore.RED + " [!] FOUND A POSSIBLE ERROR WITH VALORANT")
                self.startvalorant()
                break

            onplay = pyautogui.locateOnScreen("images/onplay.png",
                                              grayscale=True)
            onplay2 = pyautogui.locateOnScreen("images/onplay.png",
                                               grayscale=True,
                                               confidence=0.5)

            if onplay is not None or onplay2 is not None:

                if onplay is not None:
                    print(Style.RESET_ALL)
                    print(Fore.GREEN +
                          " [√] DETECTED THAT PLAY BUTTON IS SELECTED")
                    time.sleep(1)
                    self.deathmatchbutton()

                if onplay2 is not None:
                    print(Style.RESET_ALL)
                    print(Fore.GREEN +
                          " [√] DETECTED THAT PLAY BUTTON IS SELECTED")
                    time.sleep(1)
                    self.deathmatchbutton()

            if onplay is None or onplay2 is None:
                print(Style.RESET_ALL)
                print(Fore.RED +
                      " [!] DETECTED THAT PLAY BUTTON IS NOT SELECTED")
                time.sleep(1)
                self.playbutton()

    def firststart(self):
        if os.path.exists("runtime_values"):
            try:
                f = open('runtime_values', 'r')
                for i, line in enumerate(f):
                    if i == 0:
                        self.xpamount = int(line.strip())
                    elif i == 1:
                        self.gamesplayed = int(line.strip())
                    elif i == 2:
                        self.restarted = int(line.strip())
                f.close()

                if os.path.exists("runtime_values"):
                    os.remove("runtime_values")

            except Exception:
                pass

        foundval = False
        for proc in psutil.process_iter():
            if proc.name() == "VALORANT-Win64-Shipping.exe":
                window = gw.getWindowsWithTitle('Valorant')[0]
                foundval = True
        if not foundval:
            self.startvalorant()

        if not window.isMaximized:
            window.maximize()
            self.firststart()

        response = requests.get(
            "https://api.github.com/repos/MrFums/Valbot/releases/latest")
        latest2 = response.json()["name"]
        latest = latest2.replace("Valbot v", "")
        latest = latest.replace(".", "")
        latest = int(latest)

        version = self.version.replace("Valbot v", "")
        version = version.replace(".", "")
        version = int(version)

        if version < latest:
            print(Style.RESET_ALL)
            print(
                Fore.RED, "[!] YOUR VERSION OF VALBOT (" +
                self.version.replace("Valbot v", "") + ") IS OUTDATED")
            print(Style.RESET_ALL)
            print(
                Fore.RED, "[!] PLEASE DOWNLOAD THE LATEST VERSION (" +
                latest2.replace("Valbot v", "") + ") FROM THE REPO")
            print(Style.RESET_ALL)
            if self.foundwebhook == True:
                try:
                    webhook = DiscordWebhook(url=self.hookline,
                                             username="******")
                    embed = DiscordEmbed(
                        color=0xFF0000,
                        title="Version Notification",
                        description=
                        "Your version of Valbot is outdated\nPlease download the newer version from the repo\nhttps://github.com/MrFums/Valbot"
                    )

                    embed.set_author(
                        name=self.version,
                        url="https://github.com/MrFums/Valbot",
                        icon_url=
                        "https://raw.githubusercontent.com/MrFums/ValbotAssets/main/jett.png",
                    )
                    embed.set_footer(text=self.version.replace("Valbot", ""))
                    embed.set_timestamp()
                    webhook.add_embed(embed)
                    webhook.execute()
                except Exception:
                    print(Fore.RED +
                          " [!] TRIED TO SEND A WEBHOOK BUT IT IS NOT SETUP")

            time.sleep(5)
        else:
            print(Style.RESET_ALL)
            print(
                Fore.GREEN, "[√] RUNNING LATEST VERSION (" +
                self.version.replace("Valbot v", "") + ") OF VALBOT")
        print(Style.RESET_ALL)
        print(Style.RESET_ALL)
        print(Fore.RED,
              Style.BRIGHT + "[!] SCHEDULED TO RESTART EVERY 2 HOURS")
        for i in range(15, -1, -1):
            print(Fore.RED,
                  Style.BRIGHT + "[!] BOT WILL BEGIN IN",
                  i,
                  "SECONDS                  ",
                  end='\r')
            sleep(1)

        print(Style.RESET_ALL)
        if self.foundwebhook:
            try:
                webhook = DiscordWebhook(url=self.hookline, username="******")
                embed = DiscordEmbed(
                    color=0xFFD700,
                    title="Start Notification",
                    description="Bot is starting in 15 seconds")

                embed.set_author(
                    name=self.version,
                    url="https://github.com/MrFums/Valbot",
                    icon_url=
                    "https://raw.githubusercontent.com/MrFums/ValbotAssets/main/jett.png",
                )
                embed.set_footer(text=self.version.replace("Valbot", ""))
                embed.set_timestamp()
                webhook.add_embed(embed)
                webhook.execute()
            except Exception:
                print(Fore.RED +
                      " [!] TRIED TO SEND A WEBHOOK BUT IT IS NOT SETUP")

        self.playbutton()

    def searchforgame(self):

        print(Style.RESET_ALL)
        print(Fore.YELLOW + " [-] SEARCHING FOR START BUTTON")

        now = time.time()

        future = now + 240

        while True:
            if keyboard.is_pressed('f3'):
                self.pause()
            if time.time() > future:
                # detects possible issue with valorant and restarts the game
                print(Style.RESET_ALL)
                print(Fore.RED + " [!] FOUND A POSSIBLE ERROR WITH VALORANT")
                self.startvalorant()
                break

            startbutton = pyautogui.locateOnScreen("images/start.png",
                                                   grayscale=True)
            start2 = pyautogui.locateOnScreen("images/start.png",
                                              confidence=0.6,
                                              grayscale=True)

            again = pyautogui.locateOnScreen("images/playagain.png",
                                             grayscale=True)
            again2 = pyautogui.locateOnScreen("images/playagain.png",
                                              confidence=0.6,
                                              grayscale=True)

            if startbutton is not None or start2 is not None or again is not None or again2 is not None:
                print(Style.RESET_ALL)
                print(Fore.GREEN + " [√] DETECTED START BUTTON")

                if startbutton is not None:
                    pyautogui.click(x=960, y=540)
                    time.sleep(.5)
                    pyautogui.moveTo(startbutton)
                    pyautogui.click(startbutton)
                    self.inqueue()

                if start2 is not None:
                    pyautogui.click(x=960, y=540)
                    time.sleep(.5)
                    pyautogui.moveTo(start2)
                    pyautogui.click(start2)
                    self.inqueue()

                if again is not None:
                    pyautogui.click(x=960, y=540)
                    time.sleep(.5)
                    pyautogui.moveTo(again2)
                    pyautogui.click(again2)
                    self.inqueue()

                if again2 is not None:
                    pyautogui.click(x=960, y=540)
                    time.sleep(.5)
                    pyautogui.moveTo(again2)
                    pyautogui.click(again2)
                    self.inqueue()

    def inqueue(self):

        print(Style.RESET_ALL)
        print(Fore.YELLOW + " [-] CHECKING IF IN QUEUE")
        now = time.time()
        time.sleep(.2)
        future = now + 240

        while True:
            if keyboard.is_pressed('f3'):
                self.pause()
            if time.time() > future:
                # detects possible issue with valorant and restarts the game
                print(Style.RESET_ALL)
                print(Fore.RED + " [!] FOUND A POSSIBLE ERROR WITH VALORANT")
                self.startvalorant()
                break

            q = pyautogui.locateOnScreen("images/inqueue.png", grayscale=True)
            q2 = pyautogui.locateOnScreen("images/inqueue.png",
                                          grayscale=True,
                                          confidence=0.6)

            if q is not None or q2 is not None:
                if q is not None:
                    print(Style.RESET_ALL)
                    print(Fore.GREEN + " [√] DETECTED IN QUEUE")
                    time.sleep(.5)
                    self.waitingforgame()
                if q2 is not None:
                    print(Style.RESET_ALL)
                    print(Fore.GREEN + " [√] DETECTED IN QUEUE")
                    time.sleep(.5)
                    self.waitingforgame()

            if q is None:
                print(Style.RESET_ALL)
                print(Fore.RED + " [!] DETECTED NOT IN QUEUE")
                time.sleep(1)

                self.playbutton()

    def waitingforgame(self):
        time.sleep(1)
        print(Style.RESET_ALL)

        activeactivity = "In Queue"

        earned = "{:,}".format(self.xpamount)

        try:

            self.RPC.update(state=("Earned " + earned + " XP"),
                            start=time.time(),
                            large_image="valbotnew",
                            large_text=self.version,
                            details=activeactivity)

        except Exception:
            pass
        print(Fore.YELLOW + " [-] WAITING FOR A GAME")

        now = time.time()

        future = now + 900  # if not in game after 15 mins, restart valorant as there may be an error. Change this if
        # your servers are bad (in seconds)

        while True:
            if keyboard.is_pressed('f3'):
                self.pause()
            if time.time() > future:
                # detects possible issue with valorant and restarts the game
                print(Style.RESET_ALL)
                print(Fore.RED + " [!] FOUND A POSSIBLE ERROR WITH VALORANT")
                self.startvalorant()
                break

            ingame = pyautogui.locateOnScreen("images/ingame.png")
            ingame2 = pyautogui.locateOnScreen("images/ingame.png",
                                               confidence=0.6)

            defaultcard = pyautogui.locateOnScreen("images/defaultcard.png",
                                                   grayscale=True)
            defaultcard2 = pyautogui.locateOnScreen("images/defaultcard.png",
                                                    grayscale=True,
                                                    confidence=0.6)

            if ingame is not None or ingame2 is not None or defaultcard is not None or defaultcard2 is not None:
                print(Style.RESET_ALL)
                print(Fore.GREEN + " [√] DETECTED IN A GAME")
                time.sleep(
                    15
                )  # so it doesnt detect the end game screen as soon as it searches
                pyautogui.click(x=960, y=540)
                activeactivity = "In Match"

                earned = "{:,}".format(self.xpamount)

                try:

                    self.RPC.update(state=("Earned " + earned + " XP"),
                                    start=time.time(),
                                    large_image="valbotnew",
                                    large_text=self.version,
                                    details=activeactivity)

                except Exception:
                    pass

                time.sleep(1)
                print(Style.RESET_ALL)
                print(Fore.YELLOW + " [-] WAITING FOR THE GAME TO END")
                time.sleep(2)
                print(Style.RESET_ALL)
                print(Fore.YELLOW + " [-] TO PAUSE THE BOT HOLD F3")
                print(Fore.YELLOW + " [-] TO RESUME THE BOT HOLD F4")
                self.endofgame()

    def pause(self):
        print(Style.RESET_ALL)
        print(Fore.RED + " [!] PAUSING BOT")
        print(Fore.RED + " [!] HOLD F4 TO RESUME THE BOT")
        pyautogui.keyUp('w')
        pyautogui.keyUp('a')
        pyautogui.keyUp('s')
        pyautogui.keyUp('d')

        while True:
            time.sleep(.25)
            try:
                if keyboard.is_pressed('f4'):
                    print(Style.RESET_ALL)
                    print(Fore.GREEN + " [√] RESUMING BOT")
                    print(Fore.GREEN + " [√] TO PAUSE THE BOT AGAIN HOLD F3")
                    time.sleep(1)
                    break
            except:
                pass

        menu = pyautogui.locateOnScreen("images/play.png", grayscale=True)
        menu2 = pyautogui.locateOnScreen("images/play.png",
                                         confidence=0.7,
                                         grayscale=True)
        q = pyautogui.locateOnScreen("images/inqueue.png", grayscale=True)
        q2 = pyautogui.locateOnScreen("images/inqueue.png",
                                      grayscale=True,
                                      confidence=0.6)

        if menu is not None or menu2 is not None:
            self.playbutton()

        if q is not None or q2 is not None:
            self.inqueue()

        if q is None or q2 is None or menu is None or menu2 is None:
            self.antiafk()

    def endofgame(self):

        now = time.time()

        future = now + 780

        time.sleep(2)

        while True:
            if keyboard.is_pressed('f3'):
                self.pause()
            if time.time() > future:
                # detects possible issue with valorant and restarts the game
                print(Style.RESET_ALL)
                print(Fore.RED + " [!] FOUND A POSSIBLE ERROR WITH VALORANT")
                self.startvalorant()
                break

            menu = pyautogui.locateOnScreen("images/play.png", grayscale=True)
            menu2 = pyautogui.locateOnScreen("images/play.png",
                                             confidence=0.7,
                                             grayscale=True)

            if menu is not None or menu2 is not None:
                self.gamesplayed += 1
                self.xpamount += 900

                print(Style.RESET_ALL)
                print(Fore.GREEN + " [√] DETECTED AT END GAME SCREEN")

                activeactivity = "At Menu"

                earned = "{:,}".format(self.xpamount)

                try:

                    self.RPC.update(state=("Earned " + earned + " XP"),
                                    start=time.time(),
                                    large_image="valbotnew",
                                    large_text=self.version,
                                    details=activeactivity)

                except Exception:
                    pass

                time.sleep(2)
                self.xpscreen()

            else:
                self.antiafk()

    def antiafk(self):
        time.sleep(.5)
        n = randint(20, 35)
        a = 0
        while a <= n:
            if keyboard.is_pressed('f3'):
                self.pause()
            a += 1
            n2 = randint(1, 6)
            if n2 == 1:
                pyautogui.keyDown('w')
                sleep(randint(2, 6) / 10)

            if n2 == 2:
                pyautogui.keyUp('w')
                pyautogui.keyDown('d')
                pyautogui.click()

            if n2 == 3:
                pyautogui.keyDown('a')
                sleep(randint(1, 3) / 10)
                pyautogui.keyDown('a')
                pyautogui.click()
                sleep(randint(2, 4) / 10)

            if n2 == 4:
                pyautogui.keyUp('d')
                pyautogui.keyDown('s')
                sleep(randint(2, 5) / 10)

            if n2 == 5:
                pyautogui.keyUp('s')
                sleep(randint(4, 6) / 10)

            if n2 == 6:
                pyautogui.keyDown('w')
                sleep(randint(2, 4) / 10)
                pyautogui.keyUp('w')
        self.endofgame()

    def xpscreen(self):
        global line
        print(Style.RESET_ALL)
        print(Fore.YELLOW + " [-] SEARCHING FOR THE XP SCREEN")

        time.sleep(3)

        now = time.time()

        future = now + 600

        restarttime = start_time + self.secondsuntilrestart

        while True:
            if keyboard.is_pressed('f3'):
                self.pause()
            if time.time() > future:
                # detects possible issue with valorant and restarts the game
                print(Style.RESET_ALL)
                print(Fore.RED + " [!] FOUND A POSSIBLE ERROR WITH VALORANT")
                self.startvalorant()
                break

            xpscreen = pyautogui.locateOnScreen("images/play.png",
                                                grayscale=True)
            xpscreen2 = pyautogui.locateOnScreen("images/play.png",
                                                 confidence=0.6,
                                                 grayscale=True)
            if xpscreen is not None or xpscreen2 is not None:
                print(Style.RESET_ALL)
                print(Fore.GREEN + " [√] DETECTED THE XP SCREEN")
                time.sleep(2)

                runtime = datetime.now() - start
                runtime = str(runtime)
                runtime = runtime[:-7]

                exact = start.strftime("%H:%M:%S")
                dat = start.strftime("%d %h %Y")
                print(Style.RESET_ALL)
                print(Style.RESET_ALL)

                print(Style.RESET_ALL + Fore.YELLOW +
                      "——————————————————————————————————————————————————————")
                print(Style.RESET_ALL)
                print(
                    Fore.YELLOW + " Earned", Style.BRIGHT + Fore.YELLOW +
                    str(self.xpamount) + " XP" + Style.RESET_ALL + Fore.YELLOW,
                    "in total")
                print(
                    Fore.YELLOW + " Bot has been running for", Style.BRIGHT +
                    Fore.YELLOW + str(runtime) + Style.RESET_ALL + Fore.YELLOW)
                print(
                    Fore.YELLOW + " Bot was started at",
                    Style.BRIGHT + Fore.YELLOW + str(exact), Style.RESET_ALL +
                    Fore.YELLOW + "on the" + Style.BRIGHT + Fore.YELLOW,
                    dat + Style.RESET_ALL + Fore.YELLOW)
                print(Fore.YELLOW + " Played",
                      Style.BRIGHT + Fore.YELLOW + str(self.gamesplayed),
                      "games" + Style.RESET_ALL + Fore.YELLOW)
                print(" Valorant has been",
                      Style.BRIGHT + Fore.YELLOW + "restarted", self.restarted,
                      "times")
                print(Style.RESET_ALL)
                print(Style.RESET_ALL + Fore.YELLOW +
                      "——————————————————————————————————————————————————————")
                print(Style.RESET_ALL)
                print(Fore.YELLOW + "                     " + self.version)
                print(Style.RESET_ALL)
                print(Style.RESET_ALL)
                time.sleep(1)

                if self.foundwebhook is True:
                    restartstring = (str(self.restarted) + " times")
                    if self.restarted == 0:
                        restartstring = "Not yet restarted"
                    elif self.restarted == 1:
                        restartstring = "1 time"
                    webhook = DiscordWebhook(url=self.hookline,
                                             username="******")

                    embed = DiscordEmbed(color=34343)
                    embed.set_author(
                        name=self.version,
                        url="https://github.com/MrFums/Valbot",
                        icon_url=
                        "https://raw.githubusercontent.com/MrFums/ValbotAssets/main/jett.png",
                    )
                    embed.set_footer(text=self.version.replace("Valbot", ""))
                    embed.set_timestamp()
                    embed.set_thumbnail(
                        url=
                        'https://raw.githubusercontent.com/MrFums/ValbotAssets/main/valbot18_circle.png'
                    )

                    embed.add_embed_field(name="Total XP",
                                          value=self.xpamount,
                                          inline=False)
                    embed.add_embed_field(name="Games Played",
                                          value=self.gamesplayed,
                                          inline=False)
                    embed.add_embed_field(name="Current Runtime",
                                          value=runtime,
                                          inline=False)
                    embed.add_embed_field(name="Valorant Restarted",
                                          value=restartstring,
                                          inline=False)

                    webhook.add_embed(embed)
                    webhook.execute()

                else:
                    print(Style.RESET_ALL)
                    print(Fore.RED + " [!] DISCORD WEBHOOK IS NOT SETUP")

                time.sleep(4)
                pyautogui.click(x=960, y=540)
                time.sleep(1)

                if time.time() > restarttime:

                    if os.path.exists("runtime_values"):
                        os.remove("runtime_values")

                    f = open("runtime_values", "a+")
                    f.write(str(self.xpamount))
                    f.write("\n")
                    f.write(str(self.gamesplayed))
                    f.write("\n")
                    f.write(str(self.restarted))
                    f.close()

                    # detects possible issue with valorant and restarts the game
                    print(Style.RESET_ALL)
                    print(
                        Fore.RED +
                        " [!] BOT IS NOW RESTARTING AFTER 2 HOURS OF RUNTIME")
                    self.restartbot()
                    break

                self.playbutton()
Exemplo n.º 26
0
class PresenceGUI:
    def __init__(self, master):
        self.master = master
        self.DICT_OF_FIELDS = {
            "client_id": "Client ID",
            "state": "State",
            "details": "Details",
            "large_image": "Large Image Name",
            "large_text": "Large Image Text",
            "small_image": "Small Image Name",
            "small_text": "Small Image Text",
            "buttons": "Buttons",
        }
        self.icon_path = pathlib.Path(__file__).parent / "data/myicon.ico"
        self.master.title("Sharky PyPresence")
        self.master.geometry("435x320")
        self.master.configure(bg="gray")
        self.master.iconbitmap(self.icon_path)

        self.started_rpc = False
        self.RPC = None
        self.wait_until = None
        self.restore_file = pathlib.Path(
            __file__).parent.absolute() / "data/settings.pickle"
        # https://stackoverflow.com/questions/33553200/save-and-load-gui-tkinter

        self.start_building_widget()
        self._restore_state()

    def start_building_widget(self):
        ents = self.presence_form(self.DICT_OF_FIELDS)

        b1 = tk.Button(self.master,
                       text="Process",
                       command=(lambda e=ents: self.temp_rpc(e)))
        b1.pack(side=tk.LEFT, padx=5, pady=5)

        b2 = tk.Button(self.master,
                       text="Stop",
                       command=(lambda e=self.master: self._stop_rpc()))
        b2.pack(side=tk.LEFT, padx=5, pady=5)

        b3 = tk.Button(self.master,
                       text="Help",
                       command=(lambda e=self.master: self.help_window()))
        b3.pack(side=tk.RIGHT, padx=5, pady=5)

        # b4 = tk.Button(self.master, text="Quit", command=(lambda e=ents: self._save_state(e)))
        # b4.pack(side=tk.LEFT, padx=5, pady=5)
        self.master.wm_protocol("WM_DELETE_WINDOW",
                                (lambda e=ents: self._save_state(e)))

    def presence_form(self, fields):
        entries = []
        did_buttons = False
        for key in fields.keys():
            row = tk.Frame(self.master)
            if key == "buttons":
                if not did_buttons:
                    # need to create 4 entries w/ different labels
                    reg = self.master.register(self._limit_characters)

                    button_one_label = tk.Label(row,
                                                width=11,
                                                text="Button 1 Label",
                                                anchor="w",
                                                bg="gray")
                    button_one_link = tk.Label(row,
                                               width=11,
                                               text=" Button 1 Link",
                                               anchor="w",
                                               bg="gray")
                    b_One_label_entry = tk.Entry(row)
                    b_One_link_entry = tk.Entry(row)

                    button_two_label = tk.Label(row,
                                                width=11,
                                                text="Button 2 Label",
                                                anchor="w",
                                                bg="gray")
                    button_two_link = tk.Label(row,
                                               width=11,
                                               text=" Button 2 Link",
                                               anchor="w",
                                               bg="gray")
                    b_Two_label_entry = tk.Entry(row)
                    b_Two_link_entry = tk.Entry(row)

                    row.pack(side=tk.TOP, fill=tk.X, padx=5, pady=5)

                    button_one_label.grid(row=8,
                                          column=1,
                                          sticky=tk.W,
                                          rowspan=tk.YES)
                    button_one_link.grid(row=8,
                                         column=3,
                                         sticky=tk.W,
                                         rowspan=tk.YES)
                    b_One_label_entry.config(validate="key",
                                             validatecommand=(reg, "%P"))
                    b_One_label_entry.grid(row=8,
                                           column=2,
                                           padx=3,
                                           pady=1,
                                           rowspan=tk.YES)
                    b_One_link_entry.grid(row=8,
                                          column=4,
                                          padx=3,
                                          pady=1,
                                          rowspan=tk.YES)

                    button_two_label.grid(row=9,
                                          column=1,
                                          sticky=tk.W,
                                          rowspan=tk.YES)
                    button_two_link.grid(row=9,
                                         column=3,
                                         sticky=tk.W,
                                         rowspan=tk.YES)
                    b_Two_label_entry.config(validate="key",
                                             validatecommand=(reg, "%P"))
                    b_Two_label_entry.grid(row=9,
                                           column=2,
                                           padx=3,
                                           pady=1,
                                           rowspan=tk.YES)
                    b_Two_link_entry.grid(row=9,
                                          column=4,
                                          padx=3,
                                          pady=1,
                                          rowspan=tk.YES)

                    entries.append(("Button 1 Label", b_One_label_entry))
                    entries.append(("Button 1 Link", b_One_link_entry))
                    entries.append(("Button 2 Label", b_Two_label_entry))
                    entries.append(("Button 2 Link", b_Two_link_entry))

                    did_buttons = True

            elif key == "client_id":
                reg = self.master.register(self._client_callback)
                lab = tk.Label(row,
                               width=15,
                               text=fields[key],
                               anchor="w",
                               bg="gray")
                ent = tk.Entry(row)
                ent.config(validate="key", validatecommand=(reg, "%P"))
                row.pack(side=tk.TOP, fill=tk.X, padx=5, pady=5)
                lab.pack(side=tk.LEFT)
                ent.pack(side=tk.RIGHT, expand=tk.YES, fill=tk.X)
                entries.append((key, ent))
            elif key in ["details", "state", "large_text", "small_text"]:
                # need to print the keys to confirm
                reg = self.master.register(self._limit_longer_characters)
                lab = tk.Label(row,
                               width=15,
                               text=fields[key],
                               anchor="w",
                               bg="gray")
                ent = tk.Entry(row)
                row.pack(side=tk.TOP, fill=tk.X, padx=5, pady=5)
                lab.pack(side=tk.LEFT)
                ent.config(validate="key", validatecommand=(reg, "%P"))
                ent.pack(side=tk.RIGHT, expand=tk.YES, fill=tk.X)
                entries.append((key, ent))
            else:
                reg = self.master.register(self._limit_characters)
                lab = tk.Label(row,
                               width=15,
                               text=fields[key],
                               anchor="w",
                               bg="gray")
                ent = tk.Entry(row)
                row.pack(side=tk.TOP, fill=tk.X, padx=5, pady=5)
                lab.pack(side=tk.LEFT)
                ent.config(validate="key", validatecommand=(reg, "%P"))
                ent.pack(side=tk.RIGHT, expand=tk.YES, fill=tk.X)
                entries.append((key, ent))
        return entries

    def _client_callback(self, client_input):
        """
        Requires client_id to only be an int
        """
        if client_input.isdigit() or client_input == "":
            return True
        else:
            return False

    def _limit_characters(self, character_input):
        """
        Internal Function to check character limits of entry
        Currently 32 limitation for Buttons and Images
        """
        if len(character_input) > 32:
            return False
        else:
            return True

    def _limit_longer_characters(self, character_input):
        """
        Same as above
        Currently 128 for details, state, large and small text
        """
        if len(character_input) > 128:
            return False
        else:
            return True

    def help_window(self):
        """
        Displays help menu on start or when help is clicked.
        """
        helpwindow = tk.Toplevel(self.master)

        helpwindow.title("Help Menu")
        helpwindow.geometry("400x400")
        helpwindow.config(bg="gray")
        helpwindow.iconbitmap(self.icon_path)
        helpwindow.resizable(width=False, height=False)

        message = (
            "Hello! If you are new to this program. "
            "Please have a read at the README file on the repo. It"
            " will help explain everything if it's not listed.\n\n"
            "1. Head over to Discord's Developer Application Page.\n"
            "Click `New Application`; give it a nice name. we're gonna use "
            '"My Test Game" for ours. In discord\'s user pane, this will'
            ' show as your status "Playing My Test Game"\n\n'
            "2. Copy the `Application ID` from the website and paste "
            "that value into the `Client ID` in our App.\n\n"
            "3. In the website, on the left of the page, navigate to "
            "`Rich Presence`. The `Cover Image` is not used and we can ignore it."
            " Next, add a few images with the `Add Image(s)` button; rename the images "
            "if needed, then click `Save Changes`. It may take several minutes for it to "
            "properly save on discord's side.\n\n"
            "4. In our App, the `Large Image Name` and `Small Image Name` can be set "
            "to any image name that has been uploaded in the steps above.\n\n"
            "5. The remaining fields in our app can be set to anything you desire."
        )
        label = tk.Label(helpwindow, text=message, bg="gray")
        label.configure(wraplength=350, justify=tk.LEFT)
        label.pack()
        credit_label = tk.Label(helpwindow,
                                text="Credits: SharkyTheKing",
                                bg="gray")
        credit_label.place(relx=0.0, rely=1.0, anchor="sw")
        version_label = tk.Label(helpwindow, text="Version: 1.0.3", bg="gray")
        version_label.place(relx=1.0, rely=1.0, anchor="se")

    def _error_window(self, error: dict):
        key_view = error.keys()
        key_iterator = iter(key_view)
        first_key = next(key_iterator)

        # values_view = error.values()
        # value_iterator = iter(values_view)
        # first_value = next(value_iterator)
        first_value = error[first_key]

        messagebox.showerror(first_key, first_value)

    def fetch(self, entries):
        for entry in entries:
            field = entry[0]
            text = entry[1].get()
            print('%s: "%s"' % (field, text))

    def process_information(self, entries):
        entry_dict = {
            "client_id": False,
            "state": False,
            "details": False,
            "large_image": False,
            "large_text": False,
            "small_image": False,
            "small_text": False,
            "buttons": [],
        }
        button_type = [
            "Button 1 Label", "Button 1 Link", "Button 2 Label",
            "Button 2 Link"
        ]
        button_dict = {
            "buttons": [{
                "label": "",
                "url": ""
            }, {
                "label": "",
                "url": ""
            }]
        }
        raise_button_link = False
        for entry in entries:
            field = entry[0]
            text = entry[1].get()
            if field in button_type:
                if button_dict.get("buttons"):
                    if field == "Button 1 Label":
                        if not text:  # Since we require text on this...
                            button_dict.pop("buttons")
                            entry_dict.pop("buttons")
                            continue
                        else:
                            button_dict["buttons"][0]["label"] = text
                    elif field == "Button 1 Link":
                        if not text:
                            raise_button_link = True
                        button_dict["buttons"][0][
                            "url"] = text if text else "https://localhost/"
                    if button_dict.get("buttons"):
                        if field == "Button 2 Label":
                            if not text:
                                button_dict["buttons"].pop(1)
                            else:
                                button_dict["buttons"][1]["label"] = text
                        elif field == "Button 2 Link":
                            try:
                                if button_dict.get("buttons")[1]:
                                    if not text:
                                        raise_button_link = True
                                    button_dict["buttons"][1]["url"] = (
                                        text if text else "https://localhost/")
                            except (KeyError, IndexError):
                                continue

                    entry_dict["buttons"] = button_dict["buttons"]
            else:
                if text:
                    entry_dict[field] = text
                else:
                    entry_dict.pop(field)
        if raise_button_link is True:
            self._error_window(BUTTON_MESSAGE)

        return entry_dict

    def temp_rpc(self, entry_info):
        if self.wait_until:
            now = datetime.now()
            if self.wait_until > now:
                time_left = (self.wait_until - now).seconds
                return self._error_window({
                    "Timeout Error":
                    ("Please do not repeatedly push process.\n\n"
                     "You must wait 15 seconds before you update your status.\n\n"
                     "You have {seconds} seconds left to wait.".format(
                         seconds=time_left))
                })

        if self.started_rpc is False:
            entries = self.process_information(entry_info)
            try:
                self.RPC = Presence(entries["client_id"])
                self.RPC.connect()
            except KeyError:
                return self._error_window(NO_CLIENT_MESSAGE)

            entries.pop("client_id")

        else:
            entries = self.process_information(entry_info)
            entries.pop("client_id")

        now = datetime.now()
        self.wait_until = now + timedelta(seconds=15)

        try:
            self.RPC.update(**entries)
            self.started_rpc = True
        except exceptions.InvalidID:
            return self._error_window(INVALID_CLIENT_MESSAGE)
        except Exception as error:
            if str(error) == "unpack requires a buffer of 8 bytes":
                return self._error_window(RESTART_DISCORD_MESSAGE)
            else:
                raise error  # Odd...but I need to know if there's any other issues.

    def _stop_rpc(self):
        if not self.RPC:
            return
        self.RPC.close()
        self.started_rpc = False

    def _save_state(self, entry):
        text = entry[0][1].get()
        data = {}

        for widget in entry:
            label = widget[0]
            text = widget[1].get()
            data[label] = text

        try:
            with open(self.restore_file, "wb") as f:
                pickle.dump(data, f, pickle.HIGHEST_PROTOCOL)
        except Exception as e:
            print("error saving state:", str(e))

        self.master.quit()

    def _restore_state(self):
        """
        Currently will only restore the state, everything else is manual
        """
        # https://stackoverflow.com/questions/6112482/how-to-get-the-tkinter-label-text
        data = False
        try:
            with open(self.restore_file, "rb") as f:
                data = pickle.load(f)
        except FileNotFoundError:  # File will get created on save state anyways.
            pass
        except Exception as e:
            print("error loading saved state:", str(e))

        if not data:
            return

        self._restore_values(data)

    def get_all_children(self, widget):
        """
        Return a list of all the children, if any, of a given widget.
        Credit to https://stackoverflow.com/questions/52484359/how-to-select-all-instances-of-a-widget-in-tkinter/52484948#52484948
        """
        result = []  # Initialize.
        return self._all_children(widget.winfo_children(), result)

    def _all_children(self, children, result):
        """
        Recursively append all children of a list of widgets to result.
        """
        for child in children:
            result.append(child)
            subchildren = child.winfo_children()
            if subchildren:
                self._all_children(subchildren, result)

        return result

    def _restore_values(self, data):
        """
        Internal function to restore all entry state in UI
        """
        toplevel = self.master.winfo_toplevel()
        selection = [child for child in self.get_all_children(toplevel)]
        try:
            selection[-9].insert(
                0, data["Button 1 Label"])  # button 1 label entry
            selection[-8].insert(0,
                                 data["Button 1 Link"])  # button 1 link entry
            selection[-5].insert(
                0, data["Button 2 Label"])  # button 2 label entry
            selection[-4].insert(0, data["Button 2 Link"])  # button 2 link
        except (KeyError, IndexError, AttributeError):
            pass

        for child in self.master.winfo_children():
            try:
                label = child.winfo_children()[0]["text"]
                entry = child.winfo_children()[1]
                if label == "Client ID":
                    entry.insert(0, data["client_id"])
                elif label == "State":  # This works
                    entry.insert(0, data["state"])
                elif label == "Details":
                    entry.insert(0, data["details"])
                elif label == "Large Image Name":
                    entry.insert(0, data["large_image"])
                elif label == "Large Image Text":
                    entry.insert(0, data["large_text"])
                elif label == "Small Image Name":
                    entry.insert(0, data["small_text"])
                elif label == "Button 1 Label":
                    entry.insert(0, data["Button 1 Label"])
                elif label == "Button 1 Link":
                    entry.insert(0, data["Button 1 Link"])
                elif label == "Button 2 Label":
                    entry.insert(0, data["Button 2 Label"])
                elif label == "Button 2 Link":
                    entry.insert(0, data["Button 2 Link"])
            except (KeyError, IndexError,
                    AttributeError):  # Since no point failing on these.
                pass
Exemplo n.º 27
0
    ]


while True:

    pid = re.compile(r"[^a-zA-Z0-9-]").sub("",
                                           str(getpid('ForzaHorizon4.exe')))

    if pid != '':

        client_id = "530434490909327360"
        RPC = Presence(client_id)
        RPC.connect()

        ac = Activity(RPC, pid=int(pid), large_image="main")

        ac.start = int(time.time())
        print("Sucessfully Launched the RPC :)")

        while pid != '':
            pid = re.compile(r"[^a-zA-Z0-9-]").sub(
                "", str(getpid('ForzaHorizon4.exe')))

            time.sleep(15)
        RPC.close()

    else:
        print("Forza Horizon 4 isn't running...")

    time.sleep(15)
Exemplo n.º 28
0
class Client(object):
    """
    Main client object for WebRichPresence_Client. Handles all interactions between the API and Discord.
    """

    _port: int
    _hostname: str
    _config_path: str
    _config: Dict[str, Optional[str]]
    _socket: SocketIO
    _logger: logging.Logger
    _presence_callbacks: Dict[str, Callable[..., None]]
    _current_app_id: Optional[str]
    _rpc: Presence
    _presence_namespace: Type[BaseNamespace]

    def __init__(self, port: int, hostname: str, config_path: str):
        """
        Initializes a new Client.
        :param port: The port that the API server runs on
        :param hostname: The hostname (or IP address) the API server runs on
        :param config_path: The path to the config file where the token will be stored
        """
        self._port = port
        self._hostname = hostname
        self._config_path = config_path
        self._current_app_id = None
        self._rpc = None
        self._logger = logging.getLogger("WebRichPresence_Client")
        logging.getLogger("socketIO-client").setLevel(logging.ERROR)
        self._logger.setLevel(logging.INFO)

        # Dynamically create the namespace object
        self._presence_callbacks = {
            "authenticated": self._on_authenticated,
            "new_token": self._on_new_token,
            "presence": self._on_presence,
            "connect": self._on_connected,
            "reconnect": self._on_reconnect,
            "clear": self._on_clear
        }

        self._presence_namespace = type("PresenceNamespace", (BaseNamespace, ), {
            "on_" + k: v for k, v in self._presence_callbacks.items()
        })

        if not os.path.isfile(config_path):
            self._config = {
                "token": None
            }

        else:
            with open(config_path) as f:
                self._config = json.load(f)

    def _initialize_rpc(self, app_id: str):
        """
        Initializes the connection to Discord RPC.
        :param app_id: The app ID that will be used for RPC
        """
        if self._rpc:
            self._rpc.close()
        self._rpc = Presence(app_id)
        self._rpc.connect()
        self._current_app_id = app_id

    def _on_presence(self, app_id: str, presence: dict):
        """
        Handles the presence event from the API server.
        :param app_id: The app ID to use for the presence
        :param presence: The presence details
        """
        if app_id != self._current_app_id or self._rpc is None:
            self._initialize_rpc(app_id)
        try:
            self._rpc.update(**presence)
            self._logger.info(f"App {app_id} updated presence.")
        except TypeError:
            self._logger.warning(f"App {app_id} sent invalid presence object.")
        except IOError:
            time.sleep(15)
            self._on_presence(app_id, presence)

    def _on_new_token(self, new_token: str):
        """
        Handles the new token event from the API server.
        :param new_token: The new token
        """
        self._config["token"] = new_token

        with open(self._config_path, "w") as f:
            json.dump(self._config, f)

        self._logger.info("New token retrieved from server!")
        self._logger.info(f"Your new token is {new_token}")
        self._logger.info("Provide this token to all apps you wish to use with WebRichPresence.")

    def _on_authenticated(self, _):
        """
        Handles the authenticated event from the API server.
        """
        self._logger.info("Authenticated.")

    def _on_connected(self):
        """
        Handles the connect event from the API server.
        """
        self._logger.info("Connected to server. Authenticating...")
        self._authenticate()

    def _on_reconnect(self):
        """
        Handles the reconnect event from the API server.
        """
        self._logger.info("Regained connection to the API server. Re-authenticating...")
        self._authenticate()

    def _on_clear(self, appid):
        """
        Handles the clear event from the API server.
        :param appid: The appid requesting to be cleared
        """
        if appid != self._current_app_id:
            self._logger.warning(f"Non-current app {appid} attempted to clear presence. Ignoring.")
        else:
            self._current_app_id = None
            self._rpc.close()
            self._rpc = None
            self._logger.info(f"App {appid} cleared presence info.")

    def _authenticate(self):
        """
        Handles authenticating with the API server.
        """
        if not self._config["token"]:
            self._logger.info("No token defined, retrieving new one from server...")
            self._socket.emit("new_auth")
        else:
            self._logger.info(f"Authenticating with existing token {self._config['token']}...")
            self._socket.emit("auth", self._config["token"])

    def run(self):
        """
        Runs the client and connects to the API server.
        """
        self._logger.info("Connecting to WebRichPresence...")
        self._socket = SocketIO(self._hostname, self._port)
        self._socket.define(self._presence_namespace, "/presence")

        self._socket.wait()