Skill: yangyuwen-bri/gsdata-skill (version 0.1.1)

--- name: gsdata version: 1.0.0 author: yangyuwen-bri license: MIT description: | Use GSData open platform via local adapter script for account/content/rank/pubsent/nlp queries. Use when user asks for 舆情检索、热点事件、平台榜单、账号数据、小红书/微博/抖音等数据查询。 Triggers: "gsdata", "清博", "舆情", "热点", "榜单", "关键词检索". env: - GSDATA_APP_KEY - GSDATA_APP_SECRET --- # GSData Skill This skill allows you to query the GSData open platform for public sentiment (舆情), rankings (榜单), and account data. **Prerequisites:** This skill requires the `requests` library. If a module cannot be found, you may need to run `pip install requests` once. **Credentials:** Authentication is handled via the `GSDATA_APP_KEY` and `GSDATA_APP_SECRET` environment variables. Do not attempt to use hardcoded test keys. ## Adapter Usage Use the bundled python script to interact with the API: `python3 ./gsdata_adapter.py` *Note: The script and its mapping `gsdata_tool_mapping_v1.json` are bundled in this skill's directory. Always run the script via relative path (`./gsdata_adapter.py`) from this directory.* ## Rules 1. For a new request, run `--dry-run` first to verify route/action mapping. 2. Default to read-only actions. 3. **CRITICAL FOR CONVERSATIONS**: If you are answering a user in a chat interface (like Telegram), you MUST append `--param size=5` (or a similarly small number) to prevent dumping enormous JSON payloads into the chat context. 4. Never run write-like actions unless user explicitly confirms. 5. For write-like actions, require `--allow-write`. 6. Return a concise summary first; included route + key fields used. 7. **PAGINATION LIMIT**: The API strictly enforces a maximum of 20 items per page (e.g., passing `size=100` defaults to 20). To fetch large datasets, use a loop with `--param page=1`, `--param page=2`, etc. Do NOT attempt to fetch more than 20 items in a single call. 8. **TOTAL COUNT ESTIMATION**: Before scraping multiple pages for `gsdata_pubsent_search`, ALWAYS probe the total volume first by running a test search with `--param limit=1` and checking the `numFound` field in the response JSON. Calculate total pages as `ceil(numFound / 20)`. (Note: The dedicated `num_found` action has a bug with date parameters and should NOT be used for this purpose). ## Quick Commands **1. Discover endpoint capabilities:** ```bash python3 ./gsdata_adapter.py list-tools ``` **2. Keyword search (with size limit):** ```bash python3 ./gsdata_adapter.py invoke \ --tool gsdata_pubsent_search \ --action search \ --param keywords_include=人工智能 \ --param size=5 ``` **3. Probe data volume estimation (returns `numFound`):** ```bash python3 ./gsdata_adapter.py invoke \ --tool gsdata_pubsent_search \ --action search \ --param keywords_include=人工智能 \ --param date_start=2026-03-01 \ --param date_end=2026-03-04 \ --param media_type=weibo \ --param limit=1 ``` **4. Hot events:** ```bash python3 ./gsdata_adapter.py invoke \ --tool gsdata_pubsent_hot \ --action event_list \ --params '{"type":"1"}' ``` **5. Xiaohongshu account search:** ```bash python3 ./gsdata_adapter.py invoke \ --tool gsdata_account \ --action search \ --platform xiaohongshu \ --param xiaohongshu_name=AI \ --param size=2 ``` ## Write Actions (Explicit Confirmation Required) Examples: - `gsdata_pubsent_warning create_rule/update_rule/open_rule/close_rule` - `gsdata_myrank_group group_add/group_del` - `gsdata_myrank_account acct_add/acct_del/acct_add_by_url` When approved, append `--allow-write`: ```bash python3 ./gsdata_adapter.py invoke \ --tool gsdata_pubsent_warning \ --action create_rule \ --params '{"name":"demo"}' \ --allow-write ```

path: gsdata_adapter.py
content: #!/usr/bin/env python3
"""
GSData adapter skeleton:
- Wrap 292 endpoints into 11 high-level tools
- Route (tool, action, platform) -> concrete router path
- Sign/auth requests with GSData open platform protocol
"""

from __future__ import annotations

import argparse
import base64
import hashlib
import json
import os
from dataclasses import dataclass
from datetime import datetime, timedelta
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple

import requests


DEFAULT_BASE_URL = "http://databus.gsdata.cn:8888/api/service"
DEFAULT_MAPPING_PATH = Path(__file__).with_name("gsdata_tool_mapping_v1.json")
DEFAULT_CREDS_PATH = Path.home() / ".config" / "gsdata" / "credentials.json"


ACCOUNT_PLATFORMS = {
    "weixin",
    "weibo",
    "toutiao",
    "douyin",
    "kuaishou",
    "bili",
    "wxvideo",
    "xigua",
    "xiaohongshu",
}


CONTENT_SEARCH_SORTS = {
    "weixin": {"time": "search1", "read": "search2", "watching": "search3"},
    "weibo": {
        "time": "search1",
        "comment": "search2",
        "repost": "search3",
        "like": "search4",
    },
    "toutiao": {"time": "search1", "comment": "search2", "read": "search3"},
    "xiaohongshu": {"time": "search1", "like": "search2", "comment": "search3"},
    "douyin": {"time": "search1", "like": "search2", "comment": "search3"},
    "kuaishou": {"time": "search1", "like": "search2", "comment": "search3"},
    "bili": {"time": "search1", "like": "search2", "comment": "search3"},
    "wxvideo": {"time": "search1", "like": "search2", "comment": "search3"},
    "xigua": {"time": "search1", "like": "search2", "comment": "search3"},
}


ACCOUNT_LATEST_SUFFIX = {
    "weixin": "news-latest",
    "weibo": "news-latest",
    "toutiao": "news-latest",
    "douyin": "video-latest",
    "kuaishou": "video-latest",
    "bili": "video-latest",
    "wxvideo": "video-latest",
    "xigua": "video-latest",
    "xiaohongshu": "article-latest",
}


ACCOUNT_INFLUENCE_PREFIX = {
    "weixin": "wci",
    "weibo": "bci",
    "toutiao": "tgi",
    "douyin": "dci",
    "kuaishou": "kci",
    "bili": "bvci",
    "wxvideo": "wvci",
    "xigua": "xci",
    "xiaohongshu": "hci",
}


PUBSENT_SEARCH_ACTIONS = {
    "search": "index",
    "num_found": "num-found",
    "emotion_dist": "emotion-dist",
    "yuqing_trend": "yuqing-trend",
    "hot_word": "hot-word",
    "media_dist": "media-dist",
    "hot_refer_area": "hot-refer-area",
    "hot_pub_area": "hot-pub-area",
    "news_trend": "news-trend",
}


PUBSENT_HOT_ACTIONS = {
    "event_list": "index",
    "event_keywords": "hot-keywords",
    "pub_area": "hot-pub-area",
    "refer_area": "hot-refer-area",
    "media_active": "media-active",
    "emotion_dist": "emotion-dist",
    "media_dist": "media-dist",
    "news_list": "news-list",
    "entity_person": "news-entity-person",
    "entity_org": "news-entity-organization",
}


PUBSENT_WARNING_ACTIONS = {
    "list_rules": "index",
    "create_rule": "create",
    "update_rule": "update",
    "open_rule": "open",
    "close_rule": "close",
    "news_push": "news",
    "stats": "stats",
    "add_rec_email": "add-rec-email",
    "del_rec_email": "del-rec-email",
}


NLP_ACTION_TO_PATH = {
    "emotion_long": "/nlp/emotion/emotion-long",
    "emotion_short": "/nlp/emotion/emotion-short",
    "oversea_emotion_long": "/nlp/emotion/oversea-emotion-long",
    "oversea_emotion_short": "/nlp/emotion/oversea-emotion-short",
    "emotion_weibo": "/nlp/emotion/emotion-weibo",
    "sentiment_long": "/nlp/sentiment/sentiment-long",
    "sentiment_short": "/nlp/sentiment/sentiment-short",
    "oversea_sentiment_long": "/nlp/sentiment/oversea-sentiment-long",
    "oversea_sentiment_short": "/nlp/sentiment/oversea-sentiment-short",
    "category_long": "/nlp/category/category-long",
    "category_short": "/nlp/category/category-short",
    "oversea_category_long": "/nlp/category/oversea-category-long",
    "oversea_category_short": "/nlp/category/oversea-category-short",
    "demoner": "/nlp/demoner/index",
    "summary": "/nlp/summary/index",
    "generate_title": "/nlp/summary/generate-title",
    "association": "/nlp/demo-association/index",
    "graph_vertex": "/nlp/graph/get-graph-vertex",
    "graph_edge": "/nlp/graph/get-graph-edge",
    "graph_disambiguation": "/nlp/graph/get-graph-disambiguation",
    "car_tag": "/nlp/car/tag",
    "car_auto_sentiment": "/nlp/car/auto-sentiment",
    "seg_word": "/nlp/other/seg-word",
    "keywords": "/nlp/other/demo-keywords",
    "pos": "/nlp/other/demo-pos",
    "ocr_image": "/nlp/other/ocr-image",
    "region": "/nlp/region/common-region",
    "corrector": "/nlp/corrector/index",
    "point_extract": "/nlp/point/extract",
}


WRITE_SUFFIX_HINTS = (
    "create",
    "update",
    "add",
    "del",
    "open",
    "close",
    "group-add",
    "group-del",
    "acct-add",
    "acct-del",
)


@dataclass
class Endpoint:
    tool: str
    cate_id: int
    cate_name: str
    method: str
    path: str
    desc: str


def load_mapping(mapping_path: Path) -> Tuple[List[Endpoint], Dict[str, str]]:
    payload = json.loads(mapping_path.read_text(encoding="utf-8"))
    endpoints: List[Endpoint] = []
    method_by_path: Dict[str, str] = {}
    for tool_item in payload.get("tools", []):
        tool = tool_item["tool"]
        for ep in tool_item.get("endpoints", []):
            endpoint = Endpoint(
                tool=tool,
                cate_id=int(ep["cateId"]),
                cate_name=ep["cateName"],
                method=ep["method"].upper(),
                path=ep["path"],
                desc=ep["desc"],
            )
            endpoints.append(endpoint)
            method_by_path[endpoint.path] = endpoint.method
    return endpoints, method_by_path


def make_sign(params: Dict[str, Any], app_secret: str) -> str:
    # GSData signature: md5(app_secret + "_" + sorted(kv concat) + "_" + app_secret)
    sorted_items = sorted(params.items(), key=lambda x: x[0])
    concat = "".join(f"{k}{v}" for k, v in sorted_items)
    raw = f"{app_secret}_{concat}_{app_secret}"
    return hashlib.md5(raw.encode("utf-8")).hexdigest()


def make_access_token(app_key: str, sign: str, router: str) -> str:
    return base64.b64encode(f"{app_key}:{sign}:{router}".encode("utf-8")).decode(
        "utf-8"
    )


class GsDataAdapter:
    def __init__(
        self,
        app_key: str,
        app_secret: str,
        base_url: str = DEFAULT_BASE_URL,
        mapping_path: Path = DEFAULT_MAPPING_PATH,
    ) -> None:
        self.app_key = app_key
        self.app_secret = app_secret
        self.base_url = base_url
        self.mapping_path = mapping_path
        self.endpoints, self.method_by_path = load_mapping(mapping_path)

    def list_tools(self) -> List[str]:
        tools = {ep.tool for ep in self.endpoints}
        tools.add("gsdata_raw")
        return sorted(tools)

    def list_endpoints(self, tool: Optional[str] = None) -> List[Endpoint]:
        if not tool:
            return self.endpoints
        return [ep for ep in self.endpoints if ep.tool == tool]

    def resolve_route(
        self,
        tool: str,
        action: str,
        platform: Optional[str] = None,
        sort: Optional[str] = None,
        path: Optional[str] = None,
    ) -> str:
        if tool == "gsdata_raw":
            if not path:
                raise ValueError("gsdata_raw requires explicit path")
            return path

        if tool == "gsdata_link_resolve":
            if action != "resolve_wechat_url":
                raise ValueError("gsdata_link_resolve only supports resolve_wechat_url")
            return "/spread/gsdata/wx-detail"

        if tool == "gsdata_account":
            self._require_platform(platform)
            if action == "search":
                return f"/account/{platform}/search"
            if action == "attribute":
                return f"/account/{platform}/attribute"
            if action == "latest_content":
                return f"/account/{platform}/{ACCOUNT_LATEST_SUFFIX[platform]}"
            if action == "influence_latest":
                return (
                    f"/account/{platform}/{ACCOUNT_INFLUENCE_PREFIX[platform]}-latest"
                )
            if action == "influence_weekly":
                return (
                    f"/account/{platform}/{ACCOUNT_INFLUENCE_PREFIX[platform]}-weekly"
                )
            if action == "index_latest":
                return f"/account/{platform}/index-latest"
            if action == "index_weekly":
                return f"/account/{platform}/index-weekly"
            if action == "get_ygfs":
                if platform != "weixin":
                    raise ValueError("get_ygfs only supports platform=weixin")
                return "/account/weixin/get-ygfs"
            raise ValueError(f"Unsupported action for gsdata_account: {action}")

        if tool == "gsdata_content":
            self._require_platform(platform)
            if action == "search":
                if not sort:
                    sort = "time"
                return self._resolve_content_search_route(platform, sort)
            if action == "get_content":
                if platform != "weixin":
                    raise ValueError("get_content only supports platform=weixin")
                return "/weixin/article/content"
            if action == "get_clean_content":
                if platform != "weixin":
                    raise ValueError("get_clean_content only supports platform=weixin")
                return "/weixin/article/clean-content"
            raise ValueError(f"Unsupported action for gsdata_content: {action}")

        if tool == "gsdata_rank_public":
            self._require_platform(platform)
            suffix = self._enum(action, {"day", "week", "month", "latest"}, "action")
            return f"/rank/{platform}/{suffix}"

        if tool == "gsdata_myrank_group":
            self._require_platform(platform)
            suffix_map = {
                "group_add": "group-add",
                "group_list": "group-list",
                "group_del": "group-del",
            }
            suffix = self._map_action(action, suffix_map, tool)
            return f"/myrank/{platform}/{suffix}"

        if tool == "gsdata_myrank_account":
            self._require_platform(platform)
            if action == "acct_add":
                return f"/myrank/{platform}/acct-add"
            if action == "acct_del":
                return f"/myrank/{platform}/acct-del"
            if action == "acct_list":
                return f"/myrank/{platform}/acct-list"
            if action == "acct_add_by_url":
                # Some platforms use acct-url-add, while weixin uses acct-add-by-url.
                candidates = [
                    f"/myrank/{platform}/acct-url-add",
                    f"/myrank/{platform}/acct-add-by-url",
                ]
                for cand in candidates:
                    if cand in self.method_by_path:
                        return cand
                raise ValueError(f"Platform={platform} does not support acct_add_by_url")
            if action == "search_account":
                candidate = f"/myrank/{platform}/search-account"
                if candidate not in self.method_by_path:
                    raise ValueError(
                        f"Platform={platform} does not support search_account"
                    )
                return candidate
            raise ValueError(f"Unsupported action for gsdata_myrank_account: {action}")

        if tool == "gsdata_myrank_query":
            self._require_platform(platform)
            suffix_map = {
                "day": "day",
                "week": "week",
                "month": "month",
                "latest": "latest",
                "group_article": "group-article",
            }
            suffix = self._map_action(action, suffix_map, tool)
            return f"/myrank/{platform}/{suffix}"

        if tool == "gsdata_pubsent_search":
            suffix = self._map_action(action, PUBSENT_SEARCH_ACTIONS, tool)
            return f"/pubsent/full-search/{suffix}"

        if tool == "gsdata_pubsent_hot":
            suffix = self._map_action(action, PUBSENT_HOT_ACTIONS, tool)
            return f"/pubsent/hot-news/{suffix}"

        if tool == "gsdata_pubsent_warning":
            suffix = self._map_action(action, PUBSENT_WARNING_ACTIONS, tool)
            return f"/pubsent/warning/{suffix}"

        if tool == "gsdata_nlp":
            route = NLP_ACTION_TO_PATH.get(action)
            if not route:
                raise ValueError(f"Unsupported action for gsdata_nlp: {action}")
            return route

        raise ValueError(f"Unsupported tool: {tool}")

    def invoke(
        self,
        tool: str,
        action: str,
        params: Optional[Dict[str, Any]] = None,
        platform: Optional[str] = None,
        sort: Optional[str] = None,
        dry_run: bool = False,
        allow_write: bool = False,
        explicit_path: Optional[str] = None,
        explicit_method: Optional[str] = None,
    ) -> Dict[str, Any]:
        params = params or {}
        route = self.resolve_route(
            tool=tool, action=action, platform=platform, sort=sort, path=explicit_path
        )
        params = self._normalize_params(tool=tool, action=action, route=route, params=params)
        method = (
            (explicit_method or "").upper().strip() or self.method_by_path.get(route, "GET")
        )

        if self._is_write_route(route) and not allow_write:
            return {
                "ok": False,
                "error": "WRITE_BLOCKED",
                "message": (
                    "This route is considered write/high-risk. "
                    "Pass allow_write=true after explicit confirmation."
                ),
                "route": route,
                "method": method,
            }

        sign = make_sign(params, self.app_secret)
        [REDACTED_SECRET], sign, route)
        headers = {"access-token": token}

        if dry_run:
            return {
                "ok": True,
                "dryRun": True,
                "tool": tool,
                "action": action,
                "platform": platform,
                "route": route,
                "method": method,
                "params": params,
            }

        try:
            response = self._request(method=method, params=params, headers=headers)
            body: Any
            try:
                body = response.json()
            except Exception:
                body = {"raw": response.text}
            return {
                "ok": response.ok,
                "statusCode": response.status_code,
                "tool": tool,
                "action": action,
                "platform": platform,
                "route": route,
                "method": method,
                "response": body,
            }
        except Exception as exc:
            return {
                "ok": False,
                "tool": tool,
                "action": action,
                "platform": platform,
                "route": route,
                "method": method,
                "error": str(exc),
            }

    def _request(
        self, method: str, params: Dict[str, Any], headers: Dict[str, str]
    ) -> requests.Response:
        # GSData gateway commonly accepts params in query/body against one base URL.
        if method == "POST":
            return requests.post(
                self.base_url, data=params, headers=headers, timeout=30
            )
        return requests.get(self.base_url, params=params, headers=headers, timeout=30)

    @staticmethod
    def _is_write_route(route: str) -> bool:
        if "/warning/" in route:
            # warning module has both read and write; keep reads open
            if route.endswith(("/index", "/news", "/stats")):
                return False
            return True
        return any(s in route for s in WRITE_SUFFIX_HINTS)

    @staticmethod
    def _map_action(action: str, action_map: Dict[str, str], tool: str) -> str:
        if action not in action_map:
            supported = ", ".join(sorted(action_map.keys()))
            raise ValueError(f"{tool} unsupported action={action}. supported: {supported}")
        return action_map[action]

    @staticmethod
    def _enum(value: str, enums: set, name: str) -> str:
        if value not in enums:
            supported = ", ".join(sorted(enums))
            raise ValueError(f"Unsupported {name}={value}. supported: {supported}")
        return value

    @staticmethod
    def _require_platform(platform: Optional[str]) -> str:
        if not platform:
            raise ValueError("platform is required")
        if platform not in ACCOUNT_PLATFORMS:
            supported = ", ".join(sorted(ACCOUNT_PLATFORMS))
            raise ValueError(f"Unsupported platform={platform}. supported: {supported}")
        return platform

    @staticmethod
    def _resolve_content_search_route(platform: str, sort: str) -> str:
        mapping = CONTENT_SEARCH_SORTS.get(platform)
        if not mapping:
            raise ValueError(f"Unsupported content search platform={platform}")
        if sort not in mapping:
            supported = ", ".join(sorted(mapping.keys()))
            raise ValueError(
                f"Unsupported sort={sort} for platform={platform}. supported: {supported}"
            )
        suffix = mapping[sort]

        if platform in {"weixin", "weibo", "toutiao"}:
            return f"/{platform}/article/{suffix}"
        if platform == "xiaohongshu":
            return f"/article/xiaohongshu/{suffix}"
        return f"/shortvideo/{platform}/{suffix}"

    @staticmethod
    def _normalize_params(
        tool: str, action: str, route: str, params: Dict[str, Any]
    ) -> Dict[str, Any]:
        out = dict(params)

        # common pagination aliases
        for src in ("size", "limit", "page_size", "pagesize"):
            if src in out and "pageSize" not in out:
                out["pageSize"] = out.pop(src)

        if tool != "gsdata_pubsent_search":
            return out

        # common date aliases -> GSData expected keys
        start_aliases = ("date_start", "start_date", "from_date", "from", "posttimeStart")
        end_aliases = ("date_end", "end_date", "to_date", "to", "posttimeEnd")
        for key in start_aliases:
            if key in out and "posttime_start" not in out:
                out["posttime_start"] = out.pop(key)
                break
        for key in end_aliases:
            if key in out and "posttime_end" not in out:
                out["posttime_end"] = out.pop(key)
                break

        # keyword aliases differ by endpoint.
        # yuqing_trend expects keywords; most others expect keywords_include.
        keyword_candidates = ("keywords", "keyword", "kw")
        if action == "yuqing_trend":
            if "keywords" not in out:
                if "keywords_include" in out:
                    out["keywords"] = out["keywords_include"]
                else:
                    for key in keyword_candidates:
                        if key in out:
                            out["keywords"] = out[key]
                            break
        else:
            if "keywords_include" not in out:
                for key in ("keywords_include",) + keyword_candidates:
                    if key in out:
                        out["keywords_include"] = out[key]
                        break

        # normalize date format to YYYY-MM-DD for pubsent.
        for key in ("posttime_start", "posttime_end"):
            if key not in out:
                continue
            val = str(out[key]).strip()
            if len(val) == 8 and val.isdigit():
                val = f"{val[0:4]}-{val[4:6]}-{val[6:8]}"
            if len(val) >= 10 and val[4] == "-" and val[7] == "-":
                val = val[:10]
            out[key] = val

        # make date-granularity end-date inclusive: [start, end+1day)
        if "posttime_end" in out:
            try:
                end_dt = datetime.strptime(str(out["posttime_end"]), "%Y-%m-%d")
                out["posttime_end"] = (end_dt + timedelta(days=1)).strftime("%Y-%m-%d")
            except Exception:
                pass

        return out


def _load_params(params_str: Optional[str]) -> Dict[str, Any]:
    if not params_str:
        return {}
    params_str = (
        params_str.replace("“", '"')
        .replace("”", '"')
        .replace("‘", "'")
        .replace("’", "'")
    )
    try:
        payload = json.loads(params_str)
    except json.JSONDecodeError as exc:
        raise ValueError(f"Invalid JSON for --params: {exc}") from exc
    if not isinstance(payload, dict):
        raise ValueError("--params must be a JSON object")
    return payload


def _load_params_file(path_str: Optional[str]) -> Dict[str, Any]:
    if not path_str:
        return {}
    path = Path(path_str).expanduser()
    payload = json.loads(path.read_text(encoding="utf-8"))
    if not isinstance(payload, dict):
        raise ValueError("--params-file must contain a JSON object")
    return payload


def _load_kv_params(kv_items: Optional[List[str]]) -> Dict[str, Any]:
    out: Dict[str, Any] = {}
    if not kv_items:
        return out
    for raw in kv_items:
        if "=" not in raw:
            raise ValueError(f"Invalid --param '{raw}', expected key=value")
        key, value = raw.split("=", 1)
        key = key.strip()
        if not key:
            raise ValueError(f"Invalid --param '{raw}', empty key")
        out[key] = value
    return out


def _merge_params(
    params_json: Optional[str],
    params_file: Optional[str],
    params_kv: Optional[List[str]],
) -> Dict[str, Any]:
    merged: Dict[str, Any] = {}
    merged.update(_load_params_file(params_file))
    merged.update(_load_params(params_json))
    merged.update(_load_kv_params(params_kv))
    return merged


def _build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(description="GSData high-level adapter skeleton")
    parser.add_argument(
        "--mapping",
        default=str(DEFAULT_MAPPING_PATH),
        help=f"Mapping JSON path (default: {DEFAULT_MAPPING_PATH})",
    )
    parser.add_argument(
        "--base-url",
        default=os.getenv("GSDATA_BASE_URL", DEFAULT_BASE_URL),
        help="GSData gateway base URL",
    )
    parser.add_argument("--app-key", default=None)
    parser.add_argument("--app-secret", default=None)
    parser.add_argument(
        "--creds-file",
        default=str(DEFAULT_CREDS_PATH),
        help=f"Credentials file path (default: {DEFAULT_CREDS_PATH})",
    )

    sub = parser.add_subparsers(dest="cmd", required=True)

    sub.add_parser("list-tools", help="List high-level tools")

    p_ls = sub.add_parser("list-endpoints", help="List endpoints")
    p_ls.add_argument("--tool", help="Filter by tool")

    p_invoke = sub.add_parser("invoke", help="Invoke a high-level tool")
    p_invoke.add_argument("--tool", required=True)
    p_invoke.add_argument("--action", required=True)
    p_invoke.add_argument("--platform")
    p_invoke.add_argument("--sort")
    p_invoke.add_argument("--params", help='JSON object, e.g. \'{"keyword":"AI"}\'')
    p_invoke.add_argument("--params-file", help="Path to JSON object file")
    p_invoke.add_argument(
        "--param",
        action="append",
        default=[],
        help="Repeatable key=value param. Example: --param keywords_include=AI",
    )
    p_invoke.add_argument("--dry-run", action="store_true")
    p_invoke.add_argument("--allow-write", action="store_true")

    p_raw = sub.add_parser("raw", help="Invoke arbitrary route via gsdata_raw")
    p_raw.add_argument("--path", required=True)
    p_raw.add_argument("--method", default="GET")
    p_raw.add_argument("--params", help='JSON object, e.g. \'{"id":"123"}\'')
    p_raw.add_argument("--params-file", help="Path to JSON object file")
    p_raw.add_argument(
        "--param",
        action="append",
        default=[],
        help="Repeatable key=value param. Example: --param id=123",
    )
    p_raw.add_argument("--dry-run", action="store_true")
    p_raw.add_argument("--allow-write", action="store_true")

    return parser


def _require_creds(args: argparse.Namespace) -> None:
    if not args.app_key or not args.app_secret:
        raise SystemExit(
            "Missing credentials. Pass --app-key/--app-secret, "
            "or set GSDATA_APP_KEY/GSDATA_APP_SECRET, "
            "or provide credentials file (~/.config/gsdata/credentials.json)."
        )


def _load_creds_file(path_str: str) -> Dict[str, str]:
    path = Path(path_str).expanduser()
    if not path.exists():
        return {}
    try:
        payload = json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return {}
    if not isinstance(payload, dict):
        return {}
    app_key = payload.get("app_key") or payload.get("GSDATA_APP_KEY") or ""
    app_secret = payload.get("app_secret") or payload.get("GSDATA_APP_SECRET") or ""
    return {
        "app_key": str(app_key).strip(),
        "app_secret": str(app_secret).strip(),
    }


def _resolve_creds(args: argparse.Namespace) -> Tuple[str, str]:
    # priority: CLI > ENV > creds file
    cli_key = (args.app_key or "").strip()
    cli_secret = (args.app_secret or "").strip()
    if cli_key and cli_secret:
        return cli_key, cli_secret

    env_key = (os.getenv("GSDATA_APP_KEY") or "").strip()
    env_secret = (os.getenv("GSDATA_APP_SECRET") or "").strip()
    if env_key and env_secret:
        return env_key, env_secret

    file_creds = _load_creds_file(args.creds_file)
    file_key = file_creds.get("app_key", "")
    file_secret = file_creds.get("app_secret", "")
    if file_key and file_secret:
        return file_key, file_secret
    return "", ""


def main() -> None:
    parser = _build_parser()
    args = parser.parse_args()

    resolved_key, resolved_secret = _resolve_creds(args)
    args.app_key = resolved_key
    args.app_secret = resolved_secret

    if args.cmd in {"invoke", "raw"} and not args.dry_run:
        _require_creds(args)

    # For dry-run and listing, allow empty creds.
    adapter = GsDataAdapter(
        app_key=args.app_key or "DUMMY_APP_KEY",
        app_secret=args.app_secret or "DUMMY_APP_SECRET",
        base_url=args.base_url,
        mapping_path=Path(args.mapping),
    )

    if args.cmd == "list-tools":
        print(json.dumps(adapter.list_tools(), ensure_ascii=False, indent=2))
        return

    if args.cmd == "list-endpoints":
        items = adapter.list_endpoints(tool=args.tool)
        out = [
            {
                "tool": i.tool,
                "cateId": i.cate_id,
                "cateName": i.cate_name,
                "method": i.method,
                "path": i.path,
                "desc": i.desc,
            }
            for i in items
        ]
        print(json.dumps(out, ensure_ascii=False, indent=2))
        return

    if args.cmd == "invoke":
        result = adapter.invoke(
            tool=args.tool,
            action=args.action,
            params=_merge_params(args.params, args.params_file, args.param),
            platform=args.platform,
            sort=args.sort,
            dry_run=args.dry_run,
            allow_write=args.allow_write,
        )
        print(json.dumps(result, ensure_ascii=False, indent=2))
        return

    if args.cmd == "raw":
        result = adapter.invoke(
            tool="gsdata_raw",
            action="call_any_endpoint",
            params=_merge_params(args.params, args.params_file, args.param),
            dry_run=args.dry_run,
            allow_write=args.allow_write,
            explicit_path=args.path,
            explicit_method=args.method,
        )
        print(json.dumps(result, ensure_ascii=False, indent=2))
        return

    raise SystemExit(f"Unknown cmd: {args.cmd}")


if __name__ == "__main__":
    main()


path: gsdata_tool_mapping_v1.json
content: {
  "version": "v1",
  "generatedAt": "2026-02-24T17:01:29.891907",
  "source": "GSData open platform pages (nav=apis cate=1..64 partial categories with endpoints)",
  "totalEndpoints": 292,
  "uncoveredCount": 0,
  "tools": [
    {
      "tool": "gsdata_link_resolve",
      "endpointCount": 1,
      "methods": [
        "POST"
      ],
      "cateIds": [
        1
      ],
      "endpoints": [
        {
          "cateId": 1,
          "cateName": "创建舆情方案",
          "method": "POST",
          "path": "/spread/gsdata/wx-detail",
          "desc": "根据微信链接获取详情"
        }
      ]
    },
    {
      "tool": "gsdata_account",
      "endpointCount": 64,
      "methods": [
        "GET",
        "POST"
      ],
      "cateIds": [
        6,
        7,
        8,
        27,
        28,
        48,
        53,
        57,
        61
      ],
      "endpoints": [
        {
          "cateId": 6,
          "cateName": "微信公号",
          "method": "GET",
          "path": "/account/weixin/search",
          "desc": "微信公号检索"
        },
        {
          "cateId": 6,
          "cateName": "微信公号",
          "method": "GET",
          "path": "/account/weixin/attribute",
          "desc": "微信公号详情"
        },
        {
          "cateId": 6,
          "cateName": "微信公号",
          "method": "GET",
          "path": "/account/weixin/news-latest",
          "desc": "微信公号最新一篇发文"
        },
        {
          "cateId": 6,
          "cateName": "微信公号",
          "method": "GET",
          "path": "/account/weixin/wci-latest",
          "desc": "微信公号最新传播指数——wci"
        },
        {
          "cateId": 6,
          "cateName": "微信公号",
          "method": "GET",
          "path": "/account/weixin/wci-weekly",
          "desc": "微信公号最近7天的传播指数——wci"
        },
        {
          "cateId": 6,
          "cateName": "微信公号",
          "method": "GET",
          "path": "/account/weixin/index-latest",
          "desc": "微信公号动态指标——阅读数、在看数、点赞数等"
        },
        {
          "cateId": 6,
          "cateName": "微信公号",
          "method": "GET",
          "path": "/account/weixin/index-weekly",
          "desc": "微信公号最近7天的动态指标——阅读数、在看数、点赞数等"
        },
        {
          "cateId": 6,
          "cateName": "微信公号",
          "method": "GET",
          "path": "/account/weixin/get-ygfs",
          "desc": "根据wx_name获取账号预估粉丝数"
        },
        {
          "cateId": 7,
          "cateName": "微博账号",
          "method": "GET",
          "path": "/account/weibo/search",
          "desc": "微博账号检索"
        },
        {
          "cateId": 7,
          "cateName": "微博账号",
          "method": "GET",
          "path": "/account/weibo/attribute",
          "desc": "微博账号详情"
        },
        {
          "cateId": 7,
          "cateName": "微博账号",
          "method": "GET",
          "path": "/account/weibo/news-latest",
          "desc": "微博账号最新一篇发文"
        },
        {
          "cateId": 7,
          "cateName": "微博账号",
          "method": "GET",
          "path": "/account/weibo/bci-latest",
          "desc": "微博账号最新传播指数——bci"
        },
        {
          "cateId": 7,
          "cateName": "微博账号",
          "method": "GET",
          "path": "/account/weibo/bci-weekly",
          "desc": "微博账号最近7天的传播指数——bci"
        },
        {
          "cateId": 7,
          "cateName": "微博账号",
          "method": "GET",
          "path": "/account/weibo/index-latest",
          "desc": "微博账号最新动态指标——阅读数、点赞数等"
        },
        {
          "cateId": 7,
          "cateName": "微博账号",
          "method": "GET",
          "path": "/account/weibo/index-weekly",
          "desc": "微博账号最近7天的动态指标——阅读数、点赞数等"
        },
        {
          "cateId": 8,
          "cateName": "头条账号",
          "method": "GET",
          "path": "/account/toutiao/search",
          "desc": "头条账号检索"
        },
        {
          "cateId": 8,
          "cateName": "头条账号",
          "method": "GET",
          "path": "/account/toutiao/attribute",
          "desc": "头条账号详情"
        },
        {
          "cateId": 8,
          "cateName": "头条账号",
          "method": "GET",
          "path": "/account/toutiao/news-latest",
          "desc": "头条账号最新一篇发文"
        },
        {
          "cateId": 8,
          "cateName": "头条账号",
          "method": "GET",
          "path": "/account/toutiao/tgi-latest",
          "desc": "头条账号最新传播指数——tgi"
        },
        {
          "cateId": 8,
          "cateName": "头条账号",
          "method": "GET",
          "path": "/account/toutiao/tgi-weekly",
          "desc": "头条账号最近7天的传播指数——tgi"
        },
        {
          "cateId": 8,
          "cateName": "头条账号",
          "method": "GET",
          "path": "/account/toutiao/index-latest",
          "desc": "头条账号最新动态指标——阅读数、评论数等"
        },
        {
          "cateId": 8,
          "cateName": "头条账号",
          "method": "GET",
          "path": "/account/toutiao/index-weekly",
          "desc": "头条账号最近7天的动态指标——阅读数、评论数等"
        },
        {
          "cateId": 27,
          "cateName": "抖音账号",
          "method": "GET",
          "path": "/account/douyin/search",
          "desc": "抖音账号检索"
        },
        {
          "cateId": 27,
          "cateName": "抖音账号",
          "method": "GET",
          "path": "/account/douyin/attribute",
          "desc": "抖音账号详情"
        },
        {
          "cateId": 27,
          "cateName": "抖音账号",
          "method": "GET",
          "path": "/account/douyin/video-latest",
          "desc": "抖音账号最新一条视频"
        },
        {
          "cateId": 27,
          "cateName": "抖音账号",
          "method": "GET",
          "path": "/account/douyin/dci-latest",
          "desc": "抖音账号最新传播指数——dci"
        },
        {
          "cateId": 27,
          "cateName": "抖音账号",
          "method": "GET",
          "path": "/account/douyin/dci-weekly",
          "desc": "抖音账号最近七天的传播指数"
        },
        {
          "cateId": 27,
          "cateName": "抖音账号",
          "method": "GET",
          "path": "/account/douyin/index-latest",
          "desc": "抖音账号最新动态指标——阅读数、评论数等"
        },
        {
          "cateId": 27,
          "cateName": "抖音账号",
          "method": "GET",
          "path": "/account/douyin/index-weekly",
          "desc": "抖音账号最近7天的动态指标——阅读数、评论数等"
        },
        {
          "cateId": 28,
          "cateName": "快手账号",
          "method": "GET",
          "path": "/account/kuaishou/search",
          "desc": "快手账号检索"
        },
        {
          "cateId": 28,
          "cateName": "快手账号",
          "method": "GET",
          "path": "/account/kuaishou/attribute",
          "desc": "快手账号详情"
        },
        {
          "cateId": 28,
          "cateName": "快手账号",
          "method": "GET",
          "path": "/account/kuaishou/video-latest",
          "desc": "快手账号最新一条视频"
        },
        {
          "cateId": 28,
          "cateName": "快手账号",
          "method": "GET",
          "path": "/account/kuaishou/kci-latest",
          "desc": "快手账号最新传播指数——kci"
        },
        {
          "cateId": 28,
          "cateName": "快手账号",
          "method": "GET",
          "path": "/account/kuaishou/kci-weekly",
          "desc": "快手账号最近七天的传播指数"
        },
        {
          "cateId": 28,
          "cateName": "快手账号",
          "method": "GET",
          "path": "/account/kuaishou/index-latest",
          "desc": "快手账号最新动态指标——阅读数、评论数等"
        },
        {
          "cateId": 28,
          "cateName": "快手账号",
          "method": "GET",
          "path": "/account/kuaishou/index-weekly",
          "desc": "获取快手账号最近一周最新动态指标"
        },
        {
          "cateId": 48,
          "cateName": "哔哩哔哩账号",
          "method": "GET",
          "path": "/account/bili/search",
          "desc": "哔哩哔哩账号检索"
        },
        {
          "cateId": 48,
          "cateName": "哔哩哔哩账号",
          "method": "GET",
          "path": "/account/bili/attribute",
          "desc": "哔哩哔哩账号详情"
        },
        {
          "cateId": 48,
          "cateName": "哔哩哔哩账号",
          "method": "GET",
          "path": "/account/bili/video-latest",
          "desc": "哔哩哔哩账号最新一条视频"
        },
        {
          "cateId": 48,
          "cateName": "哔哩哔哩账号",
          "method": "GET",
          "path": "/account/bili/bvci-latest",
          "desc": "哔哩哔哩账号最新传播指数——bvci"
        },
        {
          "cateId": 48,
          "cateName": "哔哩哔哩账号",
          "method": "GET",
          "path": "/account/bili/bvci-weekly",
          "desc": "获取哔哩哔哩账号最近一周传播指数——bvci"
        },
        {
          "cateId": 48,
          "cateName": "哔哩哔哩账号",
          "method": "GET",
          "path": "/account/bili/index-latest",
          "desc": "哔哩哔哩账号最新动态指标点赞数,转发数等"
        },
        {
          "cateId": 48,
          "cateName": "哔哩哔哩账号",
          "method": "GET",
          "path": "/account/bili/index-weekly",
          "desc": "获取哔哩哔哩账号最近一周最新动态指标"
        },
        {
          "cateId": 53,
          "cateName": "微信视频号账号",
          "method": "POST",
          "path": "/account/wxvideo/search",
          "desc": "微信视频号账号检索"
        },
        {
          "cateId": 53,
          "cateName": "微信视频号账号",
          "method": "POST",
          "path": "/account/wxvideo/attribute",
          "desc": "微信视频号账号详情"
        },
        {
          "cateId": 53,
          "cateName": "微信视频号账号",
          "method": "POST",
          "path": "/account/wxvideo/video-latest",
          "desc": "微信视频号最新一条视频"
        },
        {
          "cateId": 53,
          "cateName": "微信视频号账号",
          "method": "POST",
          "path": "/account/wxvideo/wvci-latest",
          "desc": "微信视频号账号最新传播指数"
        },
        {
          "cateId": 53,
          "cateName": "微信视频号账号",
          "method": "POST",
          "path": "/account/wxvideo/wvci-weekly",
          "desc": "微信视频号账号最近7天的传播指数"
        },
        {
          "cateId": 53,
          "cateName": "微信视频号账号",
          "method": "POST",
          "path": "/account/wxvideo/index-latest",
          "desc": "微信视频号账号最新动态指标——阅读数、评论数等"
        },
        {
          "cateId": 53,
          "cateName": "微信视频号账号",
          "method": "POST",
          "path": "/account/wxvideo/index-weekly",
          "desc": "微信视频号账号最近7天的动态指标——阅读数、评论数等"
        },
        {
          "cateId": 57,
          "cateName": "西瓜视频账号",
          "method": "POST",
          "path": "/account/xigua/search",
          "desc": "西瓜视频账号检索"
        },
        {
          "cateId": 57,
          "cateName": "西瓜视频账号",
          "method": "POST",
          "path": "/account/xigua/attribute",
          "desc": "西瓜视频账号详情"
        },
        {
          "cateId": 57,
          "cateName": "西瓜视频账号",
          "method": "POST",
          "path": "/account/xigua/video-latest",
          "desc": "西瓜视频账号最新一条视频"
        },
        {
          "cateId": 57,
          "cateName": "西瓜视频账号",
          "method": "POST",
          "path": "/account/xigua/xci-latest",
          "desc": "西瓜视频号账最新传播指数"
        },
        {
          "cateId": 57,
          "cateName": "西瓜视频账号",
          "method": "POST",
          "path": "/account/xigua/xci-weekly",
          "desc": "西瓜视频号账最近七天传播指数"
        },
        {
          "cateId": 57,
          "cateName": "西瓜视频账号",
          "method": "POST",
          "path": "/account/xigua/index-latest",
          "desc": "西瓜视频账号最新动态指标——阅读数、评论数等"
        },
        {
          "cateId": 57,
          "cateName": "西瓜视频账号",
          "method": "POST",
          "path": "/account/xigua/index-weekly",
          "desc": "西瓜视频账号最近七天的动态指标——阅读数、评论数等"
        },
        {
          "cateId": 61,
          "cateName": "小红书账号",
          "method": "POST",
          "path": "/account/xiaohongshu/search",
          "desc": "小红书账号检索"
        },
        {
          "cateId": 61,
          "cateName": "小红书账号",
          "method": "POST",
          "path": "/account/xiaohongshu/attribute",
          "desc": "小红书账号详情"
        },
        {
          "cateId": 61,
          "cateName": "小红书账号",
          "method": "POST",
          "path": "/account/xiaohongshu/article-latest",
          "desc": "小红书账号最新一条文章"
        },
        {
          "cateId": 61,
          "cateName": "小红书账号",
          "method": "POST",
          "path": "/account/xiaohongshu/hci-latest",
          "desc": "小红书号账最新传播指数"
        },
        {
          "cateId": 61,
          "cateName": "小红书账号",
          "method": "POST",
          "path": "/account/xiaohongshu/hci-weekly",
          "desc": "小红书号账最近七天传播指数"
        },
        {
          "cateId": 61,
          "cateName": "小红书账号",
          "method": "POST",
          "path": "/account/xiaohongshu/index-latest",
          "desc": "小红书账号最新动态指标——点赞数,评论数等"
        },
        {
          "cateId": 61,
          "cateName": "小红书账号",
          "method": "POST",
          "path": "/account/xiaohongshu/index-weekly",
          "desc": "小红书账号最近七天的动态指标——点赞数,评论数等"
        }
      ]
    },
    {
      "tool": "gsdata_content",
      "endpointCount": 30,
      "methods": [
        "GET",
        "POST"
      ],
      "cateIds": [
        3,
        4,
        5,
        29,
        30,
        49,
        54,
        58,
        62
      ],
      "endpoints": [
        {
          "cateId": 3,
          "cateName": "微信文章",
          "method": "GET",
          "path": "/weixin/article/search1",
          "desc": "微信文章检索——按时间排序"
        },
        {
          "cateId": 3,
          "cateName": "微信文章",
          "method": "GET",
          "path": "/weixin/article/content",
          "desc": "获取微信文章正文"
        },
        {
          "cateId": 3,
          "cateName": "微信文章",
          "method": "GET",
          "path": "/weixin/article/search2",
          "desc": "微信文章检索——按阅读数排序"
        },
        {
          "cateId": 3,
          "cateName": "微信文章",
          "method": "GET",
          "path": "/weixin/article/search3",
          "desc": "微信文章检索——按在看数排序"
        },
        {
          "cateId": 3,
          "cateName": "微信文章",
          "method": "GET",
          "path": "/weixin/article/clean-content",
          "desc": "获取清理隐藏内容后的微信正文"
        },
        {
          "cateId": 4,
          "cateName": "微博文章",
          "method": "GET",
          "path": "/weibo/article/search1",
          "desc": "微博文章检索——按发布时间排序"
        },
        {
          "cateId": 4,
          "cateName": "微博文章",
          "method": "GET",
          "path": "/weibo/article/search2",
          "desc": "微博文章检索——按评论数排序"
        },
        {
          "cateId": 4,
          "cateName": "微博文章",
          "method": "GET",
          "path": "/weibo/article/search3",
          "desc": "微博文章检索——按转发数排序"
        },
        {
          "cateId": 4,
          "cateName": "微博文章",
          "method": "GET",
          "path": "/weibo/article/search4",
          "desc": "微博文章检索——按点赞数排序"
        },
        {
          "cateId": 5,
          "cateName": "头条文章",
          "method": "GET",
          "path": "/toutiao/article/search1",
          "desc": "头条文章检索——按发布时间排序"
        },
        {
          "cateId": 5,
          "cateName": "头条文章",
          "method": "GET",
          "path": "/toutiao/article/search2",
          "desc": "头条文章检索——按评论数排序"
        },
        {
          "cateId": 5,
          "cateName": "头条文章",
          "method": "GET",
          "path": "/toutiao/article/search3",
          "desc": "头条文章检索——按阅读数排序"
        },
        {
          "cateId": 29,
          "cateName": "抖音视频",
          "method": "GET",
          "path": "/shortvideo/douyin/search1",
          "desc": "抖音视频检索——按发布时间排序"
        },
        {
          "cateId": 29,
          "cateName": "抖音视频",
          "method": "GET",
          "path": "/shortvideo/douyin/search2",
          "desc": "抖音视频检索——按视频点赞数排序"
        },
        {
          "cateId": 29,
          "cateName": "抖音视频",
          "method": "GET",
          "path": "/shortvideo/douyin/search3",
          "desc": "抖音视频检索——按视频评论数排序"
        },
        {
          "cateId": 30,
          "cateName": "快手视频",
          "method": "GET",
          "path": "/shortvideo/kuaishou/search1",
          "desc": "快手视频检索——按发布时间排序"
        },
        {
          "cateId": 30,
          "cateName": "快手视频",
          "method": "GET",
          "path": "/shortvideo/kuaishou/search2",
          "desc": "快手视频检索——按视频点赞数排序"
        },
        {
          "cateId": 30,
          "cateName": "快手视频",
          "method": "GET",
          "path": "/shortvideo/kuaishou/search3",
          "desc": "快手视频检索——按视频评论数排序"
        },
        {
          "cateId": 49,
          "cateName": "哔哩哔哩视频",
          "method": "GET",
          "path": "/shortvideo/bili/search1",
          "desc": "哔哩哔哩视频检索——按发布时间排序"
        },
        {
          "cateId": 49,
          "cateName": "哔哩哔哩视频",
          "method": "GET",
          "path": "/shortvideo/bili/search2",
          "desc": "哔哩哔哩视频检索——按视频点赞数排序"
        },
        {
          "cateId": 49,
          "cateName": "哔哩哔哩视频",
          "method": "GET",
          "path": "/shortvideo/bili/search3",
          "desc": "哔哩哔哩视频检索——按视频评论数排序"
        },
        {
          "cateId": 54,
          "cateName": "微信视频号视频",
          "method": "POST",
          "path": "/shortvideo/wxvideo/search1",
          "desc": "微信视频号视频检索——按发布时间排序"
        },
        {
          "cateId": 54,
          "cateName": "微信视频号视频",
          "method": "GET",
          "path": "/shortvideo/wxvideo/search2",
          "desc": "微信视频号视频检索——按视频点赞数排序"
        },
        {
          "cateId": 54,
          "cateName": "微信视频号视频",
          "method": "GET",
          "path": "/shortvideo/wxvideo/search3",
          "desc": "微信视频号视频检索——按视频评论数排序"
        },
        {
          "cateId": 58,
          "cateName": "西瓜视频视频",
          "method": "POST",
          "path": "/shortvideo/xigua/search1",
          "desc": "西瓜视频视频检索——按发布时间排序"
        },
        {
          "cateId": 58,
          "cateName": "西瓜视频视频",
          "method": "POST",
          "path": "/shortvideo/xigua/search2",
          "desc": "西瓜视频视频检索——按视频点赞数排序"
        },
        {
          "cateId": 58,
          "cateName": "西瓜视频视频",
          "method": "POST",
          "path": "/shortvideo/xigua/search3",
          "desc": "西瓜视频视频检索——按视频评论数排序"
        },
        {
          "cateId": 62,
          "cateName": "小红书文章",
          "method": "POST",
          "path": "/article/xiaohongshu/search1",
          "desc": "小红书文章检索——按发布时间排序"
        },
        {
          "cateId": 62,
          "cateName": "小红书文章",
          "method": "POST",
          "path": "/article/xiaohongshu/search2",
          "desc": "小红书文章检索——按点赞数排序"
        },
        {
          "cateId": 62,
          "cateName": "小红书文章",
          "method": "POST",
          "path": "/article/xiaohongshu/search3",
          "desc": "小红书文章检索——按评论数排序"
        }
      ]
    },
    {
      "tool": "gsdata_rank_public",
      "endpointCount": 36,
      "methods": [
        "GET",
        "POST"
      ],
      "cateIds": [
        12,
        13,
        14,
        31,
        32,
        50,
        55,
        59,
        63
      ],
      "endpoints": [
        {
          "cateId": 12,
          "cateName": "微信榜单",
          "method": "GET",
          "path": "/rank/weixin/day",
          "desc": "微信日榜"
        },
        {
          "cateId": 12,
          "cateName": "微信榜单",
          "method": "GET",
          "path": "/rank/weixin/week",
          "desc": "微信周榜"
        },
        {
          "cateId": 12,
          "cateName": "微信榜单",
          "method": "GET",
          "path": "/rank/weixin/month",
          "desc": "微信月榜"
        },
        {
          "cateId": 12,
          "cateName": "微信榜单",
          "method": "GET",
          "path": "/rank/weixin/latest",
          "desc": "微信最新榜单日期"
        },
        {
          "cateId": 13,
          "cateName": "微博榜单",
          "method": "GET",
          "path": "/rank/weibo/day",
          "desc": "微博日榜"
        },
        {
          "cateId": 13,
          "cateName": "微博榜单",
          "method": "GET",
          "path": "/rank/weibo/week",
          "desc": "微博周榜"
        },
        {
          "cateId": 13,
          "cateName": "微博榜单",
          "method": "GET",
          "path": "/rank/weibo/month",
          "desc": "微博月榜"
        },
        {
          "cateId": 13,
          "cateName": "微博榜单",
          "method": "GET",
          "path": "/rank/weibo/latest",
          "desc": "微博最新榜单日期"
        },
        {
          "cateId": 14,
          "cateName": "头条榜单",
          "method": "GET",
          "path": "/rank/toutiao/day",
          "desc": "头条日榜"
        },
        {
          "cateId": 14,
          "cateName": "头条榜单",
          "method": "GET",
          "path": "/rank/toutiao/week",
          "desc": "头条周榜"
        },
        {
          "cateId": 14,
          "cateName": "头条榜单",
          "method": "GET",
          "path": "/rank/toutiao/latest",
          "desc": "头条最新榜单日期"
        },
        {
          "cateId": 14,
          "cateName": "头条榜单",
          "method": "GET",
          "path": "/rank/toutiao/month",
          "desc": "头条月榜"
        },
        {
          "cateId": 31,
          "cateName": "抖音榜单",
          "method": "GET",
          "path": "/rank/douyin/day",
          "desc": "抖音日榜"
        },
        {
          "cateId": 31,
          "cateName": "抖音榜单",
          "method": "GET",
          "path": "/rank/douyin/week",
          "desc": "抖音周榜"
        },
        {
          "cateId": 31,
          "cateName": "抖音榜单",
          "method": "GET",
          "path": "/rank/douyin/month",
          "desc": "抖音月榜"
        },
        {
          "cateId": 31,
          "cateName": "抖音榜单",
          "method": "GET",
          "path": "/rank/douyin/latest",
          "desc": "抖音最新榜单日期"
        },
        {
          "cateId": 32,
          "cateName": "快手榜单",
          "method": "GET",
          "path": "/rank/kuaishou/day",
          "desc": "快手日榜"
        },
        {
          "cateId": 32,
          "cateName": "快手榜单",
          "method": "GET",
          "path": "/rank/kuaishou/week",
          "desc": "快手周榜"
        },
        {
          "cateId": 32,
          "cateName": "快手榜单",
          "method": "GET",
          "path": "/rank/kuaishou/month",
          "desc": "快手月榜"
        },
        {
          "cateId": 32,
          "cateName": "快手榜单",
          "method": "GET",
          "path": "/rank/kuaishou/latest",
          "desc": "快手最新榜单日期"
        },
        {
          "cateId": 50,
          "cateName": "哔哩哔哩榜单",
          "method": "GET",
          "path": "/rank/bili/day",
          "desc": "哔哩哔哩日榜"
        },
        {
          "cateId": 50,
          "cateName": "哔哩哔哩榜单",
          "method": "GET",
          "path": "/rank/bili/week",
          "desc": "哔哩哔哩周榜"
        },
        {
          "cateId": 50,
          "cateName": "哔哩哔哩榜单",
          "method": "GET",
          "path": "/rank/bili/month",
          "desc": "哔哩哔哩月榜"
        },
        {
          "cateId": 50,
          "cateName": "哔哩哔哩榜单",
          "method": "GET",
          "path": "/rank/bili/latest",
          "desc": "哔哩哔哩最新榜单日期"
        },
        {
          "cateId": 55,
          "cateName": "微信视频号榜单",
          "method": "POST",
          "path": "/rank/wxvideo/day",
          "desc": "微信视频号日榜"
        },
        {
          "cateId": 55,
          "cateName": "微信视频号榜单",
          "method": "POST",
          "path": "/rank/wxvideo/week",
          "desc": "微信视频号周榜"
        },
        {
          "cateId": 55,
          "cateName": "微信视频号榜单",
          "method": "POST",
          "path": "/rank/wxvideo/month",
          "desc": "微信视频号月榜"
        },
        {
          "cateId": 55,
          "cateName": "微信视频号榜单",
          "method": "POST",
          "path": "/rank/wxvideo/latest",
          "desc": "获取最新出榜日期"
        },
        {
          "cateId": 59,
          "cateName": "西瓜视频榜单",
          "method": "POST",
          "path": "/rank/xigua/latest",
          "desc": "西瓜视频最新榜单日期"
        },
        {
          "cateId": 59,
          "cateName": "西瓜视频榜单",
          "method": "POST",
          "path": "/rank/xigua/day",
          "desc": "西瓜视频日榜"
        },
        {
          "cateId": 59,
          "cateName": "西瓜视频榜单",
          "method": "POST",
          "path": "/rank/xigua/week",
          "desc": "西瓜视频周榜"
        },
        {
          "cateId": 59,
          "cateName": "西瓜视频榜单",
          "method": "POST",
          "path": "/rank/xigua/month",
          "desc": "西瓜视频月榜"
        },
        {
          "cateId": 63,
          "cateName": "小红书榜单",
          "method": "POST",
          "path": "/rank/xiaohongshu/latest",
          "desc": "小红书最新榜单日期"
        },
        {
          "cateId": 63,
          "cateName": "小红书榜单",
          "method": "POST",
          "path": "/rank/xiaohongshu/day",
          "desc": "小红书日榜"
        },
        {
          "cateId": 63,
          "cateName": "小红书榜单",
          "method": "POST",
          "path": "/rank/xiaohongshu/week",
          "desc": "小红书周榜"
        },
        {
          "cateId": 63,
          "cateName": "小红书榜单",
          "method": "POST",
          "path": "/rank/xiaohongshu/month",
          "desc": "小红书月榜"
        }
      ]
    },
    {
      "tool": "gsdata_myrank_group",
      "endpointCount": 27,
      "methods": [
        "GET",
        "POST"
      ],
      "cateIds": [
        16,
        17,
        18,
        33,
        34,
        51,
        56,
        60,
        64
      ],
      "endpoints": [
        {
          "cateId": 16,
          "cateName": "微信榜单",
          "method": "POST",
          "path": "/myrank/weixin/group-add",
          "desc": "添加分组"
        },
        {
          "cateId": 16,
          "cateName": "微信榜单",
          "method": "GET",
          "path": "/myrank/weixin/group-list",
          "desc": "自定义分组列表"
        },
        {
          "cateId": 16,
          "cateName": "微信榜单",
          "method": "GET",
          "path": "/myrank/weixin/group-del",
          "desc": "删除微信自定义榜单分组"
        },
        {
          "cateId": 17,
          "cateName": "微博榜单",
          "method": "POST",
          "path": "/myrank/weibo/group-add",
          "desc": "添加分组"
        },
        {
          "cateId": 17,
          "cateName": "微博榜单",
          "method": "GET",
          "path": "/myrank/weibo/group-list",
          "desc": "自定义分组列表"
        },
        {
          "cateId": 17,
          "cateName": "微博榜单",
          "method": "GET",
          "path": "/myrank/weibo/group-del",
          "desc": "删除微博自定义榜单分组"
        },
        {
          "cateId": 18,
          "cateName": "头条榜单",
          "method": "POST",
          "path": "/myrank/toutiao/group-add",
          "desc": "添加分组"
        },
        {
          "cateId": 18,
          "cateName": "头条榜单",
          "method": "GET",
          "path": "/myrank/toutiao/group-list",
          "desc": "自定义分组列表"
        },
        {
          "cateId": 18,
          "cateName": "头条榜单",
          "method": "GET",
          "path": "/myrank/toutiao/group-del",
          "desc": "删除头条自定义榜单分组"
        },
        {
          "cateId": 33,
          "cateName": "抖音榜单",
          "method": "GET",
          "path": "/myrank/douyin/group-add",
          "desc": "添加分组"
        },
        {
          "cateId": 33,
          "cateName": "抖音榜单",
          "method": "GET",
          "path": "/myrank/douyin/group-list",
          "desc": "自定义分组列表"
        },
        {
          "cateId": 33,
          "cateName": "抖音榜单",
          "method": "GET",
          "path": "/myrank/douyin/group-del",
          "desc": "删除抖音自定义榜单分组"
        },
        {
          "cateId": 34,
          "cateName": "快手榜单",
          "method": "GET",
          "path": "/myrank/kuaishou/group-add",
          "desc": "添加分组"
        },
        {
          "cateId": 34,
          "cateName": "快手榜单",
          "method": "GET",
          "path": "/myrank/kuaishou/group-list",
          "desc": "自定义分组列表"
        },
        {
          "cateId": 34,
          "cateName": "快手榜单",
          "method": "GET",
          "path": "/myrank/kuaishou/group-del",
          "desc": "删除快手自定义榜单分组"
        },
        {
          "cateId": 51,
          "cateName": "哔哩哔哩榜单",
          "method": "GET",
          "path": "/myrank/bili/group-add",
          "desc": "添加分组"
        },
        {
          "cateId": 51,
          "cateName": "哔哩哔哩榜单",
          "method": "GET",
          "path": "/myrank/bili/group-list",
          "desc": "自定义分组列表"
        },
        {
          "cateId": 51,
          "cateName": "哔哩哔哩榜单",
          "method": "GET",
          "path": "/myrank/bili/group-del",
          "desc": "删除哔哩哔哩自定义榜单分组"
        },
        {
          "cateId": 56,
          "cateName": "微信视频号榜单",
          "method": "POST",
          "path": "/myrank/wxvideo/group-add",
          "desc": "添加分组"
        },
        {
          "cateId": 56,
          "cateName": "微信视频号榜单",
          "method": "POST",
          "path": "/myrank/wxvideo/group-list",
          "desc": "自定义分组列表"
        },
        {
          "cateId": 56,
          "cateName": "微信视频号榜单",
          "method": "POST",
          "path": "/myrank/wxvideo/group-del",
          "desc": "删除微信视频号自定义榜单分组"
        },
        {
          "cateId": 60,
          "cateName": "西瓜视频榜单",
          "method": "POST",
          "path": "/myrank/xigua/group-add",
          "desc": "添加分组"
        },
        {
          "cateId": 60,
          "cateName": "西瓜视频榜单",
          "method": "POST",
          "path": "/myrank/xigua/group-list",
          "desc": "自定义分组列表"
        },
        {
          "cateId": 60,
          "cateName": "西瓜视频榜单",
          "method": "POST",
          "path": "/myrank/xigua/group-del",
          "desc": "删除自定义分组"
        },
        {
          "cateId": 64,
          "cateName": "小红书榜单",
          "method": "POST",
          "path": "/myrank/xiaohongshu/group-add",
          "desc": "添加分组"
        },
        {
          "cateId": 64,
          "cateName": "小红书榜单",
          "method": "POST",
          "path": "/myrank/xiaohongshu/group-list",
          "desc": "自定义分组列表"
        },
        {
          "cateId": 64,
          "cateName": "小红书榜单",
          "method": "POST",
          "path": "/myrank/xiaohongshu/group-del",
          "desc": "删除自定义分组"
        }
      ]
    },
    {
      "tool": "gsdata_myrank_account",
      "endpointCount": 36,
      "methods": [
        "GET",
        "POST"
      ],
      "cateIds": [
        16,
        17,
        18,
        33,
        34,
        51,
        56,
        60,
        64
      ],
      "endpoints": [
        {
          "cateId": 16,
          "cateName": "微信榜单",
          "method": "POST",
          "path": "/myrank/weixin/acct-add",
          "desc": "往分组中添加微信公众号"
        },
        {
          "cateId": 16,
          "cateName": "微信榜单",
          "method": "GET",
          "path": "/myrank/weixin/acct-del",
          "desc": "删除分组内的微信公众号"
        },
        {
          "cateId": 16,
          "cateName": "微信榜单",
          "method": "GET",
          "path": "/myrank/weixin/acct-list",
          "desc": "查阅分组内的微信公众号"
        },
        {
          "cateId": 16,
          "cateName": "微信榜单",
          "method": "POST",
          "path": "/myrank/weixin/acct-add-by-url",
          "desc": "通过微信文章url添加公众号到分组"
        },
        {
          "cateId": 17,
          "cateName": "微博榜单",
          "method": "POST",
          "path": "/myrank/weibo/acct-add",
          "desc": "往分组中添加微博号"
        },
        {
          "cateId": 17,
          "cateName": "微博榜单",
          "method": "GET",
          "path": "/myrank/weibo/acct-del",
          "desc": "删除分组内的微博号"
        },
        {
          "cateId": 17,
          "cateName": "微博榜单",
          "method": "GET",
          "path": "/myrank/weibo/acct-list",
          "desc": "查阅分组内的微博号"
        },
        {
          "cateId": 17,
          "cateName": "微博榜单",
          "method": "POST",
          "path": "/myrank/weibo/acct-url-add",
          "desc": "通过链接添加微博账号至自定义分组"
        },
        {
          "cateId": 18,
          "cateName": "头条榜单",
          "method": "POST",
          "path": "/myrank/toutiao/acct-add",
          "desc": "往分组中添加头条号"
        },
        {
          "cateId": 18,
          "cateName": "头条榜单",
          "method": "GET",
          "path": "/myrank/toutiao/acct-del",
          "desc": "删除分组内的头条号"
        },
        {
          "cateId": 18,
          "cateName": "头条榜单",
          "method": "GET",
          "path": "/myrank/toutiao/acct-list",
          "desc": "查阅分组内的头条号"
        },
        {
          "cateId": 18,
          "cateName": "头条榜单",
          "method": "POST",
          "path": "/myrank/toutiao/acct-url-add",
          "desc": "通过链接添加头条号到自定义分组"
        },
        {
          "cateId": 33,
          "cateName": "抖音榜单",
          "method": "GET",
          "path": "/myrank/douyin/acct-add",
          "desc": "往分组中添加抖音号"
        },
        {
          "cateId": 33,
          "cateName": "抖音榜单",
          "method": "GET",
          "path": "/myrank/douyin/acct-del",
          "desc": "删除分组内的抖音号"
        },
        {
          "cateId": 33,
          "cateName": "抖音榜单",
          "method": "GET",
          "path": "/myrank/douyin/acct-list",
          "desc": "查阅分组内的抖音号"
        },
        {
          "cateId": 33,
          "cateName": "抖音榜单",
          "method": "GET",
          "path": "/myrank/douyin/acct-url-add",
          "desc": "通过抖音账号主页url添加抖音号到分组"
        },
        {
          "cateId": 34,
          "cateName": "快手榜单",
          "method": "GET",
          "path": "/myrank/kuaishou/acct-add",
          "desc": "往分组中添加快手号"
        },
        {
          "cateId": 34,
          "cateName": "快手榜单",
          "method": "GET",
          "path": "/myrank/kuaishou/acct-del",
          "desc": "删除分组内的快手号"
        },
        {
          "cateId": 34,
          "cateName": "快手榜单",
          "method": "GET",
          "path": "/myrank/kuaishou/acct-list",
          "desc": "查阅分组内的快手号"
        },
        {
          "cateId": 34,
          "cateName": "快手榜单",
          "method": "GET",
          "path": "/myrank/kuaishou/acct-url-add",
          "desc": "通过快手账号主页url添加快手号到分组"
        },
        {
          "cateId": 51,
          "cateName": "哔哩哔哩榜单",
          "method": "GET",
          "path": "/myrank/bili/acct-add",
          "desc": "往分组中添加哔哩哔哩号"
        },
        {
          "cateId": 51,
          "cateName": "哔哩哔哩榜单",
          "method": "GET",
          "path": "/myrank/bili/acct-del",
          "desc": "删除分组内的哔哩哔哩号"
        },
        {
          "cateId": 51,
          "cateName": "哔哩哔哩榜单",
          "method": "GET",
          "path": "/myrank/bili/acct-list",
          "desc": "查阅分组内的哔哩哔哩号"
        },
        {
          "cateId": 51,
          "cateName": "哔哩哔哩榜单",
          "method": "GET",
          "path": "/myrank/bili/acct-url-add",
          "desc": "通过哔哩哔哩账号主页url添加快手号到分组"
        },
        {
          "cateId": 56,
          "cateName": "微信视频号榜单",
          "method": "POST",
          "path": "/myrank/wxvideo/acct-add",
          "desc": "往分组中添加微信视频号"
        },
        {
          "cateId": 56,
          "cateName": "微信视频号榜单",
          "method": "POST",
          "path": "/myrank/wxvideo/acct-del",
          "desc": "删除分组中微信视频号"
        },
        {
          "cateId": 56,
          "cateName": "微信视频号榜单",
          "method": "POST",
          "path": "/myrank/wxvideo/acct-list",
          "desc": "查阅分组内的微信视频号"
        },
        {
          "cateId": 56,
          "cateName": "微信视频号榜单",
          "method": "GET",
          "path": "/myrank/wxvideo/search-account",
          "desc": "根据账号名称搜索微信视频号账号"
        },
        {
          "cateId": 60,
          "cateName": "西瓜视频榜单",
          "method": "POST",
          "path": "/myrank/xigua/acct-add",
          "desc": "往分组中添加账号"
        },
        {
          "cateId": 60,
          "cateName": "西瓜视频榜单",
          "method": "POST",
          "path": "/myrank/xigua/acct-del",
          "desc": "删除自定义分组中的账号"
        },
        {
          "cateId": 60,
          "cateName": "西瓜视频榜单",
          "method": "POST",
          "path": "/myrank/xigua/acct-list",
          "desc": "查询分组内的账号"
        },
        {
          "cateId": 60,
          "cateName": "西瓜视频榜单",
          "method": "POST",
          "path": "/myrank/xigua/acct-url-add",
          "desc": "通过账号主页链接添加账号到自定义分组"
        },
        {
          "cateId": 64,
          "cateName": "小红书榜单",
          "method": "POST",
          "path": "/myrank/xiaohongshu/acct-add",
          "desc": "往分组中添加账号"
        },
        {
          "cateId": 64,
          "cateName": "小红书榜单",
          "method": "POST",
          "path": "/myrank/xiaohongshu/acct-del",
          "desc": "删除自定义分组中的账号"
        },
        {
          "cateId": 64,
          "cateName": "小红书榜单",
          "method": "POST",
          "path": "/myrank/xiaohongshu/acct-list",
          "desc": "查询分组内的账号"
        },
        {
          "cateId": 64,
          "cateName": "小红书榜单",
          "method": "POST",
          "path": "/myrank/xiaohongshu/acct-url-add",
          "desc": "通过账号主页链接添加账号到自定义分组"
        }
      ]
    },
    {
      "tool": "gsdata_myrank_query",
      "endpointCount": 41,
      "methods": [
        "GET",
        "POST"
      ],
      "cateIds": [
        16,
        17,
        18,
        33,
        34,
        51,
        56,
        60,
        64
      ],
      "endpoints": [
        {
          "cateId": 16,
          "cateName": "微信榜单",
          "method": "GET",
          "path": "/myrank/weixin/day",
          "desc": "微信日榜"
        },
        {
          "cateId": 16,
          "cateName": "微信榜单",
          "method": "GET",
          "path": "/myrank/weixin/week",
          "desc": "微信周榜"
        },
        {
          "cateId": 16,
          "cateName": "微信榜单",
          "method": "GET",
          "path": "/myrank/weixin/month",
          "desc": "微信月榜"
        },
        {
          "cateId": 16,
          "cateName": "微信榜单",
          "method": "GET",
          "path": "/myrank/weixin/latest",
          "desc": "微信最新榜单日期"
        },
        {
          "cateId": 16,
          "cateName": "微信榜单",
          "method": "GET",
          "path": "/myrank/weixin/group-article",
          "desc": "获取用户自有分组内的榜单文章"
        },
        {
          "cateId": 17,
          "cateName": "微博榜单",
          "method": "GET",
          "path": "/myrank/weibo/day",
          "desc": "微博日榜"
        },
        {
          "cateId": 17,
          "cateName": "微博榜单",
          "method": "GET",
          "path": "/myrank/weibo/week",
          "desc": "微博周榜"
        },
        {
          "cateId": 17,
          "cateName": "微博榜单",
          "method": "GET",
          "path": "/myrank/weibo/month",
          "desc": "微博月榜"
        },
        {
          "cateId": 17,
          "cateName": "微博榜单",
          "method": "GET",
          "path": "/myrank/weibo/latest",
          "desc": "微博最新榜单日期"
        },
        {
          "cateId": 17,
          "cateName": "微博榜单",
          "method": "GET",
          "path": "/myrank/weibo/group-article",
          "desc": "获取用户自有分组内的榜单文章"
        },
        {
          "cateId": 18,
          "cateName": "头条榜单",
          "method": "GET",
          "path": "/myrank/toutiao/day",
          "desc": "头条日榜"
        },
        {
          "cateId": 18,
          "cateName": "头条榜单",
          "method": "GET",
          "path": "/myrank/toutiao/week",
          "desc": "头条周榜"
        },
        {
          "cateId": 18,
          "cateName": "头条榜单",
          "method": "GET",
          "path": "/myrank/toutiao/latest",
          "desc": "头条最新榜单日期"
        },
        {
          "cateId": 18,
          "cateName": "头条榜单",
          "method": "GET",
          "path": "/myrank/toutiao/group-article",
          "desc": "获取用户自有分组内的榜单文章"
        },
        {
          "cateId": 18,
          "cateName": "头条榜单",
          "method": "GET",
          "path": "/myrank/toutiao/month",
          "desc": "头条月榜"
        },
        {
          "cateId": 33,
          "cateName": "抖音榜单",
          "method": "GET",
          "path": "/myrank/douyin/day",
          "desc": "抖音日榜"
        },
        {
          "cateId": 33,
          "cateName": "抖音榜单",
          "method": "GET",
          "path": "/myrank/douyin/week",
          "desc": "抖音周榜"
        },
        {
          "cateId": 33,
          "cateName": "抖音榜单",
          "method": "GET",
          "path": "/myrank/douyin/month",
          "desc": "抖音月榜"
        },
        {
          "cateId": 33,
          "cateName": "抖音榜单",
          "method": "GET",
          "path": "/myrank/douyin/group-article",
          "desc": "获取用户自有分组内的榜单视频"
        },
        {
          "cateId": 33,
          "cateName": "抖音榜单",
          "method": "GET",
          "path": "/myrank/douyin/latest",
          "desc": "获取抖音榜单最新日期"
        },
        {
          "cateId": 34,
          "cateName": "快手榜单",
          "method": "GET",
          "path": "/myrank/kuaishou/day",
          "desc": "快手日榜"
        },
        {
          "cateId": 34,
          "cateName": "快手榜单",
          "method": "GET",
          "path": "/myrank/kuaishou/week",
          "desc": "快手周榜"
        },
        {
          "cateId": 34,
          "cateName": "快手榜单",
          "method": "GET",
          "path": "/myrank/kuaishou/month",
          "desc": "快手月榜"
        },
        {
          "cateId": 34,
          "cateName": "快手榜单",
          "method": "GET",
          "path": "/myrank/kuaishou/group-article",
          "desc": "获取用户自有分组内的榜单视频"
        },
        {
          "cateId": 51,
          "cateName": "哔哩哔哩榜单",
          "method": "GET",
          "path": "/myrank/bili/day",
          "desc": "哔哩哔哩日榜"
        },
        {
          "cateId": 51,
          "cateName": "哔哩哔哩榜单",
          "method": "GET",
          "path": "/myrank/bili/week",
          "desc": "哔哩哔哩周榜"
        },
        {
          "cateId": 51,
          "cateName": "哔哩哔哩榜单",
          "method": "GET",
          "path": "/myrank/bili/month",
          "desc": "哔哩哔哩月榜"
        },
        {
          "cateId": 51,
          "cateName": "哔哩哔哩榜单",
          "method": "GET",
          "path": "/myrank/bili/group-article",
          "desc": "获取用户自有分组内的榜单视频"
        },
        {
          "cateId": 56,
          "cateName": "微信视频号榜单",
          "method": "POST",
          "path": "/myrank/wxvideo/day",
          "desc": "微信视频号日榜"
        },
        {
          "cateId": 56,
          "cateName": "微信视频号榜单",
          "method": "POST",
          "path": "/myrank/wxvideo/week",
          "desc": "微信视频号周榜"
        },
        {
          "cateId": 56,
          "cateName": "微信视频号榜单",
          "method": "POST",
          "path": "/myrank/wxvideo/month",
          "desc": "微信视频号月榜"
        },
        {
          "cateId": 56,
          "cateName": "微信视频号榜单",
          "method": "POST",
          "path": "/myrank/wxvideo/group-article",
          "desc": "获取用户自有分组内的榜单视频"
        },
        {
          "cateId": 56,
          "cateName": "微信视频号榜单",
          "method": "POST",
          "path": "/myrank/wxvideo/latest",
          "desc": "获取最新出榜日期"
        },
        {
          "cateId": 60,
          "cateName": "西瓜视频榜单",
          "method": "POST",
          "path": "/myrank/xigua/day",
          "desc": "西瓜视频日榜"
        },
        {
          "cateId": 60,
          "cateName": "西瓜视频榜单",
          "method": "POST",
          "path": "/myrank/xigua/week",
          "desc": "西瓜视频周榜"
        },
        {
          "cateId": 60,
          "cateName": "西瓜视频榜单",
          "method": "POST",
          "path": "/myrank/xigua/month",
          "desc": "西瓜视频月榜"
        },
        {
          "cateId": 60,
          "cateName": "西瓜视频榜单",
          "method": "POST",
          "path": "/myrank/xigua/group-article",
          "desc": "获取用户自由分组内的榜单视频"
        },
        {
          "cateId": 64,
          "cateName": "小红书榜单",
          "method": "POST",
          "path": "/myrank/xiaohongshu/day",
          "desc": "小红书日榜"
        },
        {
          "cateId": 64,
          "cateName": "小红书榜单",
          "method": "POST",
          "path": "/myrank/xiaohongshu/week",
          "desc": "小红书周榜"
        },
        {
          "cateId": 64,
          "cateName": "小红书榜单",
          "method": "POST",
          "path": "/myrank/xiaohongshu/month",
          "desc": "小红书月榜"
        },
        {
          "cateId": 64,
          "cateName": "小红书榜单",
          "method": "POST",
          "path": "/myrank/xiaohongshu/group-article",
          "desc": "获取用户自由分组内的榜单文章"
        }
      ]
    },
    {
      "tool": "gsdata_pubsent_search",
      "endpointCount": 9,
      "methods": [
        "GET"
      ],
      "cateIds": [
        10
      ],
      "endpoints": [
        {
          "cateId": 10,
          "cateName": "关键词检索",
          "method": "GET",
          "path": "/pubsent/full-search/index",
          "desc": "关键词检索文章"
        },
        {
          "cateId": 10,
          "cateName": "关键词检索",
          "method": "GET",
          "path": "/pubsent/full-search/num-found",
          "desc": "各个媒体平台发文量统计"
        },
        {
          "cateId": 10,
          "cateName": "关键词检索",
          "method": "GET",
          "path": "/pubsent/full-search/emotion-dist",
          "desc": "情感分布"
        },
        {
          "cateId": 10,
          "cateName": "关键词检索",
          "method": "GET",
          "path": "/pubsent/full-search/yuqing-trend",
          "desc": "舆情指数"
        },
        {
          "cateId": 10,
          "cateName": "关键词检索",
          "method": "GET",
          "path": "/pubsent/full-search/hot-word",
          "desc": "热门词云图"
        },
        {
          "cateId": 10,
          "cateName": "关键词检索",
          "method": "GET",
          "path": "/pubsent/full-search/media-dist",
          "desc": "媒体分布"
        },
        {
          "cateId": 10,
          "cateName": "关键词检索",
          "method": "GET",
          "path": "/pubsent/full-search/hot-refer-area",
          "desc": "提及热区"
        },
        {
          "cateId": 10,
          "cateName": "关键词检索",
          "method": "GET",
          "path": "/pubsent/full-search/hot-pub-area",
          "desc": "发布热区"
        },
        {
          "cateId": 10,
          "cateName": "关键词检索",
          "method": "GET",
          "path": "/pubsent/full-search/news-trend",
          "desc": "舆情趋势"
        }
      ]
    },
    {
      "tool": "gsdata_pubsent_hot",
      "endpointCount": 10,
      "methods": [
        "GET"
      ],
      "cateIds": [
        35
      ],
      "endpoints": [
        {
          "cateId": 35,
          "cateName": "清博热点",
          "method": "GET",
          "path": "/pubsent/hot-news/index",
          "desc": "看事件"
        },
        {
          "cateId": 35,
          "cateName": "清博热点",
          "method": "GET",
          "path": "/pubsent/hot-news/hot-keywords",
          "desc": "某一事件的热词"
        },
        {
          "cateId": 35,
          "cateName": "清博热点",
          "method": "GET",
          "path": "/pubsent/hot-news/hot-pub-area",
          "desc": "发布热区"
        },
        {
          "cateId": 35,
          "cateName": "清博热点",
          "method": "GET",
          "path": "/pubsent/hot-news/hot-refer-area",
          "desc": "提及热区"
        },
        {
          "cateId": 35,
          "cateName": "清博热点",
          "method": "GET",
          "path": "/pubsent/hot-news/media-active",
          "desc": "活跃媒体"
        },
        {
          "cateId": 35,
          "cateName": "清博热点",
          "method": "GET",
          "path": "/pubsent/hot-news/emotion-dist",
          "desc": "情感分布"
        },
        {
          "cateId": 35,
          "cateName": "清博热点",
          "method": "GET",
          "path": "/pubsent/hot-news/media-dist",
          "desc": "媒体分布"
        },
        {
          "cateId": 35,
          "cateName": "清博热点",
          "method": "GET",
          "path": "/pubsent/hot-news/news-list",
          "desc": "热点事件相关文章"
        },
        {
          "cateId": 35,
          "cateName": "清博热点",
          "method": "GET",
          "path": "/pubsent/hot-news/news-entity-person",
          "desc": "热点事件人物实体关系"
        },
        {
          "cateId": 35,
          "cateName": "清博热点",
          "method": "GET",
          "path": "/pubsent/hot-news/news-entity-organization",
          "desc": "热点事件机构实体关系"
        }
      ]
    },
    {
      "tool": "gsdata_pubsent_warning",
      "endpointCount": 9,
      "methods": [
        "GET",
        "POST"
      ],
      "cateIds": [
        19
      ],
      "endpoints": [
        {
          "cateId": 19,
          "cateName": "舆情预警设置",
          "method": "GET",
          "path": "/pubsent/warning/index",
          "desc": "查阅预警规则"
        },
        {
          "cateId": 19,
          "cateName": "舆情预警设置",
          "method": "POST",
          "path": "/pubsent/warning/create",
          "desc": "创建预警规则"
        },
        {
          "cateId": 19,
          "cateName": "舆情预警设置",
          "method": "POST",
          "path": "/pubsent/warning/update",
          "desc": "修改预警规则"
        },
        {
          "cateId": 19,
          "cateName": "舆情预警设置",
          "method": "GET",
          "path": "/pubsent/warning/close",
          "desc": "关闭预警规则"
        },
        {
          "cateId": 19,
          "cateName": "舆情预警设置",
          "method": "GET",
          "path": "/pubsent/warning/open",
          "desc": "开启预警规则"
        },
        {
          "cateId": 19,
          "cateName": "舆情预警设置",
          "method": "GET",
          "path": "/pubsent/warning/news",
          "desc": "预警消息推送"
        },
        {
          "cateId": 19,
          "cateName": "舆情预警设置",
          "method": "GET",
          "path": "/pubsent/warning/stats",
          "desc": "统计预警信息"
        },
        {
          "cateId": 19,
          "cateName": "舆情预警设置",
          "method": "POST",
          "path": "/pubsent/warning/add-rec-email",
          "desc": "添加预警信息接收人邮箱"
        },
        {
          "cateId": 19,
          "cateName": "舆情预警设置",
          "method": "POST",
          "path": "/pubsent/warning/del-rec-email",
          "desc": "删除预警信息接收人邮箱"
        }
      ]
    },
    {
      "tool": "gsdata_nlp",
      "endpointCount": 29,
      "methods": [
        "GET",
        "POST"
      ],
      "cateIds": [
        36,
        37,
        38,
        39,
        40,
        41,
        42,
        43,
        44,
        45,
        47,
        52
      ],
      "endpoints": [
        {
          "cateId": 36,
          "cateName": "情感分析",
          "method": "POST",
          "path": "/nlp/emotion/emotion-long",
          "desc": "面向长文本的情感分析【大于等于200字】"
        },
        {
          "cateId": 36,
          "cateName": "情感分析",
          "method": "POST",
          "path": "/nlp/emotion/emotion-short",
          "desc": "面向短文本的情感分析【小于200字】"
        },
        {
          "cateId": 36,
          "cateName": "情感分析",
          "method": "POST",
          "path": "/nlp/emotion/oversea-emotion-long",
          "desc": "面向多语种长文本的情感分析【大于等于200字】"
        },
        {
          "cateId": 36,
          "cateName": "情感分析",
          "method": "POST",
          "path": "/nlp/emotion/oversea-emotion-short",
          "desc": "面向多语种短文本的情感分析【小于200字】"
        },
        {
          "cateId": 36,
          "cateName": "情感分析",
          "method": "POST",
          "path": "/nlp/emotion/emotion-weibo",
          "desc": "面向微博的情感分析"
        },
        {
          "cateId": 37,
          "cateName": "情绪分类",
          "method": "POST",
          "path": "/nlp/sentiment/sentiment-long",
          "desc": "面向长文本的情绪分类【大于等于200字】"
        },
        {
          "cateId": 37,
          "cateName": "情绪分类",
          "method": "POST",
          "path": "/nlp/sentiment/sentiment-short",
          "desc": "面向短文本的情绪分类【小于200字】"
        },
        {
          "cateId": 37,
          "cateName": "情绪分类",
          "method": "POST",
          "path": "/nlp/sentiment/oversea-sentiment-long",
          "desc": "面向多语种长文本的情绪分类【大于等于200字】"
        },
        {
          "cateId": 37,
          "cateName": "情绪分类",
          "method": "POST",
          "path": "/nlp/sentiment/oversea-sentiment-short",
          "desc": "面向多语种短文本的情绪分类【小于200字】"
        },
        {
          "cateId": 38,
          "cateName": "文本分类",
          "method": "POST",
          "path": "/nlp/category/category-long",
          "desc": "面向长文本的文本分类【大于等于200字】"
        },
        {
          "cateId": 38,
          "cateName": "文本分类",
          "method": "POST",
          "path": "/nlp/category/category-short",
          "desc": "面向短文本的文本分类【小于200字】"
        },
        {
          "cateId": 38,
          "cateName": "文本分类",
          "method": "POST",
          "path": "/nlp/category/oversea-category-long",
          "desc": "面向多语种长文本的文本分类【大于等于200字】"
        },
        {
          "cateId": 38,
          "cateName": "文本分类",
          "method": "POST",
          "path": "/nlp/category/oversea-category-short",
          "desc": "面向多语种短文本的文本分类【小于200字】"
        },
        {
          "cateId": 39,
          "cateName": "实体识别",
          "method": "POST",
          "path": "/nlp/demoner/index",
          "desc": "获取文章中时间，机构，人物，地点实体"
        },
        {
          "cateId": 40,
          "cateName": "自动摘要",
          "method": "POST",
          "path": "/nlp/summary/index",
          "desc": "文本摘要自动生成"
        },
        {
          "cateId": 40,
          "cateName": "自动摘要",
          "method": "POST",
          "path": "/nlp/summary/generate-title",
          "desc": "文本标题自动生成"
        },
        {
          "cateId": 41,
          "cateName": "语义联想",
          "method": "POST",
          "path": "/nlp/demo-association/index",
          "desc": "文本语义联想"
        },
        {
          "cateId": 42,
          "cateName": "知识图谱",
          "method": "POST",
          "path": "/nlp/graph/get-graph-vertex",
          "desc": "指定词汇名称查询知识图谱点的信息"
        },
        {
          "cateId": 42,
          "cateName": "知识图谱",
          "method": "POST",
          "path": "/nlp/graph/get-graph-edge",
          "desc": "获取指定id查询知识图谱边的信息"
        },
        {
          "cateId": 42,
          "cateName": "知识图谱",
          "method": "POST",
          "path": "/nlp/graph/get-graph-disambiguation",
          "desc": "知识图谱实体消歧"
        },
        {
          "cateId": 43,
          "cateName": "汽车领域",
          "method": "POST",
          "path": "/nlp/car/tag",
          "desc": "汽车问题及情感识别"
        },
        {
          "cateId": 43,
          "cateName": "汽车领域",
          "method": "POST",
          "path": "/nlp/car/auto-sentiment",
          "desc": "汽车细粒度情感分析，返回各个对象的情感属性的概率分布"
        },
        {
          "cateId": 44,
          "cateName": "其他NLP工具",
          "method": "POST",
          "path": "/nlp/other/seg-word",
          "desc": "文本词云图，返回词频倒序排列"
        },
        {
          "cateId": 44,
          "cateName": "其他NLP工具",
          "method": "POST",
          "path": "/nlp/other/demo-keywords",
          "desc": "文章关键词，主题词提取"
        },
        {
          "cateId": 44,
          "cateName": "其他NLP工具",
          "method": "POST",
          "path": "/nlp/other/demo-pos",
          "desc": "文本词性分析，返回包含分词结果与词性结果的二维数组"
        },
        {
          "cateId": 44,
          "cateName": "其他NLP工具",
          "method": "POST",
          "path": "/nlp/other/ocr-image",
          "desc": "获取二进制图片OCR结果"
        },
        {
          "cateId": 45,
          "cateName": "地域识别",
          "method": "GET",
          "path": "/nlp/region/common-region",
          "desc": "通用地域识别"
        },
        {
          "cateId": 47,
          "cateName": "文本纠错",
          "method": "GET",
          "path": "/nlp/corrector/index",
          "desc": "面向文本的文本纠错【小于300字】"
        },
        {
          "cateId": 52,
          "cateName": "观点提取",
          "method": "GET",
          "path": "/nlp/point/extract",
          "desc": "文章观点提取"
        }
      ]
    }
  ],
  "fallbackTool": {
    "tool": "gsdata_raw",
    "endpointCount": 0,
    "endpoints": []
  }
}

Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)