#!/usr/bin/env python3 """gateway_importer.py — eternal lane: keys.db (KeyHunter) -> dashscope rotator pool. Every cycle: 1. Pull WORKING/FREE/LIMITED DASHSCOPE keys from keys.db 2. Smoke-test new candidates (1-token chat) before import 3. Dedup, atomic-write into dashscope_keys.json 4. Rotator hot-reloads by mtime (no restart needed) Run: python gateway_importer.py >> gateway_importer.log 2>&1 """ import json import os import sqlite3 import sys import time import requests KEYS_DB = r"C:/Users/User/Desktop/keyhunter/data/keys.db" POOL_PATH = r"C:/Users/User/.omp/agent/dashscope_keys.json" INTERVAL = 120 # seconds between cycles BASE = "https://dashscope.aliyuncs.com/compatible-mode/v1" GOOD_STATUS = ("WORKING", "FREE", "LIMITED") STALE_DAYS = 30 # skip keys not seen recently def log(msg: str) -> None: print(f"{time.strftime('%H:%M:%S')} {msg}", flush=True) def load_pool() -> dict: try: with open(POOL_PATH, encoding="utf-8-sig") as f: return json.load(f) except Exception: return {"keys": []} def save_pool(cfg: dict) -> None: tmp = POOL_PATH + ".importer.tmp" with open(tmp, "w", encoding="utf-8") as f: json.dump(cfg, f, indent=2, ensure_ascii=False) f.write("\n") os.replace(tmp, POOL_PATH) def fetch_candidates() -> list[tuple]: db = sqlite3.connect(f"file:{KEYS_DB}?mode=ro", uri=True) try: rows = db.execute( """SELECT val, status, found, repo FROM keys WHERE prov = 'DASHSCOPE' AND status IN ('WORKING','FREE','LIMITED') AND val LIKE 'sk-%' AND found >= datetime('now', ?)""", (f"-{STALE_DAYS} days",), ).fetchall() finally: db.close() return rows def smoke(key: str) -> bool: try: r = requests.post( BASE + "/chat/completions", headers={"Authorization": f"Bearer {key}"}, json={"model": "qwen3.8-flash", "messages": [{"role": "user", "content": "hi"}], "max_tokens": 1}, timeout=25, ) return r.status_code == 200 except Exception: return False def main() -> None: log(f"[importer] start: db={KEYS_DB} pool={POOL_PATH}") while True: try: cfg = load_pool() pool_keys = set(cfg.get("keys") or []) rows = fetch_candidates() fresh = [(v, s, rep) for v, s, f, rep in rows if v not in pool_keys] log(f"[importer] candidates={len(rows)} new={len(fresh)} pool={len(pool_keys)}") added = 0 for val, status, repo in fresh: if smoke(val): cfg.setdefault("keys", []).append(val) pool_keys.add(val) added += 1 log(f"[importer] + {val[:14]}... ({status}) src={str(repo)[:40]}") save_pool(cfg) # save immediately: rotator picks up per key time.sleep(2) else: log(f"[importer] skip (smoke fail) {val[:14]}...") if added: log(f"[importer] pushed {added} keys -> pool={len(pool_keys)} (rotator hot-reload by mtime)") except Exception as e: log(f"[importer] ERROR {e!r}") time.sleep(INTERVAL) if __name__ == "__main__": try: main() except KeyboardInterrupt: sys.exit(0)