Commit 4103e88

benny-dou <60535774+benny-dou@users.noreply.github.com>
2026-05-15 10:37:04
feat(github): support github gist
1 parent 2371348
Changed files (2)
src/database/github.py
@@ -11,8 +11,9 @@ from glom import glom
 from httpx import AsyncClient
 from loguru import logger
 
-from config import DB, PROXY, cache
+from config import DB, PROXY, TOKEN, TZ, cache
 from networking import download_file, hx_req
+from utils import nowdt
 
 
 async def list_assets(
@@ -343,3 +344,69 @@ async def gh_clean_assets(
     num_del = len(files) - keep_latest
     logger.success(f"Deleted {num_del} assets from {gh_user}/{gh_repo}, Release: {release_name}, Tag: {tag_name}")
     return num_del
+
+
+async def list_gists(gh_token: str = TOKEN.GITHUB) -> list[dict]:
+    """List gists from user on GitHub."""
+    if not gh_token:
+        return []
+    headers = {"Accept": "application/vnd.github+json", "Authorization": f"Bearer {gh_token}", "X-GitHub-Api-Version": "2026-03-10"}
+    params = {"page": 1, "per_page": 100}
+    gists = []
+    resp = await hx_req("https://api.github.com/gists", headers=headers, params=params, proxy=PROXY.GITHUB)
+    if not isinstance(resp, list):
+        return []
+    gists.extend(resp)
+    while len(resp) == params["per_page"]:
+        params["page"] += 1
+        resp = await hx_req("https://api.github.com/gists", headers=headers, params=params, proxy=PROXY.GITHUB)
+        if not isinstance(resp, list):
+            return gists
+        gists.extend(resp)
+    return gists
+
+
+async def create_gist(content: str, filename: str, gh_token: str = TOKEN.GITHUB) -> str:
+    """Create a gist on GitHub."""
+    if not all([content, filename, gh_token]):
+        return ""
+    headers = {"Accept": "application/vnd.github+json", "Authorization": f"Bearer {gh_token}", "X-GitHub-Api-Version": "2026-03-10"}
+    today = nowdt(TZ).strftime("%Y-%m-%d")
+    gists = await list_gists(gh_token)
+    gist_id = next((x["id"] for x in gists if f"bennybot-{today}" == x["description"]), None)
+    if not gist_id:
+        # Create new gist
+        resp = await hx_req(
+            "https://api.github.com/gists",
+            "POST",
+            json_data={
+                "public": False,
+                "description": f"bennybot-{today}",
+                "files": {filename: {"content": content}},
+            },
+            check_keys=["id"],
+            headers=headers,
+            proxy=PROXY.GITHUB,
+        )
+        if "id" in resp:
+            return f"https://gists.github.com/{resp['id']}#file-{filename.replace('.', '-')}"
+        return ""
+
+    # update existing gist
+    gist = await hx_req(f"https://api.github.com/gists/{gist_id}", headers=headers, check_kv={"id": gist_id}, proxy=PROXY.GITHUB)
+    if "files" not in gist:
+        return ""
+
+    files = {k: {"content": v["content"]} for k, v in gist["files"].items()}
+    files[filename] = {"content": content}
+    resp = await hx_req(
+        f"https://api.github.com/gists/{gist_id}",
+        "PATCH",
+        json_data={"description": gist["description"], "files": files},
+        check_kv={"id": gist_id},
+        headers=headers,
+        proxy=PROXY.GITHUB,
+    )
+    if "id" in resp:
+        return f"https://gists.github.com/{gist_id}#file-{filename.replace('.', '-')}"
+    return ""
src/networking.py
@@ -34,7 +34,7 @@ async def log_resp(response: Response) -> None:
 
 async def hx_req(
     url,
-    method: str = "GET",
+    method: Literal["GET", "POST", "PUT", "DELETE", "PATCH"] = "GET",
     *,
     transport: AsyncCurlTransport | AsyncHTTPTransport | None = None,
     headers: dict | None = None,
@@ -104,7 +104,7 @@ async def hx_req(
             event_hooks={"request": [log_req], "response": [log_resp]},
         )
 
-    if method not in ["GET", "POST", "PUT", "DELETE"]:
+    if method not in ["GET", "POST", "PUT", "DELETE", "PATCH"]:
         error = f"Invalid method: {method}"
         logger.error(error)
         return {"hx_error": error}
@@ -116,6 +116,8 @@ async def hx_req(
                 response = await client.post(url, cookies=cookies, headers=headers, data=data, json=json_data, files=files, content=content_data, params=params)
             elif method == "PUT":
                 response = await client.put(url, cookies=cookies, headers=headers, data=data, json=json_data, content=content_data, files=files, params=params)
+            elif method == "PATCH":
+                response = await client.patch(url, cookies=cookies, headers=headers, data=data, json=json_data, content=content_data, files=files, params=params)
             else:
                 response = await client.delete(url, cookies=cookies, headers=headers, params=params)
             response.raise_for_status()