Initial server source import

This commit is contained in:
sashatrask
2026-09-30 20:30:56 +03:00
commit 170dd941b9
498 changed files with 261563 additions and 0 deletions
@@ -0,0 +1,498 @@
import sys
sys.dont_write_bytecode = True
import requests
import json
import os
import argparse
from itertools import cycle
from datetime import datetime, timezone
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
try:
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
sys.stderr.reconfigure(encoding='utf-8', errors='replace')
except Exception:
pass
from keycheck_common import (
acquire_file_lock,
append_checked,
append_jsonl,
commit_status_transaction,
default_input_file,
default_proxy_file,
env_int,
ensure_output_files as ensure_private_output_files,
finding_detector_secret_hash,
iter_findings,
iter_bounded_text_lines,
keycheck_input_mode,
load_known_statuses,
load_checked_statuses,
mask_secret,
now_iso,
physical_jsonl_segments,
private_append_writer,
private_atomic_writer,
reconcile_keycheck_jsonl_segments,
record_validation_result,
recover_status_transaction,
release_file_lock,
repair_keycheck_jsonl_tail,
require_provider_authority,
rotate_jsonl_if_needed,
service_output_dir,
should_skip_key,
sha256_text,
write_keycheck_event,
)
from runtime_security import reject_reparse_components, require_private_file
# --- Конфигурация ---
SERVICE = "openrouter"
OUTPUT_DIR = os.getenv("KEYCHECK_OUTPUT_DIR") or service_output_dir(SERVICE)
INPUT_FILE = os.getenv("KEYCHECK_INPUT_FILE") or default_input_file()
ALIVE_FILE = os.path.join(OUTPUT_DIR, "openrouterAlive.txt")
DEAD_FILE = os.path.join(OUTPUT_DIR, "openrouterDead.txt")
LIMITED_FILE = os.path.join(OUTPUT_DIR, "openrouterLimited.txt")
NO_BALANCE_FILE = os.path.join(OUTPUT_DIR, "openrouterNoBalance.txt")
NETWORK_FILE = os.path.join(OUTPUT_DIR, "openrouterNetwork.txt")
UNKNOWN_FILE = os.path.join(OUTPUT_DIR, "openrouterUnknown.txt")
CHECKED_FILE = os.path.join(OUTPUT_DIR, "openrouterChecked.txt")
RESULTS_FILE = os.path.join(OUTPUT_DIR, "openrouterResults.jsonl")
PROXY_FILE = os.getenv("KEYCHECK_PROXY_FILE") or default_proxy_file()
STATUS_FILES = {
"VALID": ALIVE_FILE,
"NO_BALANCE": NO_BALANCE_FILE,
"DEAD": DEAD_FILE,
"LIMITED": LIMITED_FILE,
"NETWORK": NETWORK_FILE,
"UNKNOWN": UNKNOWN_FILE,
}
CREDITS_URL = "https://openrouter.ai/api/v1/credits"
# --- Вспомогательные функции ---
def load_set_from_file(filepath):
"""Загружает ключи из файла в set для быстрой проверки."""
if not os.path.exists(filepath):
return set()
return {line.strip().split(':')[0] for line in iter_bounded_text_lines(filepath) if line.strip()}
def ensure_output_files():
ensure_private_output_files((CHECKED_FILE, RESULTS_FILE, *STATUS_FILES.values()))
recover_status_transaction(CHECKED_FILE, STATUS_FILES)
def legacy_status_key(line):
value = line.strip()
if not value:
return None
if '\t' in value:
return value.split('\t', 1)[0].strip()
return value.split(':', 1)[0].strip()
def load_openrouter_keys(path):
if keycheck_input_mode() == 'postgres':
return set()
if not os.path.exists(path):
return set()
return {key for key in (legacy_status_key(line) for line in iter_bounded_text_lines(path)) if key}
def iter_plain_openrouter_keys(paths):
if keycheck_input_mode() == 'postgres':
return
seen = set()
items = []
for path in paths or []:
if not path or not os.path.exists(path):
continue
for line in iter_bounded_text_lines(path):
key = legacy_status_key(line)
if not key or key in seen:
continue
seen.add(key)
items.append({'raw': key, 'source': path, 'finding': {}})
for item in items:
yield item
def retry_plain_files(args):
if keycheck_input_mode() == 'postgres':
return []
files = list(args.plain or [])
if args.recheck_all:
files.extend(STATUS_FILES.values())
else:
if args.retry_valid:
files.append(ALIVE_FILE)
if args.retry_no_balance:
files.append(NO_BALANCE_FILE)
if args.retry_limited:
files.append(LIMITED_FILE)
if args.retry_network:
files.append(NETWORK_FILE)
if args.retry_unknown:
files.append(UNKNOWN_FILE)
out = []
seen = set()
for path in files:
if path and path not in seen:
seen.add(path)
out.append(path)
return out
def migrate_legacy_checked():
if keycheck_input_mode() == 'postgres':
return
checked = load_checked_statuses(CHECKED_FILE)
for key in sorted(load_openrouter_keys(ALIVE_FILE)):
if key not in checked:
write_keycheck_event(SERVICE, RESULTS_FILE, key, {'status': 'VALID', 'message': 'legacy alive status migration'}, 'legacy:openrouterAlive.txt', {}, 'OpenRouter', 'legacy_status')
append_checked(CHECKED_FILE, key, 'VALID')
checked[key] = 'VALID'
for key in sorted(load_openrouter_keys(DEAD_FILE)):
if key not in checked:
write_keycheck_event(SERVICE, RESULTS_FILE, key, {'status': 'DEAD', 'message': 'legacy dead status migration'}, 'legacy:openrouterDead.txt', {}, 'OpenRouter', 'legacy_status')
append_checked(CHECKED_FILE, key, 'DEAD')
checked[key] = 'DEAD'
def _legacy_alive_checked_at(path):
details = os.stat(path, follow_symlinks=False)
return datetime.fromtimestamp(details.st_mtime, timezone.utc).isoformat(timespec='seconds')
def _legacy_balance_event_payload(key, balance, checked_at):
balance_text = f'{balance:.6f}'
key_hash = sha256_text(key)
event_id = sha256_text('|'.join([
SERVICE,
'legacy_status',
'openrouterAlive.txt',
key_hash,
'NO_BALANCE',
balance_text,
]))
return {
'key_masked': mask_secret(key),
'key_hash': key_hash,
'secret_hash': key_hash,
'detector_secret_hash': finding_detector_secret_hash({}),
'finding_uid': '',
'detector': 'OpenRouter',
'source': 'legacy:openrouterAlive.txt',
'finding': {},
'checked_at': checked_at,
'result_source': 'legacy_status',
'status': 'NO_BALANCE',
'message': f'credits={balance_text}',
'event_id': event_id,
}
def _publish_legacy_no_balance(moved):
lock_path = f'{NO_BALANCE_FILE}.lock'
lock = acquire_file_lock(lock_path, timeout_sec=30)
try:
require_private_file(NO_BALANCE_FILE)
existing = load_openrouter_keys(NO_BALANCE_FILE)
with private_append_writer(NO_BALANCE_FILE) as handle:
for key, balance in moved:
if key in existing:
continue
balance_text = f'{balance:.6f}'
handle.write(f'{key}\tNO_BALANCE\tcredits={balance_text}\tmigrated_from_alive\n')
existing.add(key)
finally:
release_file_lock(lock, lock_path)
def _existing_legacy_event_ids(expected):
found = set()
max_line_bytes = max(1024, env_int('KEYCHECK_INPUT_MAX_LINE_BYTES', 16 * 1024 * 1024))
max_file_bytes = max(
max_line_bytes,
max(1, env_int('KEYCHECK_INPUT_LIST_MAX_BYTES', 32 * 1024 * 1024)),
max(0, env_int('KEYCHECK_RESULTS_MAX_MB', 32)) * 1024 * 1024 + max_line_bytes,
)
paths = [path for _, path in physical_jsonl_segments(RESULTS_FILE)]
if os.path.isfile(RESULTS_FILE):
paths.append(os.path.abspath(RESULTS_FILE))
for path in paths:
reject_reparse_components(path)
if os.path.getsize(path) > max_file_bytes:
raise RuntimeError(f'OpenRouter result file exceeds the bounded migration scan size: {path}')
with open(path, 'rb') as handle:
while True:
raw_line = handle.readline(max_line_bytes + 1)
if not raw_line:
break
if len(raw_line) > max_line_bytes:
raise RuntimeError(f'OpenRouter result line exceeds the bounded migration scan size: {path}')
if not raw_line.endswith(b'\n'):
raise RuntimeError(f'torn OpenRouter result line during legacy migration: {path}')
try:
payload = json.loads(raw_line.decode('utf-8', errors='replace'))
except (TypeError, ValueError):
continue
event_id = str(payload.get('event_id') or '') if isinstance(payload, dict) else ''
if event_id not in expected:
continue
wanted = expected[event_id]
for field in ('key_hash', 'status', 'source', 'result_source'):
if str(payload.get(field) or '') != str(wanted.get(field) or ''):
raise RuntimeError(f'conflicting OpenRouter legacy migration event: {event_id}')
found.add(event_id)
return found
def _publish_legacy_balance_events(moved, checked_at):
payloads = [_legacy_balance_event_payload(key, balance, checked_at) for key, balance in moved]
expected = {payload['event_id']: payload for payload in payloads}
lock_path = f'{RESULTS_FILE}.lock'
lock = acquire_file_lock(lock_path, timeout_sec=30)
try:
require_private_file(RESULTS_FILE)
repair_keycheck_jsonl_tail(RESULTS_FILE)
reconcile_keycheck_jsonl_segments(RESULTS_FILE)
existing = _existing_legacy_event_ids(expected)
max_bytes = max(0, env_int('KEYCHECK_RESULTS_MAX_MB', 32)) * 1024 * 1024
for payload in payloads:
event_id = payload['event_id']
if event_id in existing:
continue
rotate_jsonl_if_needed(RESULTS_FILE, max_bytes)
with private_append_writer(RESULTS_FILE) as handle:
handle.write(json.dumps(payload, ensure_ascii=False, default=str) + '\n')
existing.add(event_id)
finally:
release_file_lock(lock, lock_path)
def _publish_legacy_checked(moved, checked_at):
lock_path = f'{CHECKED_FILE}.lock'
lock = acquire_file_lock(lock_path, timeout_sec=30)
try:
require_private_file(CHECKED_FILE)
existing = set()
for line in iter_bounded_text_lines(CHECKED_FILE):
parts = line.rstrip('\r\n').split('\t')
if len(parts) >= 2:
existing.add((parts[0], parts[1]))
with private_append_writer(CHECKED_FILE) as handle:
for key, _ in moved:
identity = (key, 'NO_BALANCE')
if identity in existing:
continue
handle.write(f'{key}\tNO_BALANCE\t{checked_at}\n')
existing.add(identity)
finally:
release_file_lock(lock, lock_path)
def _rewrite_legacy_alive(keep):
with private_atomic_writer(ALIVE_FILE, binary=True, suffix='.legacy.tmp') as handle:
for line in keep:
handle.write(line.encode('utf-8'))
def migrate_legacy_alive_balances():
if keycheck_input_mode() == 'postgres':
return
lock_path = f'{ALIVE_FILE}.lock'
lock = acquire_file_lock(lock_path, timeout_sec=30)
try:
if not os.path.exists(ALIVE_FILE):
return
require_private_file(ALIVE_FILE)
checked_at = _legacy_alive_checked_at(ALIVE_FILE)
keep = []
moved = []
seen = set()
for line in iter_bounded_text_lines(ALIVE_FILE):
text = line.strip()
if not text:
keep.append(line)
continue
key = legacy_status_key(text)
balance = None
if ':' in text and '\t' not in text:
try:
balance = float(text.rsplit(':', 1)[1])
except ValueError:
balance = None
if key and balance is not None and balance <= 0:
if key not in seen:
moved.append((key, balance))
seen.add(key)
else:
keep.append(line)
if not moved:
return
_publish_legacy_no_balance(moved)
_publish_legacy_balance_events(moved, checked_at)
_publish_legacy_checked(moved, checked_at)
_rewrite_legacy_alive(keep)
finally:
release_file_lock(lock, lock_path)
def load_proxies(proxy_file=None):
"""Загружает и подготавливает прокси."""
proxy_file = proxy_file or PROXY_FILE
if not os.path.exists(proxy_file):
print("ℹ️ Файл proxy.txt не найден, запросы будут идти напрямую.")
return None
proxies = []
with open(proxy_file, 'r') as f:
for line in f:
line = line.strip()
if not line: continue
try:
ip, port, login, password = line.split(':')
proxy_url = f"http://{login}:{password}@{ip}:{port}"
proxies.append({"http": proxy_url, "https": proxy_url})
except ValueError:
print(f"⚠️ Неверный формат прокси: '{line}'. Пропускаем.")
if not proxies:
print("⚠️ Файл proxy.txt пуст. Запросы будут идти напрямую.")
return None
print(f"✅ Загружено {len(proxies)} прокси.")
return cycle(proxies)
def write_result(key, result, source_line, finding=None, previous_status=None):
status = result.get('status') or 'UNKNOWN'
credits = result.get('remaining_credits')
extra = f"credits={credits:.6f}" if isinstance(credits, (int, float)) else source_line
checked_at = now_iso()
result = {**result, 'checked_at': result.get('checked_at') or checked_at}
write_keycheck_event(SERVICE, RESULTS_FILE, key, result, source_line, finding, 'OpenRouter')
commit_status_transaction(
CHECKED_FILE, STATUS_FILES, key, status, result.get('message', ''), extra,
)
record_validation_result(SERVICE, key, result, source_line, finding, 'OpenRouter')
# --- Функция проверки ---
def check_openrouter_key(key, proxy):
"""Проверяет один ключ OpenRouter и возвращает normalized result."""
headers = {"Authorization": f"Bearer {key}"}
try:
resp = requests.get(CREDITS_URL, headers=headers, proxies=proxy, timeout=15)
except requests.exceptions.RequestException as e:
return {'status': 'NETWORK', 'message': str(e)}
if resp.status_code == 200:
try:
data = resp.json().get("data", {})
total = float(data.get("total_credits", 0.0) or 0.0)
used = float(data.get("total_usage", 0.0) or 0.0)
except (TypeError, ValueError, json.JSONDecodeError) as exc:
return {'status': 'UNKNOWN', 'http_status': resp.status_code, 'message': f'invalid credits response: {exc}'}
remaining = total - used
if remaining > 0:
return {'status': 'VALID', 'remaining_credits': remaining, 'message': f'credits={remaining:.6f}'}
return {'status': 'NO_BALANCE', 'remaining_credits': remaining, 'message': f'credits={remaining:.6f}'}
if resp.status_code in (401, 403):
return {'status': 'DEAD', 'http_status': resp.status_code, 'message': resp.text[:1000]}
if resp.status_code == 429:
return {'status': 'LIMITED', 'http_status': resp.status_code, 'message': resp.text[:1000]}
if 500 <= resp.status_code <= 599:
return {'status': 'NETWORK', 'http_status': resp.status_code, 'message': resp.text[:1000]}
return {'status': 'UNKNOWN', 'http_status': resp.status_code, 'message': resp.text[:1000]}
def parse_args():
parser = argparse.ArgumentParser(description='OpenRouter key checker')
parser.add_argument('--input', default=INPUT_FILE)
parser.add_argument('--plain', action='append', default=[])
parser.add_argument('--proxy-file', default=PROXY_FILE)
parser.add_argument('--max-keys', type=int, default=0)
parser.add_argument('--retry-network', action='store_true')
parser.add_argument('--retry-limited', action='store_true')
parser.add_argument('--retry-unknown', action='store_true')
parser.add_argument('--retry-no-balance', action='store_true')
parser.add_argument('--retry-valid', action='store_true')
parser.add_argument('--recheck-all', action='store_true')
return parser.parse_args()
# --- Основной процесс ---
def main():
require_provider_authority(SERVICE)
args = parse_args()
ensure_output_files()
migrate_legacy_alive_balances()
migrate_legacy_checked()
print("--- 🚀 Запуск чекера ключей OpenRouter 🚀 ---")
proxy_cycler = load_proxies(args.proxy_file)
checked_statuses = load_checked_statuses(CHECKED_FILE)
known_statuses = load_known_statuses(CHECKED_FILE, STATUS_FILES)
known_keys = set(known_statuses)
for path in STATUS_FILES.values():
known_keys.update(load_openrouter_keys(path))
retry_statuses = set()
if args.retry_network:
retry_statuses.add('NETWORK')
if args.retry_limited:
retry_statuses.add('LIMITED')
if args.retry_unknown:
retry_statuses.add('UNKNOWN')
if args.retry_no_balance:
retry_statuses.add('NO_BALANCE')
if args.retry_valid:
retry_statuses.add('VALID')
print(f"📖 Загружено: {len(load_openrouter_keys(ALIVE_FILE))} живых ключей, {len(known_keys)} классифицированных ключей.")
if keycheck_input_mode() == 'jsonl' and not os.path.exists(args.input):
print(f"❌ Файл с секретами {args.input} не найден. Завершение.")
return
processed = 0
def candidates():
for item in iter_findings(args.input, ["OpenRouter"]):
key = item.get("raw") or ""
if key:
yield item
seen = set()
for item in iter_plain_openrouter_keys(retry_plain_files(args)):
key = item.get("raw") or ""
if key and key not in seen:
seen.add(key)
yield item
for item in candidates():
key = item.get("raw") or ""
if not key:
continue
source = item.get("source") or args.input
finding = item.get("finding") or {}
if should_skip_key(
key, checked_statuses, known_keys, args, retry_statuses,
service=SERVICE, source=source, finding=finding, detector='OpenRouter', known_statuses=known_statuses,
):
continue
if args.max_keys and processed >= args.max_keys:
break
processed += 1
print(f"\n[{processed}] 🎯 Новый кандидат: {key[:8]}...{key[-4:]} from {source}")
current_proxy = next(proxy_cycler) if proxy_cycler else None
result = check_openrouter_key(key, current_proxy)
print(f" STATUS: {result.get('status')} | {str(result.get('message', ''))[:200]}")
previous_status = known_statuses.get(key) or checked_statuses.get(key)
write_result(key, result, source, finding, previous_status)
known_keys.add(key)
checked_statuses[key] = result.get('status')
print("\n--- ✅ Проверка завершена. ---")
if __name__ == "__main__":
main()