xtree-sdk 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. xtree_sdk-0.1.0/.github/workflows/publish.yml +24 -0
  2. xtree_sdk-0.1.0/.github/workflows/test.yml +18 -0
  3. xtree_sdk-0.1.0/PKG-INFO +97 -0
  4. xtree_sdk-0.1.0/README.md +73 -0
  5. xtree_sdk-0.1.0/pyproject.toml +45 -0
  6. xtree_sdk-0.1.0/tests/test_client.py +54 -0
  7. xtree_sdk-0.1.0/tests/test_coverage.py +42 -0
  8. xtree_sdk-0.1.0/tests/test_envelope.py +68 -0
  9. xtree_sdk-0.1.0/xtree/__init__.py +7 -0
  10. xtree_sdk-0.1.0/xtree/_http.py +111 -0
  11. xtree_sdk-0.1.0/xtree/account.py +19 -0
  12. xtree_sdk-0.1.0/xtree/bilibili/__init__.py +40 -0
  13. xtree_sdk-0.1.0/xtree/cli.py +138 -0
  14. xtree_sdk-0.1.0/xtree/client.py +89 -0
  15. xtree_sdk-0.1.0/xtree/douyin/__init__.py +40 -0
  16. xtree_sdk-0.1.0/xtree/exceptions.py +89 -0
  17. xtree_sdk-0.1.0/xtree/facebook/__init__.py +19 -0
  18. xtree_sdk-0.1.0/xtree/jd/__init__.py +22 -0
  19. xtree_sdk-0.1.0/xtree/kuaishou/__init__.py +37 -0
  20. xtree_sdk-0.1.0/xtree/pdd/__init__.py +22 -0
  21. xtree_sdk-0.1.0/xtree/registry.py +264 -0
  22. xtree_sdk-0.1.0/xtree/taobao/__init__.py +22 -0
  23. xtree_sdk-0.1.0/xtree/tiktok/__init__.py +31 -0
  24. xtree_sdk-0.1.0/xtree/types.py +69 -0
  25. xtree_sdk-0.1.0/xtree/wechat/__init__.py +18 -0
  26. xtree_sdk-0.1.0/xtree/wechat/_helpers.py +40 -0
  27. xtree_sdk-0.1.0/xtree/wechat/accounts.py +80 -0
  28. xtree_sdk-0.1.0/xtree/wechat/articles.py +63 -0
  29. xtree_sdk-0.1.0/xtree/wechat/channels.py +108 -0
  30. xtree_sdk-0.1.0/xtree/wechat/library.py +58 -0
  31. xtree_sdk-0.1.0/xtree/wechat/links.py +17 -0
  32. xtree_sdk-0.1.0/xtree/wechat/search.py +166 -0
  33. xtree_sdk-0.1.0/xtree/weibo/__init__.py +43 -0
  34. xtree_sdk-0.1.0/xtree/xhs/__init__.py +37 -0
  35. xtree_sdk-0.1.0/xtree/youtube/__init__.py +25 -0
@@ -0,0 +1,24 @@
1
+ name: publish
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ permissions:
9
+ id-token: write
10
+
11
+ jobs:
12
+ pypi:
13
+ runs-on: ubuntu-latest
14
+ environment: pypi
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: actions/setup-python@v5
18
+ with:
19
+ python-version: "3.12"
20
+ - name: require PyPI name xtree-sdk
21
+ run: python -c "import pathlib,sys,tomllib; n=tomllib.load(pathlib.Path('pyproject.toml').open('rb'))['project']['name']; sys.exit('pyproject project.name must be xtree-sdk' if n!='xtree-sdk' else 0)"
22
+ - run: pip install build twine
23
+ - run: python -m build
24
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,18 @@
1
+ name: test
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - uses: actions/setup-python@v5
15
+ with:
16
+ python-version: "3.12"
17
+ - run: pip install -e ".[dev]"
18
+ - run: pytest tests -q
@@ -0,0 +1,97 @@
1
+ Metadata-Version: 2.5
2
+ Name: xtree-sdk
3
+ Version: 0.1.0
4
+ Summary: Python client for the XTREE API — real-time raw data layer
5
+ Project-URL: Homepage, https://www.xtree.io
6
+ Project-URL: Documentation, https://www.xtree.io/docs
7
+ Project-URL: Repository, https://github.com/xtree-io/xtree-python
8
+ Author-email: XTREE <business@xtree.io>
9
+ License: MIT
10
+ Keywords: api,sdk,wechat,xtree
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Typing :: Typed
18
+ Requires-Python: >=3.10
19
+ Requires-Dist: httpx<1,>=0.27
20
+ Provides-Extra: dev
21
+ Requires-Dist: pytest-httpx>=0.30; extra == 'dev'
22
+ Requires-Dist: pytest>=8; extra == 'dev'
23
+ Description-Content-Type: text/markdown
24
+
25
+ # XTREE Python SDK
26
+
27
+ Python client for the [XTREE](https://www.xtree.io) social media data API. Scripts and backends call REST through `XTreeClient`. Agent hosts that want tools should use [xtree-mcp](https://github.com/xtree-io/xtree-mcp) (WeChat tools today). **Not** a BI or analytics platform.
28
+
29
+ Covers the public OpenAPI surface (WeChat, Douyin, Xiaohongshu, and the other live platforms). See [API reference](https://www.xtree.io/docs/apis) and [OpenAPI](https://api.xtree.io/api/openapi.json). Facebook, JD, and PDD client classes exist in the source tree and are not live catalog products.
30
+
31
+ ## Install
32
+
33
+ ```bash
34
+ pip install xtree-sdk
35
+ ```
36
+
37
+ Requires Python 3.10+.
38
+
39
+ ## Get an API Key
40
+
41
+ 1. Sign up at [www.xtree.io](https://www.xtree.io/login?tab=signup).
42
+ 2. Copy a key from the account console (`xt_live_...`).
43
+ 3. Export it:
44
+
45
+ ```bash
46
+ export XTREE_API_KEY=xt_live_...
47
+ ```
48
+
49
+ ## Quickstart
50
+
51
+ ```python
52
+ from xtree import XTreeClient
53
+
54
+ client = XTreeClient() # reads XTREE_API_KEY
55
+
56
+ print(client.account.balance().data["balance_cny"])
57
+
58
+ resp = client.wechat.search.articles(keyword="人民日报", page=1)
59
+ print(resp.data)
60
+ print(client.last_meta.cost_cny if client.last_meta else None)
61
+ ```
62
+
63
+ ## CLI
64
+
65
+ ```bash
66
+ xtree balance
67
+ xtree wechat search videos --keyword "人民日报" --json
68
+ xtree call POST /wechat/search/articles --body '{"keyword":"人民日报"}'
69
+ ```
70
+
71
+ ## Billing
72
+
73
+ Pay per call from your wallet (CNY). Incomplete delivery is not billed. Unit prices: [API Catalog](https://www.xtree.io/catalog). No volume tiers and no `/pricing` plans page. Bulk / bank transfer / enterprise: [business@xtree.io](mailto:business@xtree.io).
74
+
75
+ ## Docs
76
+
77
+ - Quick start: https://www.xtree.io/docs
78
+ - API reference: https://www.xtree.io/docs/apis
79
+ - OpenAPI: https://api.xtree.io/api/openapi.json
80
+ - MCP for agents: https://github.com/xtree-io/xtree-mcp
81
+ - Examples: https://github.com/xtree-io/xtree-examples
82
+
83
+ ## Environment
84
+
85
+ | Variable | Required | Default |
86
+ |----------|----------|---------|
87
+ | `XTREE_API_KEY` | yes | — |
88
+ | `XTREE_BASE_URL` | no | `https://api.xtree.io/api` |
89
+ | `XTREE_VERIFY_CODE` | no | — |
90
+
91
+ ## Mirror notice
92
+
93
+ **Do not develop in this repository.** It is a public mirror synced from a private monorepo. Open issues here for SDK / docs feedback. Contact: [business@xtree.io](mailto:business@xtree.io).
94
+
95
+ ## License
96
+
97
+ MIT.
@@ -0,0 +1,73 @@
1
+ # XTREE Python SDK
2
+
3
+ Python client for the [XTREE](https://www.xtree.io) social media data API. Scripts and backends call REST through `XTreeClient`. Agent hosts that want tools should use [xtree-mcp](https://github.com/xtree-io/xtree-mcp) (WeChat tools today). **Not** a BI or analytics platform.
4
+
5
+ Covers the public OpenAPI surface (WeChat, Douyin, Xiaohongshu, and the other live platforms). See [API reference](https://www.xtree.io/docs/apis) and [OpenAPI](https://api.xtree.io/api/openapi.json). Facebook, JD, and PDD client classes exist in the source tree and are not live catalog products.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install xtree-sdk
11
+ ```
12
+
13
+ Requires Python 3.10+.
14
+
15
+ ## Get an API Key
16
+
17
+ 1. Sign up at [www.xtree.io](https://www.xtree.io/login?tab=signup).
18
+ 2. Copy a key from the account console (`xt_live_...`).
19
+ 3. Export it:
20
+
21
+ ```bash
22
+ export XTREE_API_KEY=xt_live_...
23
+ ```
24
+
25
+ ## Quickstart
26
+
27
+ ```python
28
+ from xtree import XTreeClient
29
+
30
+ client = XTreeClient() # reads XTREE_API_KEY
31
+
32
+ print(client.account.balance().data["balance_cny"])
33
+
34
+ resp = client.wechat.search.articles(keyword="人民日报", page=1)
35
+ print(resp.data)
36
+ print(client.last_meta.cost_cny if client.last_meta else None)
37
+ ```
38
+
39
+ ## CLI
40
+
41
+ ```bash
42
+ xtree balance
43
+ xtree wechat search videos --keyword "人民日报" --json
44
+ xtree call POST /wechat/search/articles --body '{"keyword":"人民日报"}'
45
+ ```
46
+
47
+ ## Billing
48
+
49
+ Pay per call from your wallet (CNY). Incomplete delivery is not billed. Unit prices: [API Catalog](https://www.xtree.io/catalog). No volume tiers and no `/pricing` plans page. Bulk / bank transfer / enterprise: [business@xtree.io](mailto:business@xtree.io).
50
+
51
+ ## Docs
52
+
53
+ - Quick start: https://www.xtree.io/docs
54
+ - API reference: https://www.xtree.io/docs/apis
55
+ - OpenAPI: https://api.xtree.io/api/openapi.json
56
+ - MCP for agents: https://github.com/xtree-io/xtree-mcp
57
+ - Examples: https://github.com/xtree-io/xtree-examples
58
+
59
+ ## Environment
60
+
61
+ | Variable | Required | Default |
62
+ |----------|----------|---------|
63
+ | `XTREE_API_KEY` | yes | — |
64
+ | `XTREE_BASE_URL` | no | `https://api.xtree.io/api` |
65
+ | `XTREE_VERIFY_CODE` | no | — |
66
+
67
+ ## Mirror notice
68
+
69
+ **Do not develop in this repository.** It is a public mirror synced from a private monorepo. Open issues here for SDK / docs feedback. Contact: [business@xtree.io](mailto:business@xtree.io).
70
+
71
+ ## License
72
+
73
+ MIT.
@@ -0,0 +1,45 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "xtree-sdk"
7
+ version = "0.1.0"
8
+ description = "Python client for the XTREE API — real-time raw data layer"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "XTREE", email = "business@xtree.io" }]
13
+ keywords = ["xtree", "wechat", "api", "sdk"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Developers",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.10",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Typing :: Typed",
22
+ ]
23
+ dependencies = [
24
+ "httpx>=0.27,<1",
25
+ ]
26
+
27
+ [project.optional-dependencies]
28
+ dev = [
29
+ "pytest>=8",
30
+ "pytest-httpx>=0.30",
31
+ ]
32
+
33
+ [project.urls]
34
+ Homepage = "https://www.xtree.io"
35
+ Documentation = "https://www.xtree.io/docs"
36
+ Repository = "https://github.com/xtree-io/xtree-python"
37
+
38
+ [project.scripts]
39
+ xtree = "xtree.cli:main"
40
+
41
+ [tool.hatch.build.targets.wheel]
42
+ packages = ["xtree"]
43
+
44
+ [tool.pytest.ini_options]
45
+ testpaths = ["tests"]
@@ -0,0 +1,54 @@
1
+ """Client integration tests with httpx mock transport."""
2
+
3
+ import httpx
4
+ import pytest
5
+
6
+ from xtree.client import XTreeClient
7
+
8
+
9
+ def _envelope(data, **meta):
10
+ return {
11
+ "ok": True,
12
+ "data": data,
13
+ "meta": {"request_id": "01JMOCK", **meta},
14
+ "error": None,
15
+ }
16
+
17
+
18
+ def test_balance():
19
+ def handler(request: httpx.Request) -> httpx.Response:
20
+ assert request.headers["X-XTREE-Key"] == "xt_test_key"
21
+ assert request.url.path == "/api/account/balance"
22
+ return httpx.Response(200, json=_envelope({"balance_cny": 42.5}))
23
+
24
+ transport = httpx.MockTransport(handler)
25
+ http = httpx.Client(transport=transport, base_url="https://api.xtree.io/api")
26
+ client = XTreeClient(api_key="xt_test_key", http_client=http)
27
+ resp = client.account.balance()
28
+ assert resp.data["balance_cny"] == 42.5
29
+ assert client.last_meta is not None
30
+ assert client.last_meta.request_id == "01JMOCK"
31
+
32
+
33
+ def test_search_videos():
34
+ captured = {}
35
+
36
+ def handler(request: httpx.Request) -> httpx.Response:
37
+ captured["body"] = request.content.decode()
38
+ return httpx.Response(
39
+ 200,
40
+ json=_envelope({"items": [], "session": "next"}, cost_cny=0.5),
41
+ )
42
+
43
+ transport = httpx.MockTransport(handler)
44
+ http = httpx.Client(transport=transport, base_url="https://api.xtree.io/api")
45
+ client = XTreeClient(api_key="xt_test_key", http_client=http)
46
+ resp = client.wechat.search.videos(keyword="test", page=2)
47
+ assert resp.data["session"] == "next"
48
+ assert client.last_meta.cost_cny == 0.5
49
+ assert '"keyword":"test"' in captured["body"]
50
+
51
+
52
+ def test_missing_api_key():
53
+ with pytest.raises(ValueError, match="XTREE_API_KEY"):
54
+ XTreeClient(api_key=None)
@@ -0,0 +1,42 @@
1
+ """Coverage tests — SDK registry vs OpenAPI spec."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+
8
+ import pytest
9
+
10
+ from xtree.registry import COVERED_ENDPOINTS, SLUG_BY_PATH
11
+
12
+ ROOT = Path(__file__).resolve().parents[2] # sdk/
13
+ SPEC = ROOT / "openapi" / "spec" / "openapi.json"
14
+
15
+
16
+ def _openapi_operations(spec: dict) -> set[tuple[str, str]]:
17
+ ops: set[tuple[str, str]] = set()
18
+ for path, item in spec["paths"].items():
19
+ for method in item:
20
+ if method in ("get", "post", "put", "patch", "delete"):
21
+ ops.add((method.upper(), path))
22
+ return ops
23
+
24
+
25
+ @pytest.fixture(scope="module")
26
+ def openapi_spec() -> dict:
27
+ return json.loads(SPEC.read_text(encoding="utf-8"))
28
+
29
+
30
+ def test_openapi_has_114_operations(openapi_spec: dict) -> None:
31
+ assert len(_openapi_operations(openapi_spec)) == 120
32
+
33
+
34
+ def test_registry_covers_openapi(openapi_spec: dict) -> None:
35
+ openapi_ops = _openapi_operations(openapi_spec)
36
+ covered = set(COVERED_ENDPOINTS)
37
+ assert openapi_ops == covered
38
+
39
+
40
+ def test_slug_map_covers_all_registry_entries() -> None:
41
+ assert set(SLUG_BY_PATH) == set(COVERED_ENDPOINTS)
42
+ assert len(SLUG_BY_PATH) == 120
@@ -0,0 +1,68 @@
1
+ """Envelope parsing tests."""
2
+
3
+ import pytest
4
+
5
+ from xtree._http import _unwrap_envelope
6
+ from xtree.exceptions import (
7
+ XTreeInsufficientBalanceError,
8
+ XTreeRateLimitError,
9
+ XTreeServiceError,
10
+ XTreeUpstreamError,
11
+ )
12
+
13
+
14
+ def test_unwrap_success():
15
+ resp = _unwrap_envelope(
16
+ {
17
+ "ok": True,
18
+ "data": {"balance_cny": 100.0},
19
+ "meta": {"request_id": "01JTEST", "cost_cny": 0},
20
+ "error": None,
21
+ },
22
+ status_code=200,
23
+ )
24
+ assert resp.data["balance_cny"] == 100.0
25
+ assert resp.meta.request_id == "01JTEST"
26
+
27
+
28
+ def test_unwrap_insufficient_balance():
29
+ with pytest.raises(XTreeInsufficientBalanceError) as exc:
30
+ _unwrap_envelope(
31
+ {
32
+ "ok": False,
33
+ "data": None,
34
+ "meta": {"request_id": "01JFAIL"},
35
+ "error": {"code": "INSUFFICIENT_BALANCE", "message": "余额不足"},
36
+ },
37
+ status_code=402,
38
+ )
39
+ assert exc.value.code == "INSUFFICIENT_BALANCE"
40
+ assert exc.value.request_id == "01JFAIL"
41
+
42
+
43
+ def test_unwrap_internal_delivery_failure():
44
+ with pytest.raises(XTreeServiceError) as exc:
45
+ _unwrap_envelope(
46
+ {
47
+ "ok": False,
48
+ "data": None,
49
+ "meta": {"request_id": "01JERR"},
50
+ "error": {"code": "INTERNAL_ERROR", "message": "接口异常"},
51
+ },
52
+ status_code=502,
53
+ )
54
+ assert exc.value.code == "INTERNAL_ERROR"
55
+ assert isinstance(exc.value, XTreeUpstreamError)
56
+
57
+
58
+ def test_unwrap_rate_limited():
59
+ with pytest.raises(XTreeRateLimitError):
60
+ _unwrap_envelope(
61
+ {
62
+ "ok": False,
63
+ "data": None,
64
+ "meta": {"request_id": "01JRATE"},
65
+ "error": {"code": "RATE_LIMITED", "message": "too many requests"},
66
+ },
67
+ status_code=429,
68
+ )
@@ -0,0 +1,7 @@
1
+ """XTREE Python SDK — HTTP client for api.xtree.io."""
2
+
3
+ from xtree.client import XTreeClient
4
+ from xtree.exceptions import XTreeError
5
+
6
+ __all__ = ["XTreeClient", "XTreeError"]
7
+ __version__ = "0.1.0"
@@ -0,0 +1,111 @@
1
+ """HTTP transport with envelope parsing and retries."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import time
6
+ from typing import Any
7
+
8
+ import httpx
9
+
10
+ from xtree.exceptions import XTreeError, error_from_envelope
11
+ from xtree.types import APIResponse, ResponseMeta
12
+
13
+ DEFAULT_BASE_URL = "https://api.xtree.io/api"
14
+ API_KEY_HEADER = "X-XTREE-Key"
15
+ VERIFY_CODE_HEADER = "X-Verify-Code"
16
+ DEFAULT_TIMEOUT = 60.0
17
+ RETRY_STATUS = {429, 502, 503}
18
+ MAX_RETRIES = 3
19
+
20
+
21
+ class HTTPClient:
22
+ def __init__(
23
+ self,
24
+ *,
25
+ api_key: str,
26
+ base_url: str = DEFAULT_BASE_URL,
27
+ verify_code: str | None = None,
28
+ timeout: float = DEFAULT_TIMEOUT,
29
+ client: httpx.Client | None = None,
30
+ ) -> None:
31
+ self.base_url = base_url.rstrip("/")
32
+ self.verify_code = verify_code
33
+ self._owns_client = client is None
34
+ self._client = client or httpx.Client(timeout=timeout)
35
+ self._headers = {
36
+ API_KEY_HEADER: api_key,
37
+ "Accept": "application/json",
38
+ "User-Agent": "xtree-python/0.1.0",
39
+ }
40
+ if verify_code:
41
+ self._headers[VERIFY_CODE_HEADER] = verify_code
42
+ self.last_meta: ResponseMeta | None = None
43
+
44
+ def close(self) -> None:
45
+ if self._owns_client:
46
+ self._client.close()
47
+
48
+ def __enter__(self) -> HTTPClient:
49
+ return self
50
+
51
+ def __exit__(self, *args: object) -> None:
52
+ self.close()
53
+
54
+ def request(
55
+ self,
56
+ method: str,
57
+ path: str,
58
+ *,
59
+ json: dict[str, Any] | None = None,
60
+ params: dict[str, Any] | None = None,
61
+ ) -> APIResponse[Any]:
62
+ url = f"{self.base_url}{path}"
63
+ last_exc: Exception | None = None
64
+
65
+ for attempt in range(MAX_RETRIES + 1):
66
+ try:
67
+ response = self._client.request(
68
+ method,
69
+ url,
70
+ headers=self._headers,
71
+ json=json,
72
+ params=params,
73
+ )
74
+ except httpx.HTTPError as exc:
75
+ last_exc = exc
76
+ if attempt >= MAX_RETRIES:
77
+ raise XTreeError(str(exc), code="TRANSPORT_ERROR") from exc
78
+ time.sleep(2**attempt * 0.5)
79
+ continue
80
+
81
+ payload = _parse_json(response)
82
+ if response.status_code in RETRY_STATUS and attempt < MAX_RETRIES:
83
+ time.sleep(2**attempt * 0.5)
84
+ continue
85
+
86
+ return _unwrap_envelope(payload, status_code=response.status_code)
87
+
88
+ if last_exc:
89
+ raise XTreeError(str(last_exc), code="TRANSPORT_ERROR") from last_exc
90
+ raise XTreeError("Request failed after retries", code="TRANSPORT_ERROR")
91
+
92
+
93
+ def _parse_json(response: httpx.Response) -> dict[str, Any]:
94
+ try:
95
+ data = response.json()
96
+ if isinstance(data, dict):
97
+ return data
98
+ except ValueError:
99
+ pass
100
+ raise XTreeError(
101
+ f"Non-JSON response (HTTP {response.status_code})",
102
+ code="INVALID_RESPONSE",
103
+ status_code=response.status_code,
104
+ )
105
+
106
+
107
+ def _unwrap_envelope(payload: dict[str, Any], *, status_code: int) -> APIResponse[Any]:
108
+ meta = ResponseMeta.from_dict(payload.get("meta") or {})
109
+ if payload.get("ok") is True:
110
+ return APIResponse(data=payload.get("data"), meta=meta)
111
+ raise error_from_envelope(payload, status_code=status_code)
@@ -0,0 +1,19 @@
1
+ """Account endpoints."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from xtree._http import HTTPClient
8
+ from xtree.types import APIResponse
9
+
10
+
11
+ class AccountAPI:
12
+ def __init__(self, http: HTTPClient) -> None:
13
+ self._http = http
14
+
15
+ def balance(self) -> APIResponse[dict[str, Any]]:
16
+ """GET /account/balance — free, returns balance_cny."""
17
+ resp = self._http.request("GET", "/account/balance")
18
+ self._http.last_meta = resp.meta
19
+ return resp
@@ -0,0 +1,40 @@
1
+ """AUTO-GENERATED `bilibili` platform client."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from xtree._http import HTTPClient
8
+ from xtree.types import APIResponse
9
+
10
+
11
+ class BilibiliAPI:
12
+ def __init__(self, http: HTTPClient) -> None:
13
+ self._http = http
14
+
15
+ def search_videos(self, **body: Any) -> APIResponse[Any]:
16
+ return self._http.request("POST", "/bilibili/search/videos", json=body)
17
+
18
+ def videos_detail(self, **body: Any) -> APIResponse[Any]:
19
+ return self._http.request("POST", "/bilibili/videos/detail", json=body)
20
+
21
+ def videos_comments(self, **body: Any) -> APIResponse[Any]:
22
+ return self._http.request("POST", "/bilibili/videos/comments", json=body)
23
+
24
+ def videos_download(self, **body: Any) -> APIResponse[Any]:
25
+ return self._http.request("POST", "/bilibili/videos/download", json=body)
26
+
27
+ def users_profile(self, **body: Any) -> APIResponse[Any]:
28
+ return self._http.request("POST", "/bilibili/users/profile", json=body)
29
+
30
+ def users_posts(self, **body: Any) -> APIResponse[Any]:
31
+ return self._http.request("POST", "/bilibili/users/posts", json=body)
32
+
33
+ def users_videos(self, **body: Any) -> APIResponse[Any]:
34
+ return self._http.request("POST", "/bilibili/users/videos", json=body)
35
+
36
+ def popular_list(self, **body: Any) -> APIResponse[Any]:
37
+ return self._http.request("POST", "/bilibili/popular/list", json=body)
38
+
39
+ def rank_list(self, **body: Any) -> APIResponse[Any]:
40
+ return self._http.request("POST", "/bilibili/rank/list", json=body)