86 lines
2.9 KiB
Python
86 lines
2.9 KiB
Python
"""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']})")
|