bookstack_connector.py aktualisiert
This commit is contained in:
@@ -3,7 +3,7 @@ title: BookStack Connector
|
|||||||
description: Connects to a BookStack wiki server via its REST API. Allows the LLM to list, read, create, edit and delete shelves, books, chapters and pages. All capabilities can be individually enabled/disabled by an admin via Valves.
|
description: Connects to a BookStack wiki server via its REST API. Allows the LLM to list, read, create, edit and delete shelves, books, chapters and pages. All capabilities can be individually enabled/disabled by an admin via Valves.
|
||||||
author: Claude / Anthropic
|
author: Claude / Anthropic
|
||||||
author_url: https://claude.ai
|
author_url: https://claude.ai
|
||||||
version: 1.3.0
|
version: 1.4.0
|
||||||
license: MIT
|
license: MIT
|
||||||
requirements: requests
|
requirements: requests
|
||||||
"""
|
"""
|
||||||
@@ -14,13 +14,12 @@ import requests
|
|||||||
from typing import Any, Callable, Optional
|
from typing import Any, Callable, Optional
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
# Logging-Setup: schreibt in den OWUI-Server-Log (podman logs open-webui)
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
log = logging.getLogger("BookStackConnector")
|
log = logging.getLogger("BookStackConnector")
|
||||||
|
|
||||||
|
|
||||||
# ─────────────────────────────────────────────
|
# ─────────────────────────────────────────────
|
||||||
# Helper: EventEmitter wrapper
|
# Helper: EventEmitter wrapper
|
||||||
# ─────────────────────────────────────────────
|
# ─────────────────────────────────────────────
|
||||||
class EventEmitter:
|
class EventEmitter:
|
||||||
def __init__(self, event_emitter: Callable[[dict], Any] = None):
|
def __init__(self, event_emitter: Callable[[dict], Any] = None):
|
||||||
@@ -46,7 +45,7 @@ class EventEmitter:
|
|||||||
|
|
||||||
|
|
||||||
# ─────────────────────────────────────────────
|
# ─────────────────────────────────────────────
|
||||||
# Helper: BookStack API client
|
# Helper: BookStack API client
|
||||||
# ─────────────────────────────────────────────
|
# ─────────────────────────────────────────────
|
||||||
class BookStackAPI:
|
class BookStackAPI:
|
||||||
def __init__(self, base_url: str, token_id: str, token_secret: str):
|
def __init__(self, base_url: str, token_id: str, token_secret: str):
|
||||||
@@ -57,7 +56,6 @@ class BookStackAPI:
|
|||||||
}
|
}
|
||||||
|
|
||||||
def _parse_response(self, resp: requests.Response) -> dict:
|
def _parse_response(self, resp: requests.Response) -> dict:
|
||||||
"""Parst die API-Antwort mit ausführlichem Logging bei Fehlern."""
|
|
||||||
log.debug(
|
log.debug(
|
||||||
"[BookStack] %s %s → HTTP %s | Content-Type: %s | Body[:300]: %s",
|
"[BookStack] %s %s → HTTP %s | Content-Type: %s | Body[:300]: %s",
|
||||||
resp.request.method,
|
resp.request.method,
|
||||||
@@ -79,10 +77,10 @@ class BookStackAPI:
|
|||||||
f"Content-Type: {content_type}\n"
|
f"Content-Type: {content_type}\n"
|
||||||
f"Antwort (Anfang): {resp.text[:300]}\n\n"
|
f"Antwort (Anfang): {resp.text[:300]}\n\n"
|
||||||
f"Mögliche Ursachen:\n"
|
f"Mögliche Ursachen:\n"
|
||||||
f" • BOOKSTACK_URL falsch oder nicht erreichbar\n"
|
f" • BOOKSTACK_URL falsch oder nicht erreichbar\n"
|
||||||
f" • API antwortet mit Login-Seite (HTML) → Token ungültig\n"
|
f" • API antwortet mit Login-Seite (HTML) → Token ungültig\n"
|
||||||
f" • SSL-Fehler oder Redirect\n"
|
f" • SSL-Fehler oder Redirect\n"
|
||||||
f" • URL endet mit '/' → doppeltes '/api/'"
|
f" • URL endet mit '/' → doppeltes '/api/'"
|
||||||
)
|
)
|
||||||
return resp.json()
|
return resp.json()
|
||||||
|
|
||||||
@@ -128,13 +126,11 @@ class BookStackAPI:
|
|||||||
|
|
||||||
|
|
||||||
# ─────────────────────────────────────────────
|
# ─────────────────────────────────────────────
|
||||||
# Main Tool class
|
# Main Tool class
|
||||||
# ─────────────────────────────────────────────
|
# ─────────────────────────────────────────────
|
||||||
class Tools:
|
class Tools:
|
||||||
|
|
||||||
# ── Admin-Valves ──────────────────────────
|
|
||||||
class Valves(BaseModel):
|
class Valves(BaseModel):
|
||||||
# --- Authentication ---
|
|
||||||
BOOKSTACK_URL: str = Field(
|
BOOKSTACK_URL: str = Field(
|
||||||
default="",
|
default="",
|
||||||
description="Base URL deiner BookStack-Instanz, z.B. https://wiki.example.com",
|
description="Base URL deiner BookStack-Instanz, z.B. https://wiki.example.com",
|
||||||
@@ -151,8 +147,6 @@ class Tools:
|
|||||||
default=100,
|
default=100,
|
||||||
description="Maximale Anzahl Items pro API-Request (Pagination)",
|
description="Maximale Anzahl Items pro API-Request (Pagination)",
|
||||||
)
|
)
|
||||||
|
|
||||||
# --- Feature Flags (Lesen) ---
|
|
||||||
ENABLE_LIST_STRUCTURE: bool = Field(
|
ENABLE_LIST_STRUCTURE: bool = Field(
|
||||||
default=True,
|
default=True,
|
||||||
description="✅ Erlauben: Gesamte Bibliotheksstruktur anzeigen (Shelves → Books → Chapters → Pages)",
|
description="✅ Erlauben: Gesamte Bibliotheksstruktur anzeigen (Shelves → Books → Chapters → Pages)",
|
||||||
@@ -165,8 +159,6 @@ class Tools:
|
|||||||
default=True,
|
default=True,
|
||||||
description="✅ Erlauben: Inhalte durchsuchen",
|
description="✅ Erlauben: Inhalte durchsuchen",
|
||||||
)
|
)
|
||||||
|
|
||||||
# --- Feature Flags (Schreiben: Seiten) ---
|
|
||||||
ENABLE_CREATE_PAGE: bool = Field(
|
ENABLE_CREATE_PAGE: bool = Field(
|
||||||
default=False,
|
default=False,
|
||||||
description="⚠️ Erlauben: Neue Seiten erstellen",
|
description="⚠️ Erlauben: Neue Seiten erstellen",
|
||||||
@@ -179,8 +171,6 @@ class Tools:
|
|||||||
default=False,
|
default=False,
|
||||||
description="🔴 Erlauben: Seiten löschen (UNWIDERRUFLICH!)",
|
description="🔴 Erlauben: Seiten löschen (UNWIDERRUFLICH!)",
|
||||||
)
|
)
|
||||||
|
|
||||||
# --- Feature Flags (Schreiben: Bücher & Kapitel) ---
|
|
||||||
ENABLE_CREATE_BOOK: bool = Field(
|
ENABLE_CREATE_BOOK: bool = Field(
|
||||||
default=False,
|
default=False,
|
||||||
description="⚠️ Erlauben: Neue Bücher erstellen",
|
description="⚠️ Erlauben: Neue Bücher erstellen",
|
||||||
@@ -199,40 +189,48 @@ class Tools:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""
|
|
||||||
Pflicht-Initialisierung: OWUI braucht self.valves = self.Valves() im __init__.
|
|
||||||
OWUI setzt danach die gespeicherten Admin-Werte auf self.valves – aber nur
|
|
||||||
wenn das Attribut bereits existiert. Ohne __init__ → 'has no attribute valves'.
|
|
||||||
"""
|
|
||||||
self.valves = self.Valves()
|
self.valves = self.Valves()
|
||||||
|
|
||||||
def _api(self) -> BookStackAPI:
|
def _get_valves(self, __user__: dict) -> "Tools.Valves":
|
||||||
|
"""
|
||||||
|
Workaround für OWUI-Bug: Valves werden nicht korrekt in self.valves injiziert.
|
||||||
|
OWUI übergibt die echten Werte zuverlässig über __user__['valves'].
|
||||||
|
"""
|
||||||
|
user_valves = __user__.get("valves") if __user__ else None
|
||||||
|
if user_valves:
|
||||||
|
try:
|
||||||
|
return self.Valves(**user_valves) if isinstance(user_valves, dict) else user_valves
|
||||||
|
except Exception as e:
|
||||||
|
log.warning("[BookStack] Valve-Parsing aus __user__ fehlgeschlagen: %s", e)
|
||||||
|
return self.valves
|
||||||
|
|
||||||
|
def _api(self, valves: "Tools.Valves") -> BookStackAPI:
|
||||||
return BookStackAPI(
|
return BookStackAPI(
|
||||||
self.valves.BOOKSTACK_URL,
|
valves.BOOKSTACK_URL,
|
||||||
self.valves.API_TOKEN_ID,
|
valves.API_TOKEN_ID,
|
||||||
self.valves.API_TOKEN_SECRET,
|
valves.API_TOKEN_SECRET,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _check_config(self) -> Optional[str]:
|
def _check_config(self, valves: "Tools.Valves") -> Optional[str]:
|
||||||
"""Prüft ob Auth-Daten gesetzt sind. Gibt Fehlermeldung zurück oder None."""
|
tid = valves.API_TOKEN_ID
|
||||||
tid = self.valves.API_TOKEN_ID
|
|
||||||
log.debug(
|
log.debug(
|
||||||
"[BookStack] Config-Check → URL='%s' | TOKEN_ID='%s' | TOKEN_SECRET='%s'",
|
"[BookStack] Config-Check → URL='%s' | TOKEN_ID='%s' | TOKEN_SECRET='%s'",
|
||||||
self.valves.BOOKSTACK_URL,
|
valves.BOOKSTACK_URL,
|
||||||
(tid[:4] + "***") if tid else "(leer)",
|
(tid[:4] + "***") if tid else "(leer)",
|
||||||
"***" if self.valves.API_TOKEN_SECRET else "(leer)",
|
"***" if valves.API_TOKEN_SECRET else "(leer)",
|
||||||
)
|
)
|
||||||
if not self.valves.BOOKSTACK_URL:
|
if not valves.BOOKSTACK_URL:
|
||||||
return "❌ Bitte BOOKSTACK_URL in den Valve-Einstellungen eintragen."
|
return "❌ Bitte BOOKSTACK_URL in den Valve-Einstellungen eintragen."
|
||||||
if not self.valves.API_TOKEN_ID or not self.valves.API_TOKEN_SECRET:
|
if not valves.API_TOKEN_ID or not valves.API_TOKEN_SECRET:
|
||||||
return "❌ Bitte API_TOKEN_ID und API_TOKEN_SECRET in den Valve-Einstellungen eintragen."
|
return "❌ Bitte API_TOKEN_ID und API_TOKEN_SECRET in den Valve-Einstellungen eintragen."
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# ─────────────────────────────────────────
|
# ─────────────────────────────────────────
|
||||||
# TOOL 1 – Gesamtstruktur anzeigen
|
# TOOL 1 – Gesamtstruktur anzeigen
|
||||||
# ─────────────────────────────────────────
|
# ─────────────────────────────────────────
|
||||||
async def get_library_structure(
|
async def get_library_structure(
|
||||||
self,
|
self,
|
||||||
|
__user__: dict = {},
|
||||||
__event_emitter__: Callable[[dict], Any] = None,
|
__event_emitter__: Callable[[dict], Any] = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
@@ -243,33 +241,33 @@ class Tools:
|
|||||||
|
|
||||||
:return: A formatted Markdown tree of the entire library with IDs.
|
:return: A formatted Markdown tree of the entire library with IDs.
|
||||||
"""
|
"""
|
||||||
|
valves = self._get_valves(__user__)
|
||||||
emitter = EventEmitter(__event_emitter__)
|
emitter = EventEmitter(__event_emitter__)
|
||||||
|
|
||||||
if not self.valves.ENABLE_LIST_STRUCTURE:
|
if not valves.ENABLE_LIST_STRUCTURE:
|
||||||
return "❌ Die Funktion 'Bibliotheksstruktur anzeigen' ist vom Administrator deaktiviert."
|
return "❌ Die Funktion 'Bibliotheksstruktur anzeigen' ist vom Administrator deaktiviert."
|
||||||
|
|
||||||
err = self._check_config()
|
err = self._check_config(valves)
|
||||||
if err:
|
if err:
|
||||||
return err
|
return err
|
||||||
|
|
||||||
try:
|
try:
|
||||||
api = self._api()
|
api = self._api(valves)
|
||||||
|
|
||||||
await emitter.status("📚 Lade Shelves...")
|
await emitter.status("📚 Lade Shelves...")
|
||||||
shelves = api.list_all("shelves", self.valves.MAX_ITEMS)
|
shelves = api.list_all("shelves", valves.MAX_ITEMS)
|
||||||
|
|
||||||
await emitter.status("📖 Lade Bücher...")
|
await emitter.status("📖 Lade Bücher...")
|
||||||
books = api.list_all("books", self.valves.MAX_ITEMS)
|
books = api.list_all("books", valves.MAX_ITEMS)
|
||||||
|
|
||||||
await emitter.status("📑 Lade Kapitel...")
|
await emitter.status("📑 Lade Kapitel...")
|
||||||
chapters = api.list_all("chapters", self.valves.MAX_ITEMS)
|
chapters = api.list_all("chapters", valves.MAX_ITEMS)
|
||||||
|
|
||||||
await emitter.status("📄 Lade Seiten...")
|
await emitter.status("📄 Lade Seiten...")
|
||||||
pages = api.list_all("pages", self.valves.MAX_ITEMS)
|
pages = api.list_all("pages", valves.MAX_ITEMS)
|
||||||
|
|
||||||
await emitter.status("🗂️ Baue Strukturbaum...")
|
await emitter.status("🗂️ Baue Strukturbaum...")
|
||||||
|
|
||||||
# Shelf → Book Mapping via Shelf-Detailendpunkt
|
|
||||||
shelf_book_map: dict = {s["id"]: [] for s in shelves}
|
shelf_book_map: dict = {s["id"]: [] for s in shelves}
|
||||||
for shelf in shelves:
|
for shelf in shelves:
|
||||||
detail = api._get(f"shelves/{shelf['id']}")
|
detail = api._get(f"shelves/{shelf['id']}")
|
||||||
@@ -346,22 +344,9 @@ class Tools:
|
|||||||
|
|
||||||
except requests.HTTPError as e:
|
except requests.HTTPError as e:
|
||||||
body = e.response.text[:500] if e.response is not None else "(kein Body)"
|
body = e.response.text[:500] if e.response is not None else "(kein Body)"
|
||||||
ct = (
|
ct = e.response.headers.get("Content-Type", "?") if e.response is not None else "?"
|
||||||
e.response.headers.get("Content-Type", "?")
|
log.error("[BookStack] HTTP-Fehler %s | Content-Type=%s | Body=%s", e.response.status_code, ct, body)
|
||||||
if e.response is not None
|
msg = f"❌ BookStack API-Fehler: HTTP {e.response.status_code}\nContent-Type: {ct}\nAntwort: {body}"
|
||||||
else "?"
|
|
||||||
)
|
|
||||||
log.error(
|
|
||||||
"[BookStack] HTTP-Fehler %s | Content-Type=%s | Body=%s",
|
|
||||||
e.response.status_code,
|
|
||||||
ct,
|
|
||||||
body,
|
|
||||||
)
|
|
||||||
msg = (
|
|
||||||
f"❌ BookStack API-Fehler: HTTP {e.response.status_code}\n"
|
|
||||||
f"Content-Type: {ct}\n"
|
|
||||||
f"Antwort: {body}"
|
|
||||||
)
|
|
||||||
await emitter.error(msg)
|
await emitter.error(msg)
|
||||||
return msg
|
return msg
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -371,12 +356,13 @@ class Tools:
|
|||||||
return msg
|
return msg
|
||||||
|
|
||||||
# ─────────────────────────────────────────
|
# ─────────────────────────────────────────
|
||||||
# TOOL 2 – Seite lesen
|
# TOOL 2 – Seite lesen
|
||||||
# ─────────────────────────────────────────
|
# ─────────────────────────────────────────
|
||||||
async def get_page_content(
|
async def get_page_content(
|
||||||
self,
|
self,
|
||||||
page_id: int,
|
page_id: int,
|
||||||
format: str = "markdown",
|
format: str = "markdown",
|
||||||
|
__user__: dict = {},
|
||||||
__event_emitter__: Callable[[dict], Any] = None,
|
__event_emitter__: Callable[[dict], Any] = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
@@ -387,16 +373,17 @@ class Tools:
|
|||||||
:param format: Content format – 'markdown', 'html', or 'both'. Default: 'markdown'.
|
:param format: Content format – 'markdown', 'html', or 'both'. Default: 'markdown'.
|
||||||
:return: The page title and its content in the requested format.
|
:return: The page title and its content in the requested format.
|
||||||
"""
|
"""
|
||||||
|
valves = self._get_valves(__user__)
|
||||||
emitter = EventEmitter(__event_emitter__)
|
emitter = EventEmitter(__event_emitter__)
|
||||||
|
|
||||||
if not self.valves.ENABLE_READ_PAGE:
|
if not valves.ENABLE_READ_PAGE:
|
||||||
return "❌ Die Funktion 'Seite lesen' ist vom Administrator deaktiviert."
|
return "❌ Die Funktion 'Seite lesen' ist vom Administrator deaktiviert."
|
||||||
err = self._check_config()
|
err = self._check_config(valves)
|
||||||
if err:
|
if err:
|
||||||
return err
|
return err
|
||||||
|
|
||||||
try:
|
try:
|
||||||
api = self._api()
|
api = self._api(valves)
|
||||||
await emitter.status(f"📄 Lade Seite {page_id}...")
|
await emitter.status(f"📄 Lade Seite {page_id}...")
|
||||||
data = api._get(f"pages/{int(page_id)}")
|
data = api._get(f"pages/{int(page_id)}")
|
||||||
|
|
||||||
@@ -420,31 +407,15 @@ class Tools:
|
|||||||
lines.append(md if md else "*Kein Markdown-Inhalt verfügbar.*")
|
lines.append(md if md else "*Kein Markdown-Inhalt verfügbar.*")
|
||||||
if fmt in ("html", "both"):
|
if fmt in ("html", "both"):
|
||||||
lines.append("\n## Inhalt (HTML)")
|
lines.append("\n## Inhalt (HTML)")
|
||||||
lines.append(
|
lines.append(f"```html\n{html}\n```" if html else "*Kein HTML-Inhalt verfügbar.*")
|
||||||
f"```html\n{html}\n```" if html else "*Kein HTML-Inhalt verfügbar.*"
|
|
||||||
)
|
|
||||||
|
|
||||||
await emitter.status("✅ Seite geladen.", done=True)
|
await emitter.status("✅ Seite geladen.", done=True)
|
||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
|
||||||
except requests.HTTPError as e:
|
except requests.HTTPError as e:
|
||||||
body = e.response.text[:500] if e.response is not None else "(kein Body)"
|
body = e.response.text[:500] if e.response is not None else "(kein Body)"
|
||||||
ct = (
|
ct = e.response.headers.get("Content-Type", "?") if e.response is not None else "?"
|
||||||
e.response.headers.get("Content-Type", "?")
|
msg = f"❌ BookStack API-Fehler: HTTP {e.response.status_code}\nContent-Type: {ct}\nAntwort: {body}"
|
||||||
if e.response is not None
|
|
||||||
else "?"
|
|
||||||
)
|
|
||||||
log.error(
|
|
||||||
"[BookStack] HTTP-Fehler %s | Content-Type=%s | Body=%s",
|
|
||||||
e.response.status_code,
|
|
||||||
ct,
|
|
||||||
body,
|
|
||||||
)
|
|
||||||
msg = (
|
|
||||||
f"❌ BookStack API-Fehler: HTTP {e.response.status_code}\n"
|
|
||||||
f"Content-Type: {ct}\n"
|
|
||||||
f"Antwort: {body}"
|
|
||||||
)
|
|
||||||
await emitter.error(msg)
|
await emitter.error(msg)
|
||||||
return msg
|
return msg
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -454,11 +425,12 @@ class Tools:
|
|||||||
return msg
|
return msg
|
||||||
|
|
||||||
# ─────────────────────────────────────────
|
# ─────────────────────────────────────────
|
||||||
# TOOL 3 – Suche
|
# TOOL 3 – Suche
|
||||||
# ─────────────────────────────────────────
|
# ─────────────────────────────────────────
|
||||||
async def search_content(
|
async def search_content(
|
||||||
self,
|
self,
|
||||||
query: str,
|
query: str,
|
||||||
|
__user__: dict = {},
|
||||||
__event_emitter__: Callable[[dict], Any] = None,
|
__event_emitter__: Callable[[dict], Any] = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
@@ -468,24 +440,23 @@ class Tools:
|
|||||||
:param query: The search string.
|
:param query: The search string.
|
||||||
:return: A list of matching content items with IDs and types.
|
:return: A list of matching content items with IDs and types.
|
||||||
"""
|
"""
|
||||||
|
valves = self._get_valves(__user__)
|
||||||
emitter = EventEmitter(__event_emitter__)
|
emitter = EventEmitter(__event_emitter__)
|
||||||
|
|
||||||
if not self.valves.ENABLE_SEARCH:
|
if not valves.ENABLE_SEARCH:
|
||||||
return "❌ Die Funktion 'Suche' ist vom Administrator deaktiviert."
|
return "❌ Die Funktion 'Suche' ist vom Administrator deaktiviert."
|
||||||
err = self._check_config()
|
err = self._check_config(valves)
|
||||||
if err:
|
if err:
|
||||||
return err
|
return err
|
||||||
|
|
||||||
try:
|
try:
|
||||||
api = self._api()
|
api = self._api(valves)
|
||||||
await emitter.status(f"🔍 Suche nach: '{query}'...")
|
await emitter.status(f"🔍 Suche nach: '{query}'...")
|
||||||
data = api._get("search", params={"query": query, "count": 30})
|
data = api._get("search", params={"query": query, "count": 30})
|
||||||
results = data.get("data", [])
|
results = data.get("data", [])
|
||||||
|
|
||||||
if not results:
|
if not results:
|
||||||
await emitter.status(
|
await emitter.status("✅ Suche abgeschlossen – keine Ergebnisse.", done=True)
|
||||||
"✅ Suche abgeschlossen – keine Ergebnisse.", done=True
|
|
||||||
)
|
|
||||||
return f"🔍 Keine Ergebnisse für **'{query}'**."
|
return f"🔍 Keine Ergebnisse für **'{query}'**."
|
||||||
|
|
||||||
type_icons = {
|
type_icons = {
|
||||||
@@ -508,7 +479,7 @@ class Tools:
|
|||||||
preview_clean = re.sub(r"<[^>]+>", "", preview)[:200]
|
preview_clean = re.sub(r"<[^>]+>", "", preview)[:200]
|
||||||
lines.append(f"{icon} **{name}** `[{itype.capitalize()} ID: {iid}]`")
|
lines.append(f"{icon} **{name}** `[{itype.capitalize()} ID: {iid}]`")
|
||||||
if preview_clean:
|
if preview_clean:
|
||||||
lines.append(f" > {preview_clean}…")
|
lines.append(f" > {preview_clean}…")
|
||||||
lines.append("")
|
lines.append("")
|
||||||
|
|
||||||
await emitter.status("✅ Suche abgeschlossen.", done=True)
|
await emitter.status("✅ Suche abgeschlossen.", done=True)
|
||||||
@@ -516,22 +487,8 @@ class Tools:
|
|||||||
|
|
||||||
except requests.HTTPError as e:
|
except requests.HTTPError as e:
|
||||||
body = e.response.text[:500] if e.response is not None else "(kein Body)"
|
body = e.response.text[:500] if e.response is not None else "(kein Body)"
|
||||||
ct = (
|
ct = e.response.headers.get("Content-Type", "?") if e.response is not None else "?"
|
||||||
e.response.headers.get("Content-Type", "?")
|
msg = f"❌ BookStack API-Fehler: HTTP {e.response.status_code}\nContent-Type: {ct}\nAntwort: {body}"
|
||||||
if e.response is not None
|
|
||||||
else "?"
|
|
||||||
)
|
|
||||||
log.error(
|
|
||||||
"[BookStack] HTTP-Fehler %s | Content-Type=%s | Body=%s",
|
|
||||||
e.response.status_code,
|
|
||||||
ct,
|
|
||||||
body,
|
|
||||||
)
|
|
||||||
msg = (
|
|
||||||
f"❌ BookStack API-Fehler: HTTP {e.response.status_code}\n"
|
|
||||||
f"Content-Type: {ct}\n"
|
|
||||||
f"Antwort: {body}"
|
|
||||||
)
|
|
||||||
await emitter.error(msg)
|
await emitter.error(msg)
|
||||||
return msg
|
return msg
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -541,7 +498,7 @@ class Tools:
|
|||||||
return msg
|
return msg
|
||||||
|
|
||||||
# ─────────────────────────────────────────
|
# ─────────────────────────────────────────
|
||||||
# TOOL 4 – Seite erstellen
|
# TOOL 4 – Seite erstellen
|
||||||
# ─────────────────────────────────────────
|
# ─────────────────────────────────────────
|
||||||
async def create_page(
|
async def create_page(
|
||||||
self,
|
self,
|
||||||
@@ -550,6 +507,7 @@ class Tools:
|
|||||||
content: str,
|
content: str,
|
||||||
content_format: str = "markdown",
|
content_format: str = "markdown",
|
||||||
chapter_id: Optional[int] = None,
|
chapter_id: Optional[int] = None,
|
||||||
|
__user__: dict = {},
|
||||||
__event_emitter__: Callable[[dict], Any] = None,
|
__event_emitter__: Callable[[dict], Any] = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
@@ -563,18 +521,17 @@ class Tools:
|
|||||||
:param chapter_id: Optional chapter ID. Omit to place page at book level.
|
:param chapter_id: Optional chapter ID. Omit to place page at book level.
|
||||||
:return: Confirmation with the new page's ID.
|
:return: Confirmation with the new page's ID.
|
||||||
"""
|
"""
|
||||||
|
valves = self._get_valves(__user__)
|
||||||
emitter = EventEmitter(__event_emitter__)
|
emitter = EventEmitter(__event_emitter__)
|
||||||
|
|
||||||
if not self.valves.ENABLE_CREATE_PAGE:
|
if not valves.ENABLE_CREATE_PAGE:
|
||||||
return (
|
return "❌ Die Funktion 'Seite erstellen' ist vom Administrator deaktiviert."
|
||||||
"❌ Die Funktion 'Seite erstellen' ist vom Administrator deaktiviert."
|
err = self._check_config(valves)
|
||||||
)
|
|
||||||
err = self._check_config()
|
|
||||||
if err:
|
if err:
|
||||||
return err
|
return err
|
||||||
|
|
||||||
try:
|
try:
|
||||||
api = self._api()
|
api = self._api(valves)
|
||||||
await emitter.status(f"✏️ Erstelle Seite '{title}'...")
|
await emitter.status(f"✏️ Erstelle Seite '{title}'...")
|
||||||
|
|
||||||
payload: dict = {"book_id": int(book_id), "name": title}
|
payload: dict = {"book_id": int(book_id), "name": title}
|
||||||
@@ -599,22 +556,8 @@ class Tools:
|
|||||||
|
|
||||||
except requests.HTTPError as e:
|
except requests.HTTPError as e:
|
||||||
body = e.response.text[:500] if e.response is not None else "(kein Body)"
|
body = e.response.text[:500] if e.response is not None else "(kein Body)"
|
||||||
ct = (
|
ct = e.response.headers.get("Content-Type", "?") if e.response is not None else "?"
|
||||||
e.response.headers.get("Content-Type", "?")
|
msg = f"❌ BookStack API-Fehler: HTTP {e.response.status_code}\nContent-Type: {ct}\nAntwort: {body}"
|
||||||
if e.response is not None
|
|
||||||
else "?"
|
|
||||||
)
|
|
||||||
log.error(
|
|
||||||
"[BookStack] HTTP-Fehler %s | Content-Type=%s | Body=%s",
|
|
||||||
e.response.status_code,
|
|
||||||
ct,
|
|
||||||
body,
|
|
||||||
)
|
|
||||||
msg = (
|
|
||||||
f"❌ BookStack API-Fehler: HTTP {e.response.status_code}\n"
|
|
||||||
f"Content-Type: {ct}\n"
|
|
||||||
f"Antwort: {body}"
|
|
||||||
)
|
|
||||||
await emitter.error(msg)
|
await emitter.error(msg)
|
||||||
return msg
|
return msg
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -624,7 +567,7 @@ class Tools:
|
|||||||
return msg
|
return msg
|
||||||
|
|
||||||
# ─────────────────────────────────────────
|
# ─────────────────────────────────────────
|
||||||
# TOOL 5 – Seite bearbeiten
|
# TOOL 5 – Seite bearbeiten
|
||||||
# ─────────────────────────────────────────
|
# ─────────────────────────────────────────
|
||||||
async def update_page(
|
async def update_page(
|
||||||
self,
|
self,
|
||||||
@@ -632,6 +575,7 @@ class Tools:
|
|||||||
title: Optional[str] = None,
|
title: Optional[str] = None,
|
||||||
content: Optional[str] = None,
|
content: Optional[str] = None,
|
||||||
content_format: str = "markdown",
|
content_format: str = "markdown",
|
||||||
|
__user__: dict = {},
|
||||||
__event_emitter__: Callable[[dict], Any] = None,
|
__event_emitter__: Callable[[dict], Any] = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
@@ -644,13 +588,12 @@ class Tools:
|
|||||||
:param content_format: Format of the content – 'markdown' or 'html'. Default: 'markdown'.
|
:param content_format: Format of the content – 'markdown' or 'html'. Default: 'markdown'.
|
||||||
:return: Confirmation of the update.
|
:return: Confirmation of the update.
|
||||||
"""
|
"""
|
||||||
|
valves = self._get_valves(__user__)
|
||||||
emitter = EventEmitter(__event_emitter__)
|
emitter = EventEmitter(__event_emitter__)
|
||||||
|
|
||||||
if not self.valves.ENABLE_EDIT_PAGE:
|
if not valves.ENABLE_EDIT_PAGE:
|
||||||
return (
|
return "❌ Die Funktion 'Seite bearbeiten' ist vom Administrator deaktiviert."
|
||||||
"❌ Die Funktion 'Seite bearbeiten' ist vom Administrator deaktiviert."
|
err = self._check_config(valves)
|
||||||
)
|
|
||||||
err = self._check_config()
|
|
||||||
if err:
|
if err:
|
||||||
return err
|
return err
|
||||||
|
|
||||||
@@ -658,7 +601,7 @@ class Tools:
|
|||||||
return "❌ Bitte mindestens 'title' oder 'content' angeben."
|
return "❌ Bitte mindestens 'title' oder 'content' angeben."
|
||||||
|
|
||||||
try:
|
try:
|
||||||
api = self._api()
|
api = self._api(valves)
|
||||||
await emitter.status(f"📝 Aktualisiere Seite {page_id}...")
|
await emitter.status(f"📝 Aktualisiere Seite {page_id}...")
|
||||||
|
|
||||||
current = api._get(f"pages/{int(page_id)}")
|
current = api._get(f"pages/{int(page_id)}")
|
||||||
@@ -686,22 +629,8 @@ class Tools:
|
|||||||
|
|
||||||
except requests.HTTPError as e:
|
except requests.HTTPError as e:
|
||||||
body = e.response.text[:500] if e.response is not None else "(kein Body)"
|
body = e.response.text[:500] if e.response is not None else "(kein Body)"
|
||||||
ct = (
|
ct = e.response.headers.get("Content-Type", "?") if e.response is not None else "?"
|
||||||
e.response.headers.get("Content-Type", "?")
|
msg = f"❌ BookStack API-Fehler: HTTP {e.response.status_code}\nContent-Type: {ct}\nAntwort: {body}"
|
||||||
if e.response is not None
|
|
||||||
else "?"
|
|
||||||
)
|
|
||||||
log.error(
|
|
||||||
"[BookStack] HTTP-Fehler %s | Content-Type=%s | Body=%s",
|
|
||||||
e.response.status_code,
|
|
||||||
ct,
|
|
||||||
body,
|
|
||||||
)
|
|
||||||
msg = (
|
|
||||||
f"❌ BookStack API-Fehler: HTTP {e.response.status_code}\n"
|
|
||||||
f"Content-Type: {ct}\n"
|
|
||||||
f"Antwort: {body}"
|
|
||||||
)
|
|
||||||
await emitter.error(msg)
|
await emitter.error(msg)
|
||||||
return msg
|
return msg
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -711,12 +640,13 @@ class Tools:
|
|||||||
return msg
|
return msg
|
||||||
|
|
||||||
# ─────────────────────────────────────────
|
# ─────────────────────────────────────────
|
||||||
# TOOL 6 – Seite löschen
|
# TOOL 6 – Seite löschen
|
||||||
# ─────────────────────────────────────────
|
# ─────────────────────────────────────────
|
||||||
async def delete_page(
|
async def delete_page(
|
||||||
self,
|
self,
|
||||||
page_id: int,
|
page_id: int,
|
||||||
confirm: bool = False,
|
confirm: bool = False,
|
||||||
|
__user__: dict = {},
|
||||||
__event_emitter__: Callable[[dict], Any] = None,
|
__event_emitter__: Callable[[dict], Any] = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
@@ -727,11 +657,12 @@ class Tools:
|
|||||||
:param confirm: Must be True to confirm deletion. Default: False (safety guard).
|
:param confirm: Must be True to confirm deletion. Default: False (safety guard).
|
||||||
:return: Confirmation or cancellation message.
|
:return: Confirmation or cancellation message.
|
||||||
"""
|
"""
|
||||||
|
valves = self._get_valves(__user__)
|
||||||
emitter = EventEmitter(__event_emitter__)
|
emitter = EventEmitter(__event_emitter__)
|
||||||
|
|
||||||
if not self.valves.ENABLE_DELETE_PAGE:
|
if not valves.ENABLE_DELETE_PAGE:
|
||||||
return "❌ Die Funktion 'Seite löschen' ist vom Administrator deaktiviert."
|
return "❌ Die Funktion 'Seite löschen' ist vom Administrator deaktiviert."
|
||||||
err = self._check_config()
|
err = self._check_config(valves)
|
||||||
if err:
|
if err:
|
||||||
return err
|
return err
|
||||||
|
|
||||||
@@ -744,7 +675,7 @@ class Tools:
|
|||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
api = self._api()
|
api = self._api(valves)
|
||||||
try:
|
try:
|
||||||
page_info = api._get(f"pages/{int(page_id)}")
|
page_info = api._get(f"pages/{int(page_id)}")
|
||||||
page_name = page_info.get("name", "Unbekannt")
|
page_name = page_info.get("name", "Unbekannt")
|
||||||
@@ -759,22 +690,8 @@ class Tools:
|
|||||||
|
|
||||||
except requests.HTTPError as e:
|
except requests.HTTPError as e:
|
||||||
body = e.response.text[:500] if e.response is not None else "(kein Body)"
|
body = e.response.text[:500] if e.response is not None else "(kein Body)"
|
||||||
ct = (
|
ct = e.response.headers.get("Content-Type", "?") if e.response is not None else "?"
|
||||||
e.response.headers.get("Content-Type", "?")
|
msg = f"❌ BookStack API-Fehler: HTTP {e.response.status_code}\nContent-Type: {ct}\nAntwort: {body}"
|
||||||
if e.response is not None
|
|
||||||
else "?"
|
|
||||||
)
|
|
||||||
log.error(
|
|
||||||
"[BookStack] HTTP-Fehler %s | Content-Type=%s | Body=%s",
|
|
||||||
e.response.status_code,
|
|
||||||
ct,
|
|
||||||
body,
|
|
||||||
)
|
|
||||||
msg = (
|
|
||||||
f"❌ BookStack API-Fehler: HTTP {e.response.status_code}\n"
|
|
||||||
f"Content-Type: {ct}\n"
|
|
||||||
f"Antwort: {body}"
|
|
||||||
)
|
|
||||||
await emitter.error(msg)
|
await emitter.error(msg)
|
||||||
return msg
|
return msg
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -784,12 +701,13 @@ class Tools:
|
|||||||
return msg
|
return msg
|
||||||
|
|
||||||
# ─────────────────────────────────────────
|
# ─────────────────────────────────────────
|
||||||
# TOOL 7 – Buch erstellen
|
# TOOL 7 – Buch erstellen
|
||||||
# ─────────────────────────────────────────
|
# ─────────────────────────────────────────
|
||||||
async def create_book(
|
async def create_book(
|
||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
description: str = "",
|
description: str = "",
|
||||||
|
__user__: dict = {},
|
||||||
__event_emitter__: Callable[[dict], Any] = None,
|
__event_emitter__: Callable[[dict], Any] = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
@@ -799,16 +717,17 @@ class Tools:
|
|||||||
:param description: Optional short description.
|
:param description: Optional short description.
|
||||||
:return: Confirmation with the new book's ID.
|
:return: Confirmation with the new book's ID.
|
||||||
"""
|
"""
|
||||||
|
valves = self._get_valves(__user__)
|
||||||
emitter = EventEmitter(__event_emitter__)
|
emitter = EventEmitter(__event_emitter__)
|
||||||
|
|
||||||
if not self.valves.ENABLE_CREATE_BOOK:
|
if not valves.ENABLE_CREATE_BOOK:
|
||||||
return "❌ Die Funktion 'Buch erstellen' ist vom Administrator deaktiviert."
|
return "❌ Die Funktion 'Buch erstellen' ist vom Administrator deaktiviert."
|
||||||
err = self._check_config()
|
err = self._check_config(valves)
|
||||||
if err:
|
if err:
|
||||||
return err
|
return err
|
||||||
|
|
||||||
try:
|
try:
|
||||||
api = self._api()
|
api = self._api(valves)
|
||||||
await emitter.status(f"📖 Erstelle Buch '{name}'...")
|
await emitter.status(f"📖 Erstelle Buch '{name}'...")
|
||||||
result = api._post("books", {"name": name, "description": description})
|
result = api._post("books", {"name": name, "description": description})
|
||||||
|
|
||||||
@@ -822,22 +741,8 @@ class Tools:
|
|||||||
|
|
||||||
except requests.HTTPError as e:
|
except requests.HTTPError as e:
|
||||||
body = e.response.text[:500] if e.response is not None else "(kein Body)"
|
body = e.response.text[:500] if e.response is not None else "(kein Body)"
|
||||||
ct = (
|
ct = e.response.headers.get("Content-Type", "?") if e.response is not None else "?"
|
||||||
e.response.headers.get("Content-Type", "?")
|
msg = f"❌ BookStack API-Fehler: HTTP {e.response.status_code}\nContent-Type: {ct}\nAntwort: {body}"
|
||||||
if e.response is not None
|
|
||||||
else "?"
|
|
||||||
)
|
|
||||||
log.error(
|
|
||||||
"[BookStack] HTTP-Fehler %s | Content-Type=%s | Body=%s",
|
|
||||||
e.response.status_code,
|
|
||||||
ct,
|
|
||||||
body,
|
|
||||||
)
|
|
||||||
msg = (
|
|
||||||
f"❌ BookStack API-Fehler: HTTP {e.response.status_code}\n"
|
|
||||||
f"Content-Type: {ct}\n"
|
|
||||||
f"Antwort: {body}"
|
|
||||||
)
|
|
||||||
await emitter.error(msg)
|
await emitter.error(msg)
|
||||||
return msg
|
return msg
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -847,13 +752,14 @@ class Tools:
|
|||||||
return msg
|
return msg
|
||||||
|
|
||||||
# ─────────────────────────────────────────
|
# ─────────────────────────────────────────
|
||||||
# TOOL 8 – Buch bearbeiten
|
# TOOL 8 – Buch bearbeiten
|
||||||
# ─────────────────────────────────────────
|
# ─────────────────────────────────────────
|
||||||
async def update_book(
|
async def update_book(
|
||||||
self,
|
self,
|
||||||
book_id: int,
|
book_id: int,
|
||||||
name: Optional[str] = None,
|
name: Optional[str] = None,
|
||||||
description: Optional[str] = None,
|
description: Optional[str] = None,
|
||||||
|
__user__: dict = {},
|
||||||
__event_emitter__: Callable[[dict], Any] = None,
|
__event_emitter__: Callable[[dict], Any] = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
@@ -865,13 +771,12 @@ class Tools:
|
|||||||
:param description: Optional new description.
|
:param description: Optional new description.
|
||||||
:return: Confirmation of the update.
|
:return: Confirmation of the update.
|
||||||
"""
|
"""
|
||||||
|
valves = self._get_valves(__user__)
|
||||||
emitter = EventEmitter(__event_emitter__)
|
emitter = EventEmitter(__event_emitter__)
|
||||||
|
|
||||||
if not self.valves.ENABLE_EDIT_BOOK:
|
if not valves.ENABLE_EDIT_BOOK:
|
||||||
return (
|
return "❌ Die Funktion 'Buch bearbeiten' ist vom Administrator deaktiviert."
|
||||||
"❌ Die Funktion 'Buch bearbeiten' ist vom Administrator deaktiviert."
|
err = self._check_config(valves)
|
||||||
)
|
|
||||||
err = self._check_config()
|
|
||||||
if err:
|
if err:
|
||||||
return err
|
return err
|
||||||
|
|
||||||
@@ -879,16 +784,12 @@ class Tools:
|
|||||||
return "❌ Bitte mindestens 'name' oder 'description' angeben."
|
return "❌ Bitte mindestens 'name' oder 'description' angeben."
|
||||||
|
|
||||||
try:
|
try:
|
||||||
api = self._api()
|
api = self._api(valves)
|
||||||
await emitter.status(f"📝 Aktualisiere Buch {book_id}...")
|
await emitter.status(f"📝 Aktualisiere Buch {book_id}...")
|
||||||
current = api._get(f"books/{int(book_id)}")
|
current = api._get(f"books/{int(book_id)}")
|
||||||
payload = {
|
payload = {
|
||||||
"name": name if name is not None else current["name"],
|
"name": name if name is not None else current["name"],
|
||||||
"description": (
|
"description": description if description is not None else current.get("description", ""),
|
||||||
description
|
|
||||||
if description is not None
|
|
||||||
else current.get("description", "")
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
result = api._put(f"books/{book_id}", payload)
|
result = api._put(f"books/{book_id}", payload)
|
||||||
|
|
||||||
@@ -902,22 +803,8 @@ class Tools:
|
|||||||
|
|
||||||
except requests.HTTPError as e:
|
except requests.HTTPError as e:
|
||||||
body = e.response.text[:500] if e.response is not None else "(kein Body)"
|
body = e.response.text[:500] if e.response is not None else "(kein Body)"
|
||||||
ct = (
|
ct = e.response.headers.get("Content-Type", "?") if e.response is not None else "?"
|
||||||
e.response.headers.get("Content-Type", "?")
|
msg = f"❌ BookStack API-Fehler: HTTP {e.response.status_code}\nContent-Type: {ct}\nAntwort: {body}"
|
||||||
if e.response is not None
|
|
||||||
else "?"
|
|
||||||
)
|
|
||||||
log.error(
|
|
||||||
"[BookStack] HTTP-Fehler %s | Content-Type=%s | Body=%s",
|
|
||||||
e.response.status_code,
|
|
||||||
ct,
|
|
||||||
body,
|
|
||||||
)
|
|
||||||
msg = (
|
|
||||||
f"❌ BookStack API-Fehler: HTTP {e.response.status_code}\n"
|
|
||||||
f"Content-Type: {ct}\n"
|
|
||||||
f"Antwort: {body}"
|
|
||||||
)
|
|
||||||
await emitter.error(msg)
|
await emitter.error(msg)
|
||||||
return msg
|
return msg
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -927,13 +814,14 @@ class Tools:
|
|||||||
return msg
|
return msg
|
||||||
|
|
||||||
# ─────────────────────────────────────────
|
# ─────────────────────────────────────────
|
||||||
# TOOL 9 – Kapitel erstellen
|
# TOOL 9 – Kapitel erstellen
|
||||||
# ─────────────────────────────────────────
|
# ─────────────────────────────────────────
|
||||||
async def create_chapter(
|
async def create_chapter(
|
||||||
self,
|
self,
|
||||||
book_id: int,
|
book_id: int,
|
||||||
name: str,
|
name: str,
|
||||||
description: str = "",
|
description: str = "",
|
||||||
|
__user__: dict = {},
|
||||||
__event_emitter__: Callable[[dict], Any] = None,
|
__event_emitter__: Callable[[dict], Any] = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
@@ -945,18 +833,17 @@ class Tools:
|
|||||||
:param description: Optional short description.
|
:param description: Optional short description.
|
||||||
:return: Confirmation with the new chapter's ID.
|
:return: Confirmation with the new chapter's ID.
|
||||||
"""
|
"""
|
||||||
|
valves = self._get_valves(__user__)
|
||||||
emitter = EventEmitter(__event_emitter__)
|
emitter = EventEmitter(__event_emitter__)
|
||||||
|
|
||||||
if not self.valves.ENABLE_CREATE_CHAPTER:
|
if not valves.ENABLE_CREATE_CHAPTER:
|
||||||
return (
|
return "❌ Die Funktion 'Kapitel erstellen' ist vom Administrator deaktiviert."
|
||||||
"❌ Die Funktion 'Kapitel erstellen' ist vom Administrator deaktiviert."
|
err = self._check_config(valves)
|
||||||
)
|
|
||||||
err = self._check_config()
|
|
||||||
if err:
|
if err:
|
||||||
return err
|
return err
|
||||||
|
|
||||||
try:
|
try:
|
||||||
api = self._api()
|
api = self._api(valves)
|
||||||
await emitter.status(f"📑 Erstelle Kapitel '{name}' in Buch {book_id}...")
|
await emitter.status(f"📑 Erstelle Kapitel '{name}' in Buch {book_id}...")
|
||||||
result = api._post(
|
result = api._post(
|
||||||
"chapters",
|
"chapters",
|
||||||
@@ -973,22 +860,8 @@ class Tools:
|
|||||||
|
|
||||||
except requests.HTTPError as e:
|
except requests.HTTPError as e:
|
||||||
body = e.response.text[:500] if e.response is not None else "(kein Body)"
|
body = e.response.text[:500] if e.response is not None else "(kein Body)"
|
||||||
ct = (
|
ct = e.response.headers.get("Content-Type", "?") if e.response is not None else "?"
|
||||||
e.response.headers.get("Content-Type", "?")
|
msg = f"❌ BookStack API-Fehler: HTTP {e.response.status_code}\nContent-Type: {ct}\nAntwort: {body}"
|
||||||
if e.response is not None
|
|
||||||
else "?"
|
|
||||||
)
|
|
||||||
log.error(
|
|
||||||
"[BookStack] HTTP-Fehler %s | Content-Type=%s | Body=%s",
|
|
||||||
e.response.status_code,
|
|
||||||
ct,
|
|
||||||
body,
|
|
||||||
)
|
|
||||||
msg = (
|
|
||||||
f"❌ BookStack API-Fehler: HTTP {e.response.status_code}\n"
|
|
||||||
f"Content-Type: {ct}\n"
|
|
||||||
f"Antwort: {body}"
|
|
||||||
)
|
|
||||||
await emitter.error(msg)
|
await emitter.error(msg)
|
||||||
return msg
|
return msg
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -998,13 +871,14 @@ class Tools:
|
|||||||
return msg
|
return msg
|
||||||
|
|
||||||
# ─────────────────────────────────────────
|
# ─────────────────────────────────────────
|
||||||
# TOOL 10 – Kapitel bearbeiten
|
# TOOL 10 – Kapitel bearbeiten
|
||||||
# ─────────────────────────────────────────
|
# ─────────────────────────────────────────
|
||||||
async def update_chapter(
|
async def update_chapter(
|
||||||
self,
|
self,
|
||||||
chapter_id: int,
|
chapter_id: int,
|
||||||
name: Optional[str] = None,
|
name: Optional[str] = None,
|
||||||
description: Optional[str] = None,
|
description: Optional[str] = None,
|
||||||
|
__user__: dict = {},
|
||||||
__event_emitter__: Callable[[dict], Any] = None,
|
__event_emitter__: Callable[[dict], Any] = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
@@ -1016,11 +890,12 @@ class Tools:
|
|||||||
:param description: Optional new description.
|
:param description: Optional new description.
|
||||||
:return: Confirmation of the update.
|
:return: Confirmation of the update.
|
||||||
"""
|
"""
|
||||||
|
valves = self._get_valves(__user__)
|
||||||
emitter = EventEmitter(__event_emitter__)
|
emitter = EventEmitter(__event_emitter__)
|
||||||
|
|
||||||
if not self.valves.ENABLE_EDIT_CHAPTER:
|
if not valves.ENABLE_EDIT_CHAPTER:
|
||||||
return "❌ Die Funktion 'Kapitel bearbeiten' ist vom Administrator deaktiviert."
|
return "❌ Die Funktion 'Kapitel bearbeiten' ist vom Administrator deaktiviert."
|
||||||
err = self._check_config()
|
err = self._check_config(valves)
|
||||||
if err:
|
if err:
|
||||||
return err
|
return err
|
||||||
|
|
||||||
@@ -1028,17 +903,13 @@ class Tools:
|
|||||||
return "❌ Bitte mindestens 'name' oder 'description' angeben."
|
return "❌ Bitte mindestens 'name' oder 'description' angeben."
|
||||||
|
|
||||||
try:
|
try:
|
||||||
api = self._api()
|
api = self._api(valves)
|
||||||
await emitter.status(f"📝 Aktualisiere Kapitel {chapter_id}...")
|
await emitter.status(f"📝 Aktualisiere Kapitel {chapter_id}...")
|
||||||
current = api._get(f"chapters/{int(chapter_id)}")
|
current = api._get(f"chapters/{int(chapter_id)}")
|
||||||
payload = {
|
payload = {
|
||||||
"book_id": current["book_id"],
|
"book_id": current["book_id"],
|
||||||
"name": name if name is not None else current["name"],
|
"name": name if name is not None else current["name"],
|
||||||
"description": (
|
"description": description if description is not None else current.get("description", ""),
|
||||||
description
|
|
||||||
if description is not None
|
|
||||||
else current.get("description", "")
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
result = api._put(f"chapters/{chapter_id}", payload)
|
result = api._put(f"chapters/{chapter_id}", payload)
|
||||||
|
|
||||||
@@ -1052,27 +923,12 @@ class Tools:
|
|||||||
|
|
||||||
except requests.HTTPError as e:
|
except requests.HTTPError as e:
|
||||||
body = e.response.text[:500] if e.response is not None else "(kein Body)"
|
body = e.response.text[:500] if e.response is not None else "(kein Body)"
|
||||||
ct = (
|
ct = e.response.headers.get("Content-Type", "?") if e.response is not None else "?"
|
||||||
e.response.headers.get("Content-Type", "?")
|
msg = f"❌ BookStack API-Fehler: HTTP {e.response.status_code}\nContent-Type: {ct}\nAntwort: {body}"
|
||||||
if e.response is not None
|
|
||||||
else "?"
|
|
||||||
)
|
|
||||||
log.error(
|
|
||||||
"[BookStack] HTTP-Fehler %s | Content-Type=%s | Body=%s",
|
|
||||||
e.response.status_code,
|
|
||||||
ct,
|
|
||||||
body,
|
|
||||||
)
|
|
||||||
msg = (
|
|
||||||
f"❌ BookStack API-Fehler: HTTP {e.response.status_code}\n"
|
|
||||||
f"Content-Type: {ct}\n"
|
|
||||||
f"Antwort: {body}"
|
|
||||||
)
|
|
||||||
await emitter.error(msg)
|
await emitter.error(msg)
|
||||||
return msg
|
return msg
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.exception("[BookStack] Unerwarteter Fehler: %s", e)
|
log.exception("[BookStack] Unerwarteter Fehler: %s", e)
|
||||||
msg = f"❌ Unerwarteter Fehler: {type(e).__name__}: {e}"
|
msg = f"❌ Unerwarteter Fehler: {type(e).__name__}: {e}"
|
||||||
await emitter.error(msg)
|
await emitter.error(msg)
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
Reference in New Issue
Block a user