diff --git a/bookstack_connector.py b/bookstack_connector.py index 8546b71..cd70a27 100644 --- a/bookstack_connector.py +++ b/bookstack_connector.py @@ -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,13 +14,12 @@ 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") # ───────────────────────────────────────────── -# Helper: EventEmitter wrapper +# Helper: EventEmitter wrapper # ───────────────────────────────────────────── class EventEmitter: 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: 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: - """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, @@ -79,10 +77,10 @@ class BookStackAPI: f"Content-Type: {content_type}\n" f"Antwort (Anfang): {resp.text[:300]}\n\n" f"Mögliche Ursachen:\n" - f" • BOOKSTACK_URL falsch oder nicht erreichbar\n" - f" • API antwortet mit Login-Seite (HTML) → Token ungültig\n" - f" • SSL-Fehler oder Redirect\n" - f" • URL endet mit '/' → doppeltes '/api/'" + f" • BOOKSTACK_URL falsch oder nicht erreichbar\n" + f" • API antwortet mit Login-Seite (HTML) → Token ungültig\n" + f" • SSL-Fehler oder Redirect\n" + f" • URL endet mit '/' → doppeltes '/api/'" ) return resp.json() @@ -128,13 +126,11 @@ class BookStackAPI: # ───────────────────────────────────────────── -# Main Tool class +# Main Tool class # ───────────────────────────────────────────── 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,40 +189,48 @@ 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 # ───────────────────────────────────────── - # TOOL 1 – Gesamtstruktur anzeigen + # TOOL 1 – Gesamtstruktur anzeigen # ───────────────────────────────────────── 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: @@ -371,12 +356,13 @@ class Tools: return msg # ───────────────────────────────────────── - # TOOL 2 – Seite lesen + # TOOL 2 – Seite lesen # ───────────────────────────────────────── async def get_page_content( 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: @@ -454,11 +425,12 @@ class Tools: return msg # ───────────────────────────────────────── - # TOOL 3 – Suche + # TOOL 3 – Suche # ───────────────────────────────────────── 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 = { @@ -508,7 +479,7 @@ class Tools: preview_clean = re.sub(r"<[^>]+>", "", preview)[:200] lines.append(f"{icon} **{name}** `[{itype.capitalize()} ID: {iid}]`") if preview_clean: - lines.append(f" > {preview_clean}…") + lines.append(f" > {preview_clean}…") lines.append("") await emitter.status("✅ Suche abgeschlossen.", done=True) @@ -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: @@ -541,7 +498,7 @@ class Tools: return msg # ───────────────────────────────────────── - # TOOL 4 – Seite erstellen + # TOOL 4 – Seite erstellen # ───────────────────────────────────────── async def create_page( self, @@ -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: @@ -624,7 +567,7 @@ class Tools: return msg # ───────────────────────────────────────── - # TOOL 5 – Seite bearbeiten + # TOOL 5 – Seite bearbeiten # ───────────────────────────────────────── async def update_page( self, @@ -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: @@ -711,12 +640,13 @@ class Tools: return msg # ───────────────────────────────────────── - # TOOL 6 – Seite löschen + # TOOL 6 – Seite löschen # ───────────────────────────────────────── async def delete_page( 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: @@ -784,12 +701,13 @@ class Tools: return msg # ───────────────────────────────────────── - # TOOL 7 – Buch erstellen + # TOOL 7 – Buch erstellen # ───────────────────────────────────────── async def create_book( 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: @@ -847,13 +752,14 @@ class Tools: return msg # ───────────────────────────────────────── - # TOOL 8 – Buch bearbeiten + # TOOL 8 – Buch bearbeiten # ───────────────────────────────────────── async def update_book( self, 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: @@ -927,13 +814,14 @@ class Tools: return msg # ───────────────────────────────────────── - # TOOL 9 – Kapitel erstellen + # TOOL 9 – Kapitel erstellen # ───────────────────────────────────────── async def create_chapter( self, 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: @@ -998,13 +871,14 @@ class Tools: return msg # ───────────────────────────────────────── - # TOOL 10 – Kapitel bearbeiten + # TOOL 10 – Kapitel bearbeiten # ───────────────────────────────────────── async def update_chapter( self, 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,27 +923,12 @@ 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: log.exception("[BookStack] Unerwarteter Fehler: %s", e) msg = f"❌ Unerwarteter Fehler: {type(e).__name__}: {e}" await emitter.error(msg) - return msg - + return msg \ No newline at end of file