Files
account-manager/scripts/am_commands_read.py
chendelian c428bedfd5
All checks were successful
技能自动化发布 / release (push) Successful in 34s
chore: auto release commit (2026-04-06 10:37:07)
2026-04-06 10:37:08 +08:00

286 lines
9.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""只读类 CLIlist / get / list-json / pick-*。"""
import json
import sys
from am_db import get_account_by_id, get_conn, init_db
from am_logging import get_skill_logger
from am_platforms import _platform_list_cn_for_help, resolve_platform_key
from am_runtime_paths import _runtime_paths_debug_text
# list 表头:与 accounts 表列顺序、列名一致(便于对照库结构)
_LIST_TABLE_COLUMNS = (
"id",
"name",
"platform",
"phone",
"profile_dir",
"url",
"login_status",
"last_login_at",
"extra_json",
"created_at",
"updated_at",
)
def _list_row_to_cells(row: tuple) -> list:
"""将 SELECT 整行转为展示用字符串列表(与 _LIST_TABLE_COLUMNS 顺序一致)。"""
rid, name, plat, phone, pdir, url, lstat, lla, exj, cat, uat = row
return [
str(rid) if rid is not None else "",
name if name is not None else "",
plat if plat is not None else "",
phone if phone is not None else "",
pdir if pdir is not None else "",
url if url is not None else "",
str(int(lstat)) if lstat is not None else "",
str(int(lla)) if lla is not None else "",
(exj or "").replace("\n", "\\n").replace("\r", ""),
str(int(cat)) if cat is not None else "",
str(int(uat)) if uat is not None else "",
]
def _print_accounts_table(rows: list) -> None:
"""表头 + 分隔线 + 每账号一行;列宽按内容对齐(终端等宽字体)。"""
headers = list(_LIST_TABLE_COLUMNS)
body = [_list_row_to_cells(r) for r in rows]
n = len(headers)
widths = [len(headers[j]) for j in range(n)]
for cells in body:
for j in range(n):
widths[j] = max(widths[j], len(cells[j]))
sep = " | "
def fmt(cells):
return sep.join(cells[j].ljust(widths[j]) for j in range(n))
print(fmt(headers))
print(sep.join("-" * widths[j] for j in range(n)))
for cells in body:
print(fmt(cells))
def cmd_list(platform="all", limit: int = 10):
get_skill_logger().info("list filter=%r", platform)
init_db()
raw = (platform or "all").strip()
if not raw or raw.lower() == "all" or raw == "全部":
key = "all"
else:
key = resolve_platform_key(raw)
if not key:
get_skill_logger().warning("list_invalid_platform raw=%r", raw)
print(f"ERROR:INVALID_PLATFORM_LIST 无法识别的平台「{raw}")
print("支持:" + _platform_list_cn_for_help())
return
if limit <= 0:
limit = 10
conn = get_conn()
try:
cur = conn.cursor()
sql = (
"SELECT id, name, platform, phone, profile_dir, url, login_status, "
"last_login_at, extra_json, created_at, updated_at FROM accounts "
)
if key == "all":
cur.execute(sql + "ORDER BY created_at DESC, id DESC LIMIT ?", (int(limit),))
else:
cur.execute(
sql + "WHERE platform = ? ORDER BY created_at DESC, id DESC LIMIT ?",
(key, int(limit)),
)
rows = cur.fetchall()
finally:
conn.close()
found = bool(rows)
if found:
get_skill_logger().info("list_ok rows=%s key=%s", len(rows), key)
sep_line = "_" * 39
for idx, row in enumerate(rows):
(
aid,
name,
plat,
phone,
profile_dir,
url,
login_status,
last_login_at,
extra_json,
created_at,
updated_at,
) = row
print(f"id{aid}")
print(f"name{name or ''}")
print(f"platform{plat or ''}")
print(f"phone{phone or ''}")
print(f"profile_dir{profile_dir or ''}")
print(f"url{url or ''}")
print(f"login_status{int(login_status) if login_status is not None else ''}")
print(f"last_login_at{int(last_login_at) if last_login_at is not None else ''}")
print(f"extra_json{extra_json or ''}")
print(f"created_at{int(created_at) if created_at is not None else ''}")
print(f"updated_at{int(updated_at) if updated_at is not None else ''}")
if idx != len(rows) - 1:
print(sep_line)
print()
if not found:
print("ERROR:NO_ACCOUNTS_FOUND")
print(_runtime_paths_debug_text(), file=sys.stderr)
def cmd_get(account_id):
get_skill_logger().info("get account_id=%r", account_id)
acc = get_account_by_id(account_id)
if acc:
print(json.dumps(acc, ensure_ascii=False))
return
get_skill_logger().warning("get_not_found account_id=%r", account_id)
print("ERROR:ACCOUNT_NOT_FOUND")
print(_runtime_paths_debug_text(), file=sys.stderr)
def cmd_list_json(platform_input: str, limit: int = 200) -> None:
"""
跨技能接口stdout 仅输出一行 JSON 数组,元素结构与 get 单条一致。
平台可为 all/全部 或各平台中文名/英文键;按 updated_at 倒序,最多 limit 条(上限 500
"""
get_skill_logger().info("list_json filter=%r limit=%s", platform_input, limit)
init_db()
raw = (platform_input or "all").strip()
if not raw or raw.lower() == "all" or raw == "全部":
key = "all"
else:
key = resolve_platform_key(raw)
if not key:
print("ERROR:INVALID_PLATFORM_LIST_JSON 无法识别的平台名称。")
print("支持:" + _platform_list_cn_for_help())
return
lim = max(1, min(int(limit), 500))
conn = get_conn()
try:
cur = conn.cursor()
if key == "all":
cur.execute(
"SELECT id FROM accounts ORDER BY updated_at DESC, id DESC LIMIT ?",
(lim,),
)
else:
cur.execute(
"SELECT id FROM accounts WHERE platform = ? "
"ORDER BY updated_at DESC, id DESC LIMIT ?",
(key, lim),
)
ids = [r[0] for r in cur.fetchall()]
finally:
conn.close()
out = []
for aid in ids:
acc = get_account_by_id(aid)
if acc:
out.append(acc)
print(json.dumps(out, ensure_ascii=False))
def cmd_pick_logged_in(platform_input: str):
"""
机器可读跨技能接口查询指定平台下「已登录」的一条账号login_status=1按 last_login_at 优先)。
成功stdout 仅输出一行 JSON结构与 get 子命令一致。
失败stdout 首行以 ERROR: 开头(由调用方判断,勿解析为 JSON
"""
get_skill_logger().info("pick_logged_in platform_input=%r", platform_input)
key = resolve_platform_key((platform_input or "").strip())
if not key:
print("ERROR:INVALID_PLATFORM 无法识别的平台名称。")
print("支持:" + _platform_list_cn_for_help())
return
init_db()
conn = get_conn()
try:
cur = conn.cursor()
cur.execute(
"""
SELECT id FROM accounts
WHERE platform = ? AND login_status = 1
ORDER BY (last_login_at IS NULL), last_login_at DESC
LIMIT 1
""",
(key,),
)
row = cur.fetchone()
finally:
conn.close()
if not row:
print("ERROR:NO_LOGGED_IN_ACCOUNT 该平台暂无已登录账号login_status=1")
print("请先 list 查看账号 id再执行python main.py login <id>")
return
acc = get_account_by_id(row[0])
if not acc:
print("ERROR:ACCOUNT_NOT_FOUND")
print(_runtime_paths_debug_text(), file=sys.stderr)
return
print(json.dumps(acc, ensure_ascii=False))
def cmd_pick_web(platform_input: str):
"""
供 llm-manager 等:取该平台用于网页自动化的账号候选。
优先 login_status=1与 pick-logged-in 一致);若无,则取该平台 updated_at 最新的一条,
便于「已 add 未标登录」时仍打开 profile在浏览器内登录后继续任务。
成功stdout 仅一行 JSON与 get 一致);失败:首行 ERROR:。
"""
get_skill_logger().info("pick_web platform_input=%r", platform_input)
key = resolve_platform_key((platform_input or "").strip())
if not key:
print("ERROR:INVALID_PLATFORM 无法识别的平台名称。")
print("支持:" + _platform_list_cn_for_help())
return
init_db()
conn = get_conn()
try:
cur = conn.cursor()
cur.execute(
"""
SELECT id FROM accounts
WHERE platform = ? AND login_status = 1
ORDER BY (last_login_at IS NULL), last_login_at DESC
LIMIT 1
""",
(key,),
)
row = cur.fetchone()
if not row:
cur.execute(
"""
SELECT id FROM accounts
WHERE platform = ?
ORDER BY updated_at DESC, id DESC
LIMIT 1
""",
(key,),
)
row = cur.fetchone()
finally:
conn.close()
if not row:
print("ERROR:NO_ACCOUNT 该平台在账号库中没有任何记录,请先执行 add。")
return
acc = get_account_by_id(row[0])
if not acc:
print("ERROR:ACCOUNT_NOT_FOUND")
print(_runtime_paths_debug_text(), file=sys.stderr)
return
print(json.dumps(acc, ensure_ascii=False))