getnotes-cli 0.1.0__tar.gz → 0.1.1__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.
@@ -5,6 +5,13 @@
5
5
  此文件的格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),
6
6
  并且本项目遵循 [语义化版本规范 (Semantic Versioning)](https://semver.org/spec/v2.0.0.html)。
7
7
 
8
+ ## [0.1.1] - 2026-02-27
9
+
10
+ ### 新增 (Added)
11
+ - **创建笔记功能**:实现创建新笔记功能,支持图片上传并自动转换链接。提供 CLI 命令集成支持本地直接新建笔记。
12
+ - **文档完善**:新增了有关知识库 (notebooks)、笔记 (notes) 以及 Agent 工作流 (AGENT.md) 的使用说明;添加了 GitHub 评论操作文档指南。
13
+ - **发布配置更新**:优化与新增部分 GitHub Workflow 以支持自动化部署与评论。
14
+
8
15
  ## [0.1.0] - 2026-02-27
9
16
 
10
17
  这是 `getnotes-cli` 的首次发布版本,包含以下核心功能集锦:
@@ -1,3 +1,20 @@
1
+ Metadata-Version: 2.4
2
+ Name: getnotes-cli
3
+ Version: 0.1.1
4
+ Summary: Get 笔记 CLI 下载工具 — 自动登录、批量下载、Markdown 导出
5
+ Author: Han Zhang
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Keywords: cli,download,getnotes,markdown,得到笔记
9
+ Requires-Python: >=3.10
10
+ Requires-Dist: httpx>=0.27.0
11
+ Requires-Dist: requests-toolbelt>=1.0.0
12
+ Requires-Dist: requests>=2.0.0
13
+ Requires-Dist: rich>=13.0.0
14
+ Requires-Dist: typer>=0.9.0
15
+ Requires-Dist: websocket-client>=1.6.0
16
+ Description-Content-Type: text/markdown
17
+
1
18
  # getnotes-cli 🗂️
2
19
 
3
20
  > Get笔记 CLI 下载工具 — 自动登录、批量下载、知识库管理、Markdown 导出、录音图片等附件下载
@@ -9,6 +26,7 @@
9
26
 
10
27
  - 🔐 **自动登录** — 通过 Chrome DevTools Protocol 自动获取 Bearer token,无需手动抓包
11
28
  - 📥 **批量下载** — 分页拉取全部笔记,支持指定数量
29
+ - 📤 **新建笔记** — 支持通过本地 Markdown 或文本文件创建笔记,并支持自动上传内嵌图片
12
30
  - 📚 **知识库管理** — 查看、下载我的知识库与订阅知识库
13
31
  - 📝 **Markdown 导出** — 每条笔记保存为 Markdown,包含元信息、标签、正文、引用内容
14
32
  - 🔊 **附件下载** — 自动下载音频、图片附件,并在 Markdown 中内嵌链接
@@ -40,6 +58,19 @@ getnotes login
40
58
  getnotes login --token "Bearer eyJhbGci..."
41
59
  ```
42
60
 
61
+ ### 新建笔记
62
+
63
+ ```bash
64
+ # 从本地 Markdown 或文本文件创建得到笔记
65
+ getnotes create --file my_note.md
66
+
67
+ # 创建笔记并附带一张图片(图片将自动上传并追加到正文末尾)
68
+ getnotes create -f my_note.md --image cover.jpg
69
+
70
+ # 创建笔记并附带多张图片(多次指定 -i 或 --image 选项)
71
+ getnotes create -f my_note.md -i img1.png -i img2.jpg
72
+ ```
73
+
43
74
  ### 下载笔记
44
75
 
45
76
  ```bash
@@ -162,6 +193,7 @@ getnotes --version
162
193
 
163
194
  # 查看帮助
164
195
  getnotes --help
196
+ getnotes create --help
165
197
  getnotes download --help
166
198
  getnotes notebook --help
167
199
  getnotes subscribe --help
@@ -225,3 +257,7 @@ getnotes_export/
225
257
  - 已下载的附件不会重复下载(自动跳过)
226
258
  - 默认下载前 100 条用于调试,确认无误后使用 `--all` 下载全部
227
259
  - 知识库下载按知识库名称创建子目录,统一保存在 `notebooks/` 下
260
+
261
+ ## 🙏 致谢
262
+
263
+ - 部分登录逻辑及设计参考自 [notebooklm-mcp-cli](https://github.com/jacob-bd/notebooklm-mcp-cli)。
@@ -1,18 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: getnotes-cli
3
- Version: 0.1.0
4
- Summary: 得到笔记 (GetNotes) CLI 下载工具 — 自动登录、批量下载、Markdown 导出
5
- Author: Han Zhang
6
- License: MIT
7
- License-File: LICENSE
8
- Keywords: cli,download,getnotes,markdown,得到笔记
9
- Requires-Python: >=3.10
10
- Requires-Dist: httpx>=0.27.0
11
- Requires-Dist: rich>=13.0.0
12
- Requires-Dist: typer>=0.9.0
13
- Requires-Dist: websocket-client>=1.6.0
14
- Description-Content-Type: text/markdown
15
-
16
1
  # getnotes-cli 🗂️
17
2
 
18
3
  > Get笔记 CLI 下载工具 — 自动登录、批量下载、知识库管理、Markdown 导出、录音图片等附件下载
@@ -24,6 +9,7 @@ Description-Content-Type: text/markdown
24
9
 
25
10
  - 🔐 **自动登录** — 通过 Chrome DevTools Protocol 自动获取 Bearer token,无需手动抓包
26
11
  - 📥 **批量下载** — 分页拉取全部笔记,支持指定数量
12
+ - 📤 **新建笔记** — 支持通过本地 Markdown 或文本文件创建笔记,并支持自动上传内嵌图片
27
13
  - 📚 **知识库管理** — 查看、下载我的知识库与订阅知识库
28
14
  - 📝 **Markdown 导出** — 每条笔记保存为 Markdown,包含元信息、标签、正文、引用内容
29
15
  - 🔊 **附件下载** — 自动下载音频、图片附件,并在 Markdown 中内嵌链接
@@ -55,6 +41,19 @@ getnotes login
55
41
  getnotes login --token "Bearer eyJhbGci..."
56
42
  ```
57
43
 
44
+ ### 新建笔记
45
+
46
+ ```bash
47
+ # 从本地 Markdown 或文本文件创建得到笔记
48
+ getnotes create --file my_note.md
49
+
50
+ # 创建笔记并附带一张图片(图片将自动上传并追加到正文末尾)
51
+ getnotes create -f my_note.md --image cover.jpg
52
+
53
+ # 创建笔记并附带多张图片(多次指定 -i 或 --image 选项)
54
+ getnotes create -f my_note.md -i img1.png -i img2.jpg
55
+ ```
56
+
58
57
  ### 下载笔记
59
58
 
60
59
  ```bash
@@ -177,6 +176,7 @@ getnotes --version
177
176
 
178
177
  # 查看帮助
179
178
  getnotes --help
179
+ getnotes create --help
180
180
  getnotes download --help
181
181
  getnotes notebook --help
182
182
  getnotes subscribe --help
@@ -240,3 +240,7 @@ getnotes_export/
240
240
  - 已下载的附件不会重复下载(自动跳过)
241
241
  - 默认下载前 100 条用于调试,确认无误后使用 `--all` 下载全部
242
242
  - 知识库下载按知识库名称创建子目录,统一保存在 `notebooks/` 下
243
+
244
+ ## 🙏 致谢
245
+
246
+ - 部分登录逻辑及设计参考自 [notebooklm-mcp-cli](https://github.com/jacob-bd/notebooklm-mcp-cli)。
@@ -4,8 +4,8 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "getnotes-cli"
7
- version = "0.1.0"
8
- description = "得到笔记 (GetNotes) CLI 下载工具 — 自动登录、批量下载、Markdown 导出"
7
+ version = "0.1.1"
8
+ description = "Get 笔记 CLI 下载工具 — 自动登录、批量下载、Markdown 导出"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
11
11
  requires-python = ">=3.10"
@@ -19,6 +19,8 @@ dependencies = [
19
19
  "rich>=13.0.0",
20
20
  "httpx>=0.27.0",
21
21
  "websocket-client>=1.6.0",
22
+ "requests-toolbelt>=1.0.0",
23
+ "requests>=2.0.0",
22
24
  ]
23
25
 
24
26
  [project.scripts]
@@ -58,6 +58,60 @@ def login(
58
58
  raise typer.Exit(1)
59
59
 
60
60
 
61
+ # ========================================================================
62
+ # create 命令
63
+ # ========================================================================
64
+
65
+
66
+ @app.command()
67
+ def create(
68
+ file: Path = typer.Option(
69
+ ..., "--file", "-f",
70
+ exists=True, dir_okay=False, readable=True, resolve_path=True,
71
+ help="要创建为得到笔记的 Markdown 文件或文本文件",
72
+ ),
73
+ images: Optional[list[Path]] = typer.Option(
74
+ None, "--image", "-i",
75
+ exists=True, dir_okay=False, readable=True, resolve_path=True,
76
+ help="要上传并插入的图片文件(可多次指定),将追加到文本末尾",
77
+ ),
78
+ token: Optional[str] = typer.Option(
79
+ None, "--token", "-t",
80
+ help="直接传入 Bearer token(跳过缓存检查)",
81
+ ),
82
+ ) -> None:
83
+ """📝 创建笔记 — 从本地文件与图片发布得到笔记"""
84
+ from getnotes_cli.auth import get_or_refresh_token, login_with_token
85
+ from getnotes_cli.creator import NoteCreator
86
+
87
+ # 获取 token
88
+ if token:
89
+ auth = login_with_token(token)
90
+ else:
91
+ try:
92
+ auth = get_or_refresh_token()
93
+ except RuntimeError as e:
94
+ console.print(f"\n[red]✗[/red] {e}")
95
+ console.print("[dim]请先运行 `getnotes login` 登录。[/dim]")
96
+ raise typer.Exit(1)
97
+
98
+ text = file.read_text(encoding="utf-8")
99
+ creator = NoteCreator(auth)
100
+
101
+ console.print(f"[bold]📝 正在创建笔记: {file.name}[/bold]")
102
+ if images:
103
+ console.print(f"[dim]包含 {len(images)} 张图片待上传...[/dim]")
104
+
105
+ try:
106
+ data = creator.create_note(text, images)
107
+ console.print(f"\n[green]✓[/green] 创建笔记成功!")
108
+ console.print(f" ID: {data.get('note_id', '')}")
109
+ console.print(f" 时间: {data.get('created_at', '')}")
110
+ except Exception as e:
111
+ console.print(f"\n[red]✗[/red] 创建失败: {e}")
112
+ raise typer.Exit(1)
113
+
114
+
61
115
  # ========================================================================
62
116
  # download 命令
63
117
  # ========================================================================
@@ -12,6 +12,12 @@ NOTES_API_URL = "https://get-notes.luojilab.com/voicenotes/web/notes"
12
12
  # 知识库列表 API
13
13
  NOTEBOOKS_API_URL = "https://knowledge-api.trytalks.com/v1/web/topic/mine/list"
14
14
 
15
+ # 创建笔记 API
16
+ NOTE_CREATE_API_URL = "https://get-notes.luojilab.com/voicenotes/web/notes"
17
+
18
+ # 获取图片上传 Token API
19
+ IMAGE_TOKEN_API_URL = "https://get-notes.luojilab.com/voicenotes/web/token/image"
20
+
15
21
  # 订阅知识库列表 API
16
22
  SUBSCRIBE_NOTEBOOKS_API_URL = "https://knowledge-api.trytalks.com/v1/web/subscribe/topic/list"
17
23
 
@@ -0,0 +1,178 @@
1
+ """笔记创建模块 — 处理图片上传与笔记发布"""
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ import requests
8
+ from requests_toolbelt.multipart.encoder import MultipartEncoder
9
+
10
+ from getnotes_cli.auth import AuthToken
11
+ from getnotes_cli.config import IMAGE_TOKEN_API_URL, NOTE_CREATE_API_URL
12
+
13
+ class NoteCreator:
14
+ """笔记创建器,处理图片上传和笔记创建"""
15
+
16
+ def __init__(self, token: AuthToken):
17
+ self.token = token
18
+ self.headers = token.get_headers()
19
+
20
+ def _get_image_token(self, ext: str) -> dict[str, Any]:
21
+ """获取阿里云 OSS 上传凭证"""
22
+ payload = {"source": "web", "type": ext.lstrip(".")}
23
+ resp = requests.post(
24
+ IMAGE_TOKEN_API_URL,
25
+ headers=self.headers,
26
+ json=payload,
27
+ timeout=10,
28
+ )
29
+ resp.raise_for_status()
30
+ data = resp.json()
31
+ if data.get("h", {}).get("c") != 0:
32
+ raise RuntimeError(f"获取上传凭证失败: {data}")
33
+ # 返回第一条凭证
34
+ return data.get("c", [])[0]
35
+
36
+ def upload_image(self, image_path: Path) -> dict[str, Any]:
37
+ """上传图片到阿里云 OSS,返回图片信息(包含 access_url)"""
38
+ if not image_path.exists():
39
+ raise FileNotFoundError(f"图片不存在: {image_path}")
40
+
41
+ ext = image_path.suffix.lower()
42
+ if ext not in (".png", ".jpg", ".jpeg", ".gif", ".webp"):
43
+ raise ValueError(f"不支持的图片格式: {ext}")
44
+
45
+ # 1. 获取上传凭证
46
+ token_info = self._get_image_token(ext)
47
+
48
+ # 2. 组装表单数据
49
+ # 阿里云 OSS 要求表单字段顺序:前面的参数,最后是 file
50
+ fields = {
51
+ "OSSAccessKeyId": token_info["accessid"],
52
+ "policy": token_info["policy"],
53
+ "Signature": token_info["signature"],
54
+ "key": token_info["object_key"],
55
+ "callback": token_info["callback"],
56
+ "success_action_status": "201",
57
+ "file": (image_path.name, open(image_path, "rb"), token_info.get("oss_content_type", f"image/{ext.lstrip('.')}")),
58
+ }
59
+
60
+ encoder = MultipartEncoder(fields=fields)
61
+ upload_headers = {
62
+ "Accept": "application/json, text/plain, */*",
63
+ "Content-Type": encoder.content_type,
64
+ "User-Agent": self.headers.get("User-Agent", ""),
65
+ "Origin": self.headers.get("Origin", ""),
66
+ "Referer": self.headers.get("Referer", ""),
67
+ }
68
+
69
+ # 3. 上传到 OSS
70
+ resp = requests.post(
71
+ token_info["host"],
72
+ headers=upload_headers,
73
+ data=encoder,
74
+ timeout=30,
75
+ )
76
+ resp.raise_for_status()
77
+
78
+ # 得到笔记的 callback 返回的是 json
79
+ try:
80
+ callback_data = resp.json()
81
+ if callback_data.get("h", {}).get("c") != 0:
82
+ raise RuntimeError(f"图片上传回调失败: {callback_data}")
83
+ except json.JSONDecodeError:
84
+ pass # 有时不会返回标准 json
85
+
86
+ return token_info
87
+
88
+ def _build_json_content(self, text: str, images: list[dict[str, Any]]) -> str:
89
+ """根据文本和图片构建 ProseMirror json_content"""
90
+ content_nodes = []
91
+
92
+ # 文本段落
93
+ if text:
94
+ for paragraph in text.split("\\n"):
95
+ content_nodes.append({
96
+ "type": "paragraph",
97
+ "attrs": {"lineHeight": "100%", "textAlign": None, "class": "", "indent": 0},
98
+ "content": [{"type": "text", "text": paragraph}] if paragraph else []
99
+ })
100
+ else:
101
+ content_nodes.append({
102
+ "type": "paragraph",
103
+ "attrs": {"lineHeight": "100%", "textAlign": None, "class": "", "indent": 0},
104
+ })
105
+
106
+ # 图片节点
107
+ for img in images:
108
+ content_nodes.append({
109
+ "type": "image",
110
+ "attrs": {
111
+ "commentIds": None,
112
+ "class": "",
113
+ "src": img["access_url"],
114
+ "alt": "",
115
+ "title": "",
116
+ "width": 350,
117
+ "height": "auto",
118
+ "align": "left",
119
+ "href": None,
120
+ "target": None,
121
+ "data-src": None,
122
+ "loading": None
123
+ }
124
+ })
125
+
126
+ # 图片后跟一个空段落
127
+ content_nodes.append({
128
+ "type": "paragraph",
129
+ "attrs": {"lineHeight": "100%", "textAlign": None, "class": None, "indent": 0}
130
+ })
131
+
132
+ json_content = {
133
+ "type": "doc",
134
+ "content": content_nodes
135
+ }
136
+ return json.dumps(json_content, ensure_ascii=False)
137
+
138
+ def create_note(self, text: str, image_paths: list[Path] = None) -> dict[str, Any]:
139
+ """创建笔记"""
140
+ image_paths = image_paths or []
141
+ uploaded_images = []
142
+
143
+ # 1. 上传所有图片
144
+ for img_path in image_paths:
145
+ img_info = self.upload_image(img_path)
146
+ uploaded_images.append(img_info)
147
+
148
+ # 2. 组装纯文本 content(末尾添加图片 markdown 语法)
149
+ content = text
150
+ if uploaded_images:
151
+ content += "\\n\\n" + "\\n".join([f"![]({img['access_url']})" for img in uploaded_images])
152
+
153
+ # 3. 组装 json_content
154
+ json_content_str = self._build_json_content(text, uploaded_images)
155
+
156
+ # 4. 发送创建请求
157
+ payload = {
158
+ "title": "",
159
+ "content": content,
160
+ "json_content": json_content_str,
161
+ "entry_type": "manual",
162
+ "note_type": "plain_text",
163
+ "source": "web",
164
+ "tags": []
165
+ }
166
+
167
+ resp = requests.post(
168
+ NOTE_CREATE_API_URL,
169
+ headers=self.headers,
170
+ json=payload,
171
+ timeout=10,
172
+ )
173
+ resp.raise_for_status()
174
+ data = resp.json()
175
+ if data.get("h", {}).get("c") != 0:
176
+ raise RuntimeError(f"创建笔记失败: {data}")
177
+
178
+ return data.get("c", {})
File without changes
File without changes