import json import os import threading try: import psycopg except ImportError: psycopg = None try: import psycopg2 except ImportError: psycopg2 = None _LOCK = threading.Lock() def _safe_read_json(path): if not os.path.exists(path): return {} try: with open(path, "r", encoding="utf-8") as f: return json.load(f) except Exception: return {} def _safe_write_json(path, data): tmp_path = f"{path}.tmp" with open(tmp_path, "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=2) os.replace(tmp_path, path) def save_config(path, config): with _LOCK: _safe_write_json(path, config) def load_config(path): data = _safe_read_json(path) if not isinstance(data, dict): return {} return data def _to_text(value): if value is None: return "" return str(value) def _normalize_optional_text(value): text = _to_text(value).strip() return text or None def _get_pg_driver(): if psycopg is not None: return "psycopg" if psycopg2 is not None: return "psycopg2" raise RuntimeError("未安装 PostgreSQL 驱动,请先安装 psycopg[binary] 或 psycopg2-binary") def _get_pg_connection(config): driver = _get_pg_driver() host = str(config.get("pg_host", "")).strip() port = int(config.get("pg_port", 5432) or 5432) dbname = str(config.get("pg_db", "")).strip() user = str(config.get("pg_user", "")).strip() password = str(config.get("pg_password", "")).strip() connect_timeout = int(config.get("pg_connect_timeout", 10) or 10) if not host or not dbname or not user: raise ValueError("PostgreSQL 配置不完整,请填写 Host、DB、User") kwargs = { "host": host, "port": port, "dbname": dbname, "user": user, "password": password, "connect_timeout": connect_timeout, } if driver == "psycopg": return psycopg.connect(**kwargs) return psycopg2.connect(**kwargs) def load_accounts(config): query = ( "select email, mail_password, mail_token, chatgpt_password, name, birthdate, created_at " "from registered_accounts order by created_at desc nulls last, email asc" ) accounts = [] with _get_pg_connection(config) as conn: with conn.cursor() as cur: cur.execute(query) rows = cur.fetchall() for row in rows: email, mail_password, mail_token, chatgpt_password, name, birthdate, created_at = row if not email: continue accounts.append( { "email": _to_text(email).strip(), "mail_password": _to_text(mail_password).strip(), "mail_token": _to_text(mail_token).strip(), "chatgpt_password": _to_text(chatgpt_password).strip(), "name": _to_text(name).strip(), "birthdate": _to_text(birthdate).strip(), "created_at": _to_text(created_at).strip(), "source": "postgres", } ) return accounts def save_account( config, email, mail_password, mail_token, chatgpt_password=None, name=None, birthdate=None, ): query = ( "insert into registered_accounts " "(email, mail_password, mail_token, chatgpt_password, name, birthdate) " "values (%s, %s, %s, %s, %s, %s) " "on conflict (email) do update set " "mail_password = excluded.mail_password, " "mail_token = excluded.mail_token, " "chatgpt_password = excluded.chatgpt_password, " "name = excluded.name, " "birthdate = excluded.birthdate" ) params = ( email, mail_password, mail_token, _normalize_optional_text(chatgpt_password), _normalize_optional_text(name), _normalize_optional_text(birthdate), ) with _LOCK: with _get_pg_connection(config) as conn: with conn.cursor() as cur: cur.execute(query, params) conn.commit()