oc:pages command in the forum extension, tools/forum_sync.py and 'make forum-sync' (run by deploy) keep data/forum_pages.json up to date; post pages end with their discussion. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YDRjtmAiNpo5mK5N3d7QwZ
72 lines
3.2 KiB
Python
Executable File
72 lines
3.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Make sure every published news post has its comment discussion in the forum.
|
|
|
|
Lists the posts with `hugo list all`, sends the list to the forum, where `php flarum oc:pages`
|
|
creates the missing discussions, and stores the returned map (content file -> discussion number)
|
|
in data/forum_pages.json, which the layouts read. Run before building (see `make forum-sync`).
|
|
|
|
--run is the command that executes a shell line where the forum lives, e.g.
|
|
'ssh -p 27 user@host' or 'docker compose -f forum/docker-compose.yml exec -T flarum sh -c'
|
|
"""
|
|
import argparse
|
|
import csv
|
|
import io
|
|
import json
|
|
import shlex
|
|
import subprocess
|
|
import sys
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
from urllib.parse import urlparse
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
MAP = ROOT / "data" / "forum_pages.json"
|
|
|
|
|
|
def posts():
|
|
"""Published posts as the forum command expects them, oldest first."""
|
|
out = subprocess.run(["hugo", "list", "all"], cwd=ROOT, check=True, capture_output=True, text=True).stdout
|
|
now, pages = datetime.now(timezone.utc), []
|
|
for row in csv.DictReader(io.StringIO(out)):
|
|
if row["kind"] != "page" or row["section"] != "post" or row["draft"] == "true":
|
|
continue
|
|
date = datetime.fromisoformat(row["date"].replace("Z", "+00:00"))
|
|
published = datetime.fromisoformat(row["publishDate"].replace("Z", "+00:00"))
|
|
if published > now:
|
|
continue
|
|
pages.append({
|
|
"key": row["path"].removeprefix("content/"),
|
|
"title": row["title"],
|
|
"date": date.strftime("%Y-%m-%d %H:%M:%S") if date.year > 1 else "",
|
|
"path": urlparse(row["permalink"]).path,
|
|
})
|
|
return sorted(pages, key=lambda p: p["date"])
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser(description=__doc__.split("\n")[0])
|
|
ap.add_argument("--run", required=True, help="command prefix that runs a shell line on the forum host")
|
|
ap.add_argument("--dir", required=True, help="forum folder on that host")
|
|
ap.add_argument("--php", default="php", help="PHP command line there")
|
|
ap.add_argument("--site-url", required=True, help="public address of the site, for the link in each discussion")
|
|
args = ap.parse_args()
|
|
|
|
pages = posts()
|
|
remote = (f"cd {shlex.quote(args.dir)} && cat > storage/oc-pages.json && "
|
|
f"{args.php} flarum oc:pages storage/oc-pages.json --site-url={shlex.quote(args.site_url.rstrip('/'))} "
|
|
f"--map=storage/oc-pages-map.json >&2 && cat storage/oc-pages-map.json; "
|
|
f"rm -f storage/oc-pages.json storage/oc-pages-map.json")
|
|
done = subprocess.run([*shlex.split(args.run), remote], input=json.dumps(pages), capture_output=True, text=True)
|
|
notes = [line for line in done.stderr.splitlines() if "discussions created" in line]
|
|
if done.returncode != 0 or not done.stdout.strip().startswith(("{", "[")):
|
|
sys.exit(f"forum sync failed:\n{done.stderr.strip()[-800:]}")
|
|
mapping = json.loads(done.stdout) or {}
|
|
text = json.dumps(dict(sorted(mapping.items())), indent=1) + "\n"
|
|
if not MAP.exists() or MAP.read_text() != text:
|
|
MAP.write_text(text)
|
|
print(f"{len(pages)} posts; {notes[-1].strip() if notes else 'done'}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|