from __future__ import annotations import hashlib import logging import os logger = logging.getLogger(__name__) class GitHubConfigAdapter: def __init__(self): self._config: dict = {} @property def _gh_cfg(self) -> dict: return self._config.get("channels", {}).get("github", {}) def list_account_ids(self, config: dict) -> list[str]: self._config = config accounts = self._gh_cfg.get("accounts", {}) if accounts: return list(accounts.keys()) if self._env_credentials_exist(): return ["default"] return [] async def resolve_account(self, account_id: str) -> dict: return self._build_account(account_id) def is_configured(self, account: dict) -> bool: return all([ account.get("app_id"), account.get("private_key"), account.get("installation_id"), account.get("webhook_secret"), ]) def is_enabled(self, account: dict) -> bool: return account.get("enabled", True) def disabled_reason(self, account: dict) -> str: if not self.is_configured(account): return "GitHub App credentials (appId, privateKey, installationId, webhookSecret) are required" return "" async def resolve_allow_from(self, config: dict, account_id: str) -> list[str] | None: account = await self.resolve_account(account_id) return account.get("allow_from", []) def describe_account(self, account: dict) -> dict: return { "account_id": account.get("account_id", ""), "name": account.get("name", ""), "bot_login": account.get("bot_login", ""), "dm_policy": account.get("dm_policy", "open"), "repositories": account.get("repositories", []), "configured": self.is_configured(account), } def default_account_id(self) -> str: return self._gh_cfg.get("defaultAccount", "default") @staticmethod def credential_fingerprint(app_id: int, installation_id: int) -> str: return hashlib.sha256(f"{app_id}:{installation_id}".encode()).hexdigest()[:8] @staticmethod def _env_credentials_exist() -> bool: return bool( os.environ.get("GITHUB_APP_ID", "") and os.environ.get("GITHUB_PRIVATE_KEY", "") and os.environ.get("GITHUB_INSTALLATION_ID", "") and os.environ.get("GITHUB_WEBHOOK_SECRET", "") ) def _build_account(self, account_id: str) -> dict: gh_cfg = self._gh_cfg accounts = gh_cfg.get("accounts", {}) account_raw = accounts.get(account_id, {}) if account_id != "default" else gh_cfg def _get(key: str, default=None): return account_raw.get(key, gh_cfg.get(key, default)) app_id = int(os.environ.get("GITHUB_APP_ID", "0") or _get("appId", 0)) private_key = os.environ.get("GITHUB_PRIVATE_KEY", "") or _get("privateKey", "") if private_key: private_key = private_key.replace("\\n", "\n") installation_id = int(os.environ.get("GITHUB_INSTALLATION_ID", "0") or _get("installationId", 0)) webhook_secret = os.environ.get("GITHUB_WEBHOOK_SECRET", "") or _get("webhookSecret", "") bot_login = os.environ.get("GITHUB_BOT_LOGIN", "") or _get("botLogin", "") return { "account_id": account_id, "app_id": app_id, "private_key": private_key, "installation_id": installation_id, "webhook_secret": webhook_secret, "webhook_path": _get("webhookPath", "/api/webhook/github"), "bot_login": bot_login, "repositories": _get("repositories", []), "name": account_raw.get("name", account_id), "label": account_raw.get("label"), "dm_policy": _get("dmPolicy", "open"), "allow_from": _get("allowFrom", []), "group_policy": _get("groupPolicy", "open"), "enabled": account_raw.get("enabled", True), "text_chunk_limit": int(_get("textChunkLimit", 65000)), "reaction_level": _get("reactionLevel", "ack"), "streaming_mode": _get("streamingMode", "block"), "config": gh_cfg, }