ForcePilot/backend/package/yuxi/channel/extensions/github/config.py
Kris 702fcd4c33 feat(github): 新增GitHub渠道插件完整实现
新增了完整的GitHub集成插件,包含认证、会话管理、Webhook处理、消息收发、反应支持等核心功能,支持Issues、Discussions、Pull Request的交互与通知。
2026-05-21 10:48:42 +08:00

113 lines
4.2 KiB
Python

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,
}