Files
claude-plugins/plugins/mediawiki/skills/mediawiki/wiki.py
T
skoandClaude Opus 4.8 7b44ceafdc plugins/mediawiki: wiki.py als Skill (fetch/update/render, wiki & devwiki)
Neues Plugin, das MediaWiki-Seiten/Templates ueber die gevendorte, self-
contained wiki.py (uv run --script; mwclient+python-dotenv) liest, rendert und
schreibt. Instanzen wiki (Prod) und devwiki via -w. Zugangsdaten bleiben lokal
(Umgebungsvariablen bzw. dotenv-Datei) - Beispiel-Vorlage beiliegend. SKILL.md
dokumentiert Nutzung + Regel "Wiki nur via wiki.py/MCP, nicht Maintenance-Skript".
Marketplace-Eintrag ergaenzt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 09:50:43 +02:00

182 lines
5.8 KiB
Python
Executable File

#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "mwclient",
# "python-dotenv",
# ]
# ///
"""CLI for fetching and updating MediaWiki pages."""
import argparse
import os
import sys
from pathlib import Path
from urllib.parse import urlparse
from dotenv import load_dotenv
import mwclient
WIKI_ALIASES = {
"wiki": ("MEDIAWIKI_URL", "https://wiki.inmedias.it"),
"devwiki": ("DEVMEDIAWIKI_URL", "https://devwiki.inmedias.it"),
}
def get_site(wiki: str = "wiki") -> mwclient.Site:
load_dotenv(Path.home() / ".config/dokumentierer/.env")
load_dotenv() # current dir, doesn't override already-set vars
if wiki not in WIKI_ALIASES:
sys.exit(f"Error: unknown wiki '{wiki}'. Known: {', '.join(WIKI_ALIASES)}")
url_env, url_default = WIKI_ALIASES[wiki]
url = os.environ.get(url_env, url_default).rstrip("/")
prefix = "DEVMEDIAWIKI_" if wiki == "devwiki" else "MEDIAWIKI_"
user = os.environ.get(f"{prefix}USER") or os.environ.get("MEDIAWIKI_USER", "")
password = os.environ.get(f"{prefix}PASSWORD") or os.environ.get("MEDIAWIKI_PASSWORD", "")
if not user:
sys.exit(f"Error: MEDIAWIKI_USER is not set in .env")
if not password:
sys.exit(f"Error: MEDIAWIKI_PASSWORD is not set in .env")
parsed = urlparse(url)
host = parsed.netloc
# mwclient expects path to end with '/'
path = (parsed.path or "/") + "/"
path = path.replace("//", "/")
scheme = parsed.scheme or "https"
site = mwclient.Site(host, path=path, scheme=scheme)
site.login(user, password)
return site
def cmd_fetch(args: argparse.Namespace) -> None:
site = get_site(args.wiki)
page = site.pages[args.title]
if not page.exists:
sys.exit(f"Error: page '{args.title}' does not exist")
print(page.text(), end="")
NAMESPACES = {
"main": 0, "talk": 1, "user": 2, "project": 4,
"file": 6, "template": 10, "category": 14,
}
def _strip_html(text: str) -> str:
"""Remove HTML tags (used to clean search snippets)."""
import re
return re.sub(r"<[^>]+>", "", text)
def cmd_search(args: argparse.Namespace) -> None:
site = get_site(args.wiki)
ns_key = args.namespace
if ns_key is None or ns_key.lower() == "all":
ns = "*"
elif ns_key.isdigit():
ns = int(ns_key)
else:
ns = NAMESPACES.get(ns_key.lower())
if ns is None:
sys.exit(f"Error: unknown namespace '{ns_key}'. "
f"Known: all, {', '.join(NAMESPACES)}")
result = site.api(
"query", list="search",
srsearch=args.query,
srnamespace=ns,
srlimit=50,
srprop="title|snippet",
)
hits = result["query"]["search"]
if not hits:
print("(keine Ergebnisse)", file=sys.stderr)
for hit in hits:
print(hit["title"])
if hit.get("snippet"):
print(f" {_strip_html(hit['snippet']).strip()}")
def cmd_render(args: argparse.Namespace) -> None:
site = get_site(args.wiki)
result = site.api("parse", page=args.title, prop="text")
print(result["parse"]["text"]["*"], end="")
def cmd_upload(args: argparse.Namespace) -> None:
path = Path(args.file_path)
if not path.exists():
sys.exit(f"Error: file not found: {path}")
site = get_site(args.wiki)
with path.open("rb") as fh:
site.upload(fh, filename=args.wiki_filename,
description=args.description or "", ignore=True)
print(f"Uploaded '{args.wiki_filename}'")
def cmd_update(args: argparse.Namespace) -> None:
if args.file == "-":
content = sys.stdin.read()
else:
try:
with open(args.file, "r", encoding="utf-8") as fh:
content = fh.read()
except OSError as exc:
sys.exit(f"Error reading file: {exc}")
site = get_site(args.wiki)
page = site.pages[args.title]
page.save(content, summary=args.summary or "")
print(f"Saved '{args.title}'")
def main() -> None:
parser = argparse.ArgumentParser(description="MediaWiki page fetch/update tool")
parser.add_argument(
"--wiki", "-w",
choices=list(WIKI_ALIASES),
default="wiki",
help="Wiki-Instanz (Standard: wiki = wiki.inmedias.it)",
)
sub = parser.add_subparsers(dest="command", required=True)
fetch_p = sub.add_parser("fetch", help="Print page wikitext to stdout")
fetch_p.add_argument("title", help="Page title")
fetch_p.set_defaults(func=cmd_fetch)
search_p = sub.add_parser("search", help="Search for pages by title")
search_p.add_argument("query", help="Search term")
search_p.add_argument("--namespace", "-n", default=None,
help="Namespace filter: all (default), main, template, category, file, ... or numeric ID")
search_p.set_defaults(func=cmd_search)
render_p = sub.add_parser("render", help="Print rendered HTML of a page to stdout")
render_p.add_argument("title", help="Page title")
render_p.set_defaults(func=cmd_render)
upload_p = sub.add_parser("upload", help="Upload a local file to the wiki file store")
upload_p.add_argument("file_path", help="Absolute path to the local file")
upload_p.add_argument("wiki_filename", help="Target filename on the wiki (without 'File:' prefix)")
upload_p.add_argument("--description", default="", help="Wikitext for the file description page")
upload_p.set_defaults(func=cmd_upload)
update_p = sub.add_parser("update", help="Save wikitext to a page")
update_p.add_argument("title", help="Page title")
update_p.add_argument("file", help="File containing wikitext, or '-' for stdin")
update_p.add_argument("--summary", default="", help="Edit summary")
update_p.set_defaults(func=cmd_update)
args = parser.parse_args()
args.func(args)
if __name__ == "__main__":
main()