Download app/web.py from 000x/MusicGen: direct link, hf CLI and curl.
- Browser
- Download file 10.4 kB
-
https://huggingface.co/spaces/000x/MusicGen/resolve/main/app/web.py
- Command line
-
hf download hf://spaces/000x/MusicGen/app/web.py
-
curl -L -o web.py https://huggingface.co/spaces/000x/MusicGen/resolve/main/app/web.py
10.4 kB
| """HTTP API and dashboard. Routes and responses match the Node version.""" | |
| from __future__ import annotations | |
| import hmac | |
| import logging | |
| import re | |
| import secrets | |
| import time | |
| from urllib.parse import urlsplit | |
| import asyncpg | |
| from fastapi import FastAPI, Request | |
| from fastapi.responses import FileResponse, JSONResponse | |
| from fastapi.staticfiles import StaticFiles | |
| log = logging.getLogger("web") | |
| ROLES = ("free", "premium", "admin") | |
| CODE_RE = re.compile(r"^[A-Za-z0-9_-]{3,20}$") | |
| ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789" | |
| LOCK_AFTER = 5 | |
| LOCK_SECONDS = 10 * 60 | |
| def _err(status: int, message: str) -> JSONResponse: | |
| return JSONResponse({"error": message}, status_code=status) | |
| def _digits(value) -> str: | |
| return re.sub(r"\D", "", str(value or "")) | |
| def _valid_url(url: str) -> bool: | |
| try: | |
| parts = urlsplit(url) | |
| except ValueError: | |
| return False | |
| return parts.scheme in ("http", "https") and bool(parts.netloc) | |
| def create_app(static_dir: str) -> FastAPI: | |
| app = FastAPI(title="File Desk", docs_url=None, redoc_url=None, openapi_url=None) | |
| failed: dict[str, dict] = {} | |
| def svc(request: Request): | |
| return request.app.state.services | |
| def client_ip(request: Request) -> str: | |
| forwarded = request.headers.get("x-forwarded-for", "") | |
| if forwarded: | |
| return forwarded.split(",")[0].strip() | |
| return request.client.host if request.client else "unknown" | |
| async def admin_auth(request: Request, call_next): | |
| path = request.url.path | |
| if not path.startswith("/api/") or path == "/api/catalog": | |
| return await call_next(request) | |
| s = getattr(request.app.state, "services", None) | |
| if s is None: | |
| return _err(503, "Starting. Try again in a moment.") | |
| ip = client_ip(request) | |
| record = failed.get(ip, {"n": 0, "until": 0.0}) | |
| if time.time() < record["until"]: | |
| return _err(429, "Too many wrong attempts. Try again in a few minutes.") | |
| given = request.headers.get("x-admin-key", "").encode("utf-8") | |
| if hmac.compare_digest(given, s.cfg.admin_password.encode("utf-8")): | |
| failed.pop(ip, None) | |
| return await call_next(request) | |
| record["n"] += 1 | |
| if record["n"] >= LOCK_AFTER: | |
| record = {"n": 0, "until": time.time() + LOCK_SECONDS} | |
| failed[ip] = record | |
| return _err(401, "Wrong password.") | |
| async def unhandled(request: Request, exc: Exception): | |
| log.error("API error on %s: %s", request.url.path, exc) | |
| return _err(500, "Server error. Check the console log.") | |
| async def body(request: Request) -> dict: | |
| try: | |
| data = await request.json() | |
| except Exception: | |
| return {} | |
| return data if isinstance(data, dict) else {} | |
| # ----- health and status ---------------------------------------------- | |
| async def health(request: Request): | |
| return {"status": "ok", "whatsapp": svc(request).wa.connected} | |
| async def status(request: Request): | |
| return svc(request).wa.status() | |
| async def pair(request: Request): | |
| s = svc(request) | |
| phone = _digits((await body(request)).get("phone")) | |
| if len(phone) < 10 or len(phone) > 15: | |
| return _err(400, "Use country code + number, e.g. 94771234567.") | |
| if s.wa.client is None: | |
| return _err(503, "Bot is starting. Try again in 10 seconds.") | |
| if s.wa.connected or s.wa.me_jid is not None: | |
| return _err(409, 'Already linked. Use "Unlink" first to link a different number.') | |
| try: | |
| code = await s.wa.pair_phone(phone) | |
| except Exception as e: | |
| return _err(500, f"Could not create pairing code: {e}") | |
| return {"code": code} | |
| async def unlink(request: Request): | |
| await svc(request).wa.unlink() | |
| return {"ok": True} | |
| # ----- files ---------------------------------------------------------- | |
| async def list_files(request: Request): | |
| return await svc(request).db.list_files() | |
| async def add_file(request: Request): | |
| s = svc(request) | |
| data = await body(request) | |
| title = str(data.get("title") or "").strip() | |
| url = str(data.get("url") or "").strip() | |
| type_ = "video" if data.get("type") == "video" else "document" | |
| code = str(data.get("code") or "").strip() | |
| delete_after = data.get("deleteAfterSend") is True | |
| if not title or len(title) > 100: | |
| return _err(400, "Title is required (max 100 characters).") | |
| if not _valid_url(url): | |
| return _err(400, "Enter a valid http(s) file URL.") | |
| if code: | |
| if not CODE_RE.match(code): | |
| return _err(400, "Code must be 3-20 letters, numbers, - or _.") | |
| if await s.db.find_file(code): | |
| return _err(409, "That code is already used.") | |
| else: | |
| code = await _new_code(s.db) | |
| try: | |
| item = await s.db.add_file(code, title, url, type_, delete_after) | |
| except asyncpg.UniqueViolationError: | |
| return _err(409, "That code is already used.") | |
| return JSONResponse(item, status_code=201) | |
| async def patch_file(code: str, request: Request): | |
| listed = (await body(request)).get("listed") | |
| if not isinstance(listed, bool): | |
| return _err(400, "listed must be true or false.") | |
| item = await svc(request).db.set_listed(code, listed) | |
| return item if item else _err(404, "Code not found.") | |
| async def delete_file(code: str, request: Request): | |
| if await svc(request).db.delete_file(code) == 0: | |
| return _err(404, "Code not found.") | |
| return {"ok": True} | |
| # ----- stats, users, settings ----------------------------------------- | |
| async def stats(request: Request): | |
| return await svc(request).db.stats() | |
| async def users(request: Request): | |
| from .bot import limit_for | |
| s = svc(request) | |
| q = _digits(request.query_params.get("q", "")) | |
| rows = await s.db.list_users(q) | |
| settings = await s.db.get_settings() | |
| out = [] | |
| for u in rows: | |
| limit = limit_for(u, settings["limits"]) | |
| out.append({**u, "limit": None if limit == float("inf") else limit}) | |
| return out | |
| async def patch_user(user_id: str, request: Request): | |
| s = svc(request) | |
| data = await body(request) | |
| phone = _digits(user_id) | |
| patch: dict = {} | |
| if data.get("role") is not None: | |
| if data["role"] not in ROLES: | |
| return _err(400, "Role must be free, premium or admin.") | |
| patch["role"] = data["role"] | |
| if data.get("blocked") is not None: | |
| patch["blocked"] = data["blocked"] is True | |
| if "dailyLimit" in data: | |
| value = data["dailyLimit"] | |
| if value is None or value == "": | |
| patch["dailyLimit"] = None | |
| else: | |
| try: | |
| n = int(value) | |
| except (TypeError, ValueError): | |
| n = -1 | |
| if n < 0 or n > 10000: | |
| return _err(400, "Daily limit must be a whole number from 0 to 10000.") | |
| patch["dailyLimit"] = n | |
| user = await s.db.update_user(phone, patch) if patch else await s.db.get_user(phone) | |
| return user if user else _err(404, "User not found.") | |
| async def get_settings(request: Request): | |
| return await svc(request).db.get_settings() | |
| async def put_settings(request: Request): | |
| s = svc(request) | |
| data = await body(request) | |
| patch: dict = {} | |
| limits = data.get("limits") | |
| if isinstance(limits, dict): | |
| patch["limits"] = {} | |
| for role in ROLES: | |
| if role not in limits: | |
| continue | |
| value = limits[role] | |
| if value is None or value == "": | |
| if role != "admin": | |
| return _err(400, f"{role} needs a number.") | |
| patch["limits"][role] = None | |
| continue | |
| try: | |
| n = int(value) | |
| except (TypeError, ValueError): | |
| n = -1 | |
| if n < 0 or n > 10000: | |
| return _err(400, f"{role} limit must be a whole number from 0 to 10000.") | |
| patch["limits"][role] = n | |
| if "autoDeleteHours" in data: | |
| try: | |
| h = int(data["autoDeleteHours"]) | |
| except (TypeError, ValueError): | |
| h = -1 | |
| if h < 0 or h > 720: | |
| return _err(400, "Auto-delete hours must be a whole number from 0 (never) to 720.") | |
| patch["autoDeleteHours"] = h | |
| return await s.db.set_settings(patch) | |
| # ----- public file list ----------------------------------------------- | |
| async def catalog(request: Request): | |
| s = svc(request) | |
| number = s.wa.bot_number | |
| items = [] | |
| for row in await s.db.list_catalog(): | |
| link = None | |
| if number: | |
| link = f"https://wa.me/{number}?text=" + _quote(".dl " + row["code"]) | |
| items.append({"code": row["code"], "title": row["title"], "type": row["type"], "link": link}) | |
| return JSONResponse({"botNumber": number, "items": items}, headers={"Cache-Control": "no-store"}) | |
| async def catalog_page(): | |
| return FileResponse(f"{static_dir}/catalog.html") | |
| app.mount("/", StaticFiles(directory=static_dir, html=True), name="static") | |
| return app | |
| def _quote(text: str) -> str: | |
| from urllib.parse import quote | |
| return quote(text, safe="") | |
| async def _new_code(db) -> str: | |
| while True: | |
| code = "".join(secrets.choice(ALPHABET) for _ in range(5)) | |
| if not await db.find_file(code): | |
| return code | |