auto-sync per-repo webhooks on startup + lifespan fix
This commit is contained in:
parent
12b411a8b5
commit
9c9e634d65
41
gitea.py
41
gitea.py
@ -114,6 +114,47 @@ class GiteaClient:
|
|||||||
|
|
||||||
return downloaded
|
return downloaded
|
||||||
|
|
||||||
|
async def list_user_repos(self, owner: str) -> list[dict]:
|
||||||
|
repos = []
|
||||||
|
page = 1
|
||||||
|
async with httpx.AsyncClient() as c:
|
||||||
|
while True:
|
||||||
|
r = await c.get(
|
||||||
|
f"{self.base}/users/{owner}/repos?page={page}&limit=50",
|
||||||
|
headers=self.headers,
|
||||||
|
)
|
||||||
|
r.raise_for_status()
|
||||||
|
batch = r.json()
|
||||||
|
if not batch:
|
||||||
|
break
|
||||||
|
repos.extend(batch)
|
||||||
|
page += 1
|
||||||
|
return repos
|
||||||
|
|
||||||
|
async def ensure_webhook(self, owner: str, repo: str, webhook_url: str):
|
||||||
|
"""Ensure a webhook exists for this repo pointing to webhook_url."""
|
||||||
|
async with httpx.AsyncClient() as c:
|
||||||
|
r = await c.get(
|
||||||
|
f"{self.base}/repos/{owner}/{repo}/hooks",
|
||||||
|
headers=self.headers,
|
||||||
|
)
|
||||||
|
if r.status_code == 200:
|
||||||
|
for h in r.json():
|
||||||
|
if h.get("config", {}).get("url") == webhook_url:
|
||||||
|
return False # already exists
|
||||||
|
r = await c.post(
|
||||||
|
f"{self.base}/repos/{owner}/{repo}/hooks",
|
||||||
|
headers=self.headers,
|
||||||
|
json={
|
||||||
|
"type": "gitea",
|
||||||
|
"active": True,
|
||||||
|
"events": ["issues", "issue_comment", "pull_request", "pull_request_comment"],
|
||||||
|
"config": {"url": webhook_url, "content_type": "json", "secret": ""},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
r.raise_for_status()
|
||||||
|
return True # created
|
||||||
|
|
||||||
async def _download_inline_images(self, texts: list[str], dest_dir: Path) -> list[dict]:
|
async def _download_inline_images(self, texts: list[str], dest_dir: Path) -> list[dict]:
|
||||||
downloaded = []
|
downloaded = []
|
||||||
seen = set()
|
seen = set()
|
||||||
|
|||||||
33
main.py
33
main.py
@ -4,6 +4,8 @@ import hmac
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
from fastapi import FastAPI, Request, HTTPException
|
from fastapi import FastAPI, Request, HTTPException
|
||||||
|
|
||||||
import config
|
import config
|
||||||
@ -13,12 +15,14 @@ from workspace import ensure_repo, repo_path, attachments_path
|
|||||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
|
||||||
log = logging.getLogger("gitea-bot")
|
log = logging.getLogger("gitea-bot")
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI(lifespan=lifespan)
|
||||||
gitea = GiteaClient()
|
gitea = GiteaClient()
|
||||||
|
|
||||||
ALLOWED_OWNER = "fam"
|
ALLOWED_OWNER = "fam"
|
||||||
MAX_RESPONSE_LEN = 60000
|
MAX_RESPONSE_LEN = 60000
|
||||||
CLAUDE_TIMEOUT = 600 # 10 minutes
|
CLAUDE_TIMEOUT = 600 # 10 minutes
|
||||||
|
WEBHOOK_SYNC_INTERVAL = 300 # 5 minutes
|
||||||
|
WEBHOOK_URL = "http://100.65.168.42:9880/webhook"
|
||||||
|
|
||||||
|
|
||||||
def verify_signature(payload: bytes, signature: str) -> bool:
|
def verify_signature(payload: bytes, signature: str) -> bool:
|
||||||
@ -251,6 +255,33 @@ async def webhook(request: Request):
|
|||||||
return {"status": "skip", "reason": f"unhandled {event}/{action}"}
|
return {"status": "skip", "reason": f"unhandled {event}/{action}"}
|
||||||
|
|
||||||
|
|
||||||
|
async def sync_webhooks():
|
||||||
|
"""Ensure all repos under ALLOWED_OWNER have our webhook."""
|
||||||
|
try:
|
||||||
|
repos = await gitea.list_user_repos(ALLOWED_OWNER)
|
||||||
|
for r in repos:
|
||||||
|
name = r["name"]
|
||||||
|
created = await gitea.ensure_webhook(ALLOWED_OWNER, name, WEBHOOK_URL)
|
||||||
|
if created:
|
||||||
|
log.info(f"Added webhook to {ALLOWED_OWNER}/{name}")
|
||||||
|
except Exception:
|
||||||
|
log.exception("Failed to sync webhooks")
|
||||||
|
|
||||||
|
|
||||||
|
async def webhook_sync_loop():
|
||||||
|
"""Periodically sync webhooks for new repos."""
|
||||||
|
while True:
|
||||||
|
await sync_webhooks()
|
||||||
|
await asyncio.sleep(WEBHOOK_SYNC_INTERVAL)
|
||||||
|
|
||||||
|
|
||||||
|
@asynccontextmanager
|
||||||
|
async def lifespan(app):
|
||||||
|
task = asyncio.create_task(webhook_sync_loop())
|
||||||
|
yield
|
||||||
|
task.cancel()
|
||||||
|
|
||||||
|
|
||||||
@app.get("/health")
|
@app.get("/health")
|
||||||
async def health():
|
async def health():
|
||||||
return {"status": "ok"}
|
return {"status": "ok"}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user