573 lines
22 KiB
Python
573 lines
22 KiB
Python
import sys
|
||
|
||
sys.dont_write_bytecode = True
|
||
|
||
import requests
|
||
import json
|
||
import os
|
||
import argparse
|
||
import re
|
||
from itertools import cycle
|
||
import time
|
||
|
||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||
|
||
from keycheck_common import (
|
||
commit_status_transaction,
|
||
default_input_file,
|
||
default_proxy_file,
|
||
ensure_output_files as ensure_private_output_files,
|
||
iter_findings,
|
||
iter_bounded_text_lines,
|
||
keycheck_input_mode,
|
||
load_known_statuses,
|
||
private_atomic_writer,
|
||
record_cached_keycheck_occurrence,
|
||
record_validation_result,
|
||
recover_status_transaction,
|
||
require_provider_authority,
|
||
service_output_dir,
|
||
should_skip_key,
|
||
write_keycheck_event,
|
||
)
|
||
|
||
try:
|
||
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
||
sys.stderr.reconfigure(encoding='utf-8', errors='replace')
|
||
except Exception:
|
||
pass
|
||
|
||
# --- Конфигурация ---
|
||
SERVICE = "openai"
|
||
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, "openaiAlive.txt")
|
||
DEAD_FILE = os.path.join(OUTPUT_DIR, "openaiDead.txt")
|
||
NETWORK_FILE = os.path.join(OUTPUT_DIR, "openaiNetwork.txt")
|
||
LIMITED_FILE = os.path.join(OUTPUT_DIR, "openaiLimited.txt")
|
||
RESTRICTED_FILE = os.path.join(OUTPUT_DIR, "openaiRestricted.txt")
|
||
UNKNOWN_FILE = os.path.join(OUTPUT_DIR, "openaiUnknown.txt")
|
||
NO_TARGET_FILE = os.path.join(OUTPUT_DIR, "openaiNoTarget.txt")
|
||
CHECKED_FILE = os.path.join(OUTPUT_DIR, "openaiChecked.txt")
|
||
RESULTS_FILE = os.path.join(OUTPUT_DIR, "openaiResults.jsonl")
|
||
PROXY_FILE = os.getenv("KEYCHECK_PROXY_FILE") or default_proxy_file()
|
||
|
||
MODEL_PRIORITY_FOR_TEST = (
|
||
'gpt-5.6-sol',
|
||
'gpt-5.6',
|
||
'gpt-5.6-luna',
|
||
'gpt-5.6-terra',
|
||
'gpt-5',
|
||
'o3-pro',
|
||
'o3',
|
||
)
|
||
TARGET_MODELS = {'gpt-5', 'o3-pro', 'o3'}
|
||
NON_CHAT_MODEL_MARKERS = (
|
||
'embedding', 'image', 'audio', 'tts', 'transcribe', 'realtime', 'search', 'moderation',
|
||
)
|
||
PROBE_MAX_COMPLETION_TOKENS = 16
|
||
STATUS_FILES = [ALIVE_FILE, DEAD_FILE, NETWORK_FILE, LIMITED_FILE, RESTRICTED_FILE, UNKNOWN_FILE, NO_TARGET_FILE]
|
||
STATUS_BY_FILE = {
|
||
ALIVE_FILE: 'ALIVE',
|
||
DEAD_FILE: 'DEAD',
|
||
NETWORK_FILE: 'NETWORK',
|
||
LIMITED_FILE: 'LIMITED',
|
||
RESTRICTED_FILE: 'RESTRICTED',
|
||
UNKNOWN_FILE: 'UNKNOWN',
|
||
NO_TARGET_FILE: 'NO_TARGET_MODELS',
|
||
}
|
||
OPENAI_KEY_REGEX = re.compile(r'sk-[A-Za-z0-9_-]{20,}')
|
||
|
||
# --- Вспомогательные функции ---
|
||
def key_from_line(line):
|
||
line = line.strip()
|
||
if not line:
|
||
return None
|
||
if '\t' in line:
|
||
return line.split('\t', 1)[0].strip()
|
||
if ':[' in line:
|
||
return line.split(':[', 1)[0].strip()
|
||
match = OPENAI_KEY_REGEX.search(line)
|
||
if match:
|
||
return match.group(0)
|
||
return line.split()[0].strip()
|
||
|
||
def load_set_from_file(filepath):
|
||
if keycheck_input_mode() == 'postgres':
|
||
return set()
|
||
if not os.path.exists(filepath): return set()
|
||
return {key for key in (key_from_line(line) for line in iter_bounded_text_lines(filepath)) if key}
|
||
|
||
|
||
def iter_plain_openai_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 = key_from_line(line)
|
||
if not key or key in seen:
|
||
continue
|
||
seen.add(key)
|
||
items.append({'key': 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)
|
||
else:
|
||
if args.retry_limited:
|
||
files.append(LIMITED_FILE)
|
||
if args.retry_network:
|
||
files.append(NETWORK_FILE)
|
||
if args.retry_unknown:
|
||
files.append(UNKNOWN_FILE)
|
||
if args.retry_restricted:
|
||
files.append(RESTRICTED_FILE)
|
||
if args.retry_no_balance:
|
||
files.append(LIMITED_FILE)
|
||
out = []
|
||
seen = set()
|
||
for path in files:
|
||
if path and path not in seen:
|
||
seen.add(path)
|
||
out.append(path)
|
||
return out
|
||
|
||
def load_checked_statuses():
|
||
if keycheck_input_mode() == 'postgres':
|
||
return {}
|
||
statuses = {}
|
||
if not os.path.exists(CHECKED_FILE):
|
||
return statuses
|
||
for line in iter_bounded_text_lines(CHECKED_FILE):
|
||
parts = line.rstrip('\n').split('\t')
|
||
if parts and parts[0]:
|
||
statuses[parts[0]] = parts[1] if len(parts) > 1 else 'UNKNOWN'
|
||
return statuses
|
||
|
||
def load_known_keys():
|
||
if keycheck_input_mode() == 'postgres':
|
||
return set()
|
||
known = set(load_checked_statuses().keys())
|
||
for path in STATUS_FILES:
|
||
known.update(load_set_from_file(path))
|
||
return known
|
||
|
||
def ensure_output_files():
|
||
ensure_private_output_files([CHECKED_FILE, RESULTS_FILE, *STATUS_FILES])
|
||
recover_status_transaction(CHECKED_FILE, STATUS_BY_FILE)
|
||
|
||
def compact_status_file(path):
|
||
if keycheck_input_mode() == 'postgres':
|
||
return
|
||
if not os.path.exists(path):
|
||
return
|
||
last_by_key = {}
|
||
order = []
|
||
for line in iter_bounded_text_lines(path):
|
||
key = key_from_line(line)
|
||
if not key:
|
||
continue
|
||
if key not in last_by_key:
|
||
order.append(key)
|
||
last_by_key[key] = line
|
||
for attempt in range(6):
|
||
try:
|
||
with private_atomic_writer(path) as f:
|
||
for key in order:
|
||
f.write(last_by_key[key])
|
||
except PermissionError:
|
||
if attempt == 5:
|
||
print(f"Warning: unable to compact {path}; leaving existing file as-is")
|
||
return
|
||
time.sleep(0.1 * (attempt + 1))
|
||
else:
|
||
return
|
||
|
||
def compact_all_status_files():
|
||
for path in [CHECKED_FILE, *STATUS_FILES]:
|
||
compact_status_file(path)
|
||
|
||
def backfill_checked_file():
|
||
if keycheck_input_mode() == 'postgres':
|
||
return
|
||
checked = load_checked_statuses()
|
||
changed = False
|
||
for path, status in STATUS_BY_FILE.items():
|
||
for key in load_set_from_file(path):
|
||
if key not in checked:
|
||
checked[key] = status
|
||
changed = True
|
||
if not changed:
|
||
return
|
||
with private_atomic_writer(CHECKED_FILE) as f:
|
||
for key, status in sorted(checked.items()):
|
||
f.write(f"{key}\t{status}\tbackfilled\n")
|
||
|
||
def load_proxies(proxy_file=None):
|
||
proxy_file = proxy_file or PROXY_FILE
|
||
if not os.path.exists(proxy_file): 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 move_key_to_alive(
|
||
key, available_target_models, service_tier, source='', finding=None,
|
||
model_inventory=None, probe_model='',
|
||
):
|
||
"""
|
||
Перемещает ключ из DEAD_FILE в ALIVE_FILE, записывая модели и service_tier.
|
||
"""
|
||
models_str = ",".join(sorted(list(available_target_models)))
|
||
tier_str = str(service_tier or 'unknown').replace('\r', ' ').replace('\n', ' ')[:1000]
|
||
print(f" -> ✅ Ключ рабочий! Модели: {models_str}, Тир: {tier_str}. Перемещаем в {ALIVE_FILE}")
|
||
|
||
model_inventory = sorted(set(model_inventory or available_target_models))
|
||
result = {
|
||
'status': 'ALIVE',
|
||
'models': sorted(list(available_target_models)),
|
||
'model_inventory': model_inventory,
|
||
'model_count': len(model_inventory),
|
||
'llm_probe_model': probe_model,
|
||
'llm_probe_status': 'GENERATION_OK',
|
||
'service_tier': service_tier,
|
||
'message': f'chat ping ok; model={probe_model}; models={len(model_inventory)}',
|
||
}
|
||
write_keycheck_event(SERVICE, RESULTS_FILE, key, result, source, finding, 'OpenAI')
|
||
commit_status_transaction(
|
||
CHECKED_FILE,
|
||
STATUS_BY_FILE,
|
||
key,
|
||
'ALIVE',
|
||
status_line=f"{key}:[{models_str}]:{tier_str}\n",
|
||
checked_line=f"{key}\tALIVE\t{time.strftime('%Y-%m-%dT%H:%M:%S')}\n",
|
||
)
|
||
record_validation_result(SERVICE, key, result, source, finding, 'OpenAI')
|
||
|
||
def redact_message(message, key):
|
||
message = str(message).replace('\r', ' ').replace('\n', ' ')[:1000]
|
||
if key:
|
||
message = message.replace(key, '***REDACTED***')
|
||
return OPENAI_KEY_REGEX.sub('***REDACTED***', message)
|
||
|
||
def write_key_status(key, path, status, message='', source='', finding=None, metadata=None):
|
||
status_upper = status.upper()
|
||
projection_status = STATUS_BY_FILE.get(path)
|
||
if not projection_status:
|
||
raise ValueError(f'unknown OpenAI status projection: {path}')
|
||
message = redact_message(message, key)
|
||
result = {**(metadata or {}), 'status': status_upper, 'message': message}
|
||
write_keycheck_event(SERVICE, RESULTS_FILE, key, result, source, finding, 'OpenAI')
|
||
commit_status_transaction(
|
||
CHECKED_FILE,
|
||
STATUS_BY_FILE,
|
||
key,
|
||
projection_status,
|
||
message,
|
||
source,
|
||
status_line=f"{key}\t{status}\t{message}\n",
|
||
checked_line=f"{key}\t{projection_status}\t{time.strftime('%Y-%m-%dT%H:%M:%S')}\n",
|
||
)
|
||
record_validation_result(SERVICE, key, result, source, finding, 'OpenAI')
|
||
|
||
def extract_openai_key(data):
|
||
if data.get("DetectorName") == "OpenAI":
|
||
return data.get("Raw") or data.get("RawV2")
|
||
if data.get("detector") == "OpenAI":
|
||
return data.get("raw") or data.get("raw_v2")
|
||
finding = data.get("finding")
|
||
if isinstance(finding, dict) and finding.get("DetectorName") == "OpenAI":
|
||
return finding.get("Raw") or finding.get("RawV2")
|
||
return None
|
||
|
||
# --- Функции проверки ---
|
||
def check_authentication(key, proxy):
|
||
print(f" [1/2] Проверка аутентификации...")
|
||
url = "https://api.openai.com/v1/models"
|
||
headers = {"Authorization": f"Bearer {key}"}
|
||
try:
|
||
response = requests.get(url, headers=headers, proxies=proxy, timeout=15)
|
||
if response.status_code == 200:
|
||
print(" -> Аутентификация пройдена.")
|
||
return 'valid', response.json().get('data', [])
|
||
elif response.status_code == 401:
|
||
print(" -> Ошибка 401: Ключ недействителен или отозван.")
|
||
return 'dead', response.text
|
||
elif response.status_code == 403:
|
||
print(f" -> Ошибка 403: ключ ограничен/заблокирован: {response.text[:300]}")
|
||
return 'restricted', response.text
|
||
elif response.status_code == 429:
|
||
print(f" -> Ошибка 429: rate limit / quota: {response.text[:300]}")
|
||
return 'limited', response.text
|
||
else:
|
||
print(f" -> Ошибка {response.status_code}: {response.text}")
|
||
return 'unknown', response.text
|
||
except requests.RequestException as e:
|
||
print(f" -> Ошибка сети: {e}")
|
||
return 'network', str(e)
|
||
|
||
def reportable_target_models(model_ids):
|
||
return {
|
||
model for model in model_ids
|
||
if model in TARGET_MODELS or model.startswith('gpt-5.6-') or model == 'gpt-5.6'
|
||
}
|
||
|
||
|
||
def choose_probe_model(model_ids):
|
||
models = [str(model or '') for model in model_ids if model]
|
||
by_lower = {model.lower(): model for model in models}
|
||
for model in MODEL_PRIORITY_FOR_TEST:
|
||
if model.lower() in by_lower:
|
||
return by_lower[model.lower()]
|
||
for model in models:
|
||
lowered = model.lower()
|
||
if lowered.startswith(('gpt-', 'o')) and not any(
|
||
marker in lowered for marker in NON_CHAT_MODEL_MARKERS
|
||
):
|
||
return model
|
||
return ''
|
||
|
||
|
||
def check_balance_and_tier(key, model_to_test, proxy):
|
||
"""
|
||
Проверяет баланс и возвращает service_tier в случае успеха.
|
||
"""
|
||
print(f" [2/2] Проверка баланса и тира...")
|
||
if not model_to_test:
|
||
print(" -> Не найдено подходящих моделей для теста баланса.")
|
||
return 'unknown', 'no chat-capable model from /models'
|
||
|
||
print(f" -> Используем модель для теста: {model_to_test}")
|
||
url = "https://api.openai.com/v1/chat/completions"
|
||
headers = {"Authorization": f"Bearer {key}", "Content-Type": "application/json"}
|
||
payload = {
|
||
"model": model_to_test,
|
||
"messages": [{"role": "user", "content": "Reply with one digit."}],
|
||
"max_completion_tokens": PROBE_MAX_COMPLETION_TOKENS,
|
||
}
|
||
|
||
try:
|
||
response = requests.post(url, headers=headers, json=payload, proxies=proxy, timeout=20)
|
||
if response.status_code == 200:
|
||
# Успех, извлекаем service_tier
|
||
response_data = response.json()
|
||
service_tier = response_data.get('service_tier')
|
||
return 'ok', service_tier
|
||
elif response.status_code == 429:
|
||
print(f" -> Ошибка 429: Нет баланса или превышен лимит.")
|
||
return 'limited', response.text
|
||
elif response.status_code == 401:
|
||
print(f" -> Ошибка 401: ключ недействителен или отозван.")
|
||
return 'dead', response.text
|
||
elif response.status_code == 403:
|
||
print(f" -> Ошибка 403: ключ ограничен/заблокирован: {response.text[:300]}")
|
||
return 'restricted', response.text
|
||
else:
|
||
print(f" -> Ошибка {response.status_code}: {response.text}")
|
||
return 'unknown', response.text
|
||
except requests.RequestException as e:
|
||
print(f" -> Ошибка сети: {e}")
|
||
return 'network', str(e)
|
||
|
||
# --- Основной процесс ---
|
||
def parse_args():
|
||
parser = argparse.ArgumentParser(description='OpenAI 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-restricted', action='store_true')
|
||
parser.add_argument('--retry-no-balance', 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()
|
||
compact_all_status_files()
|
||
backfill_checked_file()
|
||
print("--- 🚀 Запуск чекера ключей OpenAI 🚀 ---")
|
||
proxy_cycler = load_proxies(args.proxy_file)
|
||
checked_statuses = load_checked_statuses()
|
||
known_statuses = load_known_statuses(CHECKED_FILE, STATUS_BY_FILE)
|
||
known_keys = set(known_statuses)
|
||
alive_keys = load_set_from_file(ALIVE_FILE)
|
||
retry_statuses = set()
|
||
if args.retry_network:
|
||
retry_statuses.add('NETWORK')
|
||
if args.retry_limited:
|
||
retry_statuses.update(('LIMITED', 'LIMITED_OR_NO_BALANCE', 'LIMITED_OR_QUOTA'))
|
||
if args.retry_unknown:
|
||
retry_statuses.add('UNKNOWN')
|
||
if args.retry_restricted:
|
||
retry_statuses.add('RESTRICTED')
|
||
if args.retry_no_balance:
|
||
retry_statuses.update(('NO_BALANCE', 'NO_QUOTA', 'LIMITED_OR_NO_BALANCE', 'LIMITED_OR_QUOTA'))
|
||
print(f"📖 Загружено: {len(alive_keys)} живых ключей, {len(known_keys)} уже классифицированных ключей, {len(checked_statuses)} checked.")
|
||
|
||
if keycheck_input_mode() == 'jsonl' and not os.path.exists(args.input):
|
||
print(f"❌ Файл с секретами {args.input} не найден. Завершение.")
|
||
return
|
||
|
||
processed = 0
|
||
skipped = 0
|
||
seen_this_run = set()
|
||
def candidates():
|
||
for item in iter_findings(args.input, ['OpenAI']):
|
||
data = item.get('finding') or {}
|
||
key = item.get('raw') or extract_openai_key(data)
|
||
if key:
|
||
yield {'key': key, 'source': item.get('source') or args.input, 'finding': data}
|
||
seen_plain = set()
|
||
for item in iter_plain_openai_keys(retry_plain_files(args)):
|
||
key = item.get('key')
|
||
if key and key not in seen_plain:
|
||
seen_plain.add(key)
|
||
yield item
|
||
|
||
for item in candidates():
|
||
data = item.get('finding') or {}
|
||
source_line = item.get('source') or args.input
|
||
key = item.get('key')
|
||
if not key:
|
||
continue
|
||
if keycheck_input_mode() != 'postgres' and key in seen_this_run:
|
||
cached_status = checked_statuses.get(key) or known_statuses.get(key) or 'UNKNOWN'
|
||
record_cached_keycheck_occurrence(SERVICE, key, cached_status, source_line, data, 'OpenAI')
|
||
skipped += 1
|
||
continue
|
||
seen_this_run.add(key)
|
||
if should_skip_key(
|
||
key, checked_statuses, known_keys, args, retry_statuses,
|
||
service=SERVICE, source=source_line, finding=data, detector='OpenAI',
|
||
known_statuses=known_statuses,
|
||
):
|
||
skipped += 1
|
||
continue
|
||
if args.max_keys and processed >= args.max_keys:
|
||
break
|
||
processed += 1
|
||
|
||
print(f"\n[{processed}] 🎯 Новый кандидат: {key[:8]}...{key[-4:]} from {source_line}")
|
||
|
||
current_proxy = next(proxy_cycler) if proxy_cycler else None
|
||
|
||
auth_status, auth_data = check_authentication(key, current_proxy)
|
||
|
||
if auth_status == 'valid' and auth_data:
|
||
all_available_models_data = auth_data
|
||
all_model_ids = sorted({
|
||
str(model.get('id') or '') for model in all_available_models_data
|
||
if isinstance(model, dict) and model.get('id')
|
||
})
|
||
found_target_models = reportable_target_models(all_model_ids)
|
||
model_to_test = choose_probe_model(all_model_ids)
|
||
|
||
if not model_to_test:
|
||
print(" -> Ключ валиден, но не имеет подходящей chat-модели. Пропускаем.")
|
||
write_key_status(
|
||
key, NO_TARGET_FILE, 'no_target_models', ','.join(all_model_ids)[:500],
|
||
source_line, data, {
|
||
'models': sorted(found_target_models),
|
||
'model_inventory': all_model_ids,
|
||
'model_count': len(all_model_ids),
|
||
'llm_probe_status': 'NO_CONTEXT',
|
||
},
|
||
)
|
||
known_keys.add(key)
|
||
checked_statuses[key] = 'NO_TARGET_MODELS'
|
||
continue
|
||
|
||
balance_status, balance_data = check_balance_and_tier(key, model_to_test, current_proxy)
|
||
probe_metadata = {
|
||
'models': sorted(found_target_models or {model_to_test}),
|
||
'model_inventory': all_model_ids,
|
||
'model_count': len(all_model_ids),
|
||
'llm_probe_model': model_to_test,
|
||
'llm_probe_status': {
|
||
'ok': 'GENERATION_OK',
|
||
'limited': 'LIMITED',
|
||
'network': 'NETWORK',
|
||
'restricted': 'RESTRICTED',
|
||
'dead': 'DEAD',
|
||
}.get(balance_status, 'UNKNOWN'),
|
||
}
|
||
|
||
# Проверяем, что результат не None (успешная проверка баланса)
|
||
if balance_status == 'ok':
|
||
move_key_to_alive(
|
||
key, found_target_models or {model_to_test}, balance_data, source_line, data,
|
||
model_inventory=all_model_ids, probe_model=model_to_test,
|
||
)
|
||
alive_keys.add(key)
|
||
final_status = 'ALIVE'
|
||
elif balance_status == 'limited':
|
||
write_key_status(key, LIMITED_FILE, 'limited_or_no_balance', balance_data, source_line, data, probe_metadata)
|
||
final_status = 'LIMITED_OR_NO_BALANCE'
|
||
elif balance_status == 'network':
|
||
write_key_status(key, NETWORK_FILE, 'network_error', balance_data, source_line, data, probe_metadata)
|
||
final_status = 'NETWORK'
|
||
elif balance_status == 'restricted':
|
||
write_key_status(key, RESTRICTED_FILE, 'restricted', balance_data, source_line, data, probe_metadata)
|
||
final_status = 'RESTRICTED'
|
||
elif balance_status == 'dead':
|
||
write_key_status(key, DEAD_FILE, 'invalid_or_revoked', balance_data, source_line, data, probe_metadata)
|
||
final_status = 'DEAD'
|
||
else:
|
||
write_key_status(key, UNKNOWN_FILE, 'unknown', balance_data, source_line, data, probe_metadata)
|
||
final_status = 'UNKNOWN'
|
||
known_keys.add(key)
|
||
checked_statuses[key] = final_status
|
||
elif auth_status == 'network':
|
||
write_key_status(key, NETWORK_FILE, 'network_error', auth_data, source_line, data)
|
||
known_keys.add(key)
|
||
checked_statuses[key] = 'NETWORK'
|
||
elif auth_status == 'limited':
|
||
write_key_status(key, LIMITED_FILE, 'limited_or_quota', auth_data, source_line, data)
|
||
known_keys.add(key)
|
||
checked_statuses[key] = 'LIMITED'
|
||
elif auth_status == 'restricted':
|
||
write_key_status(key, RESTRICTED_FILE, 'restricted', auth_data, source_line, data)
|
||
known_keys.add(key)
|
||
checked_statuses[key] = 'RESTRICTED'
|
||
elif auth_status == 'dead':
|
||
write_key_status(key, DEAD_FILE, 'invalid_or_revoked', auth_data, source_line, data)
|
||
known_keys.add(key)
|
||
checked_statuses[key] = 'DEAD'
|
||
else:
|
||
write_key_status(key, UNKNOWN_FILE, 'unknown', auth_data, source_line, data)
|
||
known_keys.add(key)
|
||
checked_statuses[key] = 'UNKNOWN'
|
||
|
||
print("\n--- ✅ Проверка завершена. ---")
|
||
print(f"Processed={processed}, skipped={skipped}")
|
||
|
||
if __name__ == "__main__":
|
||
main()
|