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:
2026-03-17 14:38:54 +08:00
commit 38b4bc5377
15 changed files with 1992 additions and 0 deletions

12
.env.example Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,4 @@
__pycache__/
*.pyc
.env
key.conf

11
mdclient/__init__.py Normal file
View 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
View 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
View 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
View 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
View 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