137 lines
4.3 KiB
Python
137 lines
4.3 KiB
Python
"""SQLite 账号库:建表、读写、登录态字段。"""
|
||
import json
|
||
import sqlite3
|
||
import time
|
||
from datetime import datetime
|
||
from typing import Optional
|
||
|
||
from am_platforms import PLATFORM_URLS, _PLATFORM_NAME_CN
|
||
from am_runtime_paths import get_db_path
|
||
|
||
|
||
# SQLite 无独立 DATETIME 类型:时间统一存 INTEGER Unix 秒(UTC),查询/JSON 再转 ISO8601。
|
||
# 下列 DDL 含注释,会原样写入 sqlite_master,便于 Navicat / DBeaver 等查看建表语句。
|
||
ACCOUNTS_TABLE_SQL = """
|
||
/* 多平台账号表:与本地 Chromium/Edge 用户数据目录(profile)绑定,供发布类技能读取登录态 */
|
||
CREATE TABLE accounts (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT, -- 账号主键(自增)
|
||
name TEXT NOT NULL, -- 展示名称,如「搜狐1号」
|
||
platform TEXT NOT NULL, -- 平台标识,与内置 PLATFORMS 键一致,如 sohu
|
||
phone TEXT, -- 可选绑定手机号
|
||
profile_dir TEXT, -- Playwright 用户数据目录(绝对路径);默认可读结构 profiles/<平台展示名>/<手机号>/
|
||
url TEXT, -- 平台入口或登录页 URL
|
||
login_status INTEGER NOT NULL DEFAULT 0, -- 是否已登录:0 否 1 是(由脚本校验后写入)
|
||
last_login_at INTEGER, -- 最近一次登录成功时间,Unix 秒 UTC;未登录过为 NULL
|
||
extra_json TEXT, -- 扩展字段 JSON
|
||
created_at INTEGER NOT NULL, -- 记录创建时间,Unix 秒 UTC
|
||
updated_at INTEGER NOT NULL -- 记录最后更新时间,Unix 秒 UTC
|
||
);
|
||
"""
|
||
|
||
|
||
def _now_unix() -> int:
|
||
return int(time.time())
|
||
|
||
|
||
def _unix_to_iso_local(ts: Optional[int]) -> Optional[str]:
|
||
"""对外 JSON:本地时区 ISO8601 字符串,便于人读。"""
|
||
if ts is None:
|
||
return None
|
||
try:
|
||
return datetime.fromtimestamp(int(ts)).isoformat(timespec="seconds")
|
||
except (ValueError, OSError, OverflowError):
|
||
return None
|
||
|
||
|
||
def get_conn():
|
||
return sqlite3.connect(get_db_path())
|
||
|
||
|
||
def init_db():
|
||
conn = get_conn()
|
||
try:
|
||
cur = conn.cursor()
|
||
cur.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='accounts'")
|
||
if not cur.fetchone():
|
||
cur.executescript(ACCOUNTS_TABLE_SQL)
|
||
conn.commit()
|
||
finally:
|
||
conn.close()
|
||
|
||
|
||
def _normalize_account_id(account_id):
|
||
if account_id is None:
|
||
return None
|
||
s = str(account_id).strip()
|
||
if s.isdigit():
|
||
return int(s)
|
||
return s
|
||
|
||
|
||
def get_account_by_id(account_id):
|
||
init_db()
|
||
aid = _normalize_account_id(account_id)
|
||
conn = get_conn()
|
||
try:
|
||
cur = conn.cursor()
|
||
cur.execute(
|
||
"""
|
||
SELECT id, name, platform, phone, profile_dir, url, login_status, last_login_at, extra_json,
|
||
created_at, updated_at
|
||
FROM accounts WHERE id = ?
|
||
""",
|
||
(aid,),
|
||
)
|
||
row = cur.fetchone()
|
||
if not row:
|
||
return None
|
||
acc = {
|
||
"id": row[0],
|
||
"name": row[1],
|
||
"platform": row[2],
|
||
"phone": row[3] or "",
|
||
"profile_dir": (row[4] or "").strip(),
|
||
"url": row[5] or PLATFORM_URLS.get(row[2], ""),
|
||
"login_status": int(row[6] or 0),
|
||
"last_login_at": _unix_to_iso_local(row[7]),
|
||
"created_at": _unix_to_iso_local(row[9]),
|
||
"updated_at": _unix_to_iso_local(row[10]),
|
||
}
|
||
if row[8]:
|
||
try:
|
||
extra = json.loads(row[8])
|
||
if isinstance(extra, dict):
|
||
acc.update(extra)
|
||
except Exception:
|
||
pass
|
||
return acc
|
||
finally:
|
||
conn.close()
|
||
|
||
|
||
def _default_name_for_platform(platform: str, index: int) -> str:
|
||
cn = _PLATFORM_NAME_CN.get(platform)
|
||
if cn:
|
||
return f"{cn}{index}号"
|
||
return f"{platform}_{index}"
|
||
|
||
|
||
def _mark_login_status(account_id, success: bool):
|
||
now = _now_unix()
|
||
conn = get_conn()
|
||
try:
|
||
cur = conn.cursor()
|
||
if success:
|
||
cur.execute(
|
||
"UPDATE accounts SET login_status = 1, last_login_at = ?, updated_at = ? WHERE id = ?",
|
||
(now, now, account_id),
|
||
)
|
||
else:
|
||
cur.execute(
|
||
"UPDATE accounts SET login_status = 0, updated_at = ? WHERE id = ?",
|
||
(now, account_id),
|
||
)
|
||
conn.commit()
|
||
finally:
|
||
conn.close()
|