{"record":{"id":"3110c50c5684c06d","repo":"harry0703/MoneyPrinterTurbo","slug":"sonilo-api-key-is-required","errorCode":null,"errorMessage":"Sonilo API key is required","messagePattern":"Sonilo API key is required","errorType":"exception","errorClass":"SoniloError","httpStatus":null,"severity":"error","filePath":"app/services/sonilo.py","lineNumber":90,"sourceCode":"    \"\"\"\n    return service_id.strip().lower().replace(\"-\", \"_\")\n\n\ndef _safe_response_error(response: requests.Response) -> str:\n    \"\"\"仅保留简短响应信息，既方便定位又避免异常页面污染日志。\"\"\"\n    body = (response.text or \"\").strip().replace(\"\\n\", \" \")[:500]\n    return body or response.reason or \"request failed\"\n\n\ndef test_connection() -> dict[str, Any]:\n    \"\"\"\n    使用不消耗配乐额度的服务列表接口验证 API Key。\n\n    返回原始 JSON 便于 UI 展示可用服务，但日志中绝不记录 Key 或请求头。\n    \"\"\"\n    api_key = get_api_key()\n    if not api_key:\n        raise SoniloError(\"Sonilo API key is required\")\n    try:\n        response = requests.get(\n            f\"{_base_url()}{SERVICES_PATH}\",\n            headers={\"Authorization\": f\"Bearer {api_key}\"},\n            timeout=(15, 30),\n        )\n    except requests.RequestException as exc:\n        raise SoniloError(f\"failed to connect to Sonilo: {exc}\") from exc\n    if not response.ok:\n        raise SoniloError(\n            f\"Sonilo connection failed ({response.status_code}): \"\n            f\"{_safe_response_error(response)}\"\n        )\n    try:\n        payload = response.json()\n    except ValueError as exc:\n        raise SoniloError(\"Sonilo returned an invalid service response\") from exc\n    if not isinstance(payload, dict):","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/harry0703/MoneyPrinterTurbo/blob/1f9f19c2021a68d04df228f33e9099a0c947f6f8/app/services/sonilo.py#L72-L108","documentation":"Raised by sonilo.test_connection() when get_api_key() returns an empty string. The key resolution order is config.app['sonilo_api_key'] then the SONILO_API_KEY environment variable; when both are empty/whitespace, the BGM connection test cannot run. This is a pre-flight guard, not an API error.","triggerScenarios":"Invoking sonilo.test_connection() (from WebUI connection test or task startup) while config.toml has no sonilo_api_key, the WebUI field is empty, and the SONILO_API_KEY env var is unset or whitespace-only.","commonSituations":"User enabled Sonilo BGM generation but never entered a key; key was cleared when config.toml was re-saved from the WebUI; service/deployment env lost SONILO_API_KEY between restarts; key saved under a misspelled config key.","solutions":["Set sonilo_api_key in config.toml under [app], or export SONILO_API_KEY in the service environment, then re-run the connection test","If using WebUI, save the key through the Sonilo settings field so config.app persists it","Verify no leading/trailing whitespace-only value was saved (it strips to empty and still fails)","Check is_enabled() before invoking BGM flows so tasks degrade gracefully instead of erroring"],"exampleFix":"# shell\nexport SONILO_API_KEY=\"sk-live-...\"\n\n# or config.toml\n[app]\nsonilo_api_key = \"sk-live-...\"","handlingStrategy":"validation","validationCode":"from app.services import sonilo\n\nif not sonilo.is_enabled():\n    # skip BGM/Sonilo flows instead of invoking test_connection()\n    disable_sonilo_bgm()","typeGuard":null,"tryCatchPattern":"except SoniloError as e: if 'API key is required' in str(e): non-retryable — collect the key from the user (config or SONILO_API_KEY) before retrying","preventionTips":["Call sonilo.is_enabled() before any Sonilo flow","Save the key via WebUI so it persists in config.app","Set SONILO_API_KEY in the deployment environment as a fallback"],"tags":["configuration","sonilo","bgm","api-key"],"backgroundTag":null,"analyzedSha":"1f9f19c2021a68d04df228f33e9099a0c947f6f8","analyzedAt":"2026-08-14T19:41:05.568Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}