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