Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/austin33133-maker_spartdata.json
T

1 line
54 KiB
JSON

{"repo": "austin33133-maker/spartdata", "files": {"deploy/configure-upstream-channels.py": {"e09b33214d973c79dc6e21195ab4f3cb7359f1d6": {"t": 1785747438.0113096, "n": 8575, "h": "4465966fc448bcb9", "c": "#!/usr/bin/env python3\n\"\"\"Configure DeepSeek / Qwen / Kling upstream channels in New API MySQL.\"\"\"\nfrom __future__ import annotations\n\nimport json\nimport os\nimport sys\nimport time\n\nimport pymysql\n\n# New API channel types (CalciumIon/new-api)\nTYPE_DEEPSEEK = 43\nTYPE_ALI = 17\nTYPE_KLING = 50 # 可灵 Kling\nTYPE_DOUBAO_VIDEO = 54 # 火山方舟 Seedance / 豆包视频\n\n# Two flagship models per direct upstream channel\nDEEPSEEK_MODELS = [\"deepseek-chat\", \"deepseek-reasoner\"]\nQWEN_MODELS = [\"qwen-max\", \"qwen-plus\"]\nKLING_MODELS = [\"kling-v2-master\", \"kling-v2-1-master\"]\nSEEDANCE_MODELS = [\"doubao-seedance-2-0-260128\", \"doubao-seedance-2-0-fast-260128\"]\n\nKLING_BASE_URL = \"https://api-beijing.klingai.com\"\n\nSTUDIO_CHAT_MAP = {\n \"doubao-pro-32k\": \"qwen-max\", # placeholder until doubao key\n \"qwen-max\": \"qwen-max\",\n \"deepseek-chat\": \"deepseek-chat\",\n}\n\nKLING_VIDEO_MAP = {\n \"kling-3\": \"kling-v2-master\",\n \"kling-v2-6\": \"kling-v2-1-master\",\n}\n\nSEEDANCE_VIDEO_MAP = {\n \"seedance-2\": \"doubao-seedance-2-0-260128\",\n \"seedance-2-fast\": \"doubao-seedance-2-0-fast-260128\",\n}\n\n\ndef normalize_kling_key(raw: str) -> str:\n \"\"\"Pass through api-key-kling-* bearer tokens; legacy AK,SK still supported.\"\"\"\n key = raw.strip()\n if key.lower().startswith(\"api-key-kling-\"):\n return key\n if \",\" in key or \"|\" in key:\n return key\n prefix = \"api-key-kling-\"\n core = key[len(prefix) :] if key.lower().startswith(prefix) else key\n if \"_\" in core:\n access, secret = core.rsplit(\"_\", 1)\n if access and secret and len(secret) >= 8:\n return f\"{access},{secret}\"\n return key\n\n\ndef load_env() -> None:\n for path in (\"/opt/spartdata-newapi/.env.mysql\", \"/tmp/upstream-keys.env\"):\n if os.path.isfile(path):\n for line in open(path, encoding=\"utf-8\"):\n line = line.strip()\n if line and \"=\" in line and not line.startswith(\"#\"):\n k, v = line.split(\"=\", 1)\n os.environ.setdefault(k.strip(), v.strip())\n\n\ndef connect_from_dsn() -> pymysql.connections.Connection:\n host = os.environ.get(\"MYSQL_HOST\", \"127.0.0.1\")\n port = int(os.environ.get(\"MYSQL_PORT\", \"3307\"))\n user = os.environ.get(\"MYSQL_USER\", \"spartapi\")\n password = os.environ.get(\"MYSQL_PASSWORD\", \"\")\n database = os.environ.get(\"MYSQL_DATABASE\", \"spartdata_newapi\")\n\n if not password:\n import subprocess\n\n out = subprocess.check_output(\n [\"sudo\", \"docker\", \"exec\", \"spartdata-newapi\", \"printenv\", \"SQL_DSN\"],\n text=True,\n ).strip()\n creds, rest = out.split(\"@\", 1)\n user, password = creds.split(\":\", 1)\n host_port, database = rest.replace(\"tcp(\", \"\").split(\")/\", 1)\n host, port_str = host_port.split(\":\")\n if host == \"mysql\":\n host = \"127.0.0.1\"\n port = 3307\n else:\n port = int(port_str.split(\"?\")[0])\n\n return pymysql.connect(\n host=host,\n port=port,\n user=user,\n password=password,\n database=database.split(\"?\")[0],\n charset=\"utf8mb4\",\n cursorclass=pymysql.cursors.DictCursor,\n )\n\n\ndef upsert_channel(\n cur: pymysql.cursors.DictCursor,\n *,\n name: str,\n channel_type: int,\n key: str,\n base_url: str,\n models: list[str],\n model_mapping: dict[str, str] | None = None,\n priority: int = 10,\n) -> int:\n cur.execute(\"SELECT id FROM channels WHERE name = %s LIMIT 1\", (name,))\n row = cur.fetchone()\n models_csv = \",\".join(models)\n mapping_json = json.dumps(model_mapping or {}, ensure_ascii=False)\n now = int(time.time())\n\n if row:\n channel_id = row[\"id\"]\n cur.execute(\n \"\"\"\n UPDATE channels SET\n type = %s, `key` = %s, status = 1, base_url = %s,\n models = %s, model_mapping = %s, priority = %s,\n weight = 1, auto_ban = 0\n WHERE id = %s\n \"\"\",\n (channel_type, key, base_url, models_csv, mapping_json, priority, channel_id),\n )\n else:\n cur.execute(\n \"\"\"\n INSERT INTO channels (\n type, `key`, status, name, weight, created_time, base_url,\n models, `group`, used_quota, model_mapping, priority, auto_ban\n ) VALUES (%s, %s, 1, %s, 1, %s, %s, %s, 'default', 0, %s, %s, 0)\n \"\"\",\n (channel_type, key, name, now, base_url, models_csv, mapping_json, priority),\n )\n channel_id = cur.lastrowid\n\n for model in models:\n cur.execute(\n \"SELECT 1 FROM abilities WHERE channel_id = %s AND model = %s AND `group` = 'default' LIMIT 1\",\n (channel_id, model),\n )\n if not cur.fetchone():\n cur.execute(\n \"INSERT INTO abilities (`group`, model, channel_id, enabled, priority, weight, tag) \"\n \"VALUES ('default', %s, %s, 1, 0, 0, '')\",\n (model, channel_id),\n )\n\n for alias, upstream in (model_mapping or {}).items():\n cur.execute(\n \"SELECT 1 FROM abilities WHERE channel_id = %s AND model = %s AND `group` = 'default' LIMIT 1\",\n (channel_id, alias),\n )\n if not cur.fetchone():\n cur.execute(\n \"INSERT INTO abilities (`group`, model, channel_id, enabled, priority, weight, tag) \"\n \"VALUES ('default', %s, %s, 1, 0, 0, '')\",\n (alias, channel_id),\n )\n\n return int(channel_id)\n\n\ndef normalize_seedance_key(raw: str) -> str:\n \"\"\"Ark expects a single Bearer API Key from 火山方舟控制台.\"\"\"\n key = raw.strip()\n if key.lower().startswith(\"secret access key:\"):\n key = key.split(\":\", 1)[1].strip()\n return key\n\n\ndef main() -> int:\n load_env()\n deepseek_key = os.environ.get(\"UPSTREAM_DEEPSEEK_KEY\", \"\").strip()\n qwen_key = os.environ.get(\"UPSTREAM_QWEN_KEY\", \"\").strip()\n kling_key = os.environ.get(\"UPSTREAM_KLING_KEY\", \"\").strip()\n seedance_key = os.environ.get(\"UPSTREAM_SEEDANCE_KEY\", \"\").strip()\n\n if not any([deepseek_key, qwen_key, kling_key, seedance_key]):\n print(\n \"Set UPSTREAM_DEEPSEEK_KEY / UPSTREAM_QWEN_KEY / UPSTREAM_KLING_KEY / UPSTREAM_SEEDANCE_KEY\",\n file=sys.stderr,\n )\n return 1\n\n conn = connect_from_dsn()\n results: dict[str, int] = {}\n try:\n with conn.cursor() as cur:\n if deepseek_key:\n results[\"deepseek\"] = upsert_channel(\n cur,\n name=\"DeepSeek 官方\",\n channel_type=TYPE_DEEPSEEK,\n key=deepseek_key,\n base_url=\"https://api.deepseek.com\",\n models=DEEPSEEK_MODELS,\n model_mapping={\"deepseek-chat\": \"deepseek-chat\"},\n priority=20,\n )\n\n if qwen_key:\n results[\"qwen\"] = upsert_channel(\n cur,\n name=\"通义千问 DashScope\",\n channel_type=TYPE_ALI,\n key=qwen_key,\n base_url=\"https://dashscope.aliyuncs.com\",\n models=QWEN_MODELS,\n model_mapping={\n \"qwen-max\": \"qwen-max\",\n \"qwen-plus\": \"qwen-plus\",\n \"doubao-pro-32k\": \"qwen-max\",\n },\n priority=18,\n )\n\n if kling_key:\n results[\"kling\"] = upsert_channel(\n cur,\n name=\"可灵 Kling\",\n channel_type=TYPE_KLING,\n key=normalize_kling_key(kling_key),\n base_url=KLING_BASE_URL,\n models=KLING_MODELS,\n model_mapping=KLING_VIDEO_MAP,\n priority=22,\n )\n\n if seedance_key:\n results[\"seedance\"] = upsert_channel(\n cur,\n name=\"火山引擎 Seedance 2.0\",\n channel_type=TYPE_DOUBAO_VIDEO,\n key=normalize_seedance_key(seedance_key),\n base_url=\"https://ark.cn-beijing.volces.com\",\n models=SEEDANCE_MODELS,\n model_mapping=SEEDANCE_VIDEO_MAP,\n priority=24,\n )\n\n conn.commit()\n print(json.dumps({\"ok\": True, \"channels\": results}, ensure_ascii=False))\n finally:\n conn.close()\n return 0\n\n\nif __name__ == \"__main__\":\n raise SystemExit(main())\n"}, "f58aba507f05795a633b8767f0640eaa9f88a333": {"t": 1785773316.2758512, "n": 8575, "h": "4465966fc448bcb9", "c": "#!/usr/bin/env python3\n\"\"\"Configure DeepSeek / Qwen / Kling upstream channels in New API MySQL.\"\"\"\nfrom __future__ import annotations\n\nimport json\nimport os\nimport sys\nimport time\n\nimport pymysql\n\n# New API channel types (CalciumIon/new-api)\nTYPE_DEEPSEEK = 43\nTYPE_ALI = 17\nTYPE_KLING = 50 # 可灵 Kling\nTYPE_DOUBAO_VIDEO = 54 # 火山方舟 Seedance / 豆包视频\n\n# Two flagship models per direct upstream channel\nDEEPSEEK_MODELS = [\"deepseek-chat\", \"deepseek-reasoner\"]\nQWEN_MODELS = [\"qwen-max\", \"qwen-plus\"]\nKLING_MODELS = [\"kling-v2-master\", \"kling-v2-1-master\"]\nSEEDANCE_MODELS = [\"doubao-seedance-2-0-260128\", \"doubao-seedance-2-0-fast-260128\"]\n\nKLING_BASE_URL = \"https://api-beijing.klingai.com\"\n\nSTUDIO_CHAT_MAP = {\n \"doubao-pro-32k\": \"qwen-max\", # placeholder until doubao key\n \"qwen-max\": \"qwen-max\",\n \"deepseek-chat\": \"deepseek-chat\",\n}\n\nKLING_VIDEO_MAP = {\n \"kling-3\": \"kling-v2-master\",\n \"kling-v2-6\": \"kling-v2-1-master\",\n}\n\nSEEDANCE_VIDEO_MAP = {\n \"seedance-2\": \"doubao-seedance-2-0-260128\",\n \"seedance-2-fast\": \"doubao-seedance-2-0-fast-260128\",\n}\n\n\ndef normalize_kling_key(raw: str) -> str:\n \"\"\"Pass through api-key-kling-* bearer tokens; legacy AK,SK still supported.\"\"\"\n key = raw.strip()\n if key.lower().startswith(\"api-key-kling-\"):\n return key\n if \",\" in key or \"|\" in key:\n return key\n prefix = \"api-key-kling-\"\n core = key[len(prefix) :] if key.lower().startswith(prefix) else key\n if \"_\" in core:\n access, secret = core.rsplit(\"_\", 1)\n if access and secret and len(secret) >= 8:\n return f\"{access},{secret}\"\n return key\n\n\ndef load_env() -> None:\n for path in (\"/opt/spartdata-newapi/.env.mysql\", \"/tmp/upstream-keys.env\"):\n if os.path.isfile(path):\n for line in open(path, encoding=\"utf-8\"):\n line = line.strip()\n if line and \"=\" in line and not line.startswith(\"#\"):\n k, v = line.split(\"=\", 1)\n os.environ.setdefault(k.strip(), v.strip())\n\n\ndef connect_from_dsn() -> pymysql.connections.Connection:\n host = os.environ.get(\"MYSQL_HOST\", \"127.0.0.1\")\n port = int(os.environ.get(\"MYSQL_PORT\", \"3307\"))\n user = os.environ.get(\"MYSQL_USER\", \"spartapi\")\n password = os.environ.get(\"MYSQL_PASSWORD\", \"\")\n database = os.environ.get(\"MYSQL_DATABASE\", \"spartdata_newapi\")\n\n if not password:\n import subprocess\n\n out = subprocess.check_output(\n [\"sudo\", \"docker\", \"exec\", \"spartdata-newapi\", \"printenv\", \"SQL_DSN\"],\n text=True,\n ).strip()\n creds, rest = out.split(\"@\", 1)\n user, password = creds.split(\":\", 1)\n host_port, database = rest.replace(\"tcp(\", \"\").split(\")/\", 1)\n host, port_str = host_port.split(\":\")\n if host == \"mysql\":\n host = \"127.0.0.1\"\n port = 3307\n else:\n port = int(port_str.split(\"?\")[0])\n\n return pymysql.connect(\n host=host,\n port=port,\n user=user,\n password=password,\n database=database.split(\"?\")[0],\n charset=\"utf8mb4\",\n cursorclass=pymysql.cursors.DictCursor,\n )\n\n\ndef upsert_channel(\n cur: pymysql.cursors.DictCursor,\n *,\n name: str,\n channel_type: int,\n key: str,\n base_url: str,\n models: list[str],\n model_mapping: dict[str, str] | None = None,\n priority: int = 10,\n) -> int:\n cur.execute(\"SELECT id FROM channels WHERE name = %s LIMIT 1\", (name,))\n row = cur.fetchone()\n models_csv = \",\".join(models)\n mapping_json = json.dumps(model_mapping or {}, ensure_ascii=False)\n now = int(time.time())\n\n if row:\n channel_id = row[\"id\"]\n cur.execute(\n \"\"\"\n UPDATE channels SET\n type = %s, `key` = %s, status = 1, base_url = %s,\n models = %s, model_mapping = %s, priority = %s,\n weight = 1, auto_ban = 0\n WHERE id = %s\n \"\"\",\n (channel_type, key, base_url, models_csv, mapping_json, priority, channel_id),\n )\n else:\n cur.execute(\n \"\"\"\n INSERT INTO channels (\n type, `key`, status, name, weight, created_time, base_url,\n models, `group`, used_quota, model_mapping, priority, auto_ban\n ) VALUES (%s, %s, 1, %s, 1, %s, %s, %s, 'default', 0, %s, %s, 0)\n \"\"\",\n (channel_type, key, name, now, base_url, models_csv, mapping_json, priority),\n )\n channel_id = cur.lastrowid\n\n for model in models:\n cur.execute(\n \"SELECT 1 FROM abilities WHERE channel_id = %s AND model = %s AND `group` = 'default' LIMIT 1\",\n (channel_id, model),\n )\n if not cur.fetchone():\n cur.execute(\n \"INSERT INTO abilities (`group`, model, channel_id, enabled, priority, weight, tag) \"\n \"VALUES ('default', %s, %s, 1, 0, 0, '')\",\n (model, channel_id),\n )\n\n for alias, upstream in (model_mapping or {}).items():\n cur.execute(\n \"SELECT 1 FROM abilities WHERE channel_id = %s AND model = %s AND `group` = 'default' LIMIT 1\",\n (channel_id, alias),\n )\n if not cur.fetchone():\n cur.execute(\n \"INSERT INTO abilities (`group`, model, channel_id, enabled, priority, weight, tag) \"\n \"VALUES ('default', %s, %s, 1, 0, 0, '')\",\n (alias, channel_id),\n )\n\n return int(channel_id)\n\n\ndef normalize_seedance_key(raw: str) -> str:\n \"\"\"Ark expects a single Bearer API Key from 火山方舟控制台.\"\"\"\n key = raw.strip()\n if key.lower().startswith(\"secret access key:\"):\n key = key.split(\":\", 1)[1].strip()\n return key\n\n\ndef main() -> int:\n load_env()\n deepseek_key = os.environ.get(\"UPSTREAM_DEEPSEEK_KEY\", \"\").strip()\n qwen_key = os.environ.get(\"UPSTREAM_QWEN_KEY\", \"\").strip()\n kling_key = os.environ.get(\"UPSTREAM_KLING_KEY\", \"\").strip()\n seedance_key = os.environ.get(\"UPSTREAM_SEEDANCE_KEY\", \"\").strip()\n\n if not any([deepseek_key, qwen_key, kling_key, seedance_key]):\n print(\n \"Set UPSTREAM_DEEPSEEK_KEY / UPSTREAM_QWEN_KEY / UPSTREAM_KLING_KEY / UPSTREAM_SEEDANCE_KEY\",\n file=sys.stderr,\n )\n return 1\n\n conn = connect_from_dsn()\n results: dict[str, int] = {}\n try:\n with conn.cursor() as cur:\n if deepseek_key:\n results[\"deepseek\"] = upsert_channel(\n cur,\n name=\"DeepSeek 官方\",\n channel_type=TYPE_DEEPSEEK,\n key=deepseek_key,\n base_url=\"https://api.deepseek.com\",\n models=DEEPSEEK_MODELS,\n model_mapping={\"deepseek-chat\": \"deepseek-chat\"},\n priority=20,\n )\n\n if qwen_key:\n results[\"qwen\"] = upsert_channel(\n cur,\n name=\"通义千问 DashScope\",\n channel_type=TYPE_ALI,\n key=qwen_key,\n base_url=\"https://dashscope.aliyuncs.com\",\n models=QWEN_MODELS,\n model_mapping={\n \"qwen-max\": \"qwen-max\",\n \"qwen-plus\": \"qwen-plus\",\n \"doubao-pro-32k\": \"qwen-max\",\n },\n priority=18,\n )\n\n if kling_key:\n results[\"kling\"] = upsert_channel(\n cur,\n name=\"可灵 Kling\",\n channel_type=TYPE_KLING,\n key=normalize_kling_key(kling_key),\n base_url=KLING_BASE_URL,\n models=KLING_MODELS,\n model_mapping=KLING_VIDEO_MAP,\n priority=22,\n )\n\n if seedance_key:\n results[\"seedance\"] = upsert_channel(\n cur,\n name=\"火山引擎 Seedance 2.0\",\n channel_type=TYPE_DOUBAO_VIDEO,\n key=normalize_seedance_key(seedance_key),\n base_url=\"https://ark.cn-beijing.volces.com\",\n models=SEEDANCE_MODELS,\n model_mapping=SEEDANCE_VIDEO_MAP,\n priority=24,\n )\n\n conn.commit()\n print(json.dumps({\"ok\": True, \"channels\": results}, ensure_ascii=False))\n finally:\n conn.close()\n return 0\n\n\nif __name__ == \"__main__\":\n raise SystemExit(main())\n"}}, "deploy/diagnose-channels.py": {"e09b33214d973c79dc6e21195ab4f3cb7359f1d6": {"t": 1785747438.5164514, "n": 2640, "h": "68fc8dc44e9bc02c", "c": "#!/usr/bin/env python3\n\"\"\"Diagnose New API channels — keys, types, balances, test hints.\"\"\"\nfrom __future__ import annotations\n\nimport json\nimport os\nimport urllib.request\n\nimport pymysql\n\nfor line in open(\"/opt/spartdata-newapi/.env.mysql\", encoding=\"utf-8\"):\n line = line.strip()\n if line and \"=\" in line and not line.startswith(\"#\"):\n k, v = line.split(\"=\", 1)\n os.environ.setdefault(k, v)\n\nconn = pymysql.connect(\n host=os.environ.get(\"MYSQL_HOST\", \"127.0.0.1\"),\n port=int(os.environ.get(\"MYSQL_PORT\", \"3307\")),\n user=os.environ[\"MYSQL_USER\"],\n password=os.environ[\"MYSQL_PASSWORD\"],\n database=os.environ[\"MYSQL_DATABASE\"],\n charset=\"utf8mb4\",\n cursorclass=pymysql.cursors.DictCursor,\n)\n\nTYPE_NAMES = {\n 1: \"OpenAI\",\n 17: \"Ali/DashScope\",\n 43: \"DeepSeek\",\n 50: \"Kling\",\n 54: \"Doubao/Seedance\",\n 20: \"OpenRouter?\",\n}\n\ndef mask_key(k: str | None) -> str:\n if not k:\n return \"(empty)\"\n k = k.strip()\n if len(k) <= 8:\n return f\"len={len(k)} ***\"\n return f\"len={len(k)} {k[:4]}...{k[-4:]}\"\n\n\nrows = []\nwith conn.cursor() as cur:\n cur.execute(\n \"SELECT id, name, type, status, base_url, priority, weight, \"\n \"response_time, balance, used_quota, models, \"\n \"LENGTH(`key`) AS key_len, `key` \"\n \"FROM channels ORDER BY id\"\n )\n for r in cur.fetchall():\n key = r.pop(\"key\", \"\") or \"\"\n r[\"key_preview\"] = mask_key(key)\n r[\"key_format_ok\"] = bool(key.strip())\n if r[\"type\"] == 50 and key and \",\" not in key:\n r[\"kling_hint\"] = \"Kling needs access_key,secret_key (comma-separated)\"\n if r[\"type\"] == 54 and not key.startswith(\"sk-\"):\n r[\"seedance_hint\"] = \"Ark usually uses sk-... API key\"\n r[\"type_name\"] = TYPE_NAMES.get(r[\"type\"], f\"type-{r['type']}\")\n models = (r.get(\"models\") or \"\")[:80]\n r[\"models_preview\"] = models\n rows.append(r)\nconn.close()\n\nprint(\"=== channels ===\")\nprint(json.dumps(rows, ensure_ascii=False, indent=2, default=str))\n\n# Quick upstream probes (no secrets printed)\nprint(\"\\n=== upstream reachability (no auth) ===\")\nprobes = [\n (\"DeepSeek\", \"https://api.deepseek.com/models\"),\n (\"DashScope\", \"https://dashscope.aliyuncs.com/compatible-mode/v1/models\"),\n (\"OpenRouter\", \"https://openrouter.ai/api/v1/models\"),\n]\nfor name, url in probes:\n try:\n req = urllib.request.Request(url, headers={\"User-Agent\": \"spart-diagnose/1\"})\n with urllib.request.urlopen(req, timeout=8) as resp:\n print(f\" {name}: HTTP {resp.status}\")\n except Exception as e:\n print(f\" {name}: FAIL {e}\")\n"}, "f58aba507f05795a633b8767f0640eaa9f88a333": {"t": 1785773319.0867221, "n": 2640, "h": "68fc8dc44e9bc02c", "c": "#!/usr/bin/env python3\n\"\"\"Diagnose New API channels — keys, types, balances, test hints.\"\"\"\nfrom __future__ import annotations\n\nimport json\nimport os\nimport urllib.request\n\nimport pymysql\n\nfor line in open(\"/opt/spartdata-newapi/.env.mysql\", encoding=\"utf-8\"):\n line = line.strip()\n if line and \"=\" in line and not line.startswith(\"#\"):\n k, v = line.split(\"=\", 1)\n os.environ.setdefault(k, v)\n\nconn = pymysql.connect(\n host=os.environ.get(\"MYSQL_HOST\", \"127.0.0.1\"),\n port=int(os.environ.get(\"MYSQL_PORT\", \"3307\")),\n user=os.environ[\"MYSQL_USER\"],\n password=os.environ[\"MYSQL_PASSWORD\"],\n database=os.environ[\"MYSQL_DATABASE\"],\n charset=\"utf8mb4\",\n cursorclass=pymysql.cursors.DictCursor,\n)\n\nTYPE_NAMES = {\n 1: \"OpenAI\",\n 17: \"Ali/DashScope\",\n 43: \"DeepSeek\",\n 50: \"Kling\",\n 54: \"Doubao/Seedance\",\n 20: \"OpenRouter?\",\n}\n\ndef mask_key(k: str | None) -> str:\n if not k:\n return \"(empty)\"\n k = k.strip()\n if len(k) <= 8:\n return f\"len={len(k)} ***\"\n return f\"len={len(k)} {k[:4]}...{k[-4:]}\"\n\n\nrows = []\nwith conn.cursor() as cur:\n cur.execute(\n \"SELECT id, name, type, status, base_url, priority, weight, \"\n \"response_time, balance, used_quota, models, \"\n \"LENGTH(`key`) AS key_len, `key` \"\n \"FROM channels ORDER BY id\"\n )\n for r in cur.fetchall():\n key = r.pop(\"key\", \"\") or \"\"\n r[\"key_preview\"] = mask_key(key)\n r[\"key_format_ok\"] = bool(key.strip())\n if r[\"type\"] == 50 and key and \",\" not in key:\n r[\"kling_hint\"] = \"Kling needs access_key,secret_key (comma-separated)\"\n if r[\"type\"] == 54 and not key.startswith(\"sk-\"):\n r[\"seedance_hint\"] = \"Ark usually uses sk-... API key\"\n r[\"type_name\"] = TYPE_NAMES.get(r[\"type\"], f\"type-{r['type']}\")\n models = (r.get(\"models\") or \"\")[:80]\n r[\"models_preview\"] = models\n rows.append(r)\nconn.close()\n\nprint(\"=== channels ===\")\nprint(json.dumps(rows, ensure_ascii=False, indent=2, default=str))\n\n# Quick upstream probes (no secrets printed)\nprint(\"\\n=== upstream reachability (no auth) ===\")\nprobes = [\n (\"DeepSeek\", \"https://api.deepseek.com/models\"),\n (\"DashScope\", \"https://dashscope.aliyuncs.com/compatible-mode/v1/models\"),\n (\"OpenRouter\", \"https://openrouter.ai/api/v1/models\"),\n]\nfor name, url in probes:\n try:\n req = urllib.request.Request(url, headers={\"User-Agent\": \"spart-diagnose/1\"})\n with urllib.request.urlopen(req, timeout=8) as resp:\n print(f\" {name}: HTTP {resp.status}\")\n except Exception as e:\n print(f\" {name}: FAIL {e}\")\n"}}, "deploy/fix-flagship-channels.py": {"e09b33214d973c79dc6e21195ab4f3cb7359f1d6": {"t": 1785747442.4911783, "n": 8215, "h": "18595458db6a1b08", "c": "#!/usr/bin/env python3\n\"\"\"Fix Kling/Seedance upstream + trim each direct channel to 2 flagship models.\"\"\"\nfrom __future__ import annotations\n\nimport json\nimport os\nimport sys\nimport time\nimport urllib.error\nimport urllib.request\n\nimport pymysql\n\nKLING_BASE = \"https://api-beijing.klingai.com\"\nSEEDANCE_BASE = \"https://ark.cn-beijing.volces.com\"\n\n# Two best models per direct upstream channel\nCHANNEL_MODELS: dict[str, dict[str, object]] = {\n \"DeepSeek 官方\": {\n \"models\": [\"deepseek-chat\", \"deepseek-reasoner\"],\n \"mapping\": {\n \"deepseek-chat\": \"deepseek-chat\",\n \"deepseek-reasoner\": \"deepseek-reasoner\",\n },\n },\n \"通义千问 DashScope\": {\n \"models\": [\"qwen-max\", \"qwen-plus\"],\n \"mapping\": {\n \"qwen-max\": \"qwen-max\",\n \"qwen-plus\": \"qwen-plus\",\n \"doubao-pro-32k\": \"qwen-max\",\n },\n },\n \"可灵 Kling\": {\n \"models\": [\"kling-v2-master\", \"kling-v2-1-master\"],\n \"mapping\": {\n \"kling-3\": \"kling-v2-master\",\n \"kling-v2-6\": \"kling-v2-1-master\",\n },\n },\n \"火山引擎 Seedance 2.0\": {\n \"models\": [\n \"doubao-seedance-2-0-260128\",\n \"doubao-seedance-2-0-fast-260128\",\n ],\n \"mapping\": {\n \"seedance-2\": \"doubao-seedance-2-0-260128\",\n \"seedance-2-fast\": \"doubao-seedance-2-0-fast-260128\",\n },\n },\n}\n\n\ndef load_env() -> None:\n for path in (\"/opt/spartdata-newapi/.env.mysql\", \"/tmp/upstream-keys.env\"):\n if os.path.isfile(path):\n for line in open(path, encoding=\"utf-8\"):\n line = line.strip()\n if line and \"=\" in line and not line.startswith(\"#\"):\n k, v = line.split(\"=\", 1)\n os.environ.setdefault(k.strip(), v.strip())\n\n\ndef connect() -> pymysql.connections.Connection:\n return pymysql.connect(\n host=os.environ.get(\"MYSQL_HOST\", \"127.0.0.1\"),\n port=int(os.environ.get(\"MYSQL_PORT\", \"3307\")),\n user=os.environ[\"MYSQL_USER\"],\n password=os.environ[\"MYSQL_PASSWORD\"],\n database=os.environ[\"MYSQL_DATABASE\"],\n charset=\"utf8mb4\",\n cursorclass=pymysql.cursors.DictCursor,\n )\n\n\ndef normalize_kling_key(raw: str) -> str:\n \"\"\"Keep api-key-kling-* bearer tokens intact; legacy AK,SK still supported.\"\"\"\n key = raw.strip()\n if key.lower().startswith(\"api-key-kling-\"):\n return key\n if \",\" in key or \"|\" in key:\n return key\n prefix = \"api-key-kling-\"\n if key.lower().startswith(prefix):\n return key\n if \"_\" in key:\n access, secret = key.rsplit(\"_\", 1)\n if access and secret and len(secret) >= 8:\n return f\"{access},{secret}\"\n return key\n\n\ndef sync_channel_models(cur: pymysql.cursors.DictCursor, channel_id: int, cfg: dict[str, object]) -> None:\n models: list[str] = cfg[\"models\"] # type: ignore[assignment]\n mapping: dict[str, str] = cfg[\"mapping\"] # type: ignore[assignment]\n all_models = list(dict.fromkeys([*models, *mapping.keys()]))\n cur.execute(\n \"UPDATE channels SET models=%s, model_mapping=%s WHERE id=%s\",\n (\",\".join(models), json.dumps(mapping, ensure_ascii=False), channel_id),\n )\n cur.execute(\n \"DELETE FROM abilities WHERE channel_id=%s AND model NOT IN (%s)\"\n % (channel_id, \",\".join([\"%s\"] * len(all_models))),\n all_models,\n )\n for model in all_models:\n cur.execute(\n \"SELECT 1 FROM abilities WHERE channel_id=%s AND model=%s AND `group`='default' LIMIT 1\",\n (channel_id, model),\n )\n if not cur.fetchone():\n cur.execute(\n \"INSERT INTO abilities (`group`, model, channel_id, enabled, priority, weight, tag) \"\n \"VALUES ('default', %s, %s, 1, 0, 0, '')\",\n (model, channel_id),\n )\n else:\n cur.execute(\n \"UPDATE abilities SET enabled=1 WHERE channel_id=%s AND model=%s\",\n (channel_id, model),\n )\n\n\ndef probe_kling_bearer(key: str) -> dict[str, object]:\n url = f\"{KLING_BASE}/v1/videos/text2video\"\n body = json.dumps(\n {\"model_name\": \"kling-v2-master\", \"prompt\": \"ping\", \"duration\": \"5\"}\n ).encode()\n req = urllib.request.Request(\n url,\n data=body,\n method=\"POST\",\n headers={\"Authorization\": f\"Bearer {key}\", \"Content-Type\": \"application/json\"},\n )\n try:\n with urllib.request.urlopen(req, timeout=20) as resp:\n return {\"ok\": True, \"http\": resp.status, \"body\": resp.read(200).decode()[:200]}\n except urllib.error.HTTPError as e:\n return {\"ok\": False, \"http\": e.code, \"error\": e.read(300).decode()[:300]}\n\n\ndef probe_seedance(key: str) -> dict[str, object]:\n url = f\"{SEEDANCE_BASE}/api/v3/models\"\n req = urllib.request.Request(url, headers={\"Authorization\": f\"Bearer {key}\"}, method=\"GET\")\n try:\n with urllib.request.urlopen(req, timeout=20) as resp:\n return {\"ok\": True, \"http\": resp.status, \"body\": resp.read(200).decode()[:200]}\n except urllib.error.HTTPError as e:\n return {\"ok\": False, \"http\": e.code, \"error\": e.read(300).decode()[:300]}\n\n\ndef main() -> int:\n load_env()\n apply = \"--apply\" in sys.argv\n kling_key = normalize_kling_key(os.environ.get(\"UPSTREAM_KLING_KEY\", \"\").strip())\n seedance_key = os.environ.get(\"UPSTREAM_SEEDANCE_KEY\", \"\").strip()\n\n report: dict[str, object] = {\"apply\": apply, \"channels\": {}, \"probes\": {}}\n\n conn = connect()\n try:\n with conn.cursor() as cur:\n if not kling_key:\n cur.execute(\"SELECT `key` FROM channels WHERE name=%s LIMIT 1\", (\"可灵 Kling\",))\n row = cur.fetchone()\n if row and row.get(\"key\"):\n kling_key = normalize_kling_key(str(row[\"key\"]))\n if not seedance_key:\n cur.execute(\"SELECT `key` FROM channels WHERE name=%s LIMIT 1\", (\"火山引擎 Seedance 2.0\",))\n row = cur.fetchone()\n if row and row.get(\"key\"):\n seedance_key = str(row[\"key\"]).strip()\n\n if kling_key:\n report[\"probes\"][\"kling_upstream\"] = probe_kling_bearer(kling_key)\n if seedance_key:\n report[\"probes\"][\"seedance_upstream\"] = probe_seedance(seedance_key)\n\n cur.execute(\"SELECT id, name, base_url, LENGTH(`key`) key_len FROM channels ORDER BY id\")\n rows = cur.fetchall()\n for row in rows:\n name = row[\"name\"]\n if name not in CHANNEL_MODELS:\n continue\n cfg = CHANNEL_MODELS[name]\n entry = {\n \"id\": row[\"id\"],\n \"base_url_before\": row[\"base_url\"],\n \"models\": cfg[\"models\"],\n }\n if apply:\n if name == \"可灵 Kling\" and kling_key:\n cur.execute(\n \"UPDATE channels SET base_url=%s, `key`=%s, status=1 WHERE id=%s\",\n (KLING_BASE, kling_key, row[\"id\"]),\n )\n entry[\"base_url_after\"] = KLING_BASE\n elif name == \"可灵 Kling\":\n cur.execute(\n \"UPDATE channels SET base_url=%s, status=1 WHERE id=%s\",\n (KLING_BASE, row[\"id\"]),\n )\n entry[\"base_url_after\"] = KLING_BASE\n if name == \"火山引擎 Seedance 2.0\" and seedance_key:\n cur.execute(\n \"UPDATE channels SET base_url=%s, `key`=%s, status=1 WHERE id=%s\",\n (SEEDANCE_BASE, seedance_key, row[\"id\"]),\n )\n entry[\"base_url_after\"] = SEEDANCE_BASE\n sync_channel_models(cur, row[\"id\"], cfg)\n report[\"channels\"][name] = entry\n if apply:\n conn.commit()\n finally:\n conn.close()\n\n print(json.dumps(report, ensure_ascii=False, indent=2))\n return 0\n\n\nif __name__ == \"__main__\":\n raise SystemExit(main())\n"}, "f58aba507f05795a633b8767f0640eaa9f88a333": {"t": 1785773316.3768394, "n": 8215, "h": "18595458db6a1b08", "c": "#!/usr/bin/env python3\n\"\"\"Fix Kling/Seedance upstream + trim each direct channel to 2 flagship models.\"\"\"\nfrom __future__ import annotations\n\nimport json\nimport os\nimport sys\nimport time\nimport urllib.error\nimport urllib.request\n\nimport pymysql\n\nKLING_BASE = \"https://api-beijing.klingai.com\"\nSEEDANCE_BASE = \"https://ark.cn-beijing.volces.com\"\n\n# Two best models per direct upstream channel\nCHANNEL_MODELS: dict[str, dict[str, object]] = {\n \"DeepSeek 官方\": {\n \"models\": [\"deepseek-chat\", \"deepseek-reasoner\"],\n \"mapping\": {\n \"deepseek-chat\": \"deepseek-chat\",\n \"deepseek-reasoner\": \"deepseek-reasoner\",\n },\n },\n \"通义千问 DashScope\": {\n \"models\": [\"qwen-max\", \"qwen-plus\"],\n \"mapping\": {\n \"qwen-max\": \"qwen-max\",\n \"qwen-plus\": \"qwen-plus\",\n \"doubao-pro-32k\": \"qwen-max\",\n },\n },\n \"可灵 Kling\": {\n \"models\": [\"kling-v2-master\", \"kling-v2-1-master\"],\n \"mapping\": {\n \"kling-3\": \"kling-v2-master\",\n \"kling-v2-6\": \"kling-v2-1-master\",\n },\n },\n \"火山引擎 Seedance 2.0\": {\n \"models\": [\n \"doubao-seedance-2-0-260128\",\n \"doubao-seedance-2-0-fast-260128\",\n ],\n \"mapping\": {\n \"seedance-2\": \"doubao-seedance-2-0-260128\",\n \"seedance-2-fast\": \"doubao-seedance-2-0-fast-260128\",\n },\n },\n}\n\n\ndef load_env() -> None:\n for path in (\"/opt/spartdata-newapi/.env.mysql\", \"/tmp/upstream-keys.env\"):\n if os.path.isfile(path):\n for line in open(path, encoding=\"utf-8\"):\n line = line.strip()\n if line and \"=\" in line and not line.startswith(\"#\"):\n k, v = line.split(\"=\", 1)\n os.environ.setdefault(k.strip(), v.strip())\n\n\ndef connect() -> pymysql.connections.Connection:\n return pymysql.connect(\n host=os.environ.get(\"MYSQL_HOST\", \"127.0.0.1\"),\n port=int(os.environ.get(\"MYSQL_PORT\", \"3307\")),\n user=os.environ[\"MYSQL_USER\"],\n password=os.environ[\"MYSQL_PASSWORD\"],\n database=os.environ[\"MYSQL_DATABASE\"],\n charset=\"utf8mb4\",\n cursorclass=pymysql.cursors.DictCursor,\n )\n\n\ndef normalize_kling_key(raw: str) -> str:\n \"\"\"Keep api-key-kling-* bearer tokens intact; legacy AK,SK still supported.\"\"\"\n key = raw.strip()\n if key.lower().startswith(\"api-key-kling-\"):\n return key\n if \",\" in key or \"|\" in key:\n return key\n prefix = \"api-key-kling-\"\n if key.lower().startswith(prefix):\n return key\n if \"_\" in key:\n access, secret = key.rsplit(\"_\", 1)\n if access and secret and len(secret) >= 8:\n return f\"{access},{secret}\"\n return key\n\n\ndef sync_channel_models(cur: pymysql.cursors.DictCursor, channel_id: int, cfg: dict[str, object]) -> None:\n models: list[str] = cfg[\"models\"] # type: ignore[assignment]\n mapping: dict[str, str] = cfg[\"mapping\"] # type: ignore[assignment]\n all_models = list(dict.fromkeys([*models, *mapping.keys()]))\n cur.execute(\n \"UPDATE channels SET models=%s, model_mapping=%s WHERE id=%s\",\n (\",\".join(models), json.dumps(mapping, ensure_ascii=False), channel_id),\n )\n cur.execute(\n \"DELETE FROM abilities WHERE channel_id=%s AND model NOT IN (%s)\"\n % (channel_id, \",\".join([\"%s\"] * len(all_models))),\n all_models,\n )\n for model in all_models:\n cur.execute(\n \"SELECT 1 FROM abilities WHERE channel_id=%s AND model=%s AND `group`='default' LIMIT 1\",\n (channel_id, model),\n )\n if not cur.fetchone():\n cur.execute(\n \"INSERT INTO abilities (`group`, model, channel_id, enabled, priority, weight, tag) \"\n \"VALUES ('default', %s, %s, 1, 0, 0, '')\",\n (model, channel_id),\n )\n else:\n cur.execute(\n \"UPDATE abilities SET enabled=1 WHERE channel_id=%s AND model=%s\",\n (channel_id, model),\n )\n\n\ndef probe_kling_bearer(key: str) -> dict[str, object]:\n url = f\"{KLING_BASE}/v1/videos/text2video\"\n body = json.dumps(\n {\"model_name\": \"kling-v2-master\", \"prompt\": \"ping\", \"duration\": \"5\"}\n ).encode()\n req = urllib.request.Request(\n url,\n data=body,\n method=\"POST\",\n headers={\"Authorization\": f\"Bearer {key}\", \"Content-Type\": \"application/json\"},\n )\n try:\n with urllib.request.urlopen(req, timeout=20) as resp:\n return {\"ok\": True, \"http\": resp.status, \"body\": resp.read(200).decode()[:200]}\n except urllib.error.HTTPError as e:\n return {\"ok\": False, \"http\": e.code, \"error\": e.read(300).decode()[:300]}\n\n\ndef probe_seedance(key: str) -> dict[str, object]:\n url = f\"{SEEDANCE_BASE}/api/v3/models\"\n req = urllib.request.Request(url, headers={\"Authorization\": f\"Bearer {key}\"}, method=\"GET\")\n try:\n with urllib.request.urlopen(req, timeout=20) as resp:\n return {\"ok\": True, \"http\": resp.status, \"body\": resp.read(200).decode()[:200]}\n except urllib.error.HTTPError as e:\n return {\"ok\": False, \"http\": e.code, \"error\": e.read(300).decode()[:300]}\n\n\ndef main() -> int:\n load_env()\n apply = \"--apply\" in sys.argv\n kling_key = normalize_kling_key(os.environ.get(\"UPSTREAM_KLING_KEY\", \"\").strip())\n seedance_key = os.environ.get(\"UPSTREAM_SEEDANCE_KEY\", \"\").strip()\n\n report: dict[str, object] = {\"apply\": apply, \"channels\": {}, \"probes\": {}}\n\n conn = connect()\n try:\n with conn.cursor() as cur:\n if not kling_key:\n cur.execute(\"SELECT `key` FROM channels WHERE name=%s LIMIT 1\", (\"可灵 Kling\",))\n row = cur.fetchone()\n if row and row.get(\"key\"):\n kling_key = normalize_kling_key(str(row[\"key\"]))\n if not seedance_key:\n cur.execute(\"SELECT `key` FROM channels WHERE name=%s LIMIT 1\", (\"火山引擎 Seedance 2.0\",))\n row = cur.fetchone()\n if row and row.get(\"key\"):\n seedance_key = str(row[\"key\"]).strip()\n\n if kling_key:\n report[\"probes\"][\"kling_upstream\"] = probe_kling_bearer(kling_key)\n if seedance_key:\n report[\"probes\"][\"seedance_upstream\"] = probe_seedance(seedance_key)\n\n cur.execute(\"SELECT id, name, base_url, LENGTH(`key`) key_len FROM channels ORDER BY id\")\n rows = cur.fetchall()\n for row in rows:\n name = row[\"name\"]\n if name not in CHANNEL_MODELS:\n continue\n cfg = CHANNEL_MODELS[name]\n entry = {\n \"id\": row[\"id\"],\n \"base_url_before\": row[\"base_url\"],\n \"models\": cfg[\"models\"],\n }\n if apply:\n if name == \"可灵 Kling\" and kling_key:\n cur.execute(\n \"UPDATE channels SET base_url=%s, `key`=%s, status=1 WHERE id=%s\",\n (KLING_BASE, kling_key, row[\"id\"]),\n )\n entry[\"base_url_after\"] = KLING_BASE\n elif name == \"可灵 Kling\":\n cur.execute(\n \"UPDATE channels SET base_url=%s, status=1 WHERE id=%s\",\n (KLING_BASE, row[\"id\"]),\n )\n entry[\"base_url_after\"] = KLING_BASE\n if name == \"火山引擎 Seedance 2.0\" and seedance_key:\n cur.execute(\n \"UPDATE channels SET base_url=%s, `key`=%s, status=1 WHERE id=%s\",\n (SEEDANCE_BASE, seedance_key, row[\"id\"]),\n )\n entry[\"base_url_after\"] = SEEDANCE_BASE\n sync_channel_models(cur, row[\"id\"], cfg)\n report[\"channels\"][name] = entry\n if apply:\n conn.commit()\n finally:\n conn.close()\n\n print(json.dumps(report, ensure_ascii=False, indent=2))\n return 0\n\n\nif __name__ == \"__main__\":\n raise SystemExit(main())\n"}}, "deploy/test-all-channels.py": {"e09b33214d973c79dc6e21195ab4f3cb7359f1d6": {"t": 1785747458.9086385, "n": 5601, "h": "a1032415c7ea18d1", "c": "#!/usr/bin/env python3\n\"\"\"End-to-end channel health check for Spart Data production.\"\"\"\nfrom __future__ import annotations\n\nimport json\nimport os\nimport urllib.error\nimport urllib.request\n\nimport pymysql\n\nfor line in open(\"/opt/spartdata-newapi/.env.mysql\", encoding=\"utf-8\"):\n line = line.strip()\n if line and \"=\" in line and not line.startswith(\"#\"):\n k, v = line.split(\"=\", 1)\n os.environ.setdefault(k.strip(), v.strip())\n\nconn = pymysql.connect(\n host=os.environ.get(\"MYSQL_HOST\", \"127.0.0.1\"),\n port=int(os.environ.get(\"MYSQL_PORT\", \"3307\")),\n user=os.environ[\"MYSQL_USER\"],\n password=os.environ[\"MYSQL_PASSWORD\"],\n database=os.environ[\"MYSQL_DATABASE\"],\n charset=\"utf8mb4\",\n cursorclass=pymysql.cursors.DictCursor,\n)\n\n\ndef post_json(url: str, payload: dict, headers: dict, timeout=25) -> dict:\n data = json.dumps(payload).encode()\n req = urllib.request.Request(\n url, data=data, headers={**headers, \"Content-Type\": \"application/json\"}, method=\"POST\"\n )\n try:\n with urllib.request.urlopen(req, timeout=timeout) as resp:\n body = resp.read(800).decode(\"utf-8\", errors=\"replace\")\n return {\"ok\": True, \"http\": resp.status, \"body\": body[:400]}\n except urllib.error.HTTPError as e:\n return {\"ok\": False, \"http\": e.code, \"error\": e.read(500).decode(\"utf-8\", errors=\"replace\")[:400]}\n except Exception as e:\n return {\"ok\": False, \"error\": str(e)}\n\n\ndef get_json(url: str, headers: dict, timeout=20) -> dict:\n req = urllib.request.Request(url, headers=headers, method=\"GET\")\n try:\n with urllib.request.urlopen(req, timeout=timeout) as resp:\n return {\"ok\": True, \"http\": resp.status}\n except urllib.error.HTTPError as e:\n return {\"ok\": False, \"http\": e.code, \"error\": e.read(300).decode(\"utf-8\", errors=\"replace\")[:300]}\n except Exception as e:\n return {\"ok\": False, \"error\": str(e)}\n\n\nresults = []\n\nwith conn.cursor() as cur:\n cur.execute(\"SELECT id, name, type, status, base_url, `key`, response_time, models FROM channels ORDER BY id\")\n channels = cur.fetchall()\nconn.close()\n\nfor ch in channels:\n cid = ch[\"id\"]\n name = ch[\"name\"]\n status = ch[\"status\"]\n key = (ch[\"key\"] or \"\").strip()\n base = (ch[\"base_url\"] or \"\").rstrip(\"/\")\n t = ch[\"type\"]\n row = {\n \"id\": cid,\n \"name\": name,\n \"enabled\": status == 1,\n \"console_ms\": ch.get(\"response_time\"),\n \"tests\": {},\n }\n\n if status != 1:\n row[\"tests\"][\"note\"] = \"channel disabled — skipped\"\n results.append(row)\n continue\n\n if not key:\n row[\"tests\"][\"note\"] = \"no key\"\n results.append(row)\n continue\n\n if t == 1: # OpenRouter\n row[\"tests\"][\"models\"] = get_json(\n f\"{base}/v1/models\",\n {\"Authorization\": f\"Bearer {key}\", \"HTTP-Referer\": \"https://www.spartdata.com\"},\n )\n row[\"tests\"][\"chat_gpt4o\"] = post_json(\n f\"{base}/v1/chat/completions\",\n {\"model\": \"openai/gpt-4o\", \"messages\": [{\"role\": \"user\", \"content\": \"ping\"}], \"max_tokens\": 5},\n {\"Authorization\": f\"Bearer {key}\", \"HTTP-Referer\": \"https://www.spartdata.com\"},\n )\n row[\"tests\"][\"chat_claude\"] = post_json(\n f\"{base}/v1/chat/completions\",\n {\"model\": \"anthropic/claude-sonnet-4\", \"messages\": [{\"role\": \"user\", \"content\": \"ping\"}], \"max_tokens\": 5},\n {\"Authorization\": f\"Bearer {key}\", \"HTTP-Referer\": \"https://www.spartdata.com\"},\n )\n elif t == 43: # DeepSeek\n row[\"tests\"][\"models\"] = get_json(f\"{base}/models\", {\"Authorization\": f\"Bearer {key}\"})\n row[\"tests\"][\"chat\"] = post_json(\n f\"{base}/chat/completions\",\n {\"model\": \"deepseek-chat\", \"messages\": [{\"role\": \"user\", \"content\": \"ping\"}], \"max_tokens\": 5},\n {\"Authorization\": f\"Bearer {key}\"},\n )\n elif t == 17: # Qwen workspace\n qbase = f\"{base}/compatible-mode/v1\"\n row[\"tests\"][\"models\"] = get_json(f\"{qbase}/models\", {\"Authorization\": f\"Bearer {key}\"})\n row[\"tests\"][\"chat\"] = post_json(\n f\"{qbase}/chat/completions\",\n {\"model\": \"qwen-max\", \"messages\": [{\"role\": \"user\", \"content\": \"ping\"}], \"max_tokens\": 5},\n {\"Authorization\": f\"Bearer {key}\"},\n )\n elif t == 50: # Kling\n if \",\" not in key:\n row[\"tests\"][\"note\"] = \"invalid key format — need AccessKey,SecretKey\"\n else:\n ak, sk = key.split(\",\", 1)\n row[\"tests\"][\"probe\"] = get_json(\n f\"{base}/v1/models\",\n {\"Authorization\": f\"Bearer {ak}\"},\n )\n elif t == 54: # Seedance\n row[\"tests\"][\"models\"] = get_json(\n f\"{base}/api/v3/models\",\n {\"Authorization\": f\"Bearer {key}\"},\n )\n elif t == 33: # Bedrock\n if \"|\" in key:\n token, region = key.split(\"|\", 1)\n row[\"tests\"][\"chat\"] = post_json(\n f\"https://bedrock-runtime.{region}.amazonaws.com/model/jp.anthropic.claude-sonnet-4-5-20250929-v1:0/converse\",\n {\"messages\": [{\"role\": \"user\", \"content\": [{\"text\": \"ping\"}]}], \"inferenceConfig\": {\"maxTokens\": 5}},\n {\"Authorization\": f\"Bearer {token}\"},\n )\n\n results.append(row)\n\n# Gateway public /v1/models without user key should 401\ngw = get_json(\"https://www.spartdata.com/v1/models\", {})\nresults.append({\"id\": \"gw\", \"name\": \"Spart /v1/models (no token)\", \"tests\": {\"anonymous\": gw}})\n\nprint(json.dumps({\"channels\": results}, ensure_ascii=False, indent=2))\n"}, "f58aba507f05795a633b8767f0640eaa9f88a333": {"t": 1785773313.8252263, "n": 5601, "h": "a1032415c7ea18d1", "c": "#!/usr/bin/env python3\n\"\"\"End-to-end channel health check for Spart Data production.\"\"\"\nfrom __future__ import annotations\n\nimport json\nimport os\nimport urllib.error\nimport urllib.request\n\nimport pymysql\n\nfor line in open(\"/opt/spartdata-newapi/.env.mysql\", encoding=\"utf-8\"):\n line = line.strip()\n if line and \"=\" in line and not line.startswith(\"#\"):\n k, v = line.split(\"=\", 1)\n os.environ.setdefault(k.strip(), v.strip())\n\nconn = pymysql.connect(\n host=os.environ.get(\"MYSQL_HOST\", \"127.0.0.1\"),\n port=int(os.environ.get(\"MYSQL_PORT\", \"3307\")),\n user=os.environ[\"MYSQL_USER\"],\n password=os.environ[\"MYSQL_PASSWORD\"],\n database=os.environ[\"MYSQL_DATABASE\"],\n charset=\"utf8mb4\",\n cursorclass=pymysql.cursors.DictCursor,\n)\n\n\ndef post_json(url: str, payload: dict, headers: dict, timeout=25) -> dict:\n data = json.dumps(payload).encode()\n req = urllib.request.Request(\n url, data=data, headers={**headers, \"Content-Type\": \"application/json\"}, method=\"POST\"\n )\n try:\n with urllib.request.urlopen(req, timeout=timeout) as resp:\n body = resp.read(800).decode(\"utf-8\", errors=\"replace\")\n return {\"ok\": True, \"http\": resp.status, \"body\": body[:400]}\n except urllib.error.HTTPError as e:\n return {\"ok\": False, \"http\": e.code, \"error\": e.read(500).decode(\"utf-8\", errors=\"replace\")[:400]}\n except Exception as e:\n return {\"ok\": False, \"error\": str(e)}\n\n\ndef get_json(url: str, headers: dict, timeout=20) -> dict:\n req = urllib.request.Request(url, headers=headers, method=\"GET\")\n try:\n with urllib.request.urlopen(req, timeout=timeout) as resp:\n return {\"ok\": True, \"http\": resp.status}\n except urllib.error.HTTPError as e:\n return {\"ok\": False, \"http\": e.code, \"error\": e.read(300).decode(\"utf-8\", errors=\"replace\")[:300]}\n except Exception as e:\n return {\"ok\": False, \"error\": str(e)}\n\n\nresults = []\n\nwith conn.cursor() as cur:\n cur.execute(\"SELECT id, name, type, status, base_url, `key`, response_time, models FROM channels ORDER BY id\")\n channels = cur.fetchall()\nconn.close()\n\nfor ch in channels:\n cid = ch[\"id\"]\n name = ch[\"name\"]\n status = ch[\"status\"]\n key = (ch[\"key\"] or \"\").strip()\n base = (ch[\"base_url\"] or \"\").rstrip(\"/\")\n t = ch[\"type\"]\n row = {\n \"id\": cid,\n \"name\": name,\n \"enabled\": status == 1,\n \"console_ms\": ch.get(\"response_time\"),\n \"tests\": {},\n }\n\n if status != 1:\n row[\"tests\"][\"note\"] = \"channel disabled — skipped\"\n results.append(row)\n continue\n\n if not key:\n row[\"tests\"][\"note\"] = \"no key\"\n results.append(row)\n continue\n\n if t == 1: # OpenRouter\n row[\"tests\"][\"models\"] = get_json(\n f\"{base}/v1/models\",\n {\"Authorization\": f\"Bearer {key}\", \"HTTP-Referer\": \"https://www.spartdata.com\"},\n )\n row[\"tests\"][\"chat_gpt4o\"] = post_json(\n f\"{base}/v1/chat/completions\",\n {\"model\": \"openai/gpt-4o\", \"messages\": [{\"role\": \"user\", \"content\": \"ping\"}], \"max_tokens\": 5},\n {\"Authorization\": f\"Bearer {key}\", \"HTTP-Referer\": \"https://www.spartdata.com\"},\n )\n row[\"tests\"][\"chat_claude\"] = post_json(\n f\"{base}/v1/chat/completions\",\n {\"model\": \"anthropic/claude-sonnet-4\", \"messages\": [{\"role\": \"user\", \"content\": \"ping\"}], \"max_tokens\": 5},\n {\"Authorization\": f\"Bearer {key}\", \"HTTP-Referer\": \"https://www.spartdata.com\"},\n )\n elif t == 43: # DeepSeek\n row[\"tests\"][\"models\"] = get_json(f\"{base}/models\", {\"Authorization\": f\"Bearer {key}\"})\n row[\"tests\"][\"chat\"] = post_json(\n f\"{base}/chat/completions\",\n {\"model\": \"deepseek-chat\", \"messages\": [{\"role\": \"user\", \"content\": \"ping\"}], \"max_tokens\": 5},\n {\"Authorization\": f\"Bearer {key}\"},\n )\n elif t == 17: # Qwen workspace\n qbase = f\"{base}/compatible-mode/v1\"\n row[\"tests\"][\"models\"] = get_json(f\"{qbase}/models\", {\"Authorization\": f\"Bearer {key}\"})\n row[\"tests\"][\"chat\"] = post_json(\n f\"{qbase}/chat/completions\",\n {\"model\": \"qwen-max\", \"messages\": [{\"role\": \"user\", \"content\": \"ping\"}], \"max_tokens\": 5},\n {\"Authorization\": f\"Bearer {key}\"},\n )\n elif t == 50: # Kling\n if \",\" not in key:\n row[\"tests\"][\"note\"] = \"invalid key format — need AccessKey,SecretKey\"\n else:\n ak, sk = key.split(\",\", 1)\n row[\"tests\"][\"probe\"] = get_json(\n f\"{base}/v1/models\",\n {\"Authorization\": f\"Bearer {ak}\"},\n )\n elif t == 54: # Seedance\n row[\"tests\"][\"models\"] = get_json(\n f\"{base}/api/v3/models\",\n {\"Authorization\": f\"Bearer {key}\"},\n )\n elif t == 33: # Bedrock\n if \"|\" in key:\n token, region = key.split(\"|\", 1)\n row[\"tests\"][\"chat\"] = post_json(\n f\"https://bedrock-runtime.{region}.amazonaws.com/model/jp.anthropic.claude-sonnet-4-5-20250929-v1:0/converse\",\n {\"messages\": [{\"role\": \"user\", \"content\": [{\"text\": \"ping\"}]}], \"inferenceConfig\": {\"maxTokens\": 5}},\n {\"Authorization\": f\"Bearer {token}\"},\n )\n\n results.append(row)\n\n# Gateway public /v1/models without user key should 401\ngw = get_json(\"https://www.spartdata.com/v1/models\", {})\nresults.append({\"id\": \"gw\", \"name\": \"Spart /v1/models (no token)\", \"tests\": {\"anonymous\": gw}})\n\nprint(json.dumps({\"channels\": results}, ensure_ascii=False, indent=2))\n"}}}}