Files
devdisc/config.py
2026-06-08 06:16:54 +00:00

536 lines
9.5 KiB
Python

# 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