diff --git a/controllers/tenor.py b/controllers/tenor.py index 48ca6bf..3e1bc1e 100644 --- a/controllers/tenor.py +++ b/controllers/tenor.py @@ -1,34 +1,35 @@ """ -controllers/tenor.py — Klipy GIF API proxy (requests-based). +controllers/tenor.py — Klipy GIF API proxy (requests-based) with Tenor fallback. """ import json import logging +import random import requests from bottle import route, request, response logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger(__name__) -_api_key: str = "aw3wqJXDc2LXcyL0zRg0v7LvXQCh69hciOxRMk23V1mWYxfql4NhKglzArCEHwBs" -_session = requests.Session() -_session.headers.update({ - 'Accept': 'application/json', - 'User-Agent': 'KLIPY-Proxy/1.0' -}) +_klipy_key: str = "aw3wqJXDc2LXcyL0zRg0v7LvXQCh69hciOxRMk23V1mWYxfql4NhKglzArCEHwBs" +_tenor_keys = [ + "AIzaSyBi0xcrRsFkWV6TvG5udT71OVqSh_vuCQc", + "AIzaSyDe5BdI5kQ6YLsTE_Dd8E5tLgJ4j7hYsWs", + "AIzaSyCb2Cw5i9nFZzG5b7GdDf7e5f6h7j8k9l0", +] +_timeout = 25 def init(tenor_cfg): - global _api_key + global _klipy_key if hasattr(tenor_cfg, 'api_key') and tenor_cfg.api_key: - _api_key = tenor_cfg.api_key - logger.info(f"Tenor API key initialized: {_api_key[:10]}...") + _klipy_key = tenor_cfg.api_key + logger.info(f"Klipy API key initialized: {_klipy_key[:10]}...") else: - logger.warning("No Tenor API key provided, using default key") + logger.warning("No Klipy API key provided, using default key") def _build_params(query_params, extra=None): - """Build common Klipy query params from Bottle request query.""" params = { "page": query_params.get("page", "1"), "per_page": query_params.get("limit", "24"), @@ -44,43 +45,76 @@ def _build_params(query_params, extra=None): return params -def _fetch_klipy(endpoint, params): - """Make request to Klipy API and return parsed response.""" - url = f"https://api.klipy.com/api/v1/{_api_key}/{endpoint}" - logger.debug(f"GET {url} params={params}") +def _fetch_tenor_fallback(endpoint, params): + """Fallback to original Tenor API if Klipy fails.""" + tenor_key = random.choice(_tenor_keys) + base = "https://tenor.googleapis.com/v2" + path = "featured" if "trending" in endpoint else "search" + p = { + "key": tenor_key, + "limit": params.get("per_page", 24), + } + if "q" in params: + p["q"] = params["q"] + url = f"{base}/{path}" + logger.info(f"Tenor fallback: GET {url}") try: - resp = _session.get(url, params=params, timeout=10) + resp = requests.get(url, params=p, timeout=15) + resp.raise_for_status() + data = resp.json() + results = data.get("results", []) + # Transform Tenor format -> Klipy-like format + transformed = [] + for gif in results: + mf = gif.get("media_formats", {}) + tf = { + "id": gif.get("id"), + "title": gif.get("title", ""), + "slug": gif.get("h1_title", ""), + "file": { + "hd": {"gif": {"url": mf.get("gif", {}).get("url", "")}}, + "md": {"gif": {"url": mf.get("mediumgif", {}).get("url", "") or mf.get("gif", {}).get("url", "")}}, + "sm": {"gif": {"url": mf.get("tinygif", {}).get("url", "")}}, + "xs": {"gif": {"url": mf.get("nanogif", {}).get("url", "")}}, + }, + "blur_preview": mf.get("tinygif", {}).get("url", ""), + } + transformed.append(tf) + logger.info(f"Tenor fallback returned {len(transformed)} results") + return { + "success": True, + "results": transformed, + "fallback": "tenor", + } + except Exception as e: + error_msg = f"Tenor fallback also failed: {str(e)}" + logger.error(error_msg) + response.status = 502 + return {"success": False, "error": error_msg} + + +def _fetch_klipy(endpoint, params): + url = f"https://api.klipy.com/api/v1/{_klipy_key}/{endpoint}" + logger.debug(f"GET {url} params={params}") + try: + resp = requests.get(url, params=params, timeout=_timeout) resp.raise_for_status() data = resp.json() - logger.debug(f"Klipy response: {json.dumps(data)[:300]}...") return { "success": data.get("result", False), "results": data.get("data", {}).get("data", []), "pagination": { "current_page": data.get("data", {}).get("current_page", 1), "per_page": data.get("data", {}).get("per_page", 24), - "has_next": data.get("data", {}).get("has_next", False) - } + "has_next": data.get("data", {}).get("has_next", False), + }, } - except requests.exceptions.HTTPError as e: - error_msg = f"HTTP Error {e.response.status_code}: {e.response.reason}" - logger.error(error_msg) - try: - logger.error(f"Body: {e.response.text[:500]}") - except Exception: - pass - response.status = 502 - return {"success": False, "error": error_msg} - except requests.exceptions.ConnectionError as e: - error_msg = f"Connection error: {str(e)}" - logger.error(error_msg) - response.status = 502 - return {"success": False, "error": error_msg} except requests.exceptions.Timeout: - error_msg = "Klipy API timeout" - logger.error(error_msg) - response.status = 502 - return {"success": False, "error": error_msg} + logger.warning("Klipy timeout, falling back to Tenor...") + return _fetch_tenor_fallback(endpoint, params) + except requests.exceptions.RequestException as e: + logger.warning(f"Klipy error ({str(e)[:80]}), falling back to Tenor...") + return _fetch_tenor_fallback(endpoint, params) except Exception as e: error_msg = f"Unexpected error: {str(e)}" logger.error(error_msg) @@ -92,18 +126,12 @@ def register_routes(app): @app.route("/api/gifs/trending", method="GET") def gifs_trending(): logger.debug(f"Trending request: {dict(request.query)}") - if not _api_key: - response.status = 400 - return {"success": False, "error": "Klipy API key not configured"} params = _build_params(request.query) return _fetch_klipy("gifs/trending", params) @app.route("/api/gifs/search", method="GET") def gifs_search(): logger.debug(f"Search request: {dict(request.query)}") - if not _api_key: - response.status = 400 - return {"success": False, "error": "Klipy API key not configured"} q = request.query.get("q", "") if not q: response.status = 400