Add CLI source code and update .gitignore
This commit is contained in:
183
cli_anything/codimd/core/note.py
Normal file
183
cli_anything/codimd/core/note.py
Normal file
@@ -0,0 +1,183 @@
|
||||
"""
|
||||
Note operations for CodiMD CLI.
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import base64
|
||||
from typing import Optional, List, Dict, Any
|
||||
from datetime import datetime
|
||||
import uuid
|
||||
|
||||
from .client import CodiMDClient
|
||||
|
||||
|
||||
class NoteManager:
|
||||
"""Manager for note operations."""
|
||||
|
||||
def __init__(self, client: CodiMDClient):
|
||||
self.client = client
|
||||
|
||||
@staticmethod
|
||||
def encode_note_id(note_id: str) -> str:
|
||||
"""Encode UUID note ID to base64url format."""
|
||||
# Remove dashes and convert to bytes
|
||||
hex_str = note_id.replace("-", "")
|
||||
raw_bytes = bytes.fromhex(hex_str)
|
||||
# Encode to base64url
|
||||
return base64.urlsafe_b64encode(raw_bytes).decode("utf-8").rstrip("=")
|
||||
|
||||
@staticmethod
|
||||
def decode_note_id(encoded_id: str) -> str:
|
||||
"""Decode base64url note ID to UUID format."""
|
||||
# Add padding if needed
|
||||
padding = 4 - len(encoded_id) % 4
|
||||
if padding != 4:
|
||||
encoded_id += "=" * padding
|
||||
# Decode from base64url
|
||||
raw_bytes = base64.urlsafe_b64decode(encoded_id)
|
||||
hex_str = raw_bytes.hex()
|
||||
# Format as UUID
|
||||
parts = [
|
||||
hex_str[0:8], hex_str[8:12], hex_str[12:16],
|
||||
hex_str[16:20], hex_str[20:32]
|
||||
]
|
||||
return "-".join(parts)
|
||||
|
||||
def list_my_notes(self) -> List[Dict[str, Any]]:
|
||||
"""List notes owned by current user."""
|
||||
response = self.client.get("/api/notes/myNotes")
|
||||
if response.status_code == 403:
|
||||
raise PermissionError("Not authenticated or not authorized")
|
||||
if response.status_code != 200:
|
||||
raise Exception(f"Failed to list notes: {response.status_code}")
|
||||
|
||||
data = response.json()
|
||||
return data.get("myNotes", [])
|
||||
|
||||
def get_note(self, note_id: str) -> Dict[str, Any]:
|
||||
"""Get note content and metadata."""
|
||||
response = self.client.get(f"/{note_id}")
|
||||
if response.status_code == 404:
|
||||
raise FileNotFoundError(f"Note not found: {note_id}")
|
||||
if response.status_code == 403:
|
||||
raise PermissionError("No permission to view this note")
|
||||
if response.status_code != 200:
|
||||
raise Exception(f"Failed to get note: {response.status_code}")
|
||||
|
||||
# Parse response for note data
|
||||
# The note page contains the data in HTML, we need to extract it
|
||||
# For now, return a basic structure
|
||||
return {
|
||||
"id": note_id,
|
||||
"status": "success"
|
||||
}
|
||||
|
||||
def create_note(self, content: str = "", alias: str = None) -> Dict[str, Any]:
|
||||
"""Create a new note."""
|
||||
if content:
|
||||
response = self.client.post(
|
||||
"/new",
|
||||
data=content.encode("utf-8"),
|
||||
headers={"Content-Type": "text/markdown"}
|
||||
)
|
||||
else:
|
||||
response = self.client.get("/new")
|
||||
|
||||
if response.status_code != 200:
|
||||
raise Exception(f"Failed to create note: {response.status_code}")
|
||||
|
||||
# Extract note ID from response or redirect
|
||||
# The response might be a redirect to the new note
|
||||
if response.history:
|
||||
# Get the final URL
|
||||
final_url = response.url
|
||||
note_id = final_url.rstrip("/").split("/")[-1]
|
||||
else:
|
||||
# Parse response to find note ID
|
||||
note_id = self._extract_note_id_from_response(response)
|
||||
|
||||
return {
|
||||
"id": note_id,
|
||||
"url": f"{self.client.server}/{note_id}",
|
||||
"status": "created"
|
||||
}
|
||||
|
||||
def _extract_note_id_from_response(self, response) -> str:
|
||||
"""Extract note ID from response."""
|
||||
# Try to extract from JSON response
|
||||
try:
|
||||
data = response.json()
|
||||
return data.get("id") or data.get("noteId")
|
||||
except:
|
||||
pass
|
||||
|
||||
# Extract from HTML or use a default
|
||||
match = re.search(r'/([a-zA-Z0-9_-]+)', response.url or "")
|
||||
if match:
|
||||
return match.group(1)
|
||||
|
||||
return "unknown"
|
||||
|
||||
def update_note(self, note_id: str, content: str) -> Dict[str, Any]:
|
||||
"""Update note content."""
|
||||
response = self.client.put(
|
||||
f"/api/notes/{note_id}",
|
||||
json={"content": content},
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
|
||||
if response.status_code == 403:
|
||||
raise PermissionError("No permission to edit this note")
|
||||
if response.status_code == 404:
|
||||
raise FileNotFoundError(f"Note not found: {note_id}")
|
||||
if response.status_code != 200:
|
||||
raise Exception(f"Failed to update note: {response.status_code}")
|
||||
|
||||
return response.json()
|
||||
|
||||
def delete_note(self, note_id: str) -> bool:
|
||||
"""Delete a note."""
|
||||
response = self.client.delete(f"/api/notes/{note_id}")
|
||||
|
||||
if response.status_code == 403:
|
||||
raise PermissionError("No permission to delete this note")
|
||||
if response.status_code == 404:
|
||||
raise FileNotFoundError(f"Note not found: {note_id}")
|
||||
if response.status_code != 200:
|
||||
raise Exception(f"Failed to delete note: {response.status_code}")
|
||||
|
||||
return True
|
||||
|
||||
def get_note_info(self, note_id: str) -> Dict[str, Any]:
|
||||
"""Get note metadata without content."""
|
||||
# Use the /:noteId/info endpoint
|
||||
response = self.client.get(f"/{noteId}/info")
|
||||
|
||||
if response.status_code == 404:
|
||||
raise FileNotFoundError(f"Note not found: {note_id}")
|
||||
if response.status_code == 403:
|
||||
raise PermissionError("No permission to view this note")
|
||||
if response.status_code != 200:
|
||||
raise Exception(f"Failed to get note info: {response.status_code}")
|
||||
|
||||
try:
|
||||
return response.json()
|
||||
except:
|
||||
return {"id": note_id, "status": "success"}
|
||||
|
||||
def get_publish_link(self, note_id: str) -> Dict[str, Any]:
|
||||
"""Get or create publish link for note."""
|
||||
response = self.client.get(f"/{noteId}/publish")
|
||||
|
||||
if response.status_code == 404:
|
||||
raise FileNotFoundError(f"Note not found: {note_id}")
|
||||
if response.status_code != 200:
|
||||
raise Exception(f"Failed to get publish link: {response.status_code}")
|
||||
|
||||
# Extract short ID from response
|
||||
return {
|
||||
"url": response.url,
|
||||
"note_id": note_id,
|
||||
"status": "published"
|
||||
}
|
||||
Reference in New Issue
Block a user