Skip to main content
Glama
notes.py7.26 kB
"""Client for Nextcloud Notes app operations.""" import logging from typing import Any, AsyncIterator, Dict, Optional from .base import BaseNextcloudClient logger = logging.getLogger(__name__) class NotesClient(BaseNextcloudClient): """Client for Nextcloud Notes app operations.""" async def get_settings(self) -> Dict[str, Any]: """Get Notes app settings.""" response = await self._make_request("GET", "/apps/notes/api/v1/settings") return response.json() async def get_all_notes(self) -> AsyncIterator[Dict[str, Any]]: """Get all notes, yielding them one at a time.""" cursor = "" while True: response = await self._make_request( "GET", "/apps/notes/api/v1/notes", params={"chunkSize": 10, "chunkCursor": cursor}, ) for note in response.json(): yield note if "X-Notes-Chunk-Cursor" not in response.headers: break cursor = response.headers["X-Notes-Chunk-Cursor"] async def get_note(self, note_id: int) -> Dict[str, Any]: """Get a specific note by ID.""" response = await self._make_request( "GET", f"/apps/notes/api/v1/notes/{note_id}" ) return response.json() async def create_note( self, title: Optional[str] = None, content: Optional[str] = None, category: Optional[str] = None, ) -> Dict[str, Any]: """Create a new note.""" body = {} if title: body["title"] = title if content: body["content"] = content if category: body["category"] = category response = await self._make_request( "POST", "/apps/notes/api/v1/notes", json=body ) return response.json() async def update( self, note_id: int, etag: str, title: Optional[str] = None, content: Optional[str] = None, category: Optional[str] = None, ) -> Dict[str, Any]: """Update an existing note.""" # Get current note details to check for category change old_note = None try: if category is not None: old_note = await self.get_note(note_id) old_category = old_note.get("category", "") logger.info(f"Current category for note {note_id}: '{old_category}'") except Exception as e: logger.warning( f"Could not fetch current note {note_id} details before update: {e}" ) old_note = None # Prepare update body body = {} if title: body["title"] = title if content: body["content"] = content if category: body["category"] = category logger.info( f"Attempting to update note {note_id} with etag {etag}. Body: {body}" ) response = await self._make_request( "PUT", f"/apps/notes/api/v1/notes/{note_id}", json=body, headers={"If-Match": f'"{etag}"'}, ) logger.info( f"Update response for note {note_id}: Status {response.status_code}" ) updated_note = response.json() # Check for category change and cleanup old attachment directory if needed if ( old_note and category is not None and old_note.get("category", "") != category ): logger.info( f"Category changed from '{old_note.get('category', '')}' to '{category}' - cleaning up old attachment directory" ) try: # Import here to avoid circular imports from .webdav import WebDAVClient webdav_client = WebDAVClient(self._client, self.username) await webdav_client.cleanup_old_attachment_directory( note_id=note_id, old_category=old_note.get("category", "") ) except Exception as e: logger.error( f"Error cleaning up old attachment directory for note {note_id}: {e}" ) return updated_note async def delete_note(self, note_id: int) -> Dict[str, Any]: """Delete a note and its attachments.""" # Fetch note details first to get category for cleanup try: note_details = await self.get_note(note_id) category = note_details.get("category", "") # Determine potential categories for cleanup potential_categories = [] if category: potential_categories.append(category) if category != "": potential_categories.append("") # Empty category logger.info( f"Note {note_id} has category: '{category}', will check attachment directories in: {potential_categories}" ) except Exception as e: logger.warning( f"Could not fetch note {note_id} details before deletion: {e}" ) potential_categories = ["", "Unknown"] # Try common categories # Delete the note via API logger.info(f"Deleting note {note_id} via API") response = await self._make_request( "DELETE", f"/apps/notes/api/v1/notes/{note_id}" ) logger.info(f"Note {note_id} deleted successfully via API") json_response = response.json() # Clean up attachment directories try: from .webdav import WebDAVClient webdav_client = WebDAVClient(self._client, self.username) for cat in potential_categories: try: await webdav_client.cleanup_note_attachments(note_id, cat) except Exception as e: logger.warning( f"Failed to cleanup attachments for category '{cat}': {e}" ) except Exception as e: logger.warning(f"Error during attachment cleanup: {e}") return json_response async def append_content(self, note_id: int, content: str) -> Dict[str, Any]: """Append content to an existing note with a separator.""" logger.info(f"Appending content to note {note_id}") # Get current note current_note = await self.get_note(note_id) # Use fixed separator for consistency separator = "\n---\n" # Combine content existing_content = current_note.get("content", "") if existing_content: new_content = existing_content + separator + content else: new_content = content # No separator needed for empty notes logger.info( f"Combining existing content ({len(existing_content)} chars) with new content ({len(content)} chars)" ) # Update with combined content return await self.update( note_id=note_id, etag=current_note["etag"], content=new_content, title=None, # Keep existing title category=None, # Keep existing category )

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/No-Smoke/nextcloud-mcp-comprehensive'

If you have feedback or need assistance with the MCP directory API, please join our Discord server