| """ |
| Hugging Face Webhooks Automation Substrate |
| Automates real-time event listening and CI/CD triggers on repository changes. |
| Supports `create_webhook`, payload signature validation, and event handling. |
| """ |
|
|
| from huggingface_hub import HfApi, get_token |
| import os |
| import json |
| import hmac |
| import hashlib |
|
|
| class WebhookAutomationManager: |
| def __init__(self, token: str = None): |
| self.token = token or get_token() |
| self.api = HfApi(token=self.token) |
|
|
| def register_repo_webhook( |
| self, |
| watched_repos: list[dict], |
| job_id: str = None, |
| url: str = None, |
| secret: str = None, |
| domains: list[str] = None |
| ) -> dict: |
| """ |
| Create a webhook on Hugging Face to monitor repository or discussion changes. |
| watched: list of dicts e.g. [{"type": "user", "name": "bbkdevops"}] |
| domains: ["repo", "discussion"] |
| """ |
| domains = domains or ["repo", "discussion"] |
| secret = secret or os.environ.get("HF_WEBHOOK_SECRET", "fiber-moe-webhook-secret-2026") |
| |
| try: |
| |
| if hasattr(self.api, "create_webhook"): |
| webhook = self.api.create_webhook( |
| watched=watched_repos, |
| domains=domains, |
| secret=secret, |
| url=url, |
| job_id=job_id |
| ) |
| return {"status": "created", "webhook": str(webhook)} |
| else: |
| return { |
| "status": "simulated", |
| "watched": watched_repos, |
| "domains": domains, |
| "secret": "***" |
| } |
| except Exception as e: |
| return {"status": "error", "error": str(e)} |
|
|
| @staticmethod |
| def verify_webhook_signature(payload_bytes: bytes, signature_header: str, secret: str) -> bool: |
| """Verify the HMAC SHA-256 webhook payload signature.""" |
| if not signature_header or not secret: |
| return False |
| expected = hmac.new(secret.encode("utf-8"), payload_bytes, hashlib.sha256).hexdigest() |
| return hmac.compare_digest(signature_header, expected) |
|
|
| @staticmethod |
| def parse_event_payload(payload_json: str | dict) -> dict: |
| """Parse incoming webhook event metadata.""" |
| if isinstance(payload_json, str): |
| data = json.loads(payload_json) |
| else: |
| data = payload_json |
|
|
| event = data.get("event", {}) |
| repo = data.get("repo", {}) |
| return { |
| "action": event.get("action"), |
| "scope": event.get("scope"), |
| "repo_id": f"{repo.get('owner')}/{repo.get('name')}" if repo else None, |
| "repo_type": repo.get("type"), |
| "head_sha": repo.get("headSha") |
| } |
|
|
| if __name__ == "__main__": |
| manager = WebhookAutomationManager() |
| print("Testing WebhookAutomationManager...") |
| sample_payload = { |
| "event": {"action": "update", "scope": "repo.content"}, |
| "repo": {"owner": "bbkdevops", "name": "Fiber-MoE-Symplectic-Gating-Research", "type": "model", "headSha": "a1b2c3d4"} |
| } |
| parsed = manager.parse_event_payload(sample_payload) |
| print("Parsed Event:", json.dumps(parsed, indent=2)) |
| print("Webhook manager ready!") |
|
|