覆盖文生 3D、图生 3D、多视图、批量生成,以及重拓扑、绑定、动画、智能分割、 纹理、PBR、智能 UV、部件补全、格式导出与下载。 代码组织:一个功能一个模块(tripo/features/),入口薄壳 tripocli.py。
190 lines
6.5 KiB
Python
190 lines
6.5 KiB
Python
"""账号管理 — add / list / use / remove / show.
|
||
|
||
给别人用时不需要浏览器:把浏览器的 cookie 粘贴进来一次,CLI 之后自己续期 JWT。
|
||
|
||
tripocli account add --name work --cookie "ory_kratos_session=...; csrf_token_...=..."
|
||
tripocli account add # 交互式粘贴
|
||
tripocli account list
|
||
tripocli account use work
|
||
tripocli account remove work
|
||
"""
|
||
|
||
import getpass
|
||
import sys
|
||
import time
|
||
|
||
from .. import auth
|
||
from ..accounts import Accounts
|
||
from ..config import Config
|
||
|
||
COOKIE_HINT = (
|
||
"请粘贴 cookie(整条 Cookie 字符串,或仅 ory_kratos_session 的值),回车结束:\n"
|
||
" 提示:浏览器 DevTools → Network → 任意 api.tripo3d.ai 请求 → Request Headers → Cookie 整条复制"
|
||
)
|
||
|
||
|
||
def _fmt(epoch):
|
||
if not epoch:
|
||
return None
|
||
if isinstance(epoch, (int, float)):
|
||
return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(epoch))
|
||
return str(epoch)
|
||
|
||
|
||
def _mask(value, keep=10):
|
||
if not value:
|
||
return None
|
||
return f"{value[:keep]}...{value[-6:]}" if len(value) > keep + 8 else "***"
|
||
|
||
|
||
def _read_cookie(args):
|
||
if args.cookie_file:
|
||
with open(args.cookie_file, "r", encoding="utf-8") as handle:
|
||
return handle.read().strip()
|
||
if args.cookie == "-":
|
||
return sys.stdin.read().strip()
|
||
if args.cookie:
|
||
return args.cookie.strip()
|
||
# Interactive paste; hidden input so the cookie does not land in scrollback.
|
||
print(COOKIE_HINT)
|
||
try:
|
||
return getpass.getpass("cookie: ").strip()
|
||
except (EOFError, KeyboardInterrupt):
|
||
raise SystemExit("已取消")
|
||
|
||
|
||
def _identity(api_base, cookie, device_id):
|
||
"""Best-effort identity lookup so accounts are recognisable in `account list`."""
|
||
try:
|
||
return auth.fetch_identity(api_base, cookie, device_id)
|
||
except Exception:
|
||
return None, None
|
||
|
||
|
||
def _add(ctx):
|
||
args = ctx.args
|
||
cookie = auth.normalize_cookie(_read_cookie(args))
|
||
|
||
# Validate before storing: a bad cookie must fail here, not on the next command.
|
||
token, session_expires = auth.mint_token(ctx.config.api_base, cookie, ctx.config.device_id)
|
||
user_id, email = _identity(ctx.config.api_base, cookie, ctx.config.device_id)
|
||
|
||
name = args.name or email or user_id or "default"
|
||
ctx.accounts.add(name, cookie, token=token, session_expires_at=session_expires,
|
||
user_id=user_id, email=email)
|
||
|
||
payload = {
|
||
"added": name,
|
||
"active": ctx.accounts.active_name(),
|
||
"email": email,
|
||
"user_id": user_id,
|
||
"session_expires_at": _fmt(session_expires),
|
||
}
|
||
ctx.out.data(
|
||
payload,
|
||
human=lambda: (
|
||
ctx.out.line(f"✅ 账号已保存: {name}"),
|
||
ctx.out.line(f"身份 : {email or user_id or '未知'}"),
|
||
ctx.out.line(f"会话有效期: {_fmt(session_expires) or '未知'}"),
|
||
ctx.out.line("之后无需浏览器,CLI 会自动用 cookie 续期 JWT"),
|
||
),
|
||
)
|
||
return payload
|
||
|
||
|
||
def _list(ctx):
|
||
accounts = ctx.accounts
|
||
active = accounts.active_name()
|
||
items = []
|
||
for name in accounts.names():
|
||
record = accounts.data["accounts"][name]
|
||
items.append({
|
||
"name": name,
|
||
"active": name == active,
|
||
"email": record.get("email"),
|
||
"user_id": record.get("user_id"),
|
||
"session_expires_at": _fmt(record.get("session_expires_at")),
|
||
"cookie": _mask(record.get("cookie")),
|
||
})
|
||
|
||
def human():
|
||
if not items:
|
||
ctx.out.line("暂无账号。用 `tripocli account add` 粘贴 cookie 添加。")
|
||
return
|
||
for item in items:
|
||
mark = "*" if item["active"] else " "
|
||
ctx.out.line(f"{mark} {item['name']:<20} {item['email'] or item['user_id'] or '-'}"
|
||
f" (会话至 {item['session_expires_at'] or '未知'})")
|
||
|
||
ctx.out.data({"active": active, "accounts": items}, human=human)
|
||
return items
|
||
|
||
|
||
def _use(ctx):
|
||
name = ctx.args.name
|
||
try:
|
||
ctx.accounts.use(name)
|
||
except KeyError:
|
||
raise ValueError(f"账号不存在: {name}(可用 `tripocli account list` 查看)")
|
||
ctx.out.data({"active": name}, human=lambda: ctx.out.line(f"✅ 当前账号已切换为 {name}"))
|
||
return {"active": name}
|
||
|
||
|
||
def _remove(ctx):
|
||
name = ctx.args.name
|
||
try:
|
||
ctx.accounts.remove(name)
|
||
except KeyError:
|
||
raise ValueError(f"账号不存在: {name}")
|
||
payload = {"removed": name, "active": ctx.accounts.active_name()}
|
||
ctx.out.data(payload, human=lambda: ctx.out.line(f"✅ 已删除账号 {name}"))
|
||
return payload
|
||
|
||
|
||
def _show(ctx):
|
||
name = ctx.args.name
|
||
resolved, record = ctx.accounts.get(name)
|
||
if not record:
|
||
raise ValueError(f"账号不存在: {name or '(无当前账号)'}")
|
||
info = {
|
||
"name": resolved,
|
||
"active": resolved == ctx.accounts.active_name(),
|
||
"email": record.get("email"),
|
||
"user_id": record.get("user_id"),
|
||
"cookie": _mask(record.get("cookie")),
|
||
"session_expires_at": _fmt(record.get("session_expires_at")),
|
||
"added_at": _fmt(record.get("added_at")),
|
||
}
|
||
ctx.out.data(info, human=lambda: (
|
||
ctx.out.line(f"账号 : {resolved}{' (当前)' if info['active'] else ''}"),
|
||
ctx.out.line(f"身份 : {record.get('email') or record.get('user_id') or '未知'}"),
|
||
ctx.out.line(f"Cookie : {info['cookie']}"),
|
||
ctx.out.line(f"会话有效期: {info['session_expires_at'] or '未知'}"),
|
||
))
|
||
return info
|
||
|
||
|
||
def register(subparsers):
|
||
parser = subparsers.add_parser("account", help="账号管理(粘贴 cookie,多账号切换)")
|
||
sub = parser.add_subparsers(dest="account_action", required=True)
|
||
|
||
add = sub.add_parser("add", help="添加账号(粘贴 cookie,无需浏览器)")
|
||
add.add_argument("--name", help="账号名(默认取邮箱/用户 id)")
|
||
add.add_argument("--cookie", help="cookie 值,或整条 Cookie 串;传 - 从 stdin 读取")
|
||
add.add_argument("--cookie-file", help="从文件读取 cookie")
|
||
add.set_defaults(func=_add)
|
||
|
||
sub.add_parser("list", help="列出所有账号").set_defaults(func=_list)
|
||
|
||
use = sub.add_parser("use", help="切换当前账号")
|
||
use.add_argument("name")
|
||
use.set_defaults(func=_use)
|
||
|
||
remove = sub.add_parser("remove", help="删除账号")
|
||
remove.add_argument("name")
|
||
remove.set_defaults(func=_remove)
|
||
|
||
show = sub.add_parser("show", help="查看账号详情")
|
||
show.add_argument("name", nargs="?")
|
||
show.set_defaults(func=_show)
|