From f3aa797317724a621307e0469f788ec7cb073492 Mon Sep 17 00:00:00 2001 From: Dusk-92 Date: Wed, 2 Sep 2026 11:26:22 +0200 Subject: [PATCH] Add Discord Rich Presence detail controls --- setup_tool_dynamic.py | 192 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) diff --git a/setup_tool_dynamic.py b/setup_tool_dynamic.py index 60d4479..c1384b9 100644 --- a/setup_tool_dynamic.py +++ b/setup_tool_dynamic.py @@ -18,6 +18,22 @@ class ModernWowSetupTool(WowSetupTool): self.auction_throttle_enabled = tk.BooleanVar(master=root, value=True) self.vmmfix_enabled = tk.BooleanVar(master=root, value=False) self.interact_enabled = tk.BooleanVar(master=root, value=False) + + # Discord Rich Presence detail choices are kept independently from the + # main WowPresence checkbox so hiding/disabling the integration never + # destroys the user's selected disclosure preferences. + self.discord_show_character_details = tk.BooleanVar(master=root, value=True) + self.discord_detail_vars = { + "name": tk.BooleanVar(master=root, value=True), + "guild": tk.BooleanVar(master=root, value=True), + "race": tk.BooleanVar(master=root, value=True), + "faction": tk.BooleanVar(master=root, value=True), + "class": tk.BooleanVar(master=root, value=True), + "level": tk.BooleanVar(master=root, value=True), + "zone": tk.BooleanVar(master=root, value=True), + } + self.discord_presence_details_frame = None + self._download_window = None self._download_label = None self._download_detail = None @@ -26,6 +42,167 @@ class ModernWowSetupTool(WowSetupTool): self._download_indeterminate = False super().__init__(root) + def _collect_settings(self): + settings = super()._collect_settings() + settings["discord_presence"] = { + "show_character_details": bool(self.discord_show_character_details.get()), + "details": { + name: bool(var.get()) + for name, var in self.discord_detail_vars.items() + }, + } + return settings + + def _apply_settings_dict(self, saved): + super()._apply_settings_dict(saved) + + discord = saved.get("discord_presence") if isinstance(saved, dict) else None + if not isinstance(discord, dict): + return + + show_details = discord.get("show_character_details") + if isinstance(show_details, bool): + self.discord_show_character_details.set(show_details) + + details = discord.get("details") + if isinstance(details, dict): + for name, value in details.items(): + var = self.discord_detail_vars.get(name) + if var is not None and isinstance(value, bool): + var.set(value) + + def _load_wowpresence_broadcast_preferences(self, target_dir): + """Migrate the old six-bit mask into the new seven-choice UI.""" + mask = remote_packages.read_wowpresence_broadcast_flags(target_dir) + if mask is None: + return + + bits = { + "name": remote_packages.WOWPRESENCE_SHARE_NAME, + "guild": remote_packages.WOWPRESENCE_SHARE_GUILD, + "faction": remote_packages.WOWPRESENCE_SHARE_FACTION, + "class": remote_packages.WOWPRESENCE_SHARE_CLASS, + "level": remote_packages.WOWPRESENCE_SHARE_LEVEL, + "zone": remote_packages.WOWPRESENCE_SHARE_ZONE, + } + for name, bit in bits.items(): + self.discord_detail_vars[name].set(bool(mask & bit)) + + # WowPresence versions before the dedicated Race flag always exposed + # race information regardless of the six-bit mask. Treat masks <= 63 + # as legacy so the migration preserves exactly that visible behavior. + if mask <= 63: + self.discord_detail_vars["race"].set(True) + else: + self.discord_detail_vars["race"].set( + bool(mask & remote_packages.WOWPRESENCE_SHARE_RACE) + ) + + # A legacy mask never had a separate master switch. Keeping this on + # preserves the previous display while still allowing all individual + # choices to be edited. + self.discord_show_character_details.set(True) + + def _load_legacy_install_state(self, target_dir): + super()._load_legacy_install_state(target_dir) + self._load_wowpresence_broadcast_preferences(target_dir) + + def load_settings(self, target_dir): + settings_path = self._settings_path(target_dir) + has_saved_discord_preferences = False + try: + with open(settings_path, "r", encoding="utf-8") as handle: + saved = json.load(handle) + has_saved_discord_preferences = isinstance( + saved.get("discord_presence") if isinstance(saved, dict) else None, + dict, + ) + except (OSError, json.JSONDecodeError, ValueError, TypeError): + pass + + loaded = super().load_settings(target_dir) + + # Existing v2.1 settings.json files predate the detailed controls. Read + # their actual WowPresence mask once so manual choices are not lost. + if os.path.isfile(settings_path) and not has_saved_discord_preferences: + self._load_wowpresence_broadcast_preferences(target_dir) + + self.update_discord_presence_controls() + return loaded + + def _discord_broadcast_mask(self): + if not self.discord_show_character_details.get(): + return 0 + + bits = { + "name": remote_packages.WOWPRESENCE_SHARE_NAME, + "guild": remote_packages.WOWPRESENCE_SHARE_GUILD, + "race": remote_packages.WOWPRESENCE_SHARE_RACE, + "faction": remote_packages.WOWPRESENCE_SHARE_FACTION, + "class": remote_packages.WOWPRESENCE_SHARE_CLASS, + "level": remote_packages.WOWPRESENCE_SHARE_LEVEL, + "zone": remote_packages.WOWPRESENCE_SHARE_ZONE, + } + mask = 0 + for name, bit in bits.items(): + var = self.discord_detail_vars.get(name) + if var is not None and var.get(): + mask |= bit + return mask + + def update_discord_presence_controls(self): + frame = getattr(self, "discord_presence_details_frame", None) + if frame is None: + return + + var = self.optional_plugins.get("WowPresence.dll") + visible = bool(var is not None and var.get()) + manager = frame.winfo_manager() + + if visible and not manager: + frame.pack(fill="x", padx=(22, 8), pady=(0, 4)) + elif not visible and manager: + frame.pack_forget() + + def _build_discord_presence_details(self, parent): + frame = ttk.Frame(parent) + self.discord_presence_details_frame = frame + + show_cb = ttk.Checkbutton( + frame, + text="Show character details", + variable=self.discord_show_character_details, + ) + show_cb.pack(anchor="w", padx=6, pady=(1, 2)) + ToolTip( + show_cb, + "Controls whether the selected character details are published to Discord. " + "The choices below stay available even when this is unchecked.", + ) + + labels = { + "name": "Character Name", + "guild": "Guild", + "race": "Race", + "faction": "Faction", + "class": "Class", + "level": "Level", + "zone": "Zone", + } + for name, label in labels.items(): + cb = ttk.Checkbutton( + frame, + text=label, + variable=self.discord_detail_vars[name], + ) + cb.pack(anchor="w", padx=24, pady=1) + ToolTip( + cb, + f"Choose whether WowPresence may publish your {label.lower()} on Discord.", + ) + + self.update_discord_presence_controls() + def _show_download_progress(self): if self._download_window is not None and self._download_window.winfo_exists(): return @@ -303,14 +480,24 @@ class ModernWowSetupTool(WowSetupTool): for dll, var in self.optional_plugins.items(): if dll == "no1600x1200.dll": continue + + command = ( + self.update_discord_presence_controls + if dll == "WowPresence.dll" + else None + ) self._plugin_row( right_frame, optional_display.get(dll, os.path.splitext(dll)[0]), var, optional_attribution.get(dll, "by MarcelineVQ"), self.descriptions.get(dll, ""), + command=command, ) + if dll == "WowPresence.dll": + self._build_discord_presence_details(right_frame) + def clean_unselected_files(self, target): super().clean_unselected_files(target) @@ -867,6 +1054,11 @@ class ModernWowSetupTool(WowSetupTool): os.remove(legacy_path) except OSError: pass + + remote_packages.write_wowpresence_broadcast_flags( + target, + self._discord_broadcast_mask(), + ) else: source_dll = os.path.join(payload_weirdu, dll_name) if os.path.exists(source_dll):