import os, requests from typing import Any, Dict, List, Tuple, Optional from utils.boxapi_log import log_boxapi_response BOXAPI_USERNAME = os.environ.get("BOXAPI_USERNAME", "") BOXAPI_PASSWORD = os.environ.get("BOXAPI_PASSWORD", "") # Your .env can override to /get_media; default matches your cURL (/get_media_by_username) BOXAPI_ENDPOINT = os.environ.get( "BOXAPI_ENDPOINT", "https://boxapi.ir/api/instagram/user/get_media_by_username" ) class BoxAPIError(Exception): pass def get_media(username: str, count: int = 5) -> Tuple[List[Dict[str, Any]], str]: """ Calls BoxAPI and returns (items, log_path). """ if not BOXAPI_USERNAME or not BOXAPI_PASSWORD: raise BoxAPIError("Missing BOXAPI_USERNAME/BOXAPI_PASSWORD in environment.") payload = {"username": username, "count": count} resp = None resp_json: Optional[Dict[str, Any]] = None resp_text: Optional[str] = None status_code: Optional[int] = None try: resp = requests.post( BOXAPI_ENDPOINT, json=payload, auth=(BOXAPI_USERNAME, BOXAPI_PASSWORD), timeout=60, ) status_code = resp.status_code resp_text = resp.text # Try to parse JSON; if it fails, we'll log text and raise try: resp_json = resp.json() except Exception: log_path = log_boxapi_response( username, payload, status_code, response_text=resp_text, note="Non-JSON response" ) raise BoxAPIError(f"BoxAPI non-JSON response. See log: {log_path}") # Expected shape: status -> response -> status_code + body.items top_status = resp_json.get("status") inner = resp_json.get("response") or {} inner_code = inner.get("status_code") body = inner.get("body") or {} items = body.get("items") or [] log_path = log_boxapi_response( username, payload, inner_code, response_json=resp_json, note="OK" ) if top_status != "done" or inner_code != 200 or not isinstance(items, list): # Include a concise snapshot of keys to help debugging snapshot = { "top_status": top_status, "inner_code": inner_code, "has_body": isinstance(body, dict), "items_type": str(type(items)), "items_len": len(items) if isinstance(items, list) else None, } raise BoxAPIError(f"Unexpected response format from BoxAPI ({snapshot}). See log: {log_path}") return items, log_path except BoxAPIError: raise except Exception as e: log_path = log_boxapi_response( username, payload, status_code, response_json=resp_json, response_text=resp_text, note=f"HTTP/parse error: {e}" ) raise BoxAPIError(f"BoxAPI request failed ({e}). See log: {log_path}") # Legacy function for backward compatibility def fetch_boxapi_posts(username: str, count: int = 5) -> List[Dict]: try: items, _ = get_media(username, count) return items except BoxAPIError as e: print(f"❌ Failed to fetch posts from BoxAPI: {e}") return []