247 lines
8.5 KiB
Python
247 lines
8.5 KiB
Python
"""写入类 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")
|