Release v1.0.56: Credential & Browser Profile Manager
All checks were successful
技能自动化发布 / release (push) Successful in 7s

This commit is contained in:
2026-05-05 18:51:00 +08:00
parent 6b6d2969c3
commit 91057fa3b7
29 changed files with 4582 additions and 900 deletions

View File

@@ -1,4 +1,7 @@
"""数据目录、profile 路径、CLI 路径调试输出。"""
# -*- coding: utf-8 -*-
"""
Data directory, profile path, CLI path debugging output.
"""
import os
import re
import sys
@@ -6,21 +9,20 @@ from typing import Optional
from jiangchang_skill_core.runtime_env import get_data_root, get_user_id
from util.constants import SKILL_SLUG
from util.platforms import _PLATFORM_PRIMARY_CN
def get_skill_data_dir():
def get_skill_data_dir() -> str:
path = os.path.join(get_data_root(), get_user_id(), SKILL_SLUG)
os.makedirs(path, exist_ok=True)
return path
def get_db_path():
def get_db_path() -> str:
return os.path.join(get_skill_data_dir(), "account-manager.db")
def _runtime_paths_debug_text():
"""供排查「为何 list 没数据」:终端未注入环境变量时会落到 _anon 等默认目录,与网关里用的库不是同一个文件。"""
def _runtime_paths_debug_text() -> str:
"""For debugging: print current data path info to stderr."""
env_root = (os.getenv("JIANGCHANG_DATA_ROOT") or "").strip() or "(未设置)"
env_uid = (os.getenv("JIANGCHANG_USER_ID") or "").strip() or "(未设置→使用 _anon)"
return (
@@ -33,8 +35,8 @@ def _runtime_paths_debug_text():
)
def _maybe_print_paths_debug_cli():
"""设置 JIANGCHANG_ACCOUNT_DEBUG_PATHS=1 时,每次执行子命令前在 stderr 打印路径。"""
def _maybe_print_paths_debug_cli() -> None:
"""Set JIANGCHANG_ACCOUNT_DEBUG_PATHS=1 to print paths in stderr."""
v = (os.getenv("JIANGCHANG_ACCOUNT_DEBUG_PATHS") or "").strip().lower()
if v in ("1", "true", "yes", "on"):
print(_runtime_paths_debug_text(), file=sys.stderr)
@@ -49,7 +51,7 @@ _WIN_RESERVED_NAMES = frozenset(
def _fs_safe_segment(name: str, fallback: str) -> str:
"""单级目录名:去掉 Windows 非法字符,避免末尾点/空格;空则用 fallback"""
"""Single directory segment: strip Windows-unsafe chars; empty -> fallback."""
t = _WIN_FS_FORBIDDEN.sub("_", (name or "").strip())
t = t.rstrip(" .")
if not t:
@@ -59,22 +61,33 @@ def _fs_safe_segment(name: str, fallback: str) -> str:
return t
def get_default_profile_dir(
platform_key: str, phone: Optional[str], account_id: Optional[int] = None
def get_default_profile_dir_for_web(
platform_key: str,
label: str,
login_id: Optional[str] = None,
domain: str = "generic",
) -> str:
"""
默认可读路径profiles/<平台展示名>/<手机号>/(便于在资源管理器中辨认)。
无手机号时退化为 profiles/<平台展示名>/no_phone_<id>/。
Generate a browser profile directory path.
Structure:
{DATA_ROOT}/{USER_ID}/account-manager/profiles/{domain}/{platform_key}/{safe_label}[_{login_id_suffix]/
This keeps profiles organized by domain and platform for easy
identification in file explorer.
"""
label = _PLATFORM_PRIMARY_CN.get(platform_key, platform_key or "account")
label_seg = _fs_safe_segment(label, _fs_safe_segment(platform_key or "", "platform"))
ph = (phone or "").strip()
if ph:
phone_seg = _fs_safe_segment(ph, f"id_{account_id}" if account_id is not None else "phone")
else:
phone_seg = _fs_safe_segment(
"", f"no_phone_{account_id}" if account_id is not None else "no_phone"
)
path = os.path.join(get_skill_data_dir(), "profiles", label_seg, phone_seg)
label_seg = _fs_safe_segment(label, platform_key)
login_suffix = ""
if login_id:
safe_login = _fs_safe_segment(login_id, "")
if safe_login:
login_suffix = f"_{safe_login[:16]}"
path = os.path.join(
get_skill_data_dir(),
"profiles",
_fs_safe_segment(domain, "generic"),
_fs_safe_segment(platform_key, "unknown"),
f"{label_seg}{login_suffix}",
)
os.makedirs(path, exist_ok=True)
return path