"""写入类 CLI:add / delete / set-login-status。""" import os import shutil import sys from am_db import ( _default_name_for_platform, _mark_login_status, _normalize_account_id, _now_unix, get_account_by_id, get_conn, init_db, ) from am_logging import get_skill_logger from am_platforms import ( PLATFORM_URLS, _PLATFORM_PRIMARY_CN, _is_valid_cn_mobile11, _normalize_phone_digits, _platform_list_cn_for_help, resolve_platform_key, ) from am_runtime_paths import _runtime_paths_debug_text, get_default_profile_dir def _remove_profile_dir(path: str) -> None: p = (path or "").strip() if not p or not os.path.isdir(p): return try: shutil.rmtree(p) except OSError as e: print(f"⚠️ 未能删除用户数据目录(可手工删):{p}\n {e}", file=sys.stderr) def cmd_add(platform_input: str, phone: str): """添加账号:platform 可为中文展示名或英文键;手机号必填;同平台下同手机号不可重复。""" log = get_skill_logger() log.info("add_attempt platform_input=%r", platform_input) init_db() key = resolve_platform_key((platform_input or "").strip()) if not key: log.warning("add_invalid_platform input=%r", platform_input) print("ERROR:INVALID_PLATFORM 无法识别的平台名称。") print("支持:" + _platform_list_cn_for_help()) return phone_raw = (phone or "").strip() if not phone_raw: log.warning("add_phone_missing") print("ERROR:PHONE_REQUIRED 手机号为必填。") return phone_norm = _normalize_phone_digits(phone_raw) if not _is_valid_cn_mobile11(phone_norm): log.warning("add_phone_invalid digits_len=%s", len(phone_norm or "")) print( "ERROR:PHONE_INVALID 手机号格式不正确:须为中国大陆 11 位号码," "以 1 开头、第二位为 3~9(示例 13800138000)。" ) return url = PLATFORM_URLS[key] now = _now_unix() phone_store = phone_norm conn = get_conn() try: cur = conn.cursor() cur.execute( "SELECT id FROM accounts WHERE platform = ? AND phone = ?", (key, phone_store), ) if cur.fetchone(): log.warning("add_duplicate platform=%s phone_suffix=%s", key, phone_store[-4:]) print( f"ERROR:DUPLICATE_PHONE_PLATFORM 该平台下已存在手机号 {phone_store},请勿重复添加。" ) return cur.execute("SELECT COUNT(*) FROM accounts WHERE platform = ?", (key,)) next_idx = cur.fetchone()[0] + 1 name = _default_name_for_platform(key, next_idx) profile_dir = get_default_profile_dir(key, phone_store, None) cur.execute( """ INSERT INTO accounts (name, platform, phone, profile_dir, url, login_status, last_login_at, extra_json, created_at, updated_at) VALUES (?, ?, ?, ?, ?, 0, NULL, NULL, ?, ?) """, (name, key, phone_store, profile_dir, url, now, now), ) new_id = cur.lastrowid conn.commit() finally: conn.close() print( f"✅ 已保存账号:ID {new_id} | {name} | {_PLATFORM_PRIMARY_CN.get(key, key)} | 手机 {phone_store}" ) print(f"ℹ️ 登录请执行:python main.py login {new_id}") log.info( "add_success id=%s platform=%s profile_dir=%s", new_id, key, profile_dir, ) def cmd_delete_by_id(account_id) -> None: log = get_skill_logger() log.info("delete_by_id account_id=%r", account_id) init_db() aid = _normalize_account_id(account_id) if aid is None or (isinstance(aid, str) and not str(aid).isdigit()): log.warning("delete_invalid_id") print("ERROR:DELETE_INVALID_ID 账号 id 须为正整数。") return conn = get_conn() try: cur = conn.cursor() cur.execute( "SELECT id, name, platform, phone, profile_dir FROM accounts WHERE id = ?", (int(aid),), ) row = cur.fetchone() if not row: log.warning("delete_by_id_not_found id=%s", aid) print("ERROR:ACCOUNT_NOT_FOUND") print(_runtime_paths_debug_text(), file=sys.stderr) return rid, name, plat, phone, profile_dir = row cur.execute("DELETE FROM accounts WHERE id = ?", (rid,)) conn.commit() finally: conn.close() _remove_profile_dir(profile_dir) print( f"✅ 已删除账号:ID {rid} | {name} | {_PLATFORM_PRIMARY_CN.get(plat, plat)} | 手机 {phone or '(无)'}" ) log.info("delete_by_id_done id=%s platform=%s", rid, plat) def cmd_delete_by_platform(platform_input: str) -> None: log = get_skill_logger() log.info("delete_by_platform platform_input=%r", platform_input) init_db() key = resolve_platform_key((platform_input or "").strip()) if not key: log.warning("delete_by_platform_invalid_platform") print("ERROR:INVALID_PLATFORM 无法识别的平台名称。") print("支持:" + _platform_list_cn_for_help()) return conn = get_conn() try: cur = conn.cursor() cur.execute( "SELECT id, profile_dir FROM accounts WHERE platform = ?", (key,), ) rows = cur.fetchall() if not rows: log.warning("delete_by_platform_empty platform=%s", key) print("ERROR:NO_ACCOUNTS_FOUND 该平台下没有账号记录。") return cur.execute("DELETE FROM accounts WHERE platform = ?", (key,)) conn.commit() finally: conn.close() for _rid, pdir in rows: _remove_profile_dir(pdir) print( f"✅ 已删除 {_PLATFORM_PRIMARY_CN.get(key, key)} 下共 {len(rows)} 条账号及对应用户数据目录。" ) log.info("delete_by_platform_done platform=%s count=%s", key, len(rows)) def cmd_delete_by_platform_phone(platform_input: str, phone: str) -> None: log = get_skill_logger() log.info("delete_by_platform_phone platform_input=%r", platform_input) init_db() key = resolve_platform_key((platform_input or "").strip()) if not key: print("ERROR:INVALID_PLATFORM 无法识别的平台名称。") print("支持:" + _platform_list_cn_for_help()) return phone_raw = (phone or "").strip() if not phone_raw: print("ERROR:PHONE_REQUIRED 手机号为必填。") return phone_norm = _normalize_phone_digits(phone_raw) if not _is_valid_cn_mobile11(phone_norm): print( "ERROR:PHONE_INVALID 手机号格式不正确:须为中国大陆 11 位号码," "以 1 开头、第二位为 3~9。" ) return conn = get_conn() try: cur = conn.cursor() cur.execute( "SELECT id, name, profile_dir FROM accounts WHERE platform = ? AND phone = ?", (key, phone_norm), ) row = cur.fetchone() if not row: print("ERROR:ACCOUNT_NOT_FOUND 该平台下无此手机号。") return rid, name, profile_dir = row cur.execute( "DELETE FROM accounts WHERE platform = ? AND phone = ?", (key, phone_norm), ) conn.commit() finally: conn.close() _remove_profile_dir(profile_dir) print( f"✅ 已删除账号:ID {rid} | {name} | {_PLATFORM_PRIMARY_CN.get(key, key)} | 手机 {phone_norm}" ) log.info("delete_by_platform_phone_done id=%s platform=%s", rid, key) def cmd_set_login_status(account_id_str: str, status_str: str) -> None: """ 跨技能机器接口:回写 accounts.login_status。 成功:stdout 首行 OK:SET_LOGIN_STATUS 或 OK:CLEARED_LOGIN_STATUS,进程退出码 0。 失败:stdout 首行 ERROR:...,退出码 1。 """ aid_s = (account_id_str or "").strip() st_s = (status_str or "").strip() if not aid_s.isdigit(): print("ERROR:INVALID_ACCOUNT_ID") sys.exit(1) if st_s not in ("0", "1"): print("ERROR:INVALID_STATUS 须为 0 或 1") sys.exit(1) aid = int(aid_s) init_db() if get_account_by_id(aid) is None: get_skill_logger().warning("set_login_status_not_found account_id=%r", aid) print("ERROR:ACCOUNT_NOT_FOUND") sys.exit(1) ok = st_s == "1" _mark_login_status(aid, ok) get_skill_logger().info("set_login_status account_id=%s success=%s", aid, ok) print("OK:SET_LOGIN_STATUS" if ok else "OK:CLEARED_LOGIN_STATUS")