universal-autoreg: initial public release (restored, redacted, hardened)
This commit is contained in:
85
lib/bank.py
Normal file
85
lib/bank.py
Normal file
@@ -0,0 +1,85 @@
|
||||
"""bank.py — единый банк аккаунтов (JSONL).
|
||||
|
||||
Каждый провайдер пишет сюда: {"provider","email","password","token","extra",...,"ts"}
|
||||
Дедуп по (provider, email). Статистика по провайдерам.
|
||||
"""
|
||||
import json, os, time, threading
|
||||
|
||||
BANK_FILE = os.environ.get("UA_BANK", os.path.join(os.path.dirname(__file__), "..", "data", "bank.jsonl"))
|
||||
_lock = threading.Lock()
|
||||
_seen_cache = None # PERF: (mtime, size, set) — дедуп O(1) вместо re-read всего банка на каждый add()
|
||||
|
||||
|
||||
def _path():
|
||||
p = os.path.abspath(BANK_FILE)
|
||||
os.makedirs(os.path.dirname(p), exist_ok=True)
|
||||
return p
|
||||
|
||||
|
||||
def _seen_set():
|
||||
"""Кэш ключей (provider,email); инвалидация по mtime+size (внешние писатели)."""
|
||||
global _seen_cache
|
||||
p = _path()
|
||||
if not os.path.exists(p):
|
||||
_seen_cache = (0, 0, set())
|
||||
return _seen_cache[2]
|
||||
stt = os.stat(p)
|
||||
if _seen_cache is None or _seen_cache[0] != stt.st_mtime or _seen_cache[1] != stt.st_size:
|
||||
keys = {(r.get("provider"), r.get("email")) for r in load()}
|
||||
_seen_cache = (stt.st_mtime, stt.st_size, keys)
|
||||
return _seen_cache[2]
|
||||
|
||||
|
||||
def add(record: dict) -> bool:
|
||||
"""Добавить аккаунт. False если дубль (provider,email)."""
|
||||
global _seen_cache
|
||||
rec = dict(record)
|
||||
rec.setdefault("ts", int(time.time()))
|
||||
with _lock:
|
||||
seen = _seen_set()
|
||||
key = (rec.get("provider"), rec.get("email"))
|
||||
if key in seen:
|
||||
return False
|
||||
with open(_path(), "a", encoding="utf-8") as f:
|
||||
f.write(json.dumps(rec, ensure_ascii=False) + "\n")
|
||||
seen.add(key)
|
||||
# держим set горячим, stat — свежий (иначе следующий add перечитал бы файл)
|
||||
stt = os.stat(_path())
|
||||
_seen_cache = (stt.st_mtime, stt.st_size, seen)
|
||||
return True
|
||||
|
||||
|
||||
def load():
|
||||
p = _path()
|
||||
if not os.path.exists(p):
|
||||
return []
|
||||
out = []
|
||||
with open(p, encoding="utf-8") as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line:
|
||||
try:
|
||||
out.append(json.loads(line))
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
return out
|
||||
|
||||
|
||||
def stats():
|
||||
per = {}
|
||||
for r in load():
|
||||
p = r.get("provider", "?")
|
||||
per.setdefault(p, {"total": 0, "with_token": 0})
|
||||
per[p]["total"] += 1
|
||||
if r.get("token") or r.get("api_key"):
|
||||
per[p]["with_token"] += 1
|
||||
return per
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
s = stats()
|
||||
total = sum(v["total"] for v in s.values())
|
||||
print(f"BANK {total} аккаунтов, {len(s)} провайдеров")
|
||||
for p, v in sorted(s.items(), key=lambda x: -x[1]["total"]):
|
||||
print(f" {p}: {v['total']} (с токеном: {v['with_token']})")
|
||||
Reference in New Issue
Block a user