from __future__ import annotations import json import tempfile from pathlib import Path from typing import Any # GUI 工具名 → CLI 子命令 TOOL_TO_COMMAND: dict[str, str] = { "check_login_status": "check-login", "browser_login": "login", "delete_cookies": "delete-cookies", "list_feeds": "list-feeds", "search_feeds": "search-feeds", "get_feed_detail": "get-feed-detail", "publish_content": "publish", "publish_with_video": "publish-video", "post_comment_to_feed": "post-comment", "reply_comment_in_feed": "reply-comment", "like_feed": "like-feed", "favorite_feed": "favorite-feed", "user_profile": "user-profile", "my_account_feeds": "my-account-feeds", } _FILTER_KEYS = ( ("sort_by", "--sort-by"), ("note_type", "--note-type"), ("publish_time", "--publish-time"), ("search_scope", "--search-scope"), ("location", "--location"), ) def build_cli_argv(tool_name: str, arguments: dict[str, Any] | None) -> list[str]: """将 GUI 工具参数转为 cli.py 参数列表(不含 python/cli 路径)。""" args = dict(arguments or {}) command = TOOL_TO_COMMAND.get(tool_name) if not command: raise ValueError(f"未知工具: {tool_name}") argv = [command] if tool_name == "search_feeds": keyword = str(args.pop("keyword", "")).strip() if not keyword: raise ValueError("search_feeds 需要 keyword") argv.extend(["--keyword", keyword]) filters = args.pop("filters", None) or {} if isinstance(filters, dict): for key, flag in _FILTER_KEYS: val = filters.get(key) or args.pop(key, None) if val: argv.extend([flag, str(val)]) for key, flag in _FILTER_KEYS: val = args.pop(key, None) if val: argv.extend([flag, str(val)]) elif tool_name in ("get_feed_detail",): argv.extend(["--feed-id", str(args.pop("feed_id", ""))]) argv.extend(["--xsec-token", str(args.pop("xsec_token", ""))]) if args.pop("load_all_comments", False): argv.append("--load-all-comments") limit = args.pop("limit", None) if limit is not None: argv.extend(["--max-comment-items", str(int(limit))]) elif tool_name in ("post_comment_to_feed",): argv.extend(["--feed-id", str(args.pop("feed_id", ""))]) argv.extend(["--xsec-token", str(args.pop("xsec_token", ""))]) argv.extend(["--content", str(args.pop("content", ""))]) elif tool_name in ("reply_comment_in_feed",): argv.extend(["--feed-id", str(args.pop("feed_id", ""))]) argv.extend(["--xsec-token", str(args.pop("xsec_token", ""))]) argv.extend(["--content", str(args.pop("content", ""))]) if args.get("comment_id"): argv.extend(["--comment-id", str(args.pop("comment_id"))]) if args.get("user_id"): argv.extend(["--user-id", str(args.pop("user_id"))]) elif tool_name in ("like_feed", "favorite_feed"): argv.extend(["--feed-id", str(args.pop("feed_id", ""))]) argv.extend(["--xsec-token", str(args.pop("xsec_token", ""))]) if tool_name == "like_feed" and args.pop("unlike", False): argv.append("--unlike") if tool_name == "favorite_feed" and args.pop("unfavorite", False): argv.append("--unfavorite") elif tool_name == "user_profile": argv.extend(["--user-id", str(args.pop("user_id", ""))]) argv.extend(["--xsec-token", str(args.pop("xsec_token", ""))]) elif tool_name == "publish_content": title = str(args.pop("title", "")).strip() content = str(args.pop("content", "")).strip() images = args.pop("images", None) or [] if not images: raise ValueError("publish_content 需要至少一张图片") title_path, content_path = _write_publish_text_files(title, content) argv.extend(["--title-file", str(title_path), "--content-file", str(content_path)]) argv.append("--images") argv.extend([str(p) for p in images]) tags = args.pop("tags", None) if tags: argv.append("--tags") argv.extend([str(t) for t in tags]) visibility = args.pop("visibility", None) if visibility: argv.extend(["--visibility", str(visibility)]) elif tool_name == "publish_with_video": title = str(args.pop("title", "")).strip() content = str(args.pop("content", "")).strip() video = str(args.pop("video", "")).strip() if not video: raise ValueError("publish_with_video 需要 video 路径") title_path, content_path = _write_publish_text_files(title, content) argv.extend( [ "--title-file", str(title_path), "--content-file", str(content_path), "--video", video, ] ) tags = args.pop("tags", None) if tags: argv.append("--tags") argv.extend([str(t) for t in tags]) visibility = args.pop("visibility", None) if visibility: argv.extend(["--visibility", str(visibility)]) scroll_speed = args.pop("scroll_speed", None) if scroll_speed and tool_name in ("list_feeds", "search_feeds", "my_account_feeds"): argv.extend(["--scroll-speed", str(scroll_speed)]) return argv def _write_publish_text_files(title: str, content: str) -> tuple[Path, Path]: tmp = Path(tempfile.mkdtemp(prefix="xhs_publish_")) title_path = tmp / "title.txt" content_path = tmp / "content.txt" title_path.write_text(title, encoding="utf-8") content_path.write_text(content, encoding="utf-8") return title_path, content_path def format_tool_result_text(tool_name: str, data: dict[str, Any]) -> str: """将 CLI JSON 转为 GUI 习惯的文本(含 ✅/❌)。""" if data.get("success") is False: return f"❌ {data.get('error') or data.get('message') or '操作失败'}" if tool_name == "check_login_status": if data.get("logged_in"): return "✅ 已登录" hint = data.get("hint") or data.get("message") or "未登录" return f"❌ {hint}" if tool_name == "browser_login": if data.get("logged_in"): return f"✅ {data.get('message') or '登录成功'}" return f"❌ {data.get('message') or '登录未完成或超时'}" if tool_name == "delete_cookies": return f"✅ {data.get('message') or '已退出登录'}" if tool_name in ("list_feeds", "search_feeds"): count = data.get("count", len(data.get("feeds") or [])) return f"✅ 获取到 {count} 条笔记" if tool_name == "my_account_feeds": count = data.get("count", len(data.get("feeds") or [])) nick = (data.get("basicInfo") or {}).get("nickname") or data.get("nickname") or "" prefix = f"{nick} · " if nick else "" return f"✅ {prefix}我的笔记 {count} 条" if tool_name == "get_feed_detail": note = data.get("note") or {} title = note.get("title") or "(无标题)" return f"✅ 笔记详情: {title}\n\n{json.dumps(data, ensure_ascii=False, indent=2)}" if tool_name in ("publish_content", "publish_with_video"): return f"✅ {data.get('status') or data.get('message') or '发布完成'}" if tool_name in ("post_comment_to_feed", "reply_comment_in_feed"): return f"✅ {data.get('message') or '评论成功'}" if tool_name in ("like_feed", "favorite_feed"): return f"✅ {data.get('message') or '操作成功'}" if tool_name == "user_profile": basic = (data.get("basicInfo") or {}) if isinstance(data.get("basicInfo"), dict) else {} nick = basic.get("nickname") or "用户" return f"✅ 用户主页: {nick}\n\n{json.dumps(data, ensure_ascii=False, indent=2)}" if data.get("success") is True or data.get("logged_in"): return f"✅ {data.get('message') or '成功'}" return json.dumps(data, ensure_ascii=False, indent=2)