""" Configuration loader for adapter system. Loads from YAML with environment variable override support. """ import os from pathlib import Path from typing import Any, Dict, Optional import yaml # Environment variable mappings: env var name -> (adapter, credential key) _ENV_OVERRIDES = { "AJARBOT_SLACK_BOT_TOKEN": ("slack", "bot_token"), "AJARBOT_SLACK_APP_TOKEN": ("slack", "app_token"), "AJARBOT_TELEGRAM_BOT_TOKEN": ("telegram", "bot_token"), } class ConfigLoader: """Load adapter configuration from YAML files with env var support.""" def __init__(self, config_dir: Optional[str] = None) -> None: if config_dir is None: config_dir = str(Path(__file__).parent) self.config_dir = Path(config_dir) self.config: Dict[str, Any] = {} def load(self, filename: str = "adapters.yaml") -> Dict[str, Any]: """ Load configuration from YAML file. Looks for files in this order: 1. {filename}.local.yaml (gitignored, for secrets) 2. {filename} Environment variables can override any setting: AJARBOT_SLACK_BOT_TOKEN -> adapters.slack.credentials.bot_token AJARBOT_TELEGRAM_BOT_TOKEN -> adapters.telegram.credentials.bot_token """ local_file = self.config_dir / f"{Path(filename).stem}.local.yaml" config_file = self.config_dir / filename if local_file.exists(): print(f"[Config] Loading from {local_file}") with open(local_file) as f: self.config = yaml.safe_load(f) or {} elif config_file.exists(): print(f"[Config] Loading from {config_file}") with open(config_file) as f: self.config = yaml.safe_load(f) or {} else: print("[Config] No config file found, using defaults") self.config = {"adapters": {}} self._apply_env_overrides() return self.config def _apply_env_overrides(self) -> None: """Apply environment variable overrides.""" for env_var, (adapter, credential_key) in _ENV_OVERRIDES.items(): value = os.getenv(env_var) if not value: continue adapters = self.config.setdefault("adapters", {}) adapter_config = adapters.setdefault(adapter, {}) credentials = adapter_config.setdefault("credentials", {}) credentials[credential_key] = value print(f"[Config] Using {env_var} from environment") def get_adapter_config( self, platform: str ) -> Optional[Dict[str, Any]]: """Get configuration for a specific platform.""" return self.config.get("adapters", {}).get(platform) def is_adapter_enabled(self, platform: str) -> bool: """Check if an adapter is enabled.""" adapter_config = self.get_adapter_config(platform) if not adapter_config: return False return adapter_config.get("enabled", False) def get_user_mapping(self) -> Dict[str, str]: """Get user ID to username mapping.""" return self.config.get("user_mapping", {}) def save_template( self, filename: str = "adapters.local.yaml" ) -> Path: """Save a template configuration file.""" template = { "adapters": { "slack": { "enabled": False, "credentials": { "bot_token": "xoxb-YOUR-BOT-TOKEN", "app_token": "xapp-YOUR-APP-TOKEN", }, "settings": {}, }, "telegram": { "enabled": False, "credentials": { "bot_token": "YOUR-BOT-TOKEN", }, "settings": { "allowed_users": [], "parse_mode": "Markdown", }, }, }, "user_mapping": {}, } output_path = self.config_dir / filename with open(output_path, "w") as f: yaml.dump( template, f, default_flow_style=False, sort_keys=False ) print(f"[Config] Template saved to {output_path}") return output_path if __name__ == "__main__": import sys if len(sys.argv) > 1 and sys.argv[1] == "init": loader = ConfigLoader() path = loader.save_template() print(f"\nConfiguration template created at: {path}") print( "\nEdit this file with your credentials, " "then set enabled: true for each adapter you want to use." ) else: loader = ConfigLoader() config = loader.load() # Redact credentials before printing def redact_credentials(data): """Redact sensitive credential values.""" if isinstance(data, dict): redacted = {} for key, value in data.items(): if key == "credentials" and isinstance(value, dict): redacted[key] = { k: f"{str(v)[:4]}****{str(v)[-4:]}" if v else None for k, v in value.items() } elif isinstance(value, (dict, list)): redacted[key] = redact_credentials(value) else: redacted[key] = value return redacted elif isinstance(data, list): return [redact_credentials(item) for item in data] return data safe_config = redact_credentials(config) print("\nLoaded configuration (credentials redacted):") print(yaml.dump(safe_config, default_flow_style=False))