# v6 from __future__ import annotations import json import logging from pathlib import Path from typing import Any APP_NAME = "Discord Client" APP_VERSION = "1.0.0" START_URL = "https://discord.com/app" WINDOW_WIDTH = 1400 WINDOW_HEIGHT = 900 LOG_LEVEL = logging.INFO BASE_DIR = Path.home() / ".discord-client" DATABASE_PATH = BASE_DIR / "client.db" SETTINGS_FILE = BASE_DIR / "settings.json" PROFILES_DIR = BASE_DIR / "profiles" DOWNLOADS_DIR = BASE_DIR / "downloads" LOGS_DIR = BASE_DIR / "logs" THEMES_DIR = BASE_DIR / "themes" EXTENSIONS_DIR = BASE_DIR / "extensions" EXPORTS_DIR = BASE_DIR / "exports" HAR_DIR = EXPORTS_DIR / "har" DEFAULT_PROFILE = "default" NETWORK_RETENTION = 100_000 NETWORK_BATCH_SIZE = 100 NETWORK_FLUSH_INTERVAL = 1.0 DISCORD_TELEMETRY_BLOCKS = [ "sentry.io", "www.sentry.io", "crash.discord.com", "telemetry.discord.com", "api.statsig.com", "segment.io", "cdn.segment.com", ] DEFAULT_PROXY = { "enabled": False, "proxy_type": "http", "host": "", "port": 0, "username": "", "password": "", } DEFAULT_TRAY = { "enabled": True, "minimize_to_tray": True, "close_to_tray": True, } DEFAULT_DEVTOOLS = { "enabled": True, "capture_requests": True, "capture_responses": True, "capture_bodies": False, "auto_open": False, } DEFAULT_PERMISSIONS = { "microphone": False, "camera": False, "notifications": False, "clipboard": False, } DEFAULT_SETTINGS: dict[str, Any] = { "active_profile": DEFAULT_PROFILE, "window_width": WINDOW_WIDTH, "window_height": WINDOW_HEIGHT, "download_directory": str(DOWNLOADS_DIR), "blocked_hosts": list( DISCORD_TELEMETRY_BLOCKS ), "network_logging": True, "enable_cdp": False, "enable_vencord": False, "permissions": DEFAULT_PERMISSIONS, "proxy": DEFAULT_PROXY, "tray": DEFAULT_TRAY, "devtools": DEFAULT_DEVTOOLS, "blocklist_mode": "pro", "disabled_blocklists": [], } FEATURE_PERMISSION_MAP = { "MediaAudioCapture": "microphone", "MediaVideoCapture": "camera", "MediaAudioVideoCapture": "camera", "Notifications": "notifications", "ClipboardReadWrite": "clipboard", "ClipboardSanitizedWrite": "clipboard", } PLUGIN_FILE_PATTERN = "*.plugin.js" PLUGIN_MAX_SIZE = 5 * 1024 * 1024 PLUGIN_CRASH_LIMIT = 5 PLUGIN_AUTOLOAD = True PLUGIN_ALLOWED_EXTENSIONS = { ".plugin.js", } PLUGIN_ALLOWED_SCHEMES = { "https", } PLUGIN_BLOCKED_PATTERNS = { "child_process.exec(", "child_process.spawn(", "powershell.exe", "cmd.exe /c", "process.exec(", } PLUGIN_PERMISSIONS = { "storage", "dom", "network", "websocket", "notifications", "clipboard", } PROFILE_EXPORT_VERSION = 1 PROFILE_EXPORT_NAME = ( "profilebundle" ) # v1 BLOCKLISTS_DIR = ( BASE_DIR / "blocklists" ) BLOCKLIST_CACHE_DIR = ( BLOCKLISTS_DIR / "cache" ) def blocklists_dir( ) -> Path: BLOCKLISTS_DIR.mkdir( parents=True, exist_ok=True, ) return BLOCKLISTS_DIR # v1 def blocklist_cache_dir( ) -> Path: BLOCKLIST_CACHE_DIR.mkdir( parents=True, exist_ok=True, ) return BLOCKLIST_CACHE_DIR def ensure_directories() -> None: for directory in ( BASE_DIR, PROFILES_DIR, DOWNLOADS_DIR, LOGS_DIR, THEMES_DIR, EXTENSIONS_DIR, EXPORTS_DIR, HAR_DIR, BLOCKLISTS_DIR, BLOCKLIST_CACHE_DIR, ): directory.mkdir( parents=True, exist_ok=True, ) def merge_dict( defaults: dict, current: dict, ) -> dict: merged = dict(defaults) for key, value in current.items(): if ( key in merged and isinstance( merged[key], dict, ) and isinstance( value, dict, ) ): merged[key] = merge_dict( merged[key], value, ) else: merged[key] = value return merged def _normalize_proxy( settings: dict[str, Any], ) -> None: proxy = settings.get( "proxy", {}, ) if not isinstance( proxy, dict, ): settings["proxy"] = dict( DEFAULT_PROXY ) return if ( "type" in proxy and "proxy_type" not in proxy ): proxy["proxy_type"] = proxy.pop( "type" ) settings["proxy"] = merge_dict( DEFAULT_PROXY, proxy, ) def _normalize_permissions( settings: dict[str, Any], ) -> None: permissions = settings.get( "permissions", {}, ) if not isinstance( permissions, dict, ): permissions = {} settings["permissions"] = merge_dict( DEFAULT_PERMISSIONS, permissions, ) def _normalize_blocked_hosts( settings: dict[str, Any], ) -> None: hosts = { str(host) .strip() .lower() for host in settings.get( "blocked_hosts", [], ) if str(host).strip() } hosts.update( DISCORD_TELEMETRY_BLOCKS ) settings["blocked_hosts"] = sorted( hosts ) def load_settings() -> dict[str, Any]: ensure_directories() if not SETTINGS_FILE.exists(): settings = merge_dict( {}, DEFAULT_SETTINGS, ) save_settings( settings ) return settings try: loaded = json.loads( SETTINGS_FILE.read_text( encoding="utf-8" ) ) if not isinstance( loaded, dict, ): raise ValueError if ( "active_profile" not in loaded and "active_account" in loaded ): loaded[ "active_profile" ] = loaded[ "active_account" ] settings = merge_dict( DEFAULT_SETTINGS, loaded, ) if not settings.get( "active_profile" ): settings[ "active_profile" ] = DEFAULT_PROFILE settings[ "download_directory" ] = str( settings.get( "download_directory" ) or DOWNLOADS_DIR ) _normalize_proxy( settings ) _normalize_permissions( settings ) _normalize_blocked_hosts( settings ) return settings except Exception: settings = merge_dict( {}, DEFAULT_SETTINGS, ) save_settings( settings ) return settings def save_settings( settings: dict[str, Any], ) -> None: ensure_directories() _normalize_proxy( settings ) _normalize_permissions( settings ) _normalize_blocked_hosts( settings ) SETTINGS_FILE.write_text( json.dumps( settings, indent=2, sort_keys=True, ensure_ascii=False, ), encoding="utf-8", ) def profile_root( profile_name: str, ) -> Path: profile_name = ( str(profile_name).strip() or DEFAULT_PROFILE ) root = ( PROFILES_DIR / profile_name ) root.mkdir( parents=True, exist_ok=True, ) return root def profile_paths( profile_name: str, ) -> tuple[Path, Path]: root = profile_root( profile_name ) storage = root / "storage" cache = root / "cache" storage.mkdir( parents=True, exist_ok=True, ) cache.mkdir( parents=True, exist_ok=True, ) return ( storage, cache, ) def profile_theme_dir( profile_name: str, ) -> Path: path = ( THEMES_DIR / profile_name ) path.mkdir( parents=True, exist_ok=True, ) return path def profile_extension_dir( profile_name: str, ) -> Path: path = ( EXTENSIONS_DIR / profile_name ) path.mkdir( parents=True, exist_ok=True, ) return path def profile_vencord_dir( profile_name: str, ) -> Path: path = ( profile_root( profile_name ) / "vencord" ) path.mkdir( parents=True, exist_ok=True, ) for child in ( "plugins", "themes", "logs", ): ( path / child ).mkdir( parents=True, exist_ok=True, ) return path def profile_plugin_dir( profile_name: str, ) -> Path: root = ( profile_vencord_dir( profile_name ) / "plugins" ) root.mkdir( parents=True, exist_ok=True, ) return root