135 lines
4.4 KiB
Python
135 lines
4.4 KiB
Python
"""
|
|
Trilium notes executor for searching and creating notes via ETAPI.
|
|
|
|
This module provides integration with Trilium notes through the ETAPI HTTP API.
|
|
"""
|
|
|
|
import os
|
|
import aiohttp
|
|
from typing import Dict
|
|
|
|
|
|
TRILIUM_URL = os.getenv("TRILIUM_URL", "http://localhost:8080")
|
|
TRILIUM_TOKEN = os.getenv("TRILIUM_ETAPI_TOKEN", "")
|
|
|
|
|
|
async def search_notes(args: Dict) -> Dict:
|
|
"""Search Trilium notes via ETAPI.
|
|
|
|
Args:
|
|
args: Dictionary containing:
|
|
- query (str): Search query
|
|
- limit (int, optional): Maximum notes to return (default: 5, max: 20)
|
|
|
|
Returns:
|
|
dict: Search results containing:
|
|
- notes (list): List of notes with noteId, title, content, type
|
|
- count (int): Number of notes returned
|
|
OR
|
|
- error (str): Error message if search failed
|
|
"""
|
|
query = args.get("query")
|
|
limit = args.get("limit", 5)
|
|
|
|
# Validation
|
|
if not query:
|
|
return {"error": "No query provided"}
|
|
|
|
if not TRILIUM_TOKEN:
|
|
return {"error": "TRILIUM_ETAPI_TOKEN not configured in environment"}
|
|
|
|
# Cap limit
|
|
limit = min(max(limit, 1), 20)
|
|
|
|
try:
|
|
async with aiohttp.ClientSession() as session:
|
|
async with session.get(
|
|
f"{TRILIUM_URL}/etapi/notes",
|
|
params={"search": query, "limit": limit},
|
|
headers={"Authorization": TRILIUM_TOKEN}
|
|
) as resp:
|
|
if resp.status == 200:
|
|
data = await resp.json()
|
|
# ETAPI returns {"results": [...]} format
|
|
results = data.get("results", [])
|
|
return {
|
|
"notes": results,
|
|
"count": len(results)
|
|
}
|
|
elif resp.status == 401:
|
|
return {"error": "Authentication failed. Check TRILIUM_ETAPI_TOKEN"}
|
|
else:
|
|
error_text = await resp.text()
|
|
return {"error": f"HTTP {resp.status}: {error_text}"}
|
|
|
|
except aiohttp.ClientConnectorError:
|
|
return {"error": f"Cannot connect to Trilium at {TRILIUM_URL}"}
|
|
except Exception as e:
|
|
return {"error": f"Search failed: {str(e)}"}
|
|
|
|
|
|
async def create_note(args: Dict) -> Dict:
|
|
"""Create a note in Trilium via ETAPI.
|
|
|
|
Args:
|
|
args: Dictionary containing:
|
|
- title (str): Note title
|
|
- content (str): Note content in markdown or HTML
|
|
- parent_note_id (str, optional): Parent note ID to nest under
|
|
|
|
Returns:
|
|
dict: Creation result containing:
|
|
- noteId (str): ID of created note
|
|
- title (str): Title of created note
|
|
- success (bool): True if created successfully
|
|
OR
|
|
- error (str): Error message if creation failed
|
|
"""
|
|
title = args.get("title")
|
|
content = args.get("content")
|
|
parent_note_id = args.get("parent_note_id", "root") # Default to root if not specified
|
|
|
|
# Validation
|
|
if not title:
|
|
return {"error": "No title provided"}
|
|
|
|
if not content:
|
|
return {"error": "No content provided"}
|
|
|
|
if not TRILIUM_TOKEN:
|
|
return {"error": "TRILIUM_ETAPI_TOKEN not configured in environment"}
|
|
|
|
# Prepare payload
|
|
payload = {
|
|
"parentNoteId": parent_note_id, # Always include parentNoteId
|
|
"title": title,
|
|
"content": content,
|
|
"type": "text",
|
|
"mime": "text/html"
|
|
}
|
|
|
|
try:
|
|
async with aiohttp.ClientSession() as session:
|
|
async with session.post(
|
|
f"{TRILIUM_URL}/etapi/create-note",
|
|
json=payload,
|
|
headers={"Authorization": TRILIUM_TOKEN}
|
|
) as resp:
|
|
if resp.status in [200, 201]:
|
|
data = await resp.json()
|
|
return {
|
|
"noteId": data.get("noteId"),
|
|
"title": title,
|
|
"success": True
|
|
}
|
|
elif resp.status == 401:
|
|
return {"error": "Authentication failed. Check TRILIUM_ETAPI_TOKEN"}
|
|
else:
|
|
error_text = await resp.text()
|
|
return {"error": f"HTTP {resp.status}: {error_text}"}
|
|
|
|
except aiohttp.ClientConnectorError:
|
|
return {"error": f"Cannot connect to Trilium at {TRILIUM_URL}"}
|
|
except Exception as e:
|
|
return {"error": f"Note creation failed: {str(e)}"}
|