Spaces:
Sleeping
Sleeping
| import os | |
| import requests | |
| import uvicorn | |
| import logging | |
| from fastapi import FastAPI, Request, Header, HTTPException | |
| from typing import Optional | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| app = FastAPI() | |
| TEAMS_WEBHOOK_URL = os.getenv("TEAMS_WEBHOOK_URL") | |
| WEBHOOK_SECRET = os.getenv("WEBHOOK_SECRET") | |
| HF_TOKEN = os.getenv("HF_TOKEN") | |
| def root(): | |
| return {"status": "running"} | |
| def get_commit_info(repo_type, repo_name, head_sha): | |
| type_map = {"model": "models", "dataset": "datasets", "space": "spaces"} | |
| api_type = type_map.get(repo_type, "models") | |
| headers = {} | |
| if HF_TOKEN: | |
| headers["Authorization"] = f"Bearer {HF_TOKEN}" | |
| commit_title = head_sha[:8] | |
| commit_author = "unknown" | |
| parent_sha = None | |
| changed_files = [] | |
| # 1) ์ปค๋ฐ ๋ชฉ๋ก์์ ์ปค๋ฐ ๋ฉ์์ง, ์์ฑ์, ๋ถ๋ชจ SHA ์กฐํ | |
| try: | |
| commits_url = f"https://huggingface.co/api/{api_type}/{repo_name}/commits/main" | |
| resp = requests.get(commits_url, headers=headers, timeout=10) | |
| if resp.ok: | |
| commits = resp.json() | |
| for i, commit in enumerate(commits): | |
| if commit.get("id", "").startswith(head_sha[:8]): | |
| commit_title = commit.get("title", head_sha[:8]) | |
| authors = commit.get("authors", []) | |
| if authors: | |
| commit_author = authors[0].get("user", "unknown") | |
| # ๋ฐ๋ก ๋ค์์ด ๋ถ๋ชจ ์ปค๋ฐ | |
| if i + 1 < len(commits): | |
| parent_sha = commits[i + 1].get("id") | |
| break | |
| except Exception as e: | |
| logger.error(f"Commits API error: {e}") | |
| # 2) ํ์ฌ ์ปค๋ฐ๊ณผ ๋ถ๋ชจ ์ปค๋ฐ์ ํ์ผ ํธ๋ฆฌ ๋น๊ต | |
| if parent_sha: | |
| try: | |
| def get_tree(revision): | |
| tree_url = f"https://huggingface.co/api/{api_type}/{repo_name}/tree/{revision}" | |
| logger.info(f"Fetching tree: {tree_url}") | |
| r = requests.get(tree_url, headers=headers, timeout=10, params={"recursive": "true"}) | |
| logger.info(f" Tree status: {r.status_code}") | |
| if r.ok: | |
| return {f.get("path"): f.get("oid") for f in r.json() if f.get("type") == "file"} | |
| return {} | |
| current_files = get_tree(head_sha) | |
| parent_files = get_tree(parent_sha) | |
| # ์ถ๊ฐ๋ ํ์ผ | |
| for path in current_files: | |
| if path not in parent_files: | |
| changed_files.append(f"๐ {path}") | |
| elif current_files[path] != parent_files[path]: | |
| changed_files.append(f"โ๏ธ {path}") | |
| # ์ญ์ ๋ ํ์ผ | |
| for path in parent_files: | |
| if path not in current_files: | |
| changed_files.append(f"๐๏ธ {path}") | |
| except Exception as e: | |
| logger.error(f"Tree compare error: {e}") | |
| return commit_title, commit_author, changed_files | |
| async def handle_webhook( | |
| request: Request, | |
| x_webhook_secret: Optional[str] = Header(default=None) | |
| ): | |
| if WEBHOOK_SECRET: | |
| if x_webhook_secret != WEBHOOK_SECRET: | |
| raise HTTPException(status_code=403, detail="Invalid secret") | |
| payload = await request.json() | |
| # ์ ์ฒด payload ๋ก๊น (๋๋ฒ๊น ์ฉ) | |
| logger.info(f"=== WEBHOOK RECEIVED ===") | |
| logger.info(f"Full payload keys: {list(payload.keys())}") | |
| logger.info(f"Event: {payload.get('event')}") | |
| logger.info(f"Repo: {payload.get('repo')}") | |
| event = payload.get("event", {}) | |
| repo = payload.get("repo", {}) | |
| if event.get("action") == "update" and event.get("scope", "").startswith("repo.content"): | |
| repo_name = repo.get("name", "unknown") | |
| repo_type = repo.get("type", "model") | |
| head_sha = repo.get("headSha", "") | |
| commit_title, commit_author, changed_files = get_commit_info(repo_type, repo_name, head_sha) | |
| if changed_files: | |
| files_text = "\n".join(changed_files[:15]) | |
| if len(changed_files) > 15: | |
| files_text += f"\n...์ธ {len(changed_files) - 15}๊ฐ ํ์ผ" | |
| else: | |
| files_text = "(๋ณ๊ฒฝ ํ์ผ ์กฐํ ์คํจ - Space ๋ก๊ทธ๋ฅผ ํ์ธํ์ธ์)" | |
| card = { | |
| "type": "message", | |
| "attachments": [{ | |
| "contentType": "application/vnd.microsoft.card.adaptive", | |
| "contentUrl": None, | |
| "content": { | |
| "type": "AdaptiveCard", | |
| "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", | |
| "version": "1.4", | |
| "body": [ | |
| { | |
| "type": "TextBlock", | |
| "text": f"๐ฆ {repo_name}", | |
| "wrap": True, "weight": "Bolder", "size": "Large" | |
| }, | |
| { | |
| "type": "FactSet", | |
| "facts": [ | |
| {"title": "์ปค๋ฐ", "value": commit_title}, | |
| {"title": "์์ฑ์", "value": commit_author}, | |
| {"title": "SHA", "value": head_sha[:8]} | |
| ] | |
| }, | |
| { | |
| "type": "TextBlock", | |
| "text": "**๋ณ๊ฒฝ๋ ํ์ผ:**", | |
| "wrap": True, "spacing": "Medium" | |
| }, | |
| { | |
| "type": "TextBlock", | |
| "text": files_text, | |
| "wrap": True, "fontType": "Monospace", "size": "Small" | |
| }, | |
| { | |
| "type": "ActionSet", | |
| "actions": [{ | |
| "type": "Action.OpenUrl", | |
| "title": "์ปค๋ฐ ๋ณด๊ธฐ", | |
| "url": f"https://huggingface.co/{repo_name}/commit/{head_sha}" | |
| }] | |
| } | |
| ] | |
| } | |
| }] | |
| } | |
| resp = requests.post(TEAMS_WEBHOOK_URL, json=card) | |
| return {"sent": True, "teams_status": resp.status_code} | |
| return {"sent": False, "reason": "not a repo content update"} | |
| if __name__ == "__main__": | |
| uvicorn.run(app, host="0.0.0.0", port=7860) |