plugins/mediawiki: wiki.py als Skill (Wiki lesen/rendern/schreiben) #1

Merged
sko merged 2 commits from feature/mediawiki-plugin into main 2026-07-16 10:10:24 +02:00
5 changed files with 335 additions and 0 deletions
Showing only changes of commit 079226ea43 - Show all commits
+5
View File
@@ -23,6 +23,11 @@
"source": "./plugins/humanizer", "source": "./plugins/humanizer",
"description": "Entfernt typische Merkmale KI-generierten Texts, damit er natürlicher/menschlicher klingt (vendored von github.com/blader/humanizer)." "description": "Entfernt typische Merkmale KI-generierten Texts, damit er natürlicher/menschlicher klingt (vendored von github.com/blader/humanizer)."
}, },
{
"name": "humhub",
"source": "./plugins/humhub",
"description": "HumHub via REST-API: Wiki-Seiten und Posts auflisten, anzeigen, anlegen und aktualisieren (Token lokal)."
},
{ {
"name": "mediawiki", "name": "mediawiki",
"source": "./plugins/mediawiki", "source": "./plugins/mediawiki",
@@ -0,0 +1,8 @@
{
"name": "humhub",
"version": "0.1.0",
"description": "HumHub via REST-API: Wiki-Seiten und Posts auflisten, anzeigen, anlegen und aktualisieren (humhub.py).",
"author": {
"name": "inmedias.it"
}
}
@@ -0,0 +1,6 @@
# Zugangsdaten fuer humhub.py — kopieren nach ~/.config/dokumentierer/.env
# oder .env im Arbeitsverzeichnis, oder als Umgebungsvariable setzen.
# NICHT committen. Token nur als berechtigter Benutzer unter:
# https://humhub.inmedias.it/rest/admin/index
HUMHUB_API_TOKEN=change-me
+55
View File
@@ -0,0 +1,55 @@
---
name: humhub
description: HumHub Wiki-Seiten und Posts lesen/erstellen/aktualisieren via REST-API. Use when the user mentions "HumHub", "Space", "Wiki-Seite in HumHub" or "Post/Beitrag".
version: 0.1.0
---
# HumHub (humhub.py)
Interagiert mit HumHub (https://humhub.inmedias.it) über die REST-API:
Wiki-Seiten und Posts auflisten, anzeigen, anlegen und aktualisieren — über die
mitgelieferte, self-contained `humhub.py`.
## Ausführen
`humhub.py` ist ein eigenständiges `uv run --script` (Inline-Deps: `requests`,
`python-dotenv`). Aus dem Skill-Verzeichnis:
```bash
./humhub.py <befehl> ...
```
### Wiki
```bash
./humhub.py wiki-list ["filter"] # Wiki-Seiten auflisten (Titel-Filter optional)
./humhub.py wiki-get <id> # Wiki-Seite anzeigen (Inhalt + URL)
./humhub.py wiki-update <id> [datei|-] [--title ...] [--topics a,b] [--parent-id N]
./humhub.py wiki-create <container_id> "<titel>" [datei|-] [--topics a,b] [--parent-id N]
```
### Posts
```bash
./humhub.py post-list <container_id> [--limit N]
./humhub.py post-create <container_id> [datei|-]
./humhub.py post-update <id> [datei|-]
```
Inhalte (Markdown) aus Datei oder `-` (stdin) übergeben — nicht als langes
Shell-Argument (Sonderzeichen). `container_id` ist die Space-/Profil-Container-ID
in HumHub (z.&nbsp;B. ein User- oder Space-Container). Mit `wiki-list` /
`post-list` die passende ID/Seite ermitteln.
## Zugangsdaten
`humhub.py` bringt **keinen** Token mit. Jede/r hinterlegt ein eigenes
API-Token als Umgebungsvariable oder in einer dotenv-Datei (Reihenfolge:
`~/.config/dokumentierer/.env`, dann Datei im Arbeitsverzeichnis, dann bereits
gesetzte Env-Variablen). Siehe `.env.example`:
- `HUMHUB_API_TOKEN` — REST-API-Token. Nur als **berechtigter Benutzer**
erhältlich unter https://humhub.inmedias.it/rest/admin/index
Die Basis-URL (`https://humhub.inmedias.it/api/v1`) ist fest im Skript; für eine
andere Instanz `BASE_URL` oben in `humhub.py` anpassen.
+261
View File
@@ -0,0 +1,261 @@
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "requests",
# "python-dotenv",
# ]
# ///
"""CLI for interacting with HumHub via REST API (wiki pages and posts)."""
import argparse
import json
import os
import sys
from pathlib import Path
import requests
from dotenv import load_dotenv, find_dotenv
BASE_URL = "https://humhub.inmedias.it/api/v1"
def get_session() -> tuple[requests.Session, str]:
load_dotenv(Path.home() / ".config/dokumentierer/.env")
load_dotenv() # CWD/Projektverzeichnis, überschreibt nichts
token = os.environ.get("HUMHUB_API_TOKEN", "")
if not token:
sys.exit("Error: HUMHUB_API_TOKEN is not set in .env")
session = requests.Session()
session.headers.update({"Authorization": f"Bearer {token}"})
return session, token
def api_get(session: requests.Session, path: str) -> dict:
r = session.get(f"{BASE_URL}{path}")
r.raise_for_status()
return r.json()
def api_put(session: requests.Session, path: str, payload: dict) -> dict:
r = session.put(f"{BASE_URL}{path}", json=payload)
if not r.ok:
sys.exit(f"Error {r.status_code}: {r.text}")
return r.json()
def api_post(session: requests.Session, path: str, payload: dict) -> dict:
r = session.post(f"{BASE_URL}{path}", json=payload)
if not r.ok:
sys.exit(f"Error {r.status_code}: {r.text}")
return r.json()
def parse_topics(topics_str: str | None) -> list[dict]:
if not topics_str:
return []
return [{"name": t.strip()} for t in topics_str.split(",") if t.strip()]
def read_file(path: str) -> str:
if path == "-":
return sys.stdin.read()
try:
with open(path, "r", encoding="utf-8") as fh:
return fh.read()
except OSError as exc:
sys.exit(f"Error reading file: {exc}")
# ── Wiki commands ────────────────────────────────────────────────────────────
def cmd_wiki_list(args: argparse.Namespace) -> None:
session, _ = get_session()
data = api_get(session, "/wiki?per-page=200")
query = args.filter.lower() if args.filter else None
for page in data["results"]:
if query and query not in page["title"].lower():
continue
print(f"{page['id']:>4} {page['title']}")
def cmd_wiki_get(args: argparse.Namespace) -> None:
session, _ = get_session()
data = api_get(session, f"/wiki/page/{args.id}")
title = data.get("title", "")
content = data.get("latest_revision", {}).get("content", "")
topics = [t["name"] for t in data.get("content", {}).get("topics", [])]
url = data.get("content", {}).get("metadata", {}).get("url", "")
print(f"Title: {title}")
print(f"Topics: {', '.join(topics) or '(keine)'}")
print(f"URL: https://humhub.inmedias.it{url}")
print(f"Content ({len(content)} Zeichen):")
print(content)
def cmd_wiki_update(args: argparse.Namespace) -> None:
session, _ = get_session()
content = read_file(args.file) if args.file else None
current = api_get(session, f"/wiki/page/{args.id}")
title = args.title or current.get("title", "")
if content is None:
content = current.get("latest_revision", {}).get("content", "")
wiki_page: dict = {"title": title}
if args.parent_id is not None:
wiki_page["parent_page_id"] = args.parent_id
payload: dict = {
"WikiPage": wiki_page,
"WikiPageRevision": {"content": content},
}
topics = parse_topics(args.topics)
if topics:
payload["content"] = {"topics": topics}
data = api_put(session, f"/wiki/page/{args.id}", payload)
saved_topics = [t["name"] for t in data.get("content", {}).get("topics", [])]
parent_id = data.get("parent_page_id")
print(f"Aktualisiert: '{data.get('title')}' (Revision {data.get('latest_revision', {}).get('id')}){f', parent={parent_id}' if parent_id else ''}")
if saved_topics:
print(f"Topics: {', '.join(saved_topics)}")
def cmd_wiki_create(args: argparse.Namespace) -> None:
session, _ = get_session()
content = read_file(args.file) if args.file else ""
wiki_page: dict = {"title": args.title}
if args.parent_id is not None:
wiki_page["parent_page_id"] = args.parent_id
payload: dict = {
"WikiPage": wiki_page,
"WikiPageRevision": {"content": content},
}
topics = parse_topics(args.topics)
if topics:
payload["content"] = {"topics": topics}
data = api_post(session, f"/wiki/container/{args.container_id}", payload)
page_id = data.get("id")
url = data.get("content", {}).get("metadata", {}).get("url", "")
print(f"Erstellt: '{data.get('title')}' (ID {page_id})")
if url:
print(f"URL: https://humhub.inmedias.it{url}")
# ── Post commands ────────────────────────────────────────────────────────────
def cmd_post_list(args: argparse.Namespace) -> None:
session, _ = get_session()
data = api_get(session, f"/post/container/{args.container_id}?limit={args.limit}")
results = data.get("results", [])
if not results:
print("(keine Posts)")
return
for post in results:
pid = post.get("id")
msg = post.get("message", "").replace("\n", " ")[:80]
topics = [t["name"] for t in post.get("content", {}).get("topics", [])]
topic_str = f" [{', '.join(topics)}]" if topics else ""
print(f"ID {pid}{topic_str}: {msg}")
def cmd_post_create(args: argparse.Namespace) -> None:
session, _ = get_session()
message = read_file(args.file)
topics = parse_topics(args.topics)
payload: dict = {"data": {"message": message}}
if topics:
payload["data"]["content"] = {"topics": topics}
data = api_post(session, f"/post/container/{args.container_id}", payload)
pid = data.get("id")
saved_topics = [t["name"] for t in data.get("content", {}).get("topics", [])]
print(f"Post erstellt (ID {pid})")
if saved_topics:
print(f"Topics: {', '.join(saved_topics)}")
def cmd_post_update(args: argparse.Namespace) -> None:
session, _ = get_session()
message = read_file(args.file)
topics = parse_topics(args.topics)
payload: dict = {"data": {"message": message}}
if topics:
payload["data"]["content"] = {"topics": topics}
data = api_put(session, f"/post/{args.id}", payload)
pid = data.get("id")
saved_topics = [t["name"] for t in data.get("content", {}).get("topics", [])]
print(f"Post aktualisiert (ID {pid})")
if saved_topics:
print(f"Topics: {', '.join(saved_topics)}")
# ── Main ─────────────────────────────────────────────────────────────────────
def main() -> None:
parser = argparse.ArgumentParser(description="HumHub REST API CLI")
sub = parser.add_subparsers(dest="command", required=True)
# wiki-list
p = sub.add_parser("wiki-list", help="Alle Wiki-Seiten auflisten")
p.add_argument("filter", nargs="?", help="Filter nach Titel (case-insensitiv)")
p.set_defaults(func=cmd_wiki_list)
# wiki-get
p = sub.add_parser("wiki-get", help="Wiki-Seite anzeigen")
p.add_argument("id", type=int, help="Wiki-Seiten-ID")
p.set_defaults(func=cmd_wiki_get)
# wiki-update
p = sub.add_parser("wiki-update", help="Wiki-Seite aktualisieren")
p.add_argument("id", type=int, help="Wiki-Seiten-ID")
p.add_argument("file", nargs="?", default=None, help="Markdown-Datei oder '-' für stdin (optional)")
p.add_argument("--title", default=None, help="Neuer Titel (sonst unverändert)")
p.add_argument("--topics", default=None, help="Komma-getrennte Topic-Namen (ersetzt bestehende)")
p.add_argument("--parent-id", type=int, default=None, dest="parent_id", help="ID der Oberseite")
p.set_defaults(func=cmd_wiki_update)
# wiki-create
p = sub.add_parser("wiki-create", help="Neue Wiki-Seite anlegen")
p.add_argument("container_id", type=int, help="Container-ID (z.B. 37 für user serge)")
p.add_argument("title", help="Seitentitel")
p.add_argument("file", nargs="?", default=None, help="Markdown-Datei oder '-' für stdin (optional)")
p.add_argument("--topics", default=None, help="Komma-getrennte Topic-Namen")
p.add_argument("--parent-id", type=int, default=None, dest="parent_id", help="ID der Oberseite")
p.set_defaults(func=cmd_wiki_create)
# post-list
p = sub.add_parser("post-list", help="Posts eines Containers auflisten")
p.add_argument("container_id", type=int, help="Container-ID")
p.add_argument("--limit", type=int, default=10, help="Max. Anzahl Posts (Standard: 10)")
p.set_defaults(func=cmd_post_list)
# post-create
p = sub.add_parser("post-create", help="Neuen Post erstellen")
p.add_argument("container_id", type=int, help="Container-ID")
p.add_argument("file", help="Markdown-Datei oder '-' für stdin")
p.add_argument("--topics", default=None, help="Komma-getrennte Topic-Namen")
p.set_defaults(func=cmd_post_create)
# post-update
p = sub.add_parser("post-update", help="Post aktualisieren")
p.add_argument("id", type=int, help="Post-ID")
p.add_argument("file", help="Markdown-Datei oder '-' für stdin")
p.add_argument("--topics", default=None, help="Komma-getrennte Topic-Namen (ersetzt bestehende)")
p.set_defaults(func=cmd_post_update)
args = parser.parse_args()
args.func(args)
if __name__ == "__main__":
main()