"""数据目录、profile 路径、CLI 路径调试输出。""" import os import re import sys from typing import Optional from util.constants import SKILL_SLUG from util.platforms import _PLATFORM_PRIMARY_CN def get_data_root(): env = (os.getenv("JIANGCHANG_DATA_ROOT") or "").strip() if env: return env if sys.platform == "win32": return r"D:\jiangchang-data" return os.path.join(os.path.expanduser("~"), ".jiangchang-data") def get_user_id(): uid = (os.getenv("JIANGCHANG_USER_ID") or "").strip() return uid or "_anon" def get_skill_data_dir(): path = os.path.join(get_data_root(), get_user_id(), SKILL_SLUG) os.makedirs(path, exist_ok=True) return path def get_skill_logs_dir() -> str: """{DATA_ROOT}/{USER_ID}/account-manager/logs/""" path = os.path.join(get_skill_data_dir(), "logs") os.makedirs(path, exist_ok=True) return path def get_db_path(): return os.path.join(get_skill_data_dir(), "account-manager.db") def _runtime_paths_debug_text(): """供排查「为何 list 没数据」:终端未注入环境变量时会落到 _anon 等默认目录,与网关里用的库不是同一个文件。""" env_root = (os.getenv("JIANGCHANG_DATA_ROOT") or "").strip() or "(未设置)" env_uid = (os.getenv("JIANGCHANG_USER_ID") or "").strip() or "(未设置→使用 _anon)" return ( "[account-manager] " f"JIANGCHANG_DATA_ROOT={env_root} | " f"JIANGCHANG_USER_ID={env_uid} | " f"实际数据根={get_data_root()} | " f"实际用户目录={get_user_id()} | " f"数据库文件={get_db_path()}" ) def _maybe_print_paths_debug_cli(): """设置 JIANGCHANG_ACCOUNT_DEBUG_PATHS=1 时,每次执行子命令前在 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) _WIN_FS_FORBIDDEN = re.compile(r'[<>:"/\\|?*\x00-\x1f]') _WIN_RESERVED_NAMES = frozenset( ["CON", "PRN", "AUX", "NUL"] + [f"COM{i}" for i in range(1, 10)] + [f"LPT{i}" for i in range(1, 10)] ) def _fs_safe_segment(name: str, fallback: str) -> str: """单级目录名:去掉 Windows 非法字符,避免末尾点/空格;空则用 fallback。""" t = _WIN_FS_FORBIDDEN.sub("_", (name or "").strip()) t = t.rstrip(" .") if not t: t = fallback if t.upper() in _WIN_RESERVED_NAMES: t = f"_{t}" return t def get_default_profile_dir( platform_key: str, phone: Optional[str], account_id: Optional[int] = None ) -> str: """ 默认可读路径:profiles/<平台展示名>/<手机号>/(便于在资源管理器中辨认)。 无手机号时退化为 profiles/<平台展示名>/no_phone_/。 """ 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) os.makedirs(path, exist_ok=True) return path