From 38b4bc537722161fd29fea53e10a79e718c3bf54 Mon Sep 17 00:00:00 2001 From: Timmy Date: Tue, 17 Mar 2026 14:38:54 +0800 Subject: [PATCH] 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 --- .env.example | 12 ++ .gitignore | 31 +++++ API.md | 255 ++++++++++++++++++++++++++++++++++++ CLAUDE.md | 174 +++++++++++++++++++++++++ QUICKSTART.md | 187 +++++++++++++++++++++++++++ README.md | 197 ++++++++++++++++++++++++++++ SUMMARY.md | 114 ++++++++++++++++ auth_example.py | 173 +++++++++++++++++++++++++ example.py | 148 +++++++++++++++++++++ mdclient/.gitignore | 4 + mdclient/__init__.py | 11 ++ mdclient/auth.py | 181 ++++++++++++++++++++++++++ mdclient/client.py | 301 +++++++++++++++++++++++++++++++++++++++++++ mdclient_cli.py | 146 +++++++++++++++++++++ pyproject.toml | 58 +++++++++ 15 files changed, 1992 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 API.md create mode 100644 CLAUDE.md create mode 100644 QUICKSTART.md create mode 100644 README.md create mode 100644 SUMMARY.md create mode 100644 auth_example.py create mode 100644 example.py create mode 100644 mdclient/.gitignore create mode 100644 mdclient/__init__.py create mode 100644 mdclient/auth.py create mode 100644 mdclient/client.py create mode 100644 mdclient_cli.py create mode 100644 pyproject.toml diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..0704f81 --- /dev/null +++ b/.env.example @@ -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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9fd4690 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/API.md b/API.md new file mode 100644 index 0000000..0423a24 --- /dev/null +++ b/API.md @@ -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() +``` diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..b285fa5 --- /dev/null +++ b/CLAUDE.md @@ -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 diff --git a/QUICKSTART.md b/QUICKSTART.md new file mode 100644 index 0000000..fac27f1 --- /dev/null +++ b/QUICKSTART.md @@ -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") +``` diff --git a/README.md b/README.md new file mode 100644 index 0000000..f7af326 --- /dev/null +++ b/README.md @@ -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 + +# 匯出筆記 +python3 mdclient_cli.py export -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 diff --git a/SUMMARY.md b/SUMMARY.md new file mode 100644 index 0000000..c38eb54 --- /dev/null +++ b/SUMMARY.md @@ -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 等服務,這個函式庫就是為你設計的。 diff --git a/auth_example.py b/auth_example.py new file mode 100644 index 0000000..c64dcf3 --- /dev/null +++ b/auth_example.py @@ -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() diff --git a/example.py b/example.py new file mode 100644 index 0000000..11c2fc8 --- /dev/null +++ b/example.py @@ -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() diff --git a/mdclient/.gitignore b/mdclient/.gitignore new file mode 100644 index 0000000..d0078d0 --- /dev/null +++ b/mdclient/.gitignore @@ -0,0 +1,4 @@ +__pycache__/ +*.pyc +.env +key.conf diff --git a/mdclient/__init__.py b/mdclient/__init__.py new file mode 100644 index 0000000..16f052c --- /dev/null +++ b/mdclient/__init__.py @@ -0,0 +1,11 @@ +""" +MDclient - Markdown 筆記服務的 Python 客戶端函式庫 + +支援 CodiMD、HedgeDoc 等服務的登入、筆記管理等功能 +""" + +from .client import MDClient +from .auth import MDAuth + +__version__ = "0.1.0" +__all__ = ["MDClient", "MDAuth"] diff --git a/mdclient/auth.py b/mdclient/auth.py new file mode 100644 index 0000000..baf2243 --- /dev/null +++ b/mdclient/auth.py @@ -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 diff --git a/mdclient/client.py b/mdclient/client.py new file mode 100644 index 0000000..0b3c901 --- /dev/null +++ b/mdclient/client.py @@ -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 diff --git a/mdclient_cli.py b/mdclient_cli.py new file mode 100644 index 0000000..dc8ca37 --- /dev/null +++ b/mdclient_cli.py @@ -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() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..ff3adc6 --- /dev/null +++ b/pyproject.toml @@ -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