Initial commit: Python library for Markdown note services
- Add MDAuth class for authentication and session management - Add MDClient class for note operations (CRUD + export/search) - Add CLI tool mdclient_cli.py for quick operations - Support for CodiMD, HedgeDoc, and HackMD services - Multi-endpoint API compatibility Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
This commit is contained in:
12
.env.example
Normal file
12
.env.example
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# MDclient 設定檔案範例
|
||||||
|
# 複製此檔案為 .env 並填入你的資訊
|
||||||
|
|
||||||
|
# 伺服器位址
|
||||||
|
MDCLIENT_URL=https://codimd.lotimmy.com
|
||||||
|
|
||||||
|
# 登入帳密
|
||||||
|
MDCLIENT_EMAIL=your@email.com
|
||||||
|
MDCLIENT_PASSWORD=your_password
|
||||||
|
|
||||||
|
# Cookie 檔案路徑(可選,預設為 key.conf)
|
||||||
|
# MDCLIENT_COOKIE_FILE=key.conf
|
||||||
31
.gitignore
vendored
Normal file
31
.gitignore
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# Environment and credentials
|
||||||
|
.env
|
||||||
|
key.conf
|
||||||
|
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
ENV/
|
||||||
|
|
||||||
|
# Build artifacts
|
||||||
|
mdclient.egg-info/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
255
API.md
Normal file
255
API.md
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
# MDclient API 文件
|
||||||
|
|
||||||
|
## MDAuth
|
||||||
|
|
||||||
|
認證類,用於登入和管理會話。
|
||||||
|
|
||||||
|
### 建構函式
|
||||||
|
|
||||||
|
```python
|
||||||
|
MDAuth(base_url="https://codimd.lotimmy.com", cookie_file="key.conf")
|
||||||
|
```
|
||||||
|
|
||||||
|
**參數:**
|
||||||
|
- `base_url` (str): 伺服器位址
|
||||||
|
- `cookie_file` (str): Cookie 儲存檔案路徑
|
||||||
|
|
||||||
|
### 方法
|
||||||
|
|
||||||
|
#### login()
|
||||||
|
|
||||||
|
登入並儲存 Cookie。
|
||||||
|
|
||||||
|
```python
|
||||||
|
success = auth.login(email, password)
|
||||||
|
```
|
||||||
|
|
||||||
|
**參數:**
|
||||||
|
- `email` (str): 登入信箱
|
||||||
|
- `password` (str): 登入密碼
|
||||||
|
|
||||||
|
**回傳:**
|
||||||
|
- `bool`: 是否登入成功
|
||||||
|
|
||||||
|
#### test_cookie()
|
||||||
|
|
||||||
|
測試儲存的 Cookie 是否有效。
|
||||||
|
|
||||||
|
```python
|
||||||
|
is_valid = auth.test_cookie()
|
||||||
|
```
|
||||||
|
|
||||||
|
**回傳:**
|
||||||
|
- `bool`: Cookie 是否有效
|
||||||
|
|
||||||
|
#### load_cookie()
|
||||||
|
|
||||||
|
從檔案載入 Cookie。
|
||||||
|
|
||||||
|
```python
|
||||||
|
cookies = auth.load_cookie()
|
||||||
|
```
|
||||||
|
|
||||||
|
**回傳:**
|
||||||
|
- `dict` | `None`: Cookie 字典,檔案不存在時返回 None
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## MDClient
|
||||||
|
|
||||||
|
客戶端類,提供所有筆記操作的 API。
|
||||||
|
|
||||||
|
### 建構函式
|
||||||
|
|
||||||
|
```python
|
||||||
|
MDClient(base_url="https://codimd.lotimmy.com", cookie_file="key.conf", email=None, password=None)
|
||||||
|
```
|
||||||
|
|
||||||
|
**參數:**
|
||||||
|
- `base_url` (str): 伺服器位址
|
||||||
|
- `cookie_file` (str): Cookie 檔案路徑
|
||||||
|
- `email` (str): 登入信箱(可選)
|
||||||
|
- `password` (str): 登入密碼(可選)
|
||||||
|
|
||||||
|
### 方法
|
||||||
|
|
||||||
|
#### list_notes()
|
||||||
|
|
||||||
|
列出所有筆記。
|
||||||
|
|
||||||
|
```python
|
||||||
|
notes = client.list_notes()
|
||||||
|
```
|
||||||
|
|
||||||
|
**回傳:**
|
||||||
|
- `list` | `None`: 筆記列表,失敗時返回 None
|
||||||
|
|
||||||
|
**筆記物件結構:**
|
||||||
|
```python
|
||||||
|
{
|
||||||
|
"id": "note_id",
|
||||||
|
"text": "筆記標題",
|
||||||
|
"time": 1234567890,
|
||||||
|
"tags": ["tag1", "tag2"],
|
||||||
|
"pinned": False
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### get_note()
|
||||||
|
|
||||||
|
取得筆記內容。
|
||||||
|
|
||||||
|
```python
|
||||||
|
note = client.get_note(note_id)
|
||||||
|
```
|
||||||
|
|
||||||
|
**參數:**
|
||||||
|
- `note_id` (str): 筆記 ID
|
||||||
|
|
||||||
|
**回傳:**
|
||||||
|
- `dict` | `None`: 筆記內容,失敗時返回 None
|
||||||
|
|
||||||
|
**筆記內容結構:**
|
||||||
|
```python
|
||||||
|
{
|
||||||
|
"id": "note_id",
|
||||||
|
"title": "筆記標題",
|
||||||
|
"content": "Markdown 內容",
|
||||||
|
"lastChangeAt": "2024-01-01T00:00:00.000Z",
|
||||||
|
"createdAt": "2024-01-01T00:00:00.000Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### create_note()
|
||||||
|
|
||||||
|
建立新筆記。
|
||||||
|
|
||||||
|
```python
|
||||||
|
result = client.create_note(title, content="")
|
||||||
|
```
|
||||||
|
|
||||||
|
**參數:**
|
||||||
|
- `title` (str): 筆記標題
|
||||||
|
- `content` (str): Markdown 內容
|
||||||
|
|
||||||
|
**回傳:**
|
||||||
|
- `dict` | `None`: 建立結果,失敗時返回 None
|
||||||
|
|
||||||
|
**回傳結構:**
|
||||||
|
```python
|
||||||
|
{
|
||||||
|
"id": "new_note_id",
|
||||||
|
"url": "https://server.com/new_note_id"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### delete_note()
|
||||||
|
|
||||||
|
刪除筆記。
|
||||||
|
|
||||||
|
```python
|
||||||
|
success = client.delete_note(note_id)
|
||||||
|
```
|
||||||
|
|
||||||
|
**參數:**
|
||||||
|
- `note_id` (str): 筆記 ID
|
||||||
|
|
||||||
|
**回傳:**
|
||||||
|
- `bool`: 是否刪除成功
|
||||||
|
|
||||||
|
#### export_note()
|
||||||
|
|
||||||
|
匯出筆記。
|
||||||
|
|
||||||
|
```python
|
||||||
|
content = client.export_note(note_id, format="md")
|
||||||
|
```
|
||||||
|
|
||||||
|
**參數:**
|
||||||
|
- `note_id` (str): 筆記 ID
|
||||||
|
- `format` (str): 匯出格式(md, pdf, html, slides)
|
||||||
|
|
||||||
|
**回傳:**
|
||||||
|
- `str` | `None`: 匯出的內容,失敗時返回 None
|
||||||
|
|
||||||
|
#### search_notes()
|
||||||
|
|
||||||
|
搜尋筆記。
|
||||||
|
|
||||||
|
```python
|
||||||
|
results = client.search_notes(query)
|
||||||
|
```
|
||||||
|
|
||||||
|
**參數:**
|
||||||
|
- `query` (str): 搜尋關鍵字
|
||||||
|
|
||||||
|
**回傳:**
|
||||||
|
- `list` | `None`: 符合的筆記列表
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 使用範例
|
||||||
|
|
||||||
|
### 完整工作流程
|
||||||
|
|
||||||
|
```python
|
||||||
|
from mdclient import MDAuth, MDClient
|
||||||
|
|
||||||
|
# 1. 登入
|
||||||
|
auth = MDAuth()
|
||||||
|
auth.login("user@example.com", "password")
|
||||||
|
|
||||||
|
# 2. 使用客戶端
|
||||||
|
client = MDClient()
|
||||||
|
|
||||||
|
# 3. 列出筆記
|
||||||
|
notes = client.list_notes()
|
||||||
|
|
||||||
|
# 4. 建立筆記
|
||||||
|
result = client.create_note("新筆記", "# 內容")
|
||||||
|
|
||||||
|
# 5. 取得筆記
|
||||||
|
note = client.get_note(result['id'])
|
||||||
|
|
||||||
|
# 6. 匯出筆記
|
||||||
|
content = client.export_note(result['id'])
|
||||||
|
|
||||||
|
# 7. 刪除筆記
|
||||||
|
client.delete_note(result['id'])
|
||||||
|
```
|
||||||
|
|
||||||
|
### 錯誤處理
|
||||||
|
|
||||||
|
```python
|
||||||
|
from mdclient import MDClient
|
||||||
|
|
||||||
|
client = MDClient()
|
||||||
|
|
||||||
|
# 檢查回傳值
|
||||||
|
notes = client.list_notes()
|
||||||
|
if notes is None:
|
||||||
|
print("取得筆記失敗")
|
||||||
|
else:
|
||||||
|
for note in notes:
|
||||||
|
print(note['text'])
|
||||||
|
|
||||||
|
# 檢查操作結果
|
||||||
|
result = client.create_note("標題", "內容")
|
||||||
|
if result:
|
||||||
|
print(f"建立成功: {result['id']}")
|
||||||
|
else:
|
||||||
|
print("建立失敗")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 自訂伺服器
|
||||||
|
|
||||||
|
```python
|
||||||
|
from mdclient import MDAuth, MDClient
|
||||||
|
|
||||||
|
# 連接到不同的伺服器
|
||||||
|
auth = MDAuth(base_url="https://demo.hedgedoc.org")
|
||||||
|
auth.login("user@example.com", "password")
|
||||||
|
|
||||||
|
client = MDClient(base_url="https://demo.hedgedoc.org")
|
||||||
|
notes = client.list_notes()
|
||||||
|
```
|
||||||
174
CLAUDE.md
Normal file
174
CLAUDE.md
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
**mdclient** is a Python library for interacting with Markdown note services (CodiMD, HedgeDoc, HackMD). The project is designed as a Python-first library with an optional CLI tool for quick operations.
|
||||||
|
|
||||||
|
**Key Design Principle:** Library-first architecture - all functionality is exposed through Python APIs (`MDAuth`, `MDClient` classes), with the CLI tool (`mdclient_cli.py`) being a thin wrapper that uses these APIs.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Core Components
|
||||||
|
|
||||||
|
1. **`mdclient/`** - Main Python package
|
||||||
|
- `MDAuth` class (`auth.py`) - Handles authentication and session management
|
||||||
|
- `MDClient` class (`client.py`) - All note operations (CRUD + export/search)
|
||||||
|
- `__init__.py` - Package entry point, exports `MDClient` and `MDAuth`
|
||||||
|
|
||||||
|
2. **`mdclient_cli.py`** - Optional CLI tool
|
||||||
|
- Standalone script that imports and uses the mdclient package
|
||||||
|
- Supports .env file integration for credentials
|
||||||
|
- Not installed as a package command (run directly with `python3 mdclient_cli.py`)
|
||||||
|
|
||||||
|
### Session Management Pattern
|
||||||
|
|
||||||
|
The library uses a cookie-based session system:
|
||||||
|
- `MDAuth.login()` - Authenticates and saves cookies to a JSON file (`key.conf` by default)
|
||||||
|
- `MDClient._load_cookie()` - Automatically loads cookies for each request
|
||||||
|
- Cookies are stored in simple JSON format: `{"connect.sid": "...", "_csrf": "..."}`
|
||||||
|
|
||||||
|
### Multi-Endpoint Strategy
|
||||||
|
|
||||||
|
Both authentication and note operations attempt multiple API endpoints to handle different server implementations:
|
||||||
|
- Login: `/login`, `/auth/login`, `/api/login`, `/signin`
|
||||||
|
- Note retrieval: `/api/notes/{id}`, `/api/notes/{id}/content`, `/{id}/download`, `/{id}`
|
||||||
|
- Note listing: `/api/me/notes`, `/api/notes`, `/history`, `/me/notes`
|
||||||
|
|
||||||
|
This pattern is essential for compatibility across different Markdown note service implementations.
|
||||||
|
|
||||||
|
## Development Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install in development mode
|
||||||
|
pip install -e ".[dev]"
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
pytest
|
||||||
|
|
||||||
|
# Code formatting (line length: 100)
|
||||||
|
black mdclient/
|
||||||
|
|
||||||
|
# Type checking
|
||||||
|
mypy mdclient/
|
||||||
|
|
||||||
|
# Install with .env support
|
||||||
|
pip install -e ".[env]"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing Authentication
|
||||||
|
|
||||||
|
When working on authentication-related features, test with:
|
||||||
|
```bash
|
||||||
|
# Test if current cookie is valid
|
||||||
|
python3 mdclient_cli.py test
|
||||||
|
|
||||||
|
# Login with credentials
|
||||||
|
python3 mdclient_cli.py login
|
||||||
|
|
||||||
|
# Or use Python directly
|
||||||
|
python3 -c "from mdclient import MDAuth; MDAuth().test_cookie()"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Language and Localization
|
||||||
|
|
||||||
|
**Important:** All user-facing text and documentation must use Traditional Chinese (Taiwan) with natural, direct expression. Avoid translationese and mainland Chinese terminology.
|
||||||
|
|
||||||
|
Common term mappings:
|
||||||
|
- 登录 → 登入
|
||||||
|
- 服务器 → 伺服器
|
||||||
|
- 地址 → 位址
|
||||||
|
- 保存 → 儲存
|
||||||
|
- 文件 → 檔案
|
||||||
|
- 导入 → 匯入
|
||||||
|
- 导出 → 匯出
|
||||||
|
- 创建 → 建立
|
||||||
|
- 获取 → 取得
|
||||||
|
- 用户 → 使用者
|
||||||
|
- 邮箱 → 信箱
|
||||||
|
- 请求 → 請求
|
||||||
|
- 响应 → 回應
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
The library supports these environment variables (typically set in `.env` file):
|
||||||
|
- `MDCLIENT_URL` - Server URL
|
||||||
|
- `MDCLIENT_EMAIL` - Login email
|
||||||
|
- `MDCLIENT_PASSWORD` - Login password
|
||||||
|
- `MDCLIENT_COOKIE_FILE` - Cookie file path (default: `key.conf`)
|
||||||
|
|
||||||
|
## Key API Patterns
|
||||||
|
|
||||||
|
When adding new note operations or endpoints:
|
||||||
|
|
||||||
|
1. **Always try multiple endpoints** - Different services use different API patterns
|
||||||
|
2. **Handle both JSON and text responses** - Some endpoints return HTML/Markdown directly
|
||||||
|
3. **Return `None` on failure** - Consistent error handling pattern
|
||||||
|
4. **Print status messages** - Users expect feedback during operations
|
||||||
|
5. **Use Taiwan Traditional Chinese** - For all user-facing messages
|
||||||
|
|
||||||
|
## Common Patterns
|
||||||
|
|
||||||
|
### Adding a New Note Operation
|
||||||
|
|
||||||
|
```python
|
||||||
|
def new_operation(self, param: str) -> Optional[Dict]:
|
||||||
|
"""
|
||||||
|
新操作的簡短描述
|
||||||
|
|
||||||
|
Args:
|
||||||
|
param: 參數說明
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
操作結果,失敗時返回 None
|
||||||
|
"""
|
||||||
|
print(f"正在執行操作: {param}")
|
||||||
|
|
||||||
|
# 嘗試多個可能的端點
|
||||||
|
endpoints = ['/api/endpoint1', '/api/endpoint2']
|
||||||
|
|
||||||
|
for endpoint in endpoints:
|
||||||
|
response = self._request('GET', endpoint)
|
||||||
|
if response and response.status_code == 200:
|
||||||
|
try:
|
||||||
|
data = response.json()
|
||||||
|
print("✓ 操作成功")
|
||||||
|
return data
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
# 處理非 JSON 回應
|
||||||
|
pass
|
||||||
|
|
||||||
|
print("✗ 操作失敗")
|
||||||
|
return None
|
||||||
|
```
|
||||||
|
|
||||||
|
### CLI Command Pattern
|
||||||
|
|
||||||
|
CLI commands in `mdclient_cli.py` should:
|
||||||
|
1. Load .env file automatically
|
||||||
|
2. Support both command-line args and environment variables
|
||||||
|
3. Use `MDClient` and `MDAuth` classes (no direct HTTP requests)
|
||||||
|
4. Provide clear success/failure messages
|
||||||
|
|
||||||
|
## Package Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
mdclient/
|
||||||
|
├── mdclient/ # Core library (Python package)
|
||||||
|
│ ├── __init__.py # Exports MDClient, MDAuth
|
||||||
|
│ ├── auth.py # Authentication logic
|
||||||
|
│ └── client.py # Note operations API
|
||||||
|
├── mdclient_cli.py # CLI tool (uses mdclient package)
|
||||||
|
├── pyproject.toml # Package configuration
|
||||||
|
├── example.py # Usage examples
|
||||||
|
└── .env.example # Environment variable template
|
||||||
|
```
|
||||||
|
|
||||||
|
## Important Notes
|
||||||
|
|
||||||
|
- The library uses `requests` for HTTP operations with `timeout=10` on all requests
|
||||||
|
- Cookie files are stored as JSON in the current working directory
|
||||||
|
- The CLI tool is meant for quick operations, not programmatic use
|
||||||
|
- All methods print status messages to stdout for user feedback
|
||||||
|
- Error handling returns `None` rather than raising exceptions
|
||||||
187
QUICKSTART.md
Normal file
187
QUICKSTART.md
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
# MDclient 快速開始
|
||||||
|
|
||||||
|
## 安裝
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install mdclient
|
||||||
|
```
|
||||||
|
|
||||||
|
如果需要 .env 支援:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install "mdclient[env]"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5 分鐘快速上手
|
||||||
|
|
||||||
|
### 1. 匯入函式庫
|
||||||
|
|
||||||
|
```python
|
||||||
|
from mdclient import MDClient, MDAuth
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 登入
|
||||||
|
|
||||||
|
```python
|
||||||
|
auth = MDAuth()
|
||||||
|
auth.login("your@email.com", "password")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 使用客戶端
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 列出筆記
|
||||||
|
client = MDClient()
|
||||||
|
notes = client.list_notes()
|
||||||
|
|
||||||
|
for note in notes:
|
||||||
|
print(f"{note['text']} - {note['id']}")
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常用操作
|
||||||
|
|
||||||
|
### 建立筆記
|
||||||
|
|
||||||
|
```python
|
||||||
|
client = MDClient()
|
||||||
|
result = client.create_note("我的筆記", "# 內容\n\n這是筆記內容")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 取得筆記
|
||||||
|
|
||||||
|
```python
|
||||||
|
note = client.get_note(note_id)
|
||||||
|
print(note['content'])
|
||||||
|
```
|
||||||
|
|
||||||
|
### 匯出筆記
|
||||||
|
|
||||||
|
```python
|
||||||
|
content = client.export_note(note_id, format="md")
|
||||||
|
with open("note.md", "w", encoding="utf-8") as f:
|
||||||
|
f.write(content)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 搜尋筆記
|
||||||
|
|
||||||
|
```python
|
||||||
|
results = client.search_notes("關鍵字")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 刪除筆記
|
||||||
|
|
||||||
|
```python
|
||||||
|
client.delete_note(note_id)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用 .env 檔案(推薦)
|
||||||
|
|
||||||
|
建立 `.env` 檔案:
|
||||||
|
|
||||||
|
```env
|
||||||
|
MDCLIENT_URL=https://codimd.lotimmy.com
|
||||||
|
MDCLIENT_EMAIL=your@email.com
|
||||||
|
MDCLIENT_PASSWORD=your_password
|
||||||
|
```
|
||||||
|
|
||||||
|
在程式碼中使用:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import os
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from mdclient import MDAuth, MDClient
|
||||||
|
|
||||||
|
# 載入環境變數
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
# 登入
|
||||||
|
auth = MDAuth(base_url=os.getenv('MDCLIENT_URL'))
|
||||||
|
auth.login(
|
||||||
|
os.getenv('MDCLIENT_EMAIL'),
|
||||||
|
os.getenv('MDCLIENT_PASSWORD')
|
||||||
|
)
|
||||||
|
|
||||||
|
# 使用客戶端
|
||||||
|
client = MDClient(base_url=os.getenv('MDCLIENT_URL'))
|
||||||
|
notes = client.list_notes()
|
||||||
|
```
|
||||||
|
|
||||||
|
## 連接到不同的伺服器
|
||||||
|
|
||||||
|
```python
|
||||||
|
from mdclient import MDAuth, MDClient
|
||||||
|
|
||||||
|
# 自訂伺服器
|
||||||
|
auth = MDAuth(base_url="https://your-server.com")
|
||||||
|
auth.login("user@example.com", "password")
|
||||||
|
|
||||||
|
client = MDClient(base_url="https://your-server.com")
|
||||||
|
notes = client.list_notes()
|
||||||
|
```
|
||||||
|
|
||||||
|
## 完整範例
|
||||||
|
|
||||||
|
```python
|
||||||
|
from mdclient import MDAuth, MDClient
|
||||||
|
|
||||||
|
# 登入
|
||||||
|
auth = MDAuth()
|
||||||
|
auth.login("user@example.com", "password")
|
||||||
|
|
||||||
|
# 建立客戶端
|
||||||
|
client = MDClient()
|
||||||
|
|
||||||
|
# 列出筆記
|
||||||
|
notes = client.list_notes()
|
||||||
|
print(f"找到 {len(notes)} 筆筆記")
|
||||||
|
|
||||||
|
# 建立新筆記
|
||||||
|
result = client.create_note(
|
||||||
|
title="測試筆記",
|
||||||
|
content="# 歡迎使用 MDclient\n\n這是一個測試筆記。"
|
||||||
|
)
|
||||||
|
print(f"建立成功: {result['id']}")
|
||||||
|
|
||||||
|
# 取得筆記內容
|
||||||
|
note = client.get_note(result['id'])
|
||||||
|
print(f"筆記標題: {note['title']}")
|
||||||
|
|
||||||
|
# 匯出筆記
|
||||||
|
content = client.export_note(result['id'])
|
||||||
|
with open("backup.md", "w", encoding="utf-8") as f:
|
||||||
|
f.write(content)
|
||||||
|
print("筆記已匯出")
|
||||||
|
|
||||||
|
# 刪除筆記
|
||||||
|
client.delete_note(result['id'])
|
||||||
|
print("筆記已刪除")
|
||||||
|
```
|
||||||
|
|
||||||
|
## 下一步
|
||||||
|
|
||||||
|
- 查看 [API.md](API.md) 了解完整的 API 參考
|
||||||
|
- 查看 [auth_example.py](auth_example.py) 了解更多認證範例
|
||||||
|
- 查看 [example.py](example.py) 了解更多使用範例
|
||||||
|
|
||||||
|
## 故障排除
|
||||||
|
|
||||||
|
### Cookie 過期
|
||||||
|
|
||||||
|
如果遇到認證失敗,重新登入即可:
|
||||||
|
|
||||||
|
```python
|
||||||
|
auth = MDAuth()
|
||||||
|
auth.login("email", "password")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 連接到不同的伺服器
|
||||||
|
|
||||||
|
```python
|
||||||
|
client = MDClient(base_url="https://your-server.com")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 自訂 Cookie 檔案
|
||||||
|
|
||||||
|
```python
|
||||||
|
auth = MDAuth(cookie_file="custom_cookies.conf")
|
||||||
|
```
|
||||||
197
README.md
Normal file
197
README.md
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
# MDclient
|
||||||
|
|
||||||
|
Markdown 筆記服務的 Python 客戶端函式庫。
|
||||||
|
|
||||||
|
支援 CodiMD、HedgeDoc、HackMD 等服務。
|
||||||
|
|
||||||
|
## 特色
|
||||||
|
|
||||||
|
- ✅ 簡單好用的 Python API
|
||||||
|
- ✅ 支援多種 Markdown 筆記服務
|
||||||
|
- ✅ 自動會話管理(Cookie)
|
||||||
|
- ✅ 支援 .env 設定檔
|
||||||
|
- ✅ 完整的筆記操作(建立、讀取、更新、刪除、匯出)
|
||||||
|
|
||||||
|
## 安裝
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install mdclient
|
||||||
|
```
|
||||||
|
|
||||||
|
如果需要 .env 支援:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install "mdclient[env]"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 快速開始
|
||||||
|
|
||||||
|
```python
|
||||||
|
from mdclient import MDClient, MDAuth
|
||||||
|
|
||||||
|
# 登入
|
||||||
|
auth = MDAuth()
|
||||||
|
auth.login("your@email.com", "password")
|
||||||
|
|
||||||
|
# 使用客戶端
|
||||||
|
client = MDClient()
|
||||||
|
notes = client.list_notes()
|
||||||
|
|
||||||
|
for note in notes:
|
||||||
|
print(f"{note['text']} - {note['id']}")
|
||||||
|
```
|
||||||
|
|
||||||
|
## 主要功能
|
||||||
|
|
||||||
|
### 列出筆記
|
||||||
|
|
||||||
|
```python
|
||||||
|
client = MDClient()
|
||||||
|
notes = client.list_notes()
|
||||||
|
```
|
||||||
|
|
||||||
|
### 建立筆記
|
||||||
|
|
||||||
|
```python
|
||||||
|
result = client.create_note("標題", "# 內容")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 取得筆記
|
||||||
|
|
||||||
|
```python
|
||||||
|
note = client.get_note(note_id)
|
||||||
|
print(note['content'])
|
||||||
|
```
|
||||||
|
|
||||||
|
### 匯出筆記
|
||||||
|
|
||||||
|
```python
|
||||||
|
content = client.export_note(note_id, format="md")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 搜尋筆記
|
||||||
|
|
||||||
|
```python
|
||||||
|
results = client.search_notes("關鍵字")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 刪除筆記
|
||||||
|
|
||||||
|
```python
|
||||||
|
client.delete_note(note_id)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用 .env 檔案
|
||||||
|
|
||||||
|
建立 `.env` 檔案:
|
||||||
|
|
||||||
|
```env
|
||||||
|
MDCLIENT_URL=https://codimd.lotimmy.com
|
||||||
|
MDCLIENT_EMAIL=your@email.com
|
||||||
|
MDCLIENT_PASSWORD=your_password
|
||||||
|
```
|
||||||
|
|
||||||
|
在程式碼中使用:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import os
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from mdclient import MDAuth, MDClient
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
auth = MDAuth(base_url=os.getenv('MDCLIENT_URL'))
|
||||||
|
auth.login(
|
||||||
|
os.getenv('MDCLIENT_EMAIL'),
|
||||||
|
os.getenv('MDCLIENT_PASSWORD')
|
||||||
|
)
|
||||||
|
|
||||||
|
client = MDClient(base_url=os.getenv('MDCLIENT_URL'))
|
||||||
|
notes = client.list_notes()
|
||||||
|
```
|
||||||
|
|
||||||
|
## 自訂伺服器
|
||||||
|
|
||||||
|
```python
|
||||||
|
from mdclient import MDAuth, MDClient
|
||||||
|
|
||||||
|
auth = MDAuth(base_url="https://your-server.com")
|
||||||
|
auth.login("user@example.com", "password")
|
||||||
|
|
||||||
|
client = MDClient(base_url="https://your-server.com")
|
||||||
|
notes = client.list_notes()
|
||||||
|
```
|
||||||
|
|
||||||
|
## CLI 工具(可選)
|
||||||
|
|
||||||
|
專案包含一個可選的 CLI 工具:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 登入
|
||||||
|
python3 mdclient_cli.py login
|
||||||
|
|
||||||
|
# 列出筆記
|
||||||
|
python3 mdclient_cli.py list
|
||||||
|
|
||||||
|
# 建立筆記
|
||||||
|
python3 mdclient_cli.py create "標題"
|
||||||
|
|
||||||
|
# 取得筆記
|
||||||
|
python3 mdclient_cli.py get <note_id>
|
||||||
|
|
||||||
|
# 匯出筆記
|
||||||
|
python3 mdclient_cli.py export <note_id> -o note.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## API 參考
|
||||||
|
|
||||||
|
詳細的 API 文件請查看 [API.md](API.md)。
|
||||||
|
|
||||||
|
## 範例
|
||||||
|
|
||||||
|
- [example.py](example.py) - 基本使用範例
|
||||||
|
- [auth_example.py](auth_example.py) - 認證功能範例
|
||||||
|
|
||||||
|
## 專案結構
|
||||||
|
|
||||||
|
```
|
||||||
|
mdclient/
|
||||||
|
├── mdclient/ # 核心 Python 函式庫
|
||||||
|
│ ├── __init__.py # 匯出 MDClient, MDAuth
|
||||||
|
│ ├── auth.py # 認證功能
|
||||||
|
│ └── client.py # API 客戶端
|
||||||
|
├── mdclient_cli.py # 可選的 CLI 工具
|
||||||
|
├── example.py # Python 使用範例
|
||||||
|
├── auth_example.py # 認證範例
|
||||||
|
├── .env.example # 環境變數範例
|
||||||
|
├── API.md # API 文件
|
||||||
|
├── QUICKSTART.md # 快速開始
|
||||||
|
└── README.md # 本檔案
|
||||||
|
```
|
||||||
|
|
||||||
|
## 環境變數
|
||||||
|
|
||||||
|
- `MDCLIENT_URL` - 伺服器位址
|
||||||
|
- `MDCLIENT_EMAIL` - 登入信箱
|
||||||
|
- `MDCLIENT_PASSWORD` - 登入密碼
|
||||||
|
- `MDCLIENT_COOKIE_FILE` - Cookie 檔案路徑
|
||||||
|
|
||||||
|
## 開發
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安裝開發依賴
|
||||||
|
pip install -e ".[dev]"
|
||||||
|
|
||||||
|
# 執行測試
|
||||||
|
pytest
|
||||||
|
|
||||||
|
# 程式碼格式化
|
||||||
|
black mdclient/
|
||||||
|
|
||||||
|
# 型別檢查
|
||||||
|
mypy mdclient/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 授權
|
||||||
|
|
||||||
|
MIT
|
||||||
114
SUMMARY.md
Normal file
114
SUMMARY.md
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
# MDclient 專案概述
|
||||||
|
|
||||||
|
## 什麼是 MDclient?
|
||||||
|
|
||||||
|
MDclient 是一個 **Python 函式庫**,用於與 Markdown 筆記服務(如 CodiMD、HedgeDoc、HackMD)互動。
|
||||||
|
|
||||||
|
## 核心特色
|
||||||
|
|
||||||
|
- **Python 函式庫優先**:主要設計為在 Python 程式碼中使用
|
||||||
|
- **簡單的 API**:清晰直觀的介面
|
||||||
|
- **多服務支援**:CodiMD、HedgeDoc、HackMD 等
|
||||||
|
- **會話管理**:自動儲存和載入 Cookie
|
||||||
|
- **.env 支援**:方便的設定管理
|
||||||
|
|
||||||
|
## 專案結構
|
||||||
|
|
||||||
|
```
|
||||||
|
mdclient/
|
||||||
|
├── mdclient/ # 核心 Python 函式庫
|
||||||
|
│ ├── __init__.py # 匯出 MDClient, MDAuth
|
||||||
|
│ ├── auth.py # 認證功能
|
||||||
|
│ └── client.py # API 客戶端
|
||||||
|
├── mdclient_cli.py # 可選的 CLI 工具
|
||||||
|
├── example.py # Python 使用範例
|
||||||
|
├── auth_example.py # 認證範例
|
||||||
|
├── .env.example # 環境變數範例
|
||||||
|
├── API.md # API 文件
|
||||||
|
├── QUICKSTART.md # 快速開始
|
||||||
|
├── README.md # 完整文件
|
||||||
|
└── SUMMARY.md # 本檔案
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用方式
|
||||||
|
|
||||||
|
### 作為 Python 函式庫(主要)
|
||||||
|
|
||||||
|
```python
|
||||||
|
from mdclient import MDClient, MDAuth
|
||||||
|
|
||||||
|
# 登入
|
||||||
|
auth = MDAuth()
|
||||||
|
auth.login("email@example.com", "password")
|
||||||
|
|
||||||
|
# 使用客戶端
|
||||||
|
client = MDClient()
|
||||||
|
notes = client.list_notes()
|
||||||
|
```
|
||||||
|
|
||||||
|
### 作為 CLI 工具(可選)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 mdclient_cli.py list
|
||||||
|
python3 mdclient_cli.py create "標題"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 主要 API
|
||||||
|
|
||||||
|
### MDAuth
|
||||||
|
|
||||||
|
```python
|
||||||
|
from mdclient import MDAuth
|
||||||
|
|
||||||
|
auth = MDAuth(base_url="...", cookie_file="key.conf")
|
||||||
|
auth.login(email, password)
|
||||||
|
auth.test_cookie()
|
||||||
|
```
|
||||||
|
|
||||||
|
### MDClient
|
||||||
|
|
||||||
|
```python
|
||||||
|
from mdclient import MDClient
|
||||||
|
|
||||||
|
client = MDClient(base_url="...", cookie_file="key.conf")
|
||||||
|
client.list_notes()
|
||||||
|
client.get_note(note_id)
|
||||||
|
client.create_note(title, content)
|
||||||
|
client.delete_note(note_id)
|
||||||
|
client.export_note(note_id, format)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 適用場景
|
||||||
|
|
||||||
|
1. **自動化腳本**:定期備份筆記
|
||||||
|
2. **Web 應用**:整合筆記功能
|
||||||
|
3. **資料處理**:批次處理筆記
|
||||||
|
4. **整合工具**:作為其他工具的依賴
|
||||||
|
|
||||||
|
## 安裝
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install mdclient
|
||||||
|
```
|
||||||
|
|
||||||
|
## 文件
|
||||||
|
|
||||||
|
- [README.md](README.md) - 完整文件和安裝指南
|
||||||
|
- [API.md](API.md) - 詳細的 API 參考
|
||||||
|
- [QUICKSTART.md](QUICKSTART.md) - 快速開始指南
|
||||||
|
- [example.py](example.py) - Python 使用範例
|
||||||
|
- [auth_example.py](auth_example.py) - 認證功能範例
|
||||||
|
|
||||||
|
## 設定
|
||||||
|
|
||||||
|
支援透過 `.env` 檔案設定:
|
||||||
|
|
||||||
|
```env
|
||||||
|
MDCLIENT_URL=https://codimd.lotimmy.com
|
||||||
|
MDCLIENT_EMAIL=your@email.com
|
||||||
|
MDCLIENT_PASSWORD=your_password
|
||||||
|
```
|
||||||
|
|
||||||
|
## 總結
|
||||||
|
|
||||||
|
MDclient 是一個專注於 Python 程式碼中使用的 Markdown 筆記服務客戶端函式庫。如果你需要在 Python 專案中操作 CodiMD、HedgeDoc 等服務,這個函式庫就是為你設計的。
|
||||||
173
auth_example.py
Normal file
173
auth_example.py
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
MDclient 認證範例
|
||||||
|
|
||||||
|
展示如何進行認證和登入
|
||||||
|
"""
|
||||||
|
|
||||||
|
from mdclient import MDAuth, MDClient
|
||||||
|
import os
|
||||||
|
|
||||||
|
def basic_login_example():
|
||||||
|
"""基本登入範例"""
|
||||||
|
print("=== 基本登入範例 ===\n")
|
||||||
|
|
||||||
|
# 建立認證物件
|
||||||
|
auth = MDAuth(
|
||||||
|
base_url="https://codimd.lotimmy.com",
|
||||||
|
cookie_file="key.conf"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 登入
|
||||||
|
email = "your@email.com"
|
||||||
|
password = "your_password"
|
||||||
|
success = auth.login(email, password)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
print("✓ 登入成功!")
|
||||||
|
|
||||||
|
# 測試 Cookie
|
||||||
|
is_valid = auth.test_cookie()
|
||||||
|
print(f"Cookie 有效: {is_valid}")
|
||||||
|
else:
|
||||||
|
print("✗ 登入失敗")
|
||||||
|
|
||||||
|
def env_login_example():
|
||||||
|
"""使用環境變數登入"""
|
||||||
|
print("\n=== 使用環境變數登入 ===\n")
|
||||||
|
|
||||||
|
# 從環境變數讀取
|
||||||
|
email = os.getenv('MDCLIENT_EMAIL')
|
||||||
|
password = os.getenv('MDCLIENT_PASSWORD')
|
||||||
|
url = os.getenv('MDCLIENT_URL', 'https://codimd.lotimmy.com')
|
||||||
|
|
||||||
|
if email and password:
|
||||||
|
auth = MDAuth(base_url=url)
|
||||||
|
success = auth.login(email, password)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
print("✓ 使用環境變數登入成功")
|
||||||
|
|
||||||
|
# 立即可用客戶端
|
||||||
|
client = MDClient(base_url=url)
|
||||||
|
notes = client.list_notes()
|
||||||
|
print(f"找到 {len(notes)} 筆筆記")
|
||||||
|
else:
|
||||||
|
print("✗ 環境變數未設定")
|
||||||
|
print("請在 .env 檔案中設定:")
|
||||||
|
print(" MDCLIENT_EMAIL=your@email.com")
|
||||||
|
print(" MDCLIENT_PASSWORD=your_password")
|
||||||
|
|
||||||
|
def load_existing_session():
|
||||||
|
"""載入已儲存的會話"""
|
||||||
|
print("\n=== 載入已儲存的會話 ===\n")
|
||||||
|
|
||||||
|
auth = MDAuth()
|
||||||
|
|
||||||
|
# 檢查是否已有 Cookie
|
||||||
|
cookies = auth.load_cookie()
|
||||||
|
if cookies:
|
||||||
|
print("✓ 找到已儲存的 Cookie")
|
||||||
|
|
||||||
|
# 測試是否仍然有效
|
||||||
|
is_valid = auth.test_cookie()
|
||||||
|
if is_valid:
|
||||||
|
print("✓ Cookie 仍然有效,可以直接使用")
|
||||||
|
|
||||||
|
# 建立客戶端
|
||||||
|
client = MDClient()
|
||||||
|
notes = client.list_notes()
|
||||||
|
print(f"找到 {len(notes)} 筆筆記")
|
||||||
|
else:
|
||||||
|
print("✗ Cookie 已過期,需要重新登入")
|
||||||
|
else:
|
||||||
|
print("✗ 找不到已儲存的 Cookie,請先登入")
|
||||||
|
|
||||||
|
def custom_server_example():
|
||||||
|
"""連接到自訂伺服器"""
|
||||||
|
print("\n=== 連接到自訂伺服器 ===\n")
|
||||||
|
|
||||||
|
# 連接到不同的伺服器
|
||||||
|
auth = MDAuth(
|
||||||
|
base_url="https://demo.hedgedoc.org",
|
||||||
|
cookie_file="hedgedoc_cookies.conf"
|
||||||
|
)
|
||||||
|
|
||||||
|
print("提示:取消註解以下程式碼來登入")
|
||||||
|
# auth.login("user@example.com", "password")
|
||||||
|
|
||||||
|
# client = MDClient(
|
||||||
|
# base_url="https://demo.hedgedoc.org",
|
||||||
|
# cookie_file="hedgedoc_cookies.conf"
|
||||||
|
# )
|
||||||
|
|
||||||
|
def quick_start():
|
||||||
|
"""快速開始指南"""
|
||||||
|
print("=== 快速開始 ===\n")
|
||||||
|
print("1. 基本使用:")
|
||||||
|
print("""
|
||||||
|
from mdclient import MDAuth, MDClient
|
||||||
|
|
||||||
|
# 登入
|
||||||
|
auth = MDAuth()
|
||||||
|
auth.login("your@email.com", "password")
|
||||||
|
|
||||||
|
# 使用客戶端
|
||||||
|
client = MDClient()
|
||||||
|
notes = client.list_notes()
|
||||||
|
""")
|
||||||
|
|
||||||
|
print("\n2. 使用 .env 檔案:")
|
||||||
|
print("""
|
||||||
|
# 建立 .env 檔案
|
||||||
|
echo "MDCLIENT_EMAIL=your@email.com" > .env
|
||||||
|
echo "MDCLIENT_PASSWORD=your_password" >> .env
|
||||||
|
|
||||||
|
# 在程式碼中使用
|
||||||
|
import os
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from mdclient import MDAuth, MDClient
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
auth = MDAuth()
|
||||||
|
auth.login(os.getenv('MDCLIENT_EMAIL'), os.getenv('MDCLIENT_PASSWORD'))
|
||||||
|
|
||||||
|
client = MDClient()
|
||||||
|
notes = client.list_notes()
|
||||||
|
""")
|
||||||
|
|
||||||
|
print("\n3. 連接到自訂伺服器:")
|
||||||
|
print("""
|
||||||
|
from mdclient import MDAuth, MDClient
|
||||||
|
|
||||||
|
auth = MDAuth(base_url="https://your-server.com")
|
||||||
|
auth.login("user@example.com", "password")
|
||||||
|
|
||||||
|
client = MDClient(base_url="https://your-server.com")
|
||||||
|
notes = client.list_notes()
|
||||||
|
""")
|
||||||
|
|
||||||
|
def error_handling_example():
|
||||||
|
"""錯誤處理範例"""
|
||||||
|
print("\n=== 錯誤處理 ===\n")
|
||||||
|
|
||||||
|
auth = MDAuth()
|
||||||
|
success = auth.login("wrong@email.com", "wrong_password")
|
||||||
|
|
||||||
|
if not success:
|
||||||
|
print("✗ 登入失敗,請檢查帳密")
|
||||||
|
|
||||||
|
# 檢查 Cookie 是否存在
|
||||||
|
cookies = auth.load_cookie()
|
||||||
|
if not cookies:
|
||||||
|
print("✗ 找不到已儲存的會話,請先登入")
|
||||||
|
else:
|
||||||
|
print("✓ 找到已儲存的會話")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
quick_start()
|
||||||
|
# basic_login_example()
|
||||||
|
# env_login_example()
|
||||||
|
# load_existing_session()
|
||||||
|
# custom_server_example()
|
||||||
|
# error_handling_example()
|
||||||
148
example.py
Normal file
148
example.py
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
MDclient 使用範例 - Python 函式庫使用方式
|
||||||
|
|
||||||
|
這個範例展示了如何在 Python 程式碼中使用 MDclient 函式庫
|
||||||
|
"""
|
||||||
|
|
||||||
|
from mdclient import MDClient, MDAuth
|
||||||
|
import os
|
||||||
|
|
||||||
|
def example_basic_usage():
|
||||||
|
"""基本使用範例"""
|
||||||
|
print("=== MDclient 基本使用 ===\n")
|
||||||
|
|
||||||
|
# 建立客戶端(會自動讀取目前目錄的 key.conf)
|
||||||
|
client = MDClient()
|
||||||
|
|
||||||
|
# 列出所有筆記
|
||||||
|
print("1. 列出筆記:")
|
||||||
|
notes = client.list_notes()
|
||||||
|
if notes:
|
||||||
|
for i, note in enumerate(notes[:5], 1):
|
||||||
|
title = note.get('text', '無標題')
|
||||||
|
note_id = note.get('id', '')
|
||||||
|
pinned = " 📌" if note.get('pinned') else ""
|
||||||
|
print(f" {i}. {title}{pinned}")
|
||||||
|
print(f" ID: {note_id}")
|
||||||
|
|
||||||
|
def example_get_note():
|
||||||
|
"""取得筆記內容範例"""
|
||||||
|
print("\n2. 取得筆記內容:")
|
||||||
|
|
||||||
|
client = MDClient()
|
||||||
|
note_id = "features" # 替換為實際的筆記 ID
|
||||||
|
note = client.get_note(note_id)
|
||||||
|
|
||||||
|
if note:
|
||||||
|
content = note.get('content', '')
|
||||||
|
print(f" 內容預覽: {content[:100]}...")
|
||||||
|
|
||||||
|
def example_create_note():
|
||||||
|
"""建立筆記範例"""
|
||||||
|
print("\n3. 建立新筆記:")
|
||||||
|
|
||||||
|
client = MDClient()
|
||||||
|
result = client.create_note(
|
||||||
|
title="Python 測試筆記",
|
||||||
|
content="# 歡迎使用 MDclient\n\n這是一個透過 Python API 建立的筆記。"
|
||||||
|
)
|
||||||
|
|
||||||
|
if result:
|
||||||
|
print(f" 筆記建立成功!")
|
||||||
|
print(f" ID: {result.get('id')}")
|
||||||
|
|
||||||
|
def example_export_note():
|
||||||
|
"""匯出筆記範例"""
|
||||||
|
print("\n4. 匯出筆記:")
|
||||||
|
|
||||||
|
client = MDClient()
|
||||||
|
note_id = "features"
|
||||||
|
content = client.export_note(note_id, format="md")
|
||||||
|
|
||||||
|
if content:
|
||||||
|
filename = f"{note_id}.md"
|
||||||
|
with open(filename, "w", encoding="utf-8") as f:
|
||||||
|
f.write(content)
|
||||||
|
print(f" 筆記已匯出到: {filename}")
|
||||||
|
|
||||||
|
def example_with_env():
|
||||||
|
"""使用 .env 檔案的範例"""
|
||||||
|
print("\n=== 使用 .env 設定 ===\n")
|
||||||
|
|
||||||
|
try:
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
# 從環境變數讀取設定
|
||||||
|
url = os.getenv('MDCLIENT_URL', 'https://codimd.lotimmy.com')
|
||||||
|
email = os.getenv('MDCLIENT_EMAIL')
|
||||||
|
password = os.getenv('MDCLIENT_PASSWORD')
|
||||||
|
|
||||||
|
if email and password:
|
||||||
|
# 登入
|
||||||
|
auth = MDAuth(base_url=url)
|
||||||
|
auth.login(email, password)
|
||||||
|
|
||||||
|
# 使用客戶端
|
||||||
|
client = MDClient(base_url=url)
|
||||||
|
notes = client.list_notes()
|
||||||
|
print(f"找到 {len(notes)} 筆筆記")
|
||||||
|
else:
|
||||||
|
print(" .env 檔案中未設定帳密")
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
print(" 需要安裝 python-dotenv: pip install python-dotenv")
|
||||||
|
|
||||||
|
def example_custom_server():
|
||||||
|
"""連接到自訂伺服器的範例"""
|
||||||
|
print("\n=== 連接到自訂伺服器 ===\n")
|
||||||
|
|
||||||
|
# 連接到不同的伺服器
|
||||||
|
auth = MDAuth(
|
||||||
|
base_url="https://your-hedgedoc-server.com",
|
||||||
|
cookie_file="custom_server_cookies.conf"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 注意:需要先登入才能使用客戶端
|
||||||
|
# auth.login("user@example.com", "password")
|
||||||
|
|
||||||
|
# client = MDClient(
|
||||||
|
# base_url="https://your-hedgedoc-server.com",
|
||||||
|
# cookie_file="custom_server_cookies.conf"
|
||||||
|
# )
|
||||||
|
|
||||||
|
def example_search_notes():
|
||||||
|
"""搜尋筆記範例"""
|
||||||
|
print("\n5. 搜尋筆記:")
|
||||||
|
|
||||||
|
client = MDClient()
|
||||||
|
results = client.search_notes("Python")
|
||||||
|
|
||||||
|
if results:
|
||||||
|
print(f" 找到 {len(results)} 筆相關筆記")
|
||||||
|
for note in results[:3]:
|
||||||
|
print(f" - {note.get('text', '無標題')}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# 確保已經登入
|
||||||
|
if not os.path.exists("key.conf"):
|
||||||
|
print("請先登入:")
|
||||||
|
print(" 方式1:使用 .env 檔案")
|
||||||
|
print(" 方式2:執行認證程式碼")
|
||||||
|
print("\n範例:")
|
||||||
|
print("""
|
||||||
|
from mdclient import MDAuth
|
||||||
|
auth = MDAuth()
|
||||||
|
auth.login("your@email.com", "password")
|
||||||
|
""")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
# 執行範例
|
||||||
|
example_basic_usage()
|
||||||
|
# example_get_note()
|
||||||
|
# example_create_note()
|
||||||
|
# example_export_note()
|
||||||
|
# example_with_env()
|
||||||
|
# example_search_notes()
|
||||||
|
# example_custom_server()
|
||||||
4
mdclient/.gitignore
vendored
Normal file
4
mdclient/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
.env
|
||||||
|
key.conf
|
||||||
11
mdclient/__init__.py
Normal file
11
mdclient/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
"""
|
||||||
|
MDclient - Markdown 筆記服務的 Python 客戶端函式庫
|
||||||
|
|
||||||
|
支援 CodiMD、HedgeDoc 等服務的登入、筆記管理等功能
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .client import MDClient
|
||||||
|
from .auth import MDAuth
|
||||||
|
|
||||||
|
__version__ = "0.1.0"
|
||||||
|
__all__ = ["MDClient", "MDAuth"]
|
||||||
181
mdclient/auth.py
Normal file
181
mdclient/auth.py
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
"""
|
||||||
|
MDclient 認證模組
|
||||||
|
"""
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional, Dict
|
||||||
|
|
||||||
|
|
||||||
|
class MDAuth:
|
||||||
|
"""Markdown 筆記服務認證類"""
|
||||||
|
|
||||||
|
def __init__(self, base_url: str = "https://codimd.lotimmy.com", cookie_file: str = "key.conf"):
|
||||||
|
"""
|
||||||
|
初始化認證類
|
||||||
|
|
||||||
|
Args:
|
||||||
|
base_url: 伺服器位址
|
||||||
|
cookie_file: Cookie 儲存檔案路徑
|
||||||
|
"""
|
||||||
|
self.base_url = base_url.rstrip("/")
|
||||||
|
self.login_url = f"{self.base_url}/login"
|
||||||
|
self.cookie_file = Path(cookie_file)
|
||||||
|
|
||||||
|
def save_cookie(self, cookies: Dict[str, str]) -> Dict[str, str]:
|
||||||
|
"""
|
||||||
|
儲存 Cookie 到檔案
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cookies: Cookie 字典
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
儲存的 Cookie 字典
|
||||||
|
"""
|
||||||
|
with open(self.cookie_file, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(cookies, f, indent=2)
|
||||||
|
|
||||||
|
print(f"✓ Cookie 已儲存到: {self.cookie_file}")
|
||||||
|
return cookies
|
||||||
|
|
||||||
|
def load_cookie(self) -> Optional[Dict[str, str]]:
|
||||||
|
"""
|
||||||
|
從檔案載入 Cookie
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Cookie 字典,如果檔案不存在則返回 None
|
||||||
|
"""
|
||||||
|
if not self.cookie_file.exists():
|
||||||
|
return None
|
||||||
|
|
||||||
|
with open(self.cookie_file, 'r', encoding='utf-8') as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
def login(self, email: str, password: str) -> bool:
|
||||||
|
"""
|
||||||
|
登入並取得 Cookie
|
||||||
|
|
||||||
|
Args:
|
||||||
|
email: 登入信箱
|
||||||
|
password: 登入密碼
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
是否登入成功
|
||||||
|
"""
|
||||||
|
print(f"正在登入 {self.base_url}...")
|
||||||
|
|
||||||
|
session = requests.Session()
|
||||||
|
|
||||||
|
try:
|
||||||
|
print("1. 取得登入頁面...")
|
||||||
|
response = session.get(self.login_url, timeout=10)
|
||||||
|
print(f" 狀態碼: {response.status_code}")
|
||||||
|
|
||||||
|
print("2. 傳送登入請求...")
|
||||||
|
|
||||||
|
login_data = {
|
||||||
|
'email': email,
|
||||||
|
'password': password,
|
||||||
|
}
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
'Origin': self.base_url,
|
||||||
|
'Referer': self.login_url,
|
||||||
|
}
|
||||||
|
|
||||||
|
# 嘗試不同的登入端點
|
||||||
|
login_endpoints = ['/login', '/auth/login', '/api/login', '/signin']
|
||||||
|
|
||||||
|
for endpoint in login_endpoints:
|
||||||
|
try:
|
||||||
|
login_url = f"{self.base_url}{endpoint}"
|
||||||
|
print(f" 嘗試: {login_url}")
|
||||||
|
|
||||||
|
response = session.post(
|
||||||
|
login_url,
|
||||||
|
data=login_data,
|
||||||
|
headers=headers,
|
||||||
|
allow_redirects=True,
|
||||||
|
timeout=10
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f" 狀態碼: {response.status_code}")
|
||||||
|
print(f" Cookies: {dict(session.cookies)}")
|
||||||
|
|
||||||
|
if response.status_code in [200, 302, 301]:
|
||||||
|
if session.cookies:
|
||||||
|
print(f" ✓ 取得到 {len(session.cookies)} 個 Cookie")
|
||||||
|
|
||||||
|
# 轉換為字典
|
||||||
|
cookie_dict = {}
|
||||||
|
for cookie in session.cookies:
|
||||||
|
cookie_dict[cookie.name] = cookie.value
|
||||||
|
|
||||||
|
# 儲存 cookie
|
||||||
|
self.save_cookie(cookie_dict)
|
||||||
|
|
||||||
|
# 顯示主要 cookie 資訊
|
||||||
|
for name, value in cookie_dict.items():
|
||||||
|
if 'sid' in name.lower() or 'session' in name.lower():
|
||||||
|
print(f" → {name}: {value[:20]}...")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
print(f" ✗ 請求失敗: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
print("✗ 所有登入端點都失敗")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
print(f"✗ 登入失敗: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def test_cookie(self) -> bool:
|
||||||
|
"""
|
||||||
|
測試儲存的 Cookie 是否有效
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Cookie 是否有效
|
||||||
|
"""
|
||||||
|
cookies = self.load_cookie()
|
||||||
|
if not cookies:
|
||||||
|
print("✗ 找不到儲存的 Cookie")
|
||||||
|
return False
|
||||||
|
|
||||||
|
print(f"從 {self.cookie_file} 載入 Cookie...")
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.get(
|
||||||
|
f"{self.base_url}/",
|
||||||
|
cookies=cookies,
|
||||||
|
headers=headers,
|
||||||
|
timeout=10
|
||||||
|
)
|
||||||
|
print(f"測試請求狀態碼: {response.status_code}")
|
||||||
|
return response.status_code == 200
|
||||||
|
except requests.RequestException as e:
|
||||||
|
print(f"✗ 測試失敗: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_cookies(self) -> Optional[Dict[str, str]]:
|
||||||
|
"""
|
||||||
|
取得 Cookie(如果需要重新登入會自動處理)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Cookie 字典
|
||||||
|
"""
|
||||||
|
cookies = self.load_cookie()
|
||||||
|
if cookies:
|
||||||
|
return cookies
|
||||||
|
|
||||||
|
print("找不到有效的 Cookie,請先登入")
|
||||||
|
return None
|
||||||
301
mdclient/client.py
Normal file
301
mdclient/client.py
Normal file
@@ -0,0 +1,301 @@
|
|||||||
|
"""
|
||||||
|
MDclient 客戶端模組
|
||||||
|
"""
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional, Dict, List, Any
|
||||||
|
|
||||||
|
|
||||||
|
class MDClient:
|
||||||
|
"""Markdown 筆記服務客戶端類"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
base_url: str = "https://codimd.lotimmy.com",
|
||||||
|
cookie_file: str = "key.conf",
|
||||||
|
email: Optional[str] = None,
|
||||||
|
password: Optional[str] = None
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
初始化客戶端
|
||||||
|
|
||||||
|
Args:
|
||||||
|
base_url: 伺服器位址
|
||||||
|
cookie_file: Cookie 儲存檔案路徑
|
||||||
|
email: 登入信箱(可選,用於自動登入)
|
||||||
|
password: 登入密碼(可選,用於自動登入)
|
||||||
|
"""
|
||||||
|
self.base_url = base_url.rstrip("/")
|
||||||
|
self.cookie_file = Path(cookie_file)
|
||||||
|
self.email = email
|
||||||
|
self.password = password
|
||||||
|
|
||||||
|
def _load_cookie(self) -> Optional[Dict[str, str]]:
|
||||||
|
"""從檔案載入 Cookie"""
|
||||||
|
if not self.cookie_file.exists():
|
||||||
|
return None
|
||||||
|
|
||||||
|
with open(self.cookie_file, 'r', encoding='utf-8') as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
def _request(
|
||||||
|
self,
|
||||||
|
method: str,
|
||||||
|
endpoint: str,
|
||||||
|
data: Optional[Dict] = None,
|
||||||
|
params: Optional[Dict] = None
|
||||||
|
) -> Optional[requests.Response]:
|
||||||
|
"""
|
||||||
|
傳送帶 Cookie 的請求
|
||||||
|
|
||||||
|
Args:
|
||||||
|
method: HTTP 方法 (GET, POST, etc.)
|
||||||
|
endpoint: API 端點
|
||||||
|
data: POST 資料
|
||||||
|
params: URL 參數
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
回應物件,失敗時返回 None
|
||||||
|
"""
|
||||||
|
cookies = self._load_cookie()
|
||||||
|
|
||||||
|
if not cookies:
|
||||||
|
print(f"✗ 找不到 Cookie 檔案: {self.cookie_file}")
|
||||||
|
print("請先使用 MDAuth 登入")
|
||||||
|
return None
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
|
||||||
|
}
|
||||||
|
|
||||||
|
url = f"{self.base_url}{endpoint}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
if method == 'GET':
|
||||||
|
response = requests.get(url, cookies=cookies, headers=headers, params=params, timeout=10)
|
||||||
|
elif method == 'POST':
|
||||||
|
headers['Content-Type'] = 'application/json'
|
||||||
|
response = requests.post(url, cookies=cookies, headers=headers, json=data, timeout=10)
|
||||||
|
elif method == 'DELETE':
|
||||||
|
response = requests.delete(url, cookies=cookies, headers=headers, timeout=10)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"不支援的 HTTP 方法: {method}")
|
||||||
|
|
||||||
|
return response
|
||||||
|
except requests.RequestException as e:
|
||||||
|
print(f"✗ 請求失敗: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_user_info(self) -> Optional[Dict]:
|
||||||
|
"""
|
||||||
|
取得使用者資訊
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
使用者資訊字典
|
||||||
|
"""
|
||||||
|
print("正在取得使用者資訊...")
|
||||||
|
|
||||||
|
endpoints = ['/api/me', '/me', '/api/user/me']
|
||||||
|
|
||||||
|
for endpoint in endpoints:
|
||||||
|
response = self._request('GET', endpoint)
|
||||||
|
if response and response.status_code == 200:
|
||||||
|
try:
|
||||||
|
data = response.json()
|
||||||
|
print("✓ 取得使用者資訊成功")
|
||||||
|
return data
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
print("✗ 無法取得使用者資訊")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def list_notes(self) -> Optional[List[Dict]]:
|
||||||
|
"""
|
||||||
|
列出所有筆記
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
筆記列表
|
||||||
|
"""
|
||||||
|
print("正在取得筆記列表...")
|
||||||
|
|
||||||
|
endpoints = ['/api/me/notes', '/api/notes', '/history', '/me/notes']
|
||||||
|
|
||||||
|
for endpoint in endpoints:
|
||||||
|
response = self._request('GET', endpoint)
|
||||||
|
if response and response.status_code == 200:
|
||||||
|
try:
|
||||||
|
data = response.json()
|
||||||
|
print(f"✓ 成功取得筆記 (端點: {endpoint})")
|
||||||
|
|
||||||
|
# 處理不同的回應格式
|
||||||
|
if isinstance(data, list):
|
||||||
|
return data
|
||||||
|
elif isinstance(data, dict) and 'history' in data:
|
||||||
|
return data['history']
|
||||||
|
elif isinstance(data, dict) and 'notes' in data:
|
||||||
|
return data['notes']
|
||||||
|
|
||||||
|
return data
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
print("✗ 無法取得筆記列表")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_note(self, note_id: str) -> Optional[Dict]:
|
||||||
|
"""
|
||||||
|
取得筆記內容
|
||||||
|
|
||||||
|
Args:
|
||||||
|
note_id: 筆記 ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
筆記內容字典
|
||||||
|
"""
|
||||||
|
print(f"正在取得筆記: {note_id}")
|
||||||
|
|
||||||
|
# 嘗試不同的 API 端點
|
||||||
|
endpoints = [
|
||||||
|
f"/api/notes/{note_id}",
|
||||||
|
f"/api/notes/{note_id}/content",
|
||||||
|
f"/{note_id}/download",
|
||||||
|
f"/{note_id}",
|
||||||
|
]
|
||||||
|
|
||||||
|
for endpoint in endpoints:
|
||||||
|
response = self._request('GET', endpoint)
|
||||||
|
if response and response.status_code == 200:
|
||||||
|
try:
|
||||||
|
# 如果是直接訪問筆記頁面,可能需要解析 HTML
|
||||||
|
content_type = response.headers.get('content-type', '')
|
||||||
|
if 'text/html' in content_type:
|
||||||
|
# 這是 HTML 頁面,不是 API 回應
|
||||||
|
continue
|
||||||
|
|
||||||
|
data = response.json()
|
||||||
|
print(f"✓ 取得筆記成功 (端點: {endpoint})")
|
||||||
|
return data
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
# 如果不是 JSON,可能是純文字內容
|
||||||
|
if response.text:
|
||||||
|
print(f"✓ 取得筆記內容 (端點: {endpoint})")
|
||||||
|
return {
|
||||||
|
'id': note_id,
|
||||||
|
'content': response.text,
|
||||||
|
'title': note_id
|
||||||
|
}
|
||||||
|
|
||||||
|
print("✗ 無法取得筆記內容")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def create_note(self, title: str, content: str = "") -> Optional[Dict]:
|
||||||
|
"""
|
||||||
|
建立新筆記
|
||||||
|
|
||||||
|
Args:
|
||||||
|
title: 筆記標題
|
||||||
|
content: 筆記內容(Markdown 格式)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
建立的筆記資訊
|
||||||
|
"""
|
||||||
|
print(f"正在建立筆記: {title}")
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'title': title,
|
||||||
|
'content': content,
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self._request('POST', '/api/notes', data=data)
|
||||||
|
|
||||||
|
if response and response.status_code in [200, 201]:
|
||||||
|
try:
|
||||||
|
result = response.json()
|
||||||
|
print("✓ 筆記建立成功")
|
||||||
|
return result
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
print("✗ 無法解析回應")
|
||||||
|
else:
|
||||||
|
status = response.status_code if response else "無"
|
||||||
|
print(f"✗ 建立筆記失敗 (狀態碼: {status})")
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def delete_note(self, note_id: str) -> bool:
|
||||||
|
"""
|
||||||
|
刪除筆記
|
||||||
|
|
||||||
|
Args:
|
||||||
|
note_id: 筆記 ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
是否刪除成功
|
||||||
|
"""
|
||||||
|
print(f"正在刪除筆記: {note_id}")
|
||||||
|
response = self._request('DELETE', f"/api/notes/{note_id}")
|
||||||
|
|
||||||
|
if response and response.status_code in [200, 204]:
|
||||||
|
print("✓ 筆記刪除成功")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
status = response.status_code if response else "無"
|
||||||
|
print(f"✗ 刪除筆記失敗 (狀態碼: {status})")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def export_note(self, note_id: str, format: str = "md") -> Optional[str]:
|
||||||
|
"""
|
||||||
|
匯出筆記
|
||||||
|
|
||||||
|
Args:
|
||||||
|
note_id: 筆記 ID
|
||||||
|
format: 匯出格式 (md, pdf, html, slides)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
匯出的內容
|
||||||
|
"""
|
||||||
|
print(f"正在匯出筆記: {note_id} (格式: {format})")
|
||||||
|
|
||||||
|
endpoints = [
|
||||||
|
f"/api/notes/{note_id}/export?format={format}",
|
||||||
|
f"/{note_id}/download",
|
||||||
|
f"/{note_id}/export/{format}",
|
||||||
|
]
|
||||||
|
|
||||||
|
for endpoint in endpoints:
|
||||||
|
response = self._request('GET', endpoint)
|
||||||
|
if response and response.status_code == 200:
|
||||||
|
print("✓ 筆記匯出成功")
|
||||||
|
return response.text
|
||||||
|
|
||||||
|
print("✗ 匯出筆記失敗")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def search_notes(self, query: str) -> Optional[List[Dict]]:
|
||||||
|
"""
|
||||||
|
搜尋筆記
|
||||||
|
|
||||||
|
Args:
|
||||||
|
query: 搜尋關鍵字
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
符合的筆記列表
|
||||||
|
"""
|
||||||
|
print(f"正在搜尋筆記: {query}")
|
||||||
|
|
||||||
|
response = self._request('GET', '/api/search', params={'q': query})
|
||||||
|
|
||||||
|
if response and response.status_code == 200:
|
||||||
|
try:
|
||||||
|
data = response.json()
|
||||||
|
print("✓ 搜尋成功")
|
||||||
|
return data
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
print("✗ 無法解析回應")
|
||||||
|
else:
|
||||||
|
status = response.status_code if response else "無"
|
||||||
|
print(f"✗ 搜尋失敗 (狀態碼: {status})")
|
||||||
|
|
||||||
|
return None
|
||||||
146
mdclient_cli.py
Normal file
146
mdclient_cli.py
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
MDclient CLI 工具
|
||||||
|
|
||||||
|
使用 MDclient Python 函式庫的命令列介面
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# 從 mdclient 套件匯入
|
||||||
|
from mdclient import MDClient, MDAuth
|
||||||
|
|
||||||
|
|
||||||
|
def load_env():
|
||||||
|
"""載入 .env 檔案"""
|
||||||
|
env_file = Path(".env")
|
||||||
|
if env_file.exists():
|
||||||
|
with open(env_file, 'r') as f:
|
||||||
|
for line in f:
|
||||||
|
line = line.strip()
|
||||||
|
if line and not line.startswith('#') and '=' in line:
|
||||||
|
key, value = line.split('=', 1)
|
||||||
|
os.environ[key.strip()] = value.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主函數"""
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
# 載入 .env 檔案
|
||||||
|
load_env()
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description="MDclient - Markdown 筆記服務命令列工具")
|
||||||
|
parser.add_argument('--url', default=os.getenv('MDCLIENT_URL', 'https://codimd.lotimmy.com'), help='伺服器位址')
|
||||||
|
parser.add_argument('--cookie-file', default=os.getenv('MDCLIENT_COOKIE_FILE', 'key.conf'), help='Cookie 檔案路徑')
|
||||||
|
|
||||||
|
subparsers = parser.add_subparsers(dest='command', help='可用指令')
|
||||||
|
|
||||||
|
# 登入指令
|
||||||
|
login_parser = subparsers.add_parser('login', help='登入並儲存 Cookie')
|
||||||
|
login_parser.add_argument('email', nargs='?', help='登入信箱(可從 .env 讀取)')
|
||||||
|
login_parser.add_argument('password', nargs='?', help='登入密碼(可從 .env 讀取)')
|
||||||
|
|
||||||
|
# 測試指令
|
||||||
|
subparsers.add_parser('test', help='測試 Cookie 是否有效')
|
||||||
|
|
||||||
|
# 使用者資訊指令
|
||||||
|
subparsers.add_parser('info', help='取得使用者資訊')
|
||||||
|
|
||||||
|
# 列出筆記指令
|
||||||
|
subparsers.add_parser('list', help='列出所有筆記')
|
||||||
|
|
||||||
|
# 取得筆記指令
|
||||||
|
get_parser = subparsers.add_parser('get', help='取得筆記內容')
|
||||||
|
get_parser.add_argument('note_id', help='筆記 ID')
|
||||||
|
|
||||||
|
# 建立筆記指令
|
||||||
|
create_parser = subparsers.add_parser('create', help='建立新筆記')
|
||||||
|
create_parser.add_argument('title', help='筆記標題')
|
||||||
|
create_parser.add_argument('--content', '-c', default='', help='筆記內容')
|
||||||
|
|
||||||
|
# 匯出筆記指令
|
||||||
|
export_parser = subparsers.add_parser('export', help='匯出筆記')
|
||||||
|
export_parser.add_argument('note_id', help='筆記 ID')
|
||||||
|
export_parser.add_argument('--format', '-f', default='md', choices=['md', 'pdf', 'html', 'slides'], help='匯出格式')
|
||||||
|
export_parser.add_argument('--output', '-o', help='輸出檔案路徑')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if not args.command:
|
||||||
|
parser.print_help()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# 執行指令
|
||||||
|
if args.command == 'login':
|
||||||
|
# 從參數或環境變數取得帳密
|
||||||
|
email = args.email or os.getenv('MDCLIENT_EMAIL')
|
||||||
|
password = args.password or os.getenv('MDCLIENT_PASSWORD')
|
||||||
|
|
||||||
|
if not email or not password:
|
||||||
|
print("✗ 請提供帳密,或設定 .env 檔案中的 MDCLIENT_EMAIL 和 MDCLIENT_PASSWORD")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
auth = MDAuth(base_url=args.url, cookie_file=args.cookie_file)
|
||||||
|
success = auth.login(email, password)
|
||||||
|
if success:
|
||||||
|
print("\n✓ 登入成功!")
|
||||||
|
auth.test_cookie()
|
||||||
|
else:
|
||||||
|
print("\n✗ 登入失敗")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
elif args.command == 'test':
|
||||||
|
auth = MDAuth(base_url=args.url, cookie_file=args.cookie_file)
|
||||||
|
auth.test_cookie()
|
||||||
|
|
||||||
|
elif args.command == 'info':
|
||||||
|
client = MDClient(base_url=args.url, cookie_file=args.cookie_file)
|
||||||
|
info = client.get_user_info()
|
||||||
|
if info:
|
||||||
|
print("\n使用者資訊:")
|
||||||
|
print(f" 使用者名稱: {info.get('username', 'N/A')}")
|
||||||
|
print(f" 電子信箱: {info.get('email', 'N/A')}")
|
||||||
|
|
||||||
|
elif args.command == 'list':
|
||||||
|
client = MDClient(base_url=args.url, cookie_file=args.cookie_file)
|
||||||
|
notes = client.list_notes()
|
||||||
|
if notes:
|
||||||
|
for i, note in enumerate(notes, 1):
|
||||||
|
title = note.get('title') or note.get('text') or note.get('alias') or note.get('id', '無標題')
|
||||||
|
note_id = note.get('id', '')
|
||||||
|
pinned = " 📌" if note.get('pinned') else ""
|
||||||
|
print(f"{i}. {title}{pinned} (ID: {note_id})")
|
||||||
|
|
||||||
|
elif args.command == 'get':
|
||||||
|
client = MDClient(base_url=args.url, cookie_file=args.cookie_file)
|
||||||
|
note = client.get_note(args.note_id)
|
||||||
|
if note:
|
||||||
|
content = note.get('content', '')
|
||||||
|
print(content)
|
||||||
|
|
||||||
|
elif args.command == 'create':
|
||||||
|
client = MDClient(base_url=args.url, cookie_file=args.cookie_file)
|
||||||
|
result = client.create_note(args.title, args.content or "")
|
||||||
|
if result:
|
||||||
|
note_id = result.get('id') or result.get('alias', '')
|
||||||
|
url = result.get('url', f"{args.url}/{note_id}")
|
||||||
|
print(f"✓ 筆記建立成功!")
|
||||||
|
print(f" ID: {note_id}")
|
||||||
|
print(f" 網址: {url}")
|
||||||
|
|
||||||
|
elif args.command == 'export':
|
||||||
|
client = MDClient(base_url=args.url, cookie_file=args.cookie_file)
|
||||||
|
content = client.export_note(args.note_id, args.format)
|
||||||
|
if content and args.output:
|
||||||
|
with open(args.output, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(content)
|
||||||
|
print(f"✓ 已匯出到: {args.output}")
|
||||||
|
elif content:
|
||||||
|
print(content)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
58
pyproject.toml
Normal file
58
pyproject.toml
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=61.0", "wheel"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "mdclient"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Markdown 笔记服务的 Python 客户端库"
|
||||||
|
readme = "README.md"
|
||||||
|
license = {text = "MIT"}
|
||||||
|
authors = [
|
||||||
|
{name = "Your Name", email = "your.email@example.com"}
|
||||||
|
]
|
||||||
|
keywords = ["markdown", "notes", "codimd", "hedgedoc", "hackmd", "api", "client"]
|
||||||
|
classifiers = [
|
||||||
|
"Development Status :: 3 - Alpha",
|
||||||
|
"Intended Audience :: Developers",
|
||||||
|
"License :: OSI Approved :: MIT License",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3.8",
|
||||||
|
"Programming Language :: Python :: 3.9",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Programming Language :: Python :: 3.11",
|
||||||
|
"Programming Language :: Python :: 3.12",
|
||||||
|
]
|
||||||
|
requires-python = ">=3.8"
|
||||||
|
dependencies = [
|
||||||
|
"requests>=2.28.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = [
|
||||||
|
"pytest>=7.0.0",
|
||||||
|
"black>=22.0.0",
|
||||||
|
"flake8>=5.0.0",
|
||||||
|
"mypy>=0.990",
|
||||||
|
]
|
||||||
|
env = [
|
||||||
|
"python-dotenv>=1.0.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
Homepage = "https://github.com/yourusername/mdclient"
|
||||||
|
Repository = "https://github.com/yourusername/mdclient"
|
||||||
|
Issues = "https://github.com/yourusername/mdclient/issues"
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
where = ["."]
|
||||||
|
include = ["mdclient*"]
|
||||||
|
|
||||||
|
[tool.black]
|
||||||
|
line-length = 100
|
||||||
|
target-version = ["py38", "py39", "py310", "py311", "py312"]
|
||||||
|
|
||||||
|
[tool.mypy]
|
||||||
|
python_version = "3.8"
|
||||||
|
warn_return_any = true
|
||||||
|
warn_unused_configs = true
|
||||||
Reference in New Issue
Block a user