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")
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);
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')
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
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()
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()
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"))
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)
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"])
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()
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()
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()
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()
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)
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
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)
"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)
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()
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())
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
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!")
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()
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()
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
] 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)
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()