yuque-toolkit 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.
- yuque_toolkit-0.1.0/LICENSE +21 -0
- yuque_toolkit-0.1.0/PKG-INFO +76 -0
- yuque_toolkit-0.1.0/README.md +61 -0
- yuque_toolkit-0.1.0/pyproject.toml +27 -0
- yuque_toolkit-0.1.0/setup.cfg +4 -0
- yuque_toolkit-0.1.0/setup.py +4 -0
- yuque_toolkit-0.1.0/yuque_toolkit/__init__.py +3 -0
- yuque_toolkit-0.1.0/yuque_toolkit/cli.py +161 -0
- yuque_toolkit-0.1.0/yuque_toolkit/client.py +319 -0
- yuque_toolkit-0.1.0/yuque_toolkit.egg-info/PKG-INFO +76 -0
- yuque_toolkit-0.1.0/yuque_toolkit.egg-info/SOURCES.txt +12 -0
- yuque_toolkit-0.1.0/yuque_toolkit.egg-info/dependency_links.txt +1 -0
- yuque_toolkit-0.1.0/yuque_toolkit.egg-info/entry_points.txt +2 -0
- yuque_toolkit-0.1.0/yuque_toolkit.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 fenyuan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: yuque-toolkit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Reusable Python toolkit for Yuque OpenAPI
|
|
5
|
+
Author: fenyuan
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
11
|
+
Requires-Python: >=3.11
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# Yuque Toolkit
|
|
17
|
+
|
|
18
|
+
`yuque-toolkit` 是一个面向 Python 项目的语雀 SDK,封装了常用的语雀 OpenAPI 调用能力,适合在自动化脚本、内容同步任务和内部业务系统中复用。
|
|
19
|
+
|
|
20
|
+
## Features
|
|
21
|
+
|
|
22
|
+
- Get current user info
|
|
23
|
+
- Get repository detail
|
|
24
|
+
- List repository documents
|
|
25
|
+
- Get document detail
|
|
26
|
+
- Create document
|
|
27
|
+
- Update document
|
|
28
|
+
- Get repository TOC
|
|
29
|
+
- Update repository TOC
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install yuque-toolkit
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Environment Variables
|
|
38
|
+
|
|
39
|
+
```env
|
|
40
|
+
YUQUE_BASE_URL=https://www.yuque.com
|
|
41
|
+
YUQUE_TOKEN=your_token_here
|
|
42
|
+
YUQUE_GROUP_LOGIN=your_group_or_user_login
|
|
43
|
+
YUQUE_BOOK_SLUG=your_book_slug
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Quick Start
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
import os
|
|
50
|
+
|
|
51
|
+
from yuque_toolkit import YuqueClient, load_dotenv
|
|
52
|
+
|
|
53
|
+
load_dotenv()
|
|
54
|
+
|
|
55
|
+
client = YuqueClient(
|
|
56
|
+
token=os.getenv("YUQUE_TOKEN", ""),
|
|
57
|
+
base_url=os.getenv("YUQUE_BASE_URL", "https://www.yuque.com"),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
result = client.list_docs(
|
|
61
|
+
os.getenv("YUQUE_GROUP_LOGIN", ""),
|
|
62
|
+
os.getenv("YUQUE_BOOK_SLUG", ""),
|
|
63
|
+
limit=10,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
print(result)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## CLI
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
yuque-toolkit me
|
|
73
|
+
yuque-toolkit repo
|
|
74
|
+
yuque-toolkit docs --limit 10
|
|
75
|
+
yuque-toolkit toc
|
|
76
|
+
```
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Yuque Toolkit
|
|
2
|
+
|
|
3
|
+
`yuque-toolkit` 是一个面向 Python 项目的语雀 SDK,封装了常用的语雀 OpenAPI 调用能力,适合在自动化脚本、内容同步任务和内部业务系统中复用。
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Get current user info
|
|
8
|
+
- Get repository detail
|
|
9
|
+
- List repository documents
|
|
10
|
+
- Get document detail
|
|
11
|
+
- Create document
|
|
12
|
+
- Update document
|
|
13
|
+
- Get repository TOC
|
|
14
|
+
- Update repository TOC
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install yuque-toolkit
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Environment Variables
|
|
23
|
+
|
|
24
|
+
```env
|
|
25
|
+
YUQUE_BASE_URL=https://www.yuque.com
|
|
26
|
+
YUQUE_TOKEN=your_token_here
|
|
27
|
+
YUQUE_GROUP_LOGIN=your_group_or_user_login
|
|
28
|
+
YUQUE_BOOK_SLUG=your_book_slug
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
import os
|
|
35
|
+
|
|
36
|
+
from yuque_toolkit import YuqueClient, load_dotenv
|
|
37
|
+
|
|
38
|
+
load_dotenv()
|
|
39
|
+
|
|
40
|
+
client = YuqueClient(
|
|
41
|
+
token=os.getenv("YUQUE_TOKEN", ""),
|
|
42
|
+
base_url=os.getenv("YUQUE_BASE_URL", "https://www.yuque.com"),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
result = client.list_docs(
|
|
46
|
+
os.getenv("YUQUE_GROUP_LOGIN", ""),
|
|
47
|
+
os.getenv("YUQUE_BOOK_SLUG", ""),
|
|
48
|
+
limit=10,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
print(result)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## CLI
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
yuque-toolkit me
|
|
58
|
+
yuque-toolkit repo
|
|
59
|
+
yuque-toolkit docs --limit 10
|
|
60
|
+
yuque-toolkit toc
|
|
61
|
+
```
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "yuque-toolkit"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Reusable Python toolkit for Yuque OpenAPI"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.11"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "fenyuan" }
|
|
14
|
+
]
|
|
15
|
+
dependencies = []
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.11",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Topic :: Software Development :: Libraries :: Python Modules"
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[project.scripts]
|
|
24
|
+
yuque-toolkit = "yuque_toolkit.cli:main"
|
|
25
|
+
|
|
26
|
+
[tool.setuptools]
|
|
27
|
+
packages = ["yuque_toolkit"]
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from .client import YuqueClient, load_dotenv
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def add_repo_args(parser: argparse.ArgumentParser) -> None:
|
|
12
|
+
parser.add_argument(
|
|
13
|
+
"--group-login",
|
|
14
|
+
default=None,
|
|
15
|
+
help="Repo owner login, falls back to YUQUE_GROUP_LOGIN",
|
|
16
|
+
)
|
|
17
|
+
parser.add_argument(
|
|
18
|
+
"--book-slug",
|
|
19
|
+
default=None,
|
|
20
|
+
help="Repo slug, falls back to YUQUE_BOOK_SLUG",
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def require_repo_args(args: argparse.Namespace) -> tuple[str, str]:
|
|
25
|
+
group_login = args.group_login or os.getenv("YUQUE_GROUP_LOGIN")
|
|
26
|
+
book_slug = args.book_slug or os.getenv("YUQUE_BOOK_SLUG")
|
|
27
|
+
if not group_login or not book_slug:
|
|
28
|
+
raise SystemExit(
|
|
29
|
+
"Repo-scoped command requires --group-login/--book-slug or env defaults"
|
|
30
|
+
)
|
|
31
|
+
return group_login, book_slug
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
35
|
+
parser = argparse.ArgumentParser(description="Reusable Yuque API CLI")
|
|
36
|
+
parser.add_argument("--env-file", default=".env", help="Path to .env file")
|
|
37
|
+
parser.add_argument("--base-url", default=None, help="Override YUQUE_BASE_URL")
|
|
38
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
39
|
+
|
|
40
|
+
subparsers.add_parser("me", help="Get current user")
|
|
41
|
+
|
|
42
|
+
repo_parser = subparsers.add_parser("repo", help="Get repo detail")
|
|
43
|
+
add_repo_args(repo_parser)
|
|
44
|
+
|
|
45
|
+
docs_parser = subparsers.add_parser("docs", help="List docs")
|
|
46
|
+
add_repo_args(docs_parser)
|
|
47
|
+
docs_parser.add_argument("--offset", type=int, default=0)
|
|
48
|
+
docs_parser.add_argument("--limit", type=int, default=20)
|
|
49
|
+
|
|
50
|
+
doc_parser = subparsers.add_parser("doc", help="Get doc detail")
|
|
51
|
+
add_repo_args(doc_parser)
|
|
52
|
+
doc_parser.add_argument("--doc-slug", required=True)
|
|
53
|
+
doc_parser.add_argument("--raw", type=int, default=0)
|
|
54
|
+
|
|
55
|
+
toc_parser = subparsers.add_parser("toc", help="Get repo toc")
|
|
56
|
+
add_repo_args(toc_parser)
|
|
57
|
+
|
|
58
|
+
update_toc_parser = subparsers.add_parser("update-toc", help="Update repo toc")
|
|
59
|
+
add_repo_args(update_toc_parser)
|
|
60
|
+
update_toc_parser.add_argument("--action", required=True)
|
|
61
|
+
update_toc_parser.add_argument("--action-mode")
|
|
62
|
+
update_toc_parser.add_argument("--uuid")
|
|
63
|
+
update_toc_parser.add_argument("--target-uuid")
|
|
64
|
+
update_toc_parser.add_argument("--doc-id", type=int)
|
|
65
|
+
update_toc_parser.add_argument("--doc-ids", nargs="*", type=int)
|
|
66
|
+
update_toc_parser.add_argument("--title")
|
|
67
|
+
update_toc_parser.add_argument("--url")
|
|
68
|
+
update_toc_parser.add_argument("--visible", type=int, choices=[0, 1])
|
|
69
|
+
update_toc_parser.add_argument("--open-window", type=int, choices=[0, 1])
|
|
70
|
+
|
|
71
|
+
create_parser = subparsers.add_parser("create-doc", help="Create doc")
|
|
72
|
+
add_repo_args(create_parser)
|
|
73
|
+
create_parser.add_argument("--title", required=True)
|
|
74
|
+
create_parser.add_argument("--body", required=True)
|
|
75
|
+
create_parser.add_argument("--slug")
|
|
76
|
+
create_parser.add_argument("--public", type=int, choices=[0, 1])
|
|
77
|
+
create_parser.add_argument("--format", default="markdown")
|
|
78
|
+
|
|
79
|
+
update_parser = subparsers.add_parser("update-doc", help="Update doc")
|
|
80
|
+
add_repo_args(update_parser)
|
|
81
|
+
update_parser.add_argument("--doc-slug", required=True)
|
|
82
|
+
update_parser.add_argument("--title")
|
|
83
|
+
update_parser.add_argument("--body")
|
|
84
|
+
update_parser.add_argument("--public", type=int, choices=[0, 1])
|
|
85
|
+
update_parser.add_argument("--format")
|
|
86
|
+
return parser
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def main() -> int:
|
|
90
|
+
parser = build_parser()
|
|
91
|
+
args = parser.parse_args()
|
|
92
|
+
load_dotenv(args.env_file)
|
|
93
|
+
|
|
94
|
+
token = os.getenv("YUQUE_TOKEN", "")
|
|
95
|
+
base_url = args.base_url or os.getenv("YUQUE_BASE_URL", "https://www.yuque.com")
|
|
96
|
+
client = YuqueClient(token=token, base_url=base_url)
|
|
97
|
+
|
|
98
|
+
if args.command == "me":
|
|
99
|
+
result = client.get_user()
|
|
100
|
+
elif args.command == "repo":
|
|
101
|
+
group_login, book_slug = require_repo_args(args)
|
|
102
|
+
result = client.get_repo(group_login, book_slug)
|
|
103
|
+
elif args.command == "docs":
|
|
104
|
+
group_login, book_slug = require_repo_args(args)
|
|
105
|
+
result = client.list_docs(
|
|
106
|
+
group_login, book_slug, offset=args.offset, limit=args.limit
|
|
107
|
+
)
|
|
108
|
+
elif args.command == "doc":
|
|
109
|
+
group_login, book_slug = require_repo_args(args)
|
|
110
|
+
result = client.get_doc(group_login, book_slug, args.doc_slug, raw=args.raw)
|
|
111
|
+
elif args.command == "toc":
|
|
112
|
+
group_login, book_slug = require_repo_args(args)
|
|
113
|
+
result = client.get_toc(group_login, book_slug)
|
|
114
|
+
elif args.command == "create-doc":
|
|
115
|
+
group_login, book_slug = require_repo_args(args)
|
|
116
|
+
result = client.create_doc(
|
|
117
|
+
group_login,
|
|
118
|
+
book_slug,
|
|
119
|
+
title=args.title,
|
|
120
|
+
body=args.body,
|
|
121
|
+
slug=args.slug,
|
|
122
|
+
public=args.public,
|
|
123
|
+
format_=args.format,
|
|
124
|
+
)
|
|
125
|
+
elif args.command == "update-doc":
|
|
126
|
+
group_login, book_slug = require_repo_args(args)
|
|
127
|
+
result = client.update_doc(
|
|
128
|
+
group_login,
|
|
129
|
+
book_slug,
|
|
130
|
+
args.doc_slug,
|
|
131
|
+
title=args.title,
|
|
132
|
+
body=args.body,
|
|
133
|
+
public=args.public,
|
|
134
|
+
format_=args.format,
|
|
135
|
+
)
|
|
136
|
+
elif args.command == "update-toc":
|
|
137
|
+
group_login, book_slug = require_repo_args(args)
|
|
138
|
+
result = client.update_toc(
|
|
139
|
+
group_login,
|
|
140
|
+
book_slug,
|
|
141
|
+
action=args.action,
|
|
142
|
+
action_mode=args.action_mode,
|
|
143
|
+
uuid=args.uuid,
|
|
144
|
+
target_uuid=args.target_uuid,
|
|
145
|
+
doc_id=args.doc_id,
|
|
146
|
+
doc_ids=args.doc_ids,
|
|
147
|
+
title=args.title,
|
|
148
|
+
url=args.url,
|
|
149
|
+
visible=args.visible,
|
|
150
|
+
open_window=args.open_window,
|
|
151
|
+
)
|
|
152
|
+
else:
|
|
153
|
+
raise SystemExit(f"Unsupported command: {args.command}")
|
|
154
|
+
|
|
155
|
+
json.dump(result, sys.stdout, ensure_ascii=False, indent=2)
|
|
156
|
+
sys.stdout.write("\n")
|
|
157
|
+
return 0
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
if __name__ == "__main__":
|
|
161
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Dict, Optional
|
|
7
|
+
from urllib import error, parse, request
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def load_dotenv(path: str = ".env") -> None:
|
|
11
|
+
"""从 `.env` 文件加载环境变量。
|
|
12
|
+
|
|
13
|
+
参数:
|
|
14
|
+
- path: `.env` 文件路径,默认读取项目根目录下的 `.env`
|
|
15
|
+
|
|
16
|
+
返回值:
|
|
17
|
+
- 无
|
|
18
|
+
"""
|
|
19
|
+
env_path = Path(path)
|
|
20
|
+
if not env_path.exists():
|
|
21
|
+
return
|
|
22
|
+
|
|
23
|
+
for raw_line in env_path.read_text(encoding="utf-8").splitlines():
|
|
24
|
+
line = raw_line.strip()
|
|
25
|
+
if not line or line.startswith("#") or "=" not in line:
|
|
26
|
+
continue
|
|
27
|
+
key, value = line.split("=", 1)
|
|
28
|
+
os.environ.setdefault(key.strip(), value.strip().strip("'").strip('"'))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class YuqueClient:
|
|
32
|
+
"""语雀 OpenAPI 的轻量 Python 客户端。
|
|
33
|
+
|
|
34
|
+
参数:
|
|
35
|
+
- token: 语雀 `X-Auth-Token`
|
|
36
|
+
- base_url: 语雀服务地址,默认 `https://www.yuque.com`
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, token: str, base_url: str = "https://www.yuque.com") -> None:
|
|
40
|
+
if not token:
|
|
41
|
+
raise ValueError("YUQUE_TOKEN is required")
|
|
42
|
+
self.base_url = base_url.rstrip("/")
|
|
43
|
+
self.token = token
|
|
44
|
+
|
|
45
|
+
def request(
|
|
46
|
+
self,
|
|
47
|
+
method: str,
|
|
48
|
+
path: str,
|
|
49
|
+
*,
|
|
50
|
+
params: Optional[Dict[str, Any]] = None,
|
|
51
|
+
data: Optional[Dict[str, Any]] = None,
|
|
52
|
+
json_data: Optional[Dict[str, Any]] = None,
|
|
53
|
+
) -> Dict[str, Any]:
|
|
54
|
+
"""发送底层 HTTP 请求到语雀 API。
|
|
55
|
+
|
|
56
|
+
参数:
|
|
57
|
+
- method: HTTP 方法,例如 `GET`、`POST`、`PUT`
|
|
58
|
+
- path: 语雀 API 路径,例如 `/api/v2/user`
|
|
59
|
+
- params: URL 查询参数
|
|
60
|
+
- data: 表单请求体,使用 `application/x-www-form-urlencoded`
|
|
61
|
+
- json_data: JSON 请求体,使用 `application/json`
|
|
62
|
+
|
|
63
|
+
返回值:
|
|
64
|
+
- 解析后的 JSON 字典
|
|
65
|
+
"""
|
|
66
|
+
url = f"{self.base_url}{path}"
|
|
67
|
+
if params:
|
|
68
|
+
query = parse.urlencode(
|
|
69
|
+
{k: v for k, v in params.items() if v is not None and v != ""}
|
|
70
|
+
)
|
|
71
|
+
url = f"{url}?{query}"
|
|
72
|
+
|
|
73
|
+
payload = None
|
|
74
|
+
headers = {
|
|
75
|
+
"X-Auth-Token": self.token,
|
|
76
|
+
"User-Agent": "yuque-toolkit/0.1",
|
|
77
|
+
"Accept": "application/json",
|
|
78
|
+
}
|
|
79
|
+
if data is not None and json_data is not None:
|
|
80
|
+
raise ValueError("data and json_data cannot be used together")
|
|
81
|
+
if data is not None:
|
|
82
|
+
payload = parse.urlencode(data, doseq=True).encode("utf-8")
|
|
83
|
+
headers["Content-Type"] = "application/x-www-form-urlencoded"
|
|
84
|
+
if json_data is not None:
|
|
85
|
+
payload = json.dumps(json_data, ensure_ascii=False).encode("utf-8")
|
|
86
|
+
headers["Content-Type"] = "application/json; charset=utf-8"
|
|
87
|
+
|
|
88
|
+
req = request.Request(url, data=payload, headers=headers, method=method.upper())
|
|
89
|
+
try:
|
|
90
|
+
with request.urlopen(req) as resp:
|
|
91
|
+
body = resp.read().decode("utf-8")
|
|
92
|
+
return json.loads(body) if body else {}
|
|
93
|
+
except error.HTTPError as exc:
|
|
94
|
+
body = exc.read().decode("utf-8", errors="replace")
|
|
95
|
+
raise RuntimeError(
|
|
96
|
+
f"Yuque API error {exc.code} {exc.reason}: {body}"
|
|
97
|
+
) from exc
|
|
98
|
+
except error.URLError as exc:
|
|
99
|
+
raise RuntimeError(f"Yuque API request failed: {exc.reason}") from exc
|
|
100
|
+
|
|
101
|
+
def get_user(self) -> Dict[str, Any]:
|
|
102
|
+
"""获取当前 Token 对应的用户信息。
|
|
103
|
+
|
|
104
|
+
参数:
|
|
105
|
+
- 无
|
|
106
|
+
|
|
107
|
+
返回值:
|
|
108
|
+
- 语雀用户信息字典,通常位于响应的 `data` 字段中
|
|
109
|
+
"""
|
|
110
|
+
return self.request("GET", "/api/v2/user")
|
|
111
|
+
|
|
112
|
+
def get_repo(self, namespace: str, repo_slug: str) -> Dict[str, Any]:
|
|
113
|
+
"""获取指定知识库的详情。
|
|
114
|
+
|
|
115
|
+
参数:
|
|
116
|
+
- namespace: 知识库所属用户或团队登录名
|
|
117
|
+
- repo_slug: 知识库 slug
|
|
118
|
+
|
|
119
|
+
返回值:
|
|
120
|
+
- 知识库详情字典
|
|
121
|
+
"""
|
|
122
|
+
return self.request("GET", f"/api/v2/repos/{namespace}/{repo_slug}")
|
|
123
|
+
|
|
124
|
+
def list_docs(
|
|
125
|
+
self,
|
|
126
|
+
namespace: str,
|
|
127
|
+
repo_slug: str,
|
|
128
|
+
*,
|
|
129
|
+
offset: int = 0,
|
|
130
|
+
limit: int = 20,
|
|
131
|
+
) -> Dict[str, Any]:
|
|
132
|
+
"""分页获取知识库下的文档列表。
|
|
133
|
+
|
|
134
|
+
参数:
|
|
135
|
+
- namespace: 知识库所属用户或团队登录名
|
|
136
|
+
- repo_slug: 知识库 slug
|
|
137
|
+
- offset: 分页偏移量
|
|
138
|
+
- limit: 单次返回数量
|
|
139
|
+
|
|
140
|
+
返回值:
|
|
141
|
+
- 文档列表响应字典
|
|
142
|
+
"""
|
|
143
|
+
return self.request(
|
|
144
|
+
"GET",
|
|
145
|
+
f"/api/v2/repos/{namespace}/{repo_slug}/docs",
|
|
146
|
+
params={"offset": offset, "limit": limit},
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def get_doc(
|
|
150
|
+
self,
|
|
151
|
+
namespace: str,
|
|
152
|
+
repo_slug: str,
|
|
153
|
+
doc_slug: str,
|
|
154
|
+
*,
|
|
155
|
+
raw: int = 0,
|
|
156
|
+
) -> Dict[str, Any]:
|
|
157
|
+
"""获取单篇文档详情。
|
|
158
|
+
|
|
159
|
+
参数:
|
|
160
|
+
- namespace: 知识库所属用户或团队登录名
|
|
161
|
+
- repo_slug: 知识库 slug
|
|
162
|
+
- doc_slug: 文档 slug
|
|
163
|
+
- raw: 是否返回原始内容,通常 `0` 为默认格式,`1` 为原始正文
|
|
164
|
+
|
|
165
|
+
返回值:
|
|
166
|
+
- 文档详情字典
|
|
167
|
+
"""
|
|
168
|
+
return self.request(
|
|
169
|
+
"GET",
|
|
170
|
+
f"/api/v2/repos/{namespace}/{repo_slug}/docs/{doc_slug}",
|
|
171
|
+
params={"raw": raw},
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
def create_doc(
|
|
175
|
+
self,
|
|
176
|
+
namespace: str,
|
|
177
|
+
repo_slug: str,
|
|
178
|
+
*,
|
|
179
|
+
title: str,
|
|
180
|
+
body: str,
|
|
181
|
+
slug: Optional[str] = None,
|
|
182
|
+
public: Optional[int] = None,
|
|
183
|
+
format_: str = "markdown",
|
|
184
|
+
) -> Dict[str, Any]:
|
|
185
|
+
"""在指定知识库中创建文档。
|
|
186
|
+
|
|
187
|
+
参数:
|
|
188
|
+
- namespace: 知识库所属用户或团队登录名
|
|
189
|
+
- repo_slug: 知识库 slug
|
|
190
|
+
- title: 文档标题
|
|
191
|
+
- body: 文档正文
|
|
192
|
+
- slug: 文档 slug,可选
|
|
193
|
+
- public: 可见性,可选,常见为 `0` 或 `1`
|
|
194
|
+
- format_: 文档格式,默认 `markdown`
|
|
195
|
+
|
|
196
|
+
返回值:
|
|
197
|
+
- 新建文档后的响应字典
|
|
198
|
+
"""
|
|
199
|
+
return self.request(
|
|
200
|
+
"POST",
|
|
201
|
+
f"/api/v2/repos/{namespace}/{repo_slug}/docs",
|
|
202
|
+
data={
|
|
203
|
+
"title": title,
|
|
204
|
+
"slug": slug,
|
|
205
|
+
"public": public,
|
|
206
|
+
"format": format_,
|
|
207
|
+
"body": body,
|
|
208
|
+
},
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
def update_doc(
|
|
212
|
+
self,
|
|
213
|
+
namespace: str,
|
|
214
|
+
repo_slug: str,
|
|
215
|
+
doc_slug: str,
|
|
216
|
+
*,
|
|
217
|
+
title: Optional[str] = None,
|
|
218
|
+
body: Optional[str] = None,
|
|
219
|
+
public: Optional[int] = None,
|
|
220
|
+
format_: Optional[str] = None,
|
|
221
|
+
) -> Dict[str, Any]:
|
|
222
|
+
"""更新指定文档的内容或属性。
|
|
223
|
+
|
|
224
|
+
参数:
|
|
225
|
+
- namespace: 知识库所属用户或团队登录名
|
|
226
|
+
- repo_slug: 知识库 slug
|
|
227
|
+
- doc_slug: 文档 slug
|
|
228
|
+
- title: 新标题,可选
|
|
229
|
+
- body: 新正文,可选
|
|
230
|
+
- public: 新可见性,可选
|
|
231
|
+
- format_: 新格式,可选
|
|
232
|
+
|
|
233
|
+
返回值:
|
|
234
|
+
- 更新后的文档响应字典
|
|
235
|
+
"""
|
|
236
|
+
payload = {
|
|
237
|
+
"title": title,
|
|
238
|
+
"body": body,
|
|
239
|
+
"public": public,
|
|
240
|
+
"format": format_,
|
|
241
|
+
}
|
|
242
|
+
return self.request(
|
|
243
|
+
"PUT",
|
|
244
|
+
f"/api/v2/repos/{namespace}/{repo_slug}/docs/{doc_slug}",
|
|
245
|
+
data={k: v for k, v in payload.items() if v is not None},
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
def get_toc(self, namespace: str, repo_slug: str) -> Dict[str, Any]:
|
|
249
|
+
"""获取知识库目录树。
|
|
250
|
+
|
|
251
|
+
参数:
|
|
252
|
+
- namespace: 知识库所属用户或团队登录名
|
|
253
|
+
- repo_slug: 知识库 slug
|
|
254
|
+
|
|
255
|
+
返回值:
|
|
256
|
+
- 目录结构响应字典
|
|
257
|
+
"""
|
|
258
|
+
return self.request("GET", f"/api/v2/repos/{namespace}/{repo_slug}/toc")
|
|
259
|
+
|
|
260
|
+
def update_toc(
|
|
261
|
+
self,
|
|
262
|
+
namespace: str,
|
|
263
|
+
repo_slug: str,
|
|
264
|
+
*,
|
|
265
|
+
action: str,
|
|
266
|
+
action_mode: Optional[str] = None,
|
|
267
|
+
uuid: Optional[str] = None,
|
|
268
|
+
target_uuid: Optional[str] = None,
|
|
269
|
+
doc_id: Optional[int] = None,
|
|
270
|
+
doc_ids: Optional[list[int]] = None,
|
|
271
|
+
title: Optional[str] = None,
|
|
272
|
+
url: Optional[str] = None,
|
|
273
|
+
visible: Optional[int] = None,
|
|
274
|
+
open_window: Optional[int] = None,
|
|
275
|
+
) -> Dict[str, Any]:
|
|
276
|
+
"""调用知识库目录写接口。
|
|
277
|
+
|
|
278
|
+
参数:
|
|
279
|
+
- namespace: 知识库所属用户或团队登录名
|
|
280
|
+
- repo_slug: 知识库 slug
|
|
281
|
+
- action: 目录操作类型,例如 `appendByDocs`、`moveAfter`、`syncDoc`
|
|
282
|
+
- action_mode: 目录操作模式,常见为 `sibling` 或 `child`
|
|
283
|
+
- uuid: 当前目录节点 uuid
|
|
284
|
+
- target_uuid: 目标目录节点 uuid
|
|
285
|
+
- doc_id: 单个文档 id
|
|
286
|
+
- doc_ids: 批量文档 id 列表
|
|
287
|
+
- title: 目录节点标题
|
|
288
|
+
- url: 目录节点链接
|
|
289
|
+
- visible: 节点是否可见
|
|
290
|
+
- open_window: 是否新窗口打开
|
|
291
|
+
|
|
292
|
+
返回值:
|
|
293
|
+
- 目录更新后的响应字典
|
|
294
|
+
"""
|
|
295
|
+
payload: Dict[str, Any] = {"action": action}
|
|
296
|
+
if action_mode:
|
|
297
|
+
payload["action_mode"] = action_mode
|
|
298
|
+
if uuid:
|
|
299
|
+
payload["uuid"] = uuid
|
|
300
|
+
if target_uuid:
|
|
301
|
+
payload["target_uuid"] = target_uuid
|
|
302
|
+
if doc_id is not None:
|
|
303
|
+
payload["doc_id"] = doc_id
|
|
304
|
+
if doc_ids:
|
|
305
|
+
payload["doc_ids"] = doc_ids
|
|
306
|
+
if title is not None:
|
|
307
|
+
payload["title"] = title
|
|
308
|
+
if url is not None:
|
|
309
|
+
payload["url"] = url
|
|
310
|
+
if visible is not None:
|
|
311
|
+
payload["visible"] = visible
|
|
312
|
+
if open_window is not None:
|
|
313
|
+
payload["open_window"] = open_window
|
|
314
|
+
|
|
315
|
+
return self.request(
|
|
316
|
+
"PUT",
|
|
317
|
+
f"/api/v2/repos/{namespace}/{repo_slug}/toc",
|
|
318
|
+
json_data=payload,
|
|
319
|
+
)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: yuque-toolkit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Reusable Python toolkit for Yuque OpenAPI
|
|
5
|
+
Author: fenyuan
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
11
|
+
Requires-Python: >=3.11
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# Yuque Toolkit
|
|
17
|
+
|
|
18
|
+
`yuque-toolkit` 是一个面向 Python 项目的语雀 SDK,封装了常用的语雀 OpenAPI 调用能力,适合在自动化脚本、内容同步任务和内部业务系统中复用。
|
|
19
|
+
|
|
20
|
+
## Features
|
|
21
|
+
|
|
22
|
+
- Get current user info
|
|
23
|
+
- Get repository detail
|
|
24
|
+
- List repository documents
|
|
25
|
+
- Get document detail
|
|
26
|
+
- Create document
|
|
27
|
+
- Update document
|
|
28
|
+
- Get repository TOC
|
|
29
|
+
- Update repository TOC
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install yuque-toolkit
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Environment Variables
|
|
38
|
+
|
|
39
|
+
```env
|
|
40
|
+
YUQUE_BASE_URL=https://www.yuque.com
|
|
41
|
+
YUQUE_TOKEN=your_token_here
|
|
42
|
+
YUQUE_GROUP_LOGIN=your_group_or_user_login
|
|
43
|
+
YUQUE_BOOK_SLUG=your_book_slug
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Quick Start
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
import os
|
|
50
|
+
|
|
51
|
+
from yuque_toolkit import YuqueClient, load_dotenv
|
|
52
|
+
|
|
53
|
+
load_dotenv()
|
|
54
|
+
|
|
55
|
+
client = YuqueClient(
|
|
56
|
+
token=os.getenv("YUQUE_TOKEN", ""),
|
|
57
|
+
base_url=os.getenv("YUQUE_BASE_URL", "https://www.yuque.com"),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
result = client.list_docs(
|
|
61
|
+
os.getenv("YUQUE_GROUP_LOGIN", ""),
|
|
62
|
+
os.getenv("YUQUE_BOOK_SLUG", ""),
|
|
63
|
+
limit=10,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
print(result)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## CLI
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
yuque-toolkit me
|
|
73
|
+
yuque-toolkit repo
|
|
74
|
+
yuque-toolkit docs --limit 10
|
|
75
|
+
yuque-toolkit toc
|
|
76
|
+
```
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
setup.py
|
|
5
|
+
yuque_toolkit/__init__.py
|
|
6
|
+
yuque_toolkit/cli.py
|
|
7
|
+
yuque_toolkit/client.py
|
|
8
|
+
yuque_toolkit.egg-info/PKG-INFO
|
|
9
|
+
yuque_toolkit.egg-info/SOURCES.txt
|
|
10
|
+
yuque_toolkit.egg-info/dependency_links.txt
|
|
11
|
+
yuque_toolkit.egg-info/entry_points.txt
|
|
12
|
+
yuque_toolkit.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
yuque_toolkit
|