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.
|
||||
author: Claude / Anthropic
|
||||
author_url: https://claude.ai
|
||||
version: 1.3.0
|
||||
version: 1.4.0
|
||||
license: MIT
|
||||
requirements: requests
|
||||
"""
|
||||
@@ -14,7 +14,6 @@ import requests
|
||||
from typing import Any, Callable, Optional
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
# Logging-Setup: schreibt in den OWUI-Server-Log (podman logs open-webui)
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
log = logging.getLogger("BookStackConnector")
|
||||
|
||||
@@ -57,7 +56,6 @@ class BookStackAPI:
|
||||
}
|
||||
|
||||
def _parse_response(self, resp: requests.Response) -> dict:
|
||||
"""Parst die API-Antwort mit ausführlichem Logging bei Fehlern."""
|
||||
log.debug(
|
||||
"[BookStack] %s %s → HTTP %s | Content-Type: %s | Body[:300]: %s",
|
||||
resp.request.method,
|
||||
@@ -132,9 +130,7 @@ class BookStackAPI:
|
||||
# ─────────────────────────────────────────────
|
||||
class Tools:
|
||||
|
||||
# ── Admin-Valves ──────────────────────────
|
||||
class Valves(BaseModel):
|
||||
# --- Authentication ---
|
||||
BOOKSTACK_URL: str = Field(
|
||||
default="",
|
||||
description="Base URL deiner BookStack-Instanz, z.B. https://wiki.example.com",
|
||||
@@ -151,8 +147,6 @@ class Tools:
|
||||
default=100,
|
||||
description="Maximale Anzahl Items pro API-Request (Pagination)",
|
||||
)
|
||||
|
||||
# --- Feature Flags (Lesen) ---
|
||||
ENABLE_LIST_STRUCTURE: bool = Field(
|
||||
default=True,
|
||||
description="✅ Erlauben: Gesamte Bibliotheksstruktur anzeigen (Shelves → Books → Chapters → Pages)",
|
||||
@@ -165,8 +159,6 @@ class Tools:
|
||||
default=True,
|
||||
description="✅ Erlauben: Inhalte durchsuchen",
|
||||
)
|
||||
|
||||
# --- Feature Flags (Schreiben: Seiten) ---
|
||||
ENABLE_CREATE_PAGE: bool = Field(
|
||||
default=False,
|
||||
description="⚠️ Erlauben: Neue Seiten erstellen",
|
||||
@@ -179,8 +171,6 @@ class Tools:
|
||||
default=False,
|
||||
description="🔴 Erlauben: Seiten löschen (UNWIDERRUFLICH!)",
|
||||
)
|
||||
|
||||
# --- Feature Flags (Schreiben: Bücher & Kapitel) ---
|
||||
ENABLE_CREATE_BOOK: bool = Field(
|
||||
default=False,
|
||||
description="⚠️ Erlauben: Neue Bücher erstellen",
|
||||
@@ -199,32 +189,39 @@ class Tools:
|
||||
)
|
||||
|
||||
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()
|
||||
|
||||
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(
|
||||
self.valves.BOOKSTACK_URL,
|
||||
self.valves.API_TOKEN_ID,
|
||||
self.valves.API_TOKEN_SECRET,
|
||||
valves.BOOKSTACK_URL,
|
||||
valves.API_TOKEN_ID,
|
||||
valves.API_TOKEN_SECRET,
|
||||
)
|
||||
|
||||
def _check_config(self) -> Optional[str]:
|
||||
"""Prüft ob Auth-Daten gesetzt sind. Gibt Fehlermeldung zurück oder None."""
|
||||
tid = self.valves.API_TOKEN_ID
|
||||
def _check_config(self, valves: "Tools.Valves") -> Optional[str]:
|
||||
tid = valves.API_TOKEN_ID
|
||||
log.debug(
|
||||
"[BookStack] Config-Check → URL='%s' | TOKEN_ID='%s' | TOKEN_SECRET='%s'",
|
||||
self.valves.BOOKSTACK_URL,
|
||||
valves.BOOKSTACK_URL,
|
||||
(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."
|
||||
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 None
|
||||
|
||||
@@ -233,6 +230,7 @@ class Tools:
|
||||
# ─────────────────────────────────────────
|
||||
async def get_library_structure(
|
||||
self,
|
||||
__user__: dict = {},
|
||||
__event_emitter__: Callable[[dict], Any] = None,
|
||||
) -> str:
|
||||
"""
|
||||
@@ -243,33 +241,33 @@ class Tools:
|
||||
|
||||
:return: A formatted Markdown tree of the entire library with IDs.
|
||||
"""
|
||||
valves = self._get_valves(__user__)
|
||||
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."
|
||||
|
||||
err = self._check_config()
|
||||
err = self._check_config(valves)
|
||||
if err:
|
||||
return err
|
||||
|
||||
try:
|
||||
api = self._api()
|
||||
api = self._api(valves)
|
||||
|
||||
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...")
|
||||
books = api.list_all("books", self.valves.MAX_ITEMS)
|
||||
books = api.list_all("books", valves.MAX_ITEMS)
|
||||
|
||||
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...")
|
||||
pages = api.list_all("pages", self.valves.MAX_ITEMS)
|
||||
pages = api.list_all("pages", valves.MAX_ITEMS)
|
||||
|
||||
await emitter.status("🗂️ Baue Strukturbaum...")
|
||||
|
||||
# Shelf → Book Mapping via Shelf-Detailendpunkt
|
||||
shelf_book_map: dict = {s["id"]: [] for s in shelves}
|
||||
for shelf in shelves:
|
||||
detail = api._get(f"shelves/{shelf['id']}")
|
||||
@@ -346,22 +344,9 @@ class Tools:
|
||||
|
||||
except requests.HTTPError as e:
|
||||
body = e.response.text[:500] if e.response is not None else "(kein Body)"
|
||||
ct = (
|
||||
e.response.headers.get("Content-Type", "?")
|
||||
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}"
|
||||
)
|
||||
ct = e.response.headers.get("Content-Type", "?") 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}\nContent-Type: {ct}\nAntwort: {body}"
|
||||
await emitter.error(msg)
|
||||
return msg
|
||||
except Exception as e:
|
||||
@@ -377,6 +362,7 @@ class Tools:
|
||||
self,
|
||||
page_id: int,
|
||||
format: str = "markdown",
|
||||
__user__: dict = {},
|
||||
__event_emitter__: Callable[[dict], Any] = None,
|
||||
) -> str:
|
||||
"""
|
||||
@@ -387,16 +373,17 @@ class Tools:
|
||||
:param format: Content format – 'markdown', 'html', or 'both'. Default: 'markdown'.
|
||||
:return: The page title and its content in the requested format.
|
||||
"""
|
||||
valves = self._get_valves(__user__)
|
||||
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."
|
||||
err = self._check_config()
|
||||
err = self._check_config(valves)
|
||||
if err:
|
||||
return err
|
||||
|
||||
try:
|
||||
api = self._api()
|
||||
api = self._api(valves)
|
||||
await emitter.status(f"📄 Lade Seite {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.*")
|
||||
if fmt in ("html", "both"):
|
||||
lines.append("\n## Inhalt (HTML)")
|
||||
lines.append(
|
||||
f"```html\n{html}\n```" if html else "*Kein HTML-Inhalt verfügbar.*"
|
||||
)
|
||||
lines.append(f"```html\n{html}\n```" if html else "*Kein HTML-Inhalt verfügbar.*")
|
||||
|
||||
await emitter.status("✅ Seite geladen.", done=True)
|
||||
return "\n".join(lines)
|
||||
|
||||
except requests.HTTPError as e:
|
||||
body = e.response.text[:500] if e.response is not None else "(kein Body)"
|
||||
ct = (
|
||||
e.response.headers.get("Content-Type", "?")
|
||||
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}"
|
||||
)
|
||||
ct = e.response.headers.get("Content-Type", "?") if e.response is not None else "?"
|
||||
msg = f"❌ BookStack API-Fehler: HTTP {e.response.status_code}\nContent-Type: {ct}\nAntwort: {body}"
|
||||
await emitter.error(msg)
|
||||
return msg
|
||||
except Exception as e:
|
||||
@@ -459,6 +430,7 @@ class Tools:
|
||||
async def search_content(
|
||||
self,
|
||||
query: str,
|
||||
__user__: dict = {},
|
||||
__event_emitter__: Callable[[dict], Any] = None,
|
||||
) -> str:
|
||||
"""
|
||||
@@ -468,24 +440,23 @@ class Tools:
|
||||
:param query: The search string.
|
||||
:return: A list of matching content items with IDs and types.
|
||||
"""
|
||||
valves = self._get_valves(__user__)
|
||||
emitter = EventEmitter(__event_emitter__)
|
||||
|
||||
if not self.valves.ENABLE_SEARCH:
|
||||
if not valves.ENABLE_SEARCH:
|
||||
return "❌ Die Funktion 'Suche' ist vom Administrator deaktiviert."
|
||||
err = self._check_config()
|
||||
err = self._check_config(valves)
|
||||
if err:
|
||||
return err
|
||||
|
||||
try:
|
||||
api = self._api()
|
||||
api = self._api(valves)
|
||||
await emitter.status(f"🔍 Suche nach: '{query}'...")
|
||||
data = api._get("search", params={"query": query, "count": 30})
|
||||
results = data.get("data", [])
|
||||
|
||||
if not results:
|
||||
await emitter.status(
|
||||
"✅ Suche abgeschlossen – keine Ergebnisse.", done=True
|
||||
)
|
||||
await emitter.status("✅ Suche abgeschlossen – keine Ergebnisse.", done=True)
|
||||
return f"🔍 Keine Ergebnisse für **'{query}'**."
|
||||
|
||||
type_icons = {
|
||||
@@ -516,22 +487,8 @@ class Tools:
|
||||
|
||||
except requests.HTTPError as e:
|
||||
body = e.response.text[:500] if e.response is not None else "(kein Body)"
|
||||
ct = (
|
||||
e.response.headers.get("Content-Type", "?")
|
||||
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}"
|
||||
)
|
||||
ct = e.response.headers.get("Content-Type", "?") if e.response is not None else "?"
|
||||
msg = f"❌ BookStack API-Fehler: HTTP {e.response.status_code}\nContent-Type: {ct}\nAntwort: {body}"
|
||||
await emitter.error(msg)
|
||||
return msg
|
||||
except Exception as e:
|
||||
@@ -550,6 +507,7 @@ class Tools:
|
||||
content: str,
|
||||
content_format: str = "markdown",
|
||||
chapter_id: Optional[int] = None,
|
||||
__user__: dict = {},
|
||||
__event_emitter__: Callable[[dict], Any] = None,
|
||||
) -> str:
|
||||
"""
|
||||
@@ -563,18 +521,17 @@ class Tools:
|
||||
:param chapter_id: Optional chapter ID. Omit to place page at book level.
|
||||
:return: Confirmation with the new page's ID.
|
||||
"""
|
||||
valves = self._get_valves(__user__)
|
||||
emitter = EventEmitter(__event_emitter__)
|
||||
|
||||
if not self.valves.ENABLE_CREATE_PAGE:
|
||||
return (
|
||||
"❌ Die Funktion 'Seite erstellen' ist vom Administrator deaktiviert."
|
||||
)
|
||||
err = self._check_config()
|
||||
if not valves.ENABLE_CREATE_PAGE:
|
||||
return "❌ Die Funktion 'Seite erstellen' ist vom Administrator deaktiviert."
|
||||
err = self._check_config(valves)
|
||||
if err:
|
||||
return err
|
||||
|
||||
try:
|
||||
api = self._api()
|
||||
api = self._api(valves)
|
||||
await emitter.status(f"✏️ Erstelle Seite '{title}'...")
|
||||
|
||||
payload: dict = {"book_id": int(book_id), "name": title}
|
||||
@@ -599,22 +556,8 @@ class Tools:
|
||||
|
||||
except requests.HTTPError as e:
|
||||
body = e.response.text[:500] if e.response is not None else "(kein Body)"
|
||||
ct = (
|
||||
e.response.headers.get("Content-Type", "?")
|
||||
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}"
|
||||
)
|
||||
ct = e.response.headers.get("Content-Type", "?") if e.response is not None else "?"
|
||||
msg = f"❌ BookStack API-Fehler: HTTP {e.response.status_code}\nContent-Type: {ct}\nAntwort: {body}"
|
||||
await emitter.error(msg)
|
||||
return msg
|
||||
except Exception as e:
|
||||
@@ -632,6 +575,7 @@ class Tools:
|
||||
title: Optional[str] = None,
|
||||
content: Optional[str] = None,
|
||||
content_format: str = "markdown",
|
||||
__user__: dict = {},
|
||||
__event_emitter__: Callable[[dict], Any] = None,
|
||||
) -> str:
|
||||
"""
|
||||
@@ -644,13 +588,12 @@ class Tools:
|
||||
:param content_format: Format of the content – 'markdown' or 'html'. Default: 'markdown'.
|
||||
:return: Confirmation of the update.
|
||||
"""
|
||||
valves = self._get_valves(__user__)
|
||||
emitter = EventEmitter(__event_emitter__)
|
||||
|
||||
if not self.valves.ENABLE_EDIT_PAGE:
|
||||
return (
|
||||
"❌ Die Funktion 'Seite bearbeiten' ist vom Administrator deaktiviert."
|
||||
)
|
||||
err = self._check_config()
|
||||
if not valves.ENABLE_EDIT_PAGE:
|
||||
return "❌ Die Funktion 'Seite bearbeiten' ist vom Administrator deaktiviert."
|
||||
err = self._check_config(valves)
|
||||
if err:
|
||||
return err
|
||||
|
||||
@@ -658,7 +601,7 @@ class Tools:
|
||||
return "❌ Bitte mindestens 'title' oder 'content' angeben."
|
||||
|
||||
try:
|
||||
api = self._api()
|
||||
api = self._api(valves)
|
||||
await emitter.status(f"📝 Aktualisiere Seite {page_id}...")
|
||||
|
||||
current = api._get(f"pages/{int(page_id)}")
|
||||
@@ -686,22 +629,8 @@ class Tools:
|
||||
|
||||
except requests.HTTPError as e:
|
||||
body = e.response.text[:500] if e.response is not None else "(kein Body)"
|
||||
ct = (
|
||||
e.response.headers.get("Content-Type", "?")
|
||||
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}"
|
||||
)
|
||||
ct = e.response.headers.get("Content-Type", "?") if e.response is not None else "?"
|
||||
msg = f"❌ BookStack API-Fehler: HTTP {e.response.status_code}\nContent-Type: {ct}\nAntwort: {body}"
|
||||
await emitter.error(msg)
|
||||
return msg
|
||||
except Exception as e:
|
||||
@@ -717,6 +646,7 @@ class Tools:
|
||||
self,
|
||||
page_id: int,
|
||||
confirm: bool = False,
|
||||
__user__: dict = {},
|
||||
__event_emitter__: Callable[[dict], Any] = None,
|
||||
) -> str:
|
||||
"""
|
||||
@@ -727,11 +657,12 @@ class Tools:
|
||||
:param confirm: Must be True to confirm deletion. Default: False (safety guard).
|
||||
:return: Confirmation or cancellation message.
|
||||
"""
|
||||
valves = self._get_valves(__user__)
|
||||
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."
|
||||
err = self._check_config()
|
||||
err = self._check_config(valves)
|
||||
if err:
|
||||
return err
|
||||
|
||||
@@ -744,7 +675,7 @@ class Tools:
|
||||
)
|
||||
|
||||
try:
|
||||
api = self._api()
|
||||
api = self._api(valves)
|
||||
try:
|
||||
page_info = api._get(f"pages/{int(page_id)}")
|
||||
page_name = page_info.get("name", "Unbekannt")
|
||||
@@ -759,22 +690,8 @@ class Tools:
|
||||
|
||||
except requests.HTTPError as e:
|
||||
body = e.response.text[:500] if e.response is not None else "(kein Body)"
|
||||
ct = (
|
||||
e.response.headers.get("Content-Type", "?")
|
||||
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}"
|
||||
)
|
||||
ct = e.response.headers.get("Content-Type", "?") if e.response is not None else "?"
|
||||
msg = f"❌ BookStack API-Fehler: HTTP {e.response.status_code}\nContent-Type: {ct}\nAntwort: {body}"
|
||||
await emitter.error(msg)
|
||||
return msg
|
||||
except Exception as e:
|
||||
@@ -790,6 +707,7 @@ class Tools:
|
||||
self,
|
||||
name: str,
|
||||
description: str = "",
|
||||
__user__: dict = {},
|
||||
__event_emitter__: Callable[[dict], Any] = None,
|
||||
) -> str:
|
||||
"""
|
||||
@@ -799,16 +717,17 @@ class Tools:
|
||||
:param description: Optional short description.
|
||||
:return: Confirmation with the new book's ID.
|
||||
"""
|
||||
valves = self._get_valves(__user__)
|
||||
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."
|
||||
err = self._check_config()
|
||||
err = self._check_config(valves)
|
||||
if err:
|
||||
return err
|
||||
|
||||
try:
|
||||
api = self._api()
|
||||
api = self._api(valves)
|
||||
await emitter.status(f"📖 Erstelle Buch '{name}'...")
|
||||
result = api._post("books", {"name": name, "description": description})
|
||||
|
||||
@@ -822,22 +741,8 @@ class Tools:
|
||||
|
||||
except requests.HTTPError as e:
|
||||
body = e.response.text[:500] if e.response is not None else "(kein Body)"
|
||||
ct = (
|
||||
e.response.headers.get("Content-Type", "?")
|
||||
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}"
|
||||
)
|
||||
ct = e.response.headers.get("Content-Type", "?") if e.response is not None else "?"
|
||||
msg = f"❌ BookStack API-Fehler: HTTP {e.response.status_code}\nContent-Type: {ct}\nAntwort: {body}"
|
||||
await emitter.error(msg)
|
||||
return msg
|
||||
except Exception as e:
|
||||
@@ -854,6 +759,7 @@ class Tools:
|
||||
book_id: int,
|
||||
name: Optional[str] = None,
|
||||
description: Optional[str] = None,
|
||||
__user__: dict = {},
|
||||
__event_emitter__: Callable[[dict], Any] = None,
|
||||
) -> str:
|
||||
"""
|
||||
@@ -865,13 +771,12 @@ class Tools:
|
||||
:param description: Optional new description.
|
||||
:return: Confirmation of the update.
|
||||
"""
|
||||
valves = self._get_valves(__user__)
|
||||
emitter = EventEmitter(__event_emitter__)
|
||||
|
||||
if not self.valves.ENABLE_EDIT_BOOK:
|
||||
return (
|
||||
"❌ Die Funktion 'Buch bearbeiten' ist vom Administrator deaktiviert."
|
||||
)
|
||||
err = self._check_config()
|
||||
if not valves.ENABLE_EDIT_BOOK:
|
||||
return "❌ Die Funktion 'Buch bearbeiten' ist vom Administrator deaktiviert."
|
||||
err = self._check_config(valves)
|
||||
if err:
|
||||
return err
|
||||
|
||||
@@ -879,16 +784,12 @@ class Tools:
|
||||
return "❌ Bitte mindestens 'name' oder 'description' angeben."
|
||||
|
||||
try:
|
||||
api = self._api()
|
||||
api = self._api(valves)
|
||||
await emitter.status(f"📝 Aktualisiere Buch {book_id}...")
|
||||
current = api._get(f"books/{int(book_id)}")
|
||||
payload = {
|
||||
"name": name if name is not None else current["name"],
|
||||
"description": (
|
||||
description
|
||||
if description is not None
|
||||
else current.get("description", "")
|
||||
),
|
||||
"description": description if description is not None else current.get("description", ""),
|
||||
}
|
||||
result = api._put(f"books/{book_id}", payload)
|
||||
|
||||
@@ -902,22 +803,8 @@ class Tools:
|
||||
|
||||
except requests.HTTPError as e:
|
||||
body = e.response.text[:500] if e.response is not None else "(kein Body)"
|
||||
ct = (
|
||||
e.response.headers.get("Content-Type", "?")
|
||||
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}"
|
||||
)
|
||||
ct = e.response.headers.get("Content-Type", "?") if e.response is not None else "?"
|
||||
msg = f"❌ BookStack API-Fehler: HTTP {e.response.status_code}\nContent-Type: {ct}\nAntwort: {body}"
|
||||
await emitter.error(msg)
|
||||
return msg
|
||||
except Exception as e:
|
||||
@@ -934,6 +821,7 @@ class Tools:
|
||||
book_id: int,
|
||||
name: str,
|
||||
description: str = "",
|
||||
__user__: dict = {},
|
||||
__event_emitter__: Callable[[dict], Any] = None,
|
||||
) -> str:
|
||||
"""
|
||||
@@ -945,18 +833,17 @@ class Tools:
|
||||
:param description: Optional short description.
|
||||
:return: Confirmation with the new chapter's ID.
|
||||
"""
|
||||
valves = self._get_valves(__user__)
|
||||
emitter = EventEmitter(__event_emitter__)
|
||||
|
||||
if not self.valves.ENABLE_CREATE_CHAPTER:
|
||||
return (
|
||||
"❌ Die Funktion 'Kapitel erstellen' ist vom Administrator deaktiviert."
|
||||
)
|
||||
err = self._check_config()
|
||||
if not valves.ENABLE_CREATE_CHAPTER:
|
||||
return "❌ Die Funktion 'Kapitel erstellen' ist vom Administrator deaktiviert."
|
||||
err = self._check_config(valves)
|
||||
if err:
|
||||
return err
|
||||
|
||||
try:
|
||||
api = self._api()
|
||||
api = self._api(valves)
|
||||
await emitter.status(f"📑 Erstelle Kapitel '{name}' in Buch {book_id}...")
|
||||
result = api._post(
|
||||
"chapters",
|
||||
@@ -973,22 +860,8 @@ class Tools:
|
||||
|
||||
except requests.HTTPError as e:
|
||||
body = e.response.text[:500] if e.response is not None else "(kein Body)"
|
||||
ct = (
|
||||
e.response.headers.get("Content-Type", "?")
|
||||
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}"
|
||||
)
|
||||
ct = e.response.headers.get("Content-Type", "?") if e.response is not None else "?"
|
||||
msg = f"❌ BookStack API-Fehler: HTTP {e.response.status_code}\nContent-Type: {ct}\nAntwort: {body}"
|
||||
await emitter.error(msg)
|
||||
return msg
|
||||
except Exception as e:
|
||||
@@ -1005,6 +878,7 @@ class Tools:
|
||||
chapter_id: int,
|
||||
name: Optional[str] = None,
|
||||
description: Optional[str] = None,
|
||||
__user__: dict = {},
|
||||
__event_emitter__: Callable[[dict], Any] = None,
|
||||
) -> str:
|
||||
"""
|
||||
@@ -1016,11 +890,12 @@ class Tools:
|
||||
:param description: Optional new description.
|
||||
:return: Confirmation of the update.
|
||||
"""
|
||||
valves = self._get_valves(__user__)
|
||||
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."
|
||||
err = self._check_config()
|
||||
err = self._check_config(valves)
|
||||
if err:
|
||||
return err
|
||||
|
||||
@@ -1028,17 +903,13 @@ class Tools:
|
||||
return "❌ Bitte mindestens 'name' oder 'description' angeben."
|
||||
|
||||
try:
|
||||
api = self._api()
|
||||
api = self._api(valves)
|
||||
await emitter.status(f"📝 Aktualisiere Kapitel {chapter_id}...")
|
||||
current = api._get(f"chapters/{int(chapter_id)}")
|
||||
payload = {
|
||||
"book_id": current["book_id"],
|
||||
"name": name if name is not None else current["name"],
|
||||
"description": (
|
||||
description
|
||||
if description is not None
|
||||
else current.get("description", "")
|
||||
),
|
||||
"description": description if description is not None else current.get("description", ""),
|
||||
}
|
||||
result = api._put(f"chapters/{chapter_id}", payload)
|
||||
|
||||
@@ -1052,22 +923,8 @@ class Tools:
|
||||
|
||||
except requests.HTTPError as e:
|
||||
body = e.response.text[:500] if e.response is not None else "(kein Body)"
|
||||
ct = (
|
||||
e.response.headers.get("Content-Type", "?")
|
||||
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}"
|
||||
)
|
||||
ct = e.response.headers.get("Content-Type", "?") if e.response is not None else "?"
|
||||
msg = f"❌ BookStack API-Fehler: HTTP {e.response.status_code}\nContent-Type: {ct}\nAntwort: {body}"
|
||||
await emitter.error(msg)
|
||||
return msg
|
||||
except Exception as e:
|
||||
@@ -1075,4 +932,3 @@ class Tools:
|
||||
msg = f"❌ Unerwarteter Fehler: {type(e).__name__}: {e}"
|
||||
await emitter.error(msg)
|
||||
return msg
|
||||
|
||||
|
||||
Reference in New Issue
Block a user