Players get a fast TTL-backed homepage (local profile / Cloudflare D1) with dense UI polish; login unlocks /home without blocking on every OpenDota refresh. Co-authored-by: Cursor <cursoragent@cursor.com>
94 lines
3.0 KiB
Python
94 lines
3.0 KiB
Python
"""Hammer R2 create/list until Cloudflare API stops returning 52x."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import time
|
|
import urllib.error
|
|
import urllib.request
|
|
|
|
ACCT = "510534f7f6284344aadaf2f5a0794d48"
|
|
BUCKET = "climperor-player-data"
|
|
API = "https://api.cloudflare.com/client/v4"
|
|
|
|
|
|
def creds():
|
|
email = os.environ.get("CLOUDFLARE_EMAIL") or os.environ.get(
|
|
"KEYZOO_ASSET_META_USERNAME"
|
|
)
|
|
key = os.environ.get("CLOUDFLARE_API_KEY") or os.environ.get(
|
|
"KEYZOO_ASSET_SECRET_GLOBAL_API_KEY"
|
|
)
|
|
if not email or not key:
|
|
raise SystemExit("missing credentials")
|
|
return email, key
|
|
|
|
|
|
def call(method: str, path: str, body: dict | None = None) -> tuple[int, dict | str]:
|
|
email, key = creds()
|
|
data = None if body is None else json.dumps(body).encode()
|
|
req = urllib.request.Request(
|
|
API + path,
|
|
data=data,
|
|
method=method,
|
|
headers={
|
|
"X-Auth-Email": email,
|
|
"X-Auth-Key": key,
|
|
"Content-Type": "application/json",
|
|
"User-Agent": "climperor-retry-r2",
|
|
},
|
|
)
|
|
try:
|
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
|
return resp.status, json.loads(resp.read().decode())
|
|
except urllib.error.HTTPError as e:
|
|
raw = e.read().decode("utf-8", errors="replace")
|
|
try:
|
|
return e.code, json.loads(raw)
|
|
except Exception:
|
|
return e.code, raw[:120]
|
|
|
|
|
|
def main() -> int:
|
|
list_path = f"/accounts/{ACCT}/r2/buckets"
|
|
get_path = f"/accounts/{ACCT}/r2/buckets/{BUCKET}"
|
|
for i in range(20):
|
|
code, payload = call("GET", get_path)
|
|
print(f"[{i+1}] GET {BUCKET} -> {code}", flush=True)
|
|
if code == 200:
|
|
print("bucket ready", flush=True)
|
|
return 0
|
|
if code == 404 or (isinstance(payload, dict) and not payload.get("success")):
|
|
# not found → create
|
|
code2, payload2 = call("POST", list_path, {"name": BUCKET})
|
|
print(f"[{i+1}] POST create -> {code2}", flush=True)
|
|
if code2 in (200, 201):
|
|
print("created", flush=True)
|
|
return 0
|
|
text = str(payload2).lower()
|
|
if "already exists" in text or "10004" in text:
|
|
print("exists", flush=True)
|
|
return 0
|
|
# also try list
|
|
code3, payload3 = call("GET", list_path)
|
|
names = []
|
|
if isinstance(payload3, dict):
|
|
buckets = (payload3.get("result") or {}).get("buckets") or payload3.get(
|
|
"result"
|
|
)
|
|
if isinstance(buckets, list):
|
|
for b in buckets:
|
|
if isinstance(b, dict):
|
|
names.append(b.get("name"))
|
|
print(f"[{i+1}] LIST -> {code3} names={names}", flush=True)
|
|
if BUCKET in names:
|
|
print("bucket ready via list", flush=True)
|
|
return 0
|
|
time.sleep(5 + (i % 5))
|
|
return 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|