summaryrefslogtreecommitdiffstats
path: root/tools/site/upload_file.py
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-12 00:32:00 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-12 00:32:00 -0400
commit5df3147130c9254cd5bea34b330a2a42ee51d635 (patch)
tree6a8fdf54bbb291eaccbeef8334e2cdd70af2caa8 /tools/site/upload_file.py
parentf1edf479a167898aaf70dc8f21e9cf18bccbe112 (diff)
downloadopen-iov-5df3147130c9254cd5bea34b330a2a42ee51d635.tar.gz
open-iov-5df3147130c9254cd5bea34b330a2a42ee51d635.zip
site: open-iov.org 2026 documentation update
- tools/site: Markdown-to-MediaWiki converter (tracked-paths-only link mapping, local-marker scan, About-message insert), WikiBot publisher with diff/check modes and publish manifest, live site auditor, image uploader, site-only page sources (landing page, About, index, sidebar, banner templates, About-message template). - wiki: new public deep-dive sources (project status, install, FAQ, system overview, GSP and GSP-RM, execution frontier, host module, guest stack, UVM, physical backend, driver lifecycle, risc-v-gsp-rm-debugger, hardware/software matrix, HAAEEP, glossary); refreshed CUDA userspace status; corrected cross-document links; README rewritten for the new set. - wiki/.publish-manifest.json records each published revision for rollback.
Diffstat (limited to 'tools/site/upload_file.py')
-rw-r--r--tools/site/upload_file.py132
1 files changed, 132 insertions, 0 deletions
diff --git a/tools/site/upload_file.py b/tools/site/upload_file.py
new file mode 100644
index 0000000..8d347ba
--- /dev/null
+++ b/tools/site/upload_file.py
@@ -0,0 +1,132 @@
+#!/usr/bin/env python3
+"""upload_file.py — upload an image to open-iov.org as WikiBot.
+
+Usage:
+ upload_file.py --file PATH --name "File:Example.png" --text "wikitext description"
+"""
+
+import argparse
+import http.cookiejar
+import json
+import mimetypes
+import os
+import sys
+import urllib.parse
+import urllib.request
+import uuid
+
+API = "https://open-iov.org/api.php"
+ENVFILE = os.path.expanduser("~/.config/open-iov/wiki-bot.env")
+UA = "OpenIOV-WikiBot/1.0"
+
+
+def load_creds():
+ user = os.environ.get("WIKI_BOT_USER")
+ password = os.environ.get("WIKI_BOT_PASS")
+ if not (user and password) and os.path.exists(ENVFILE):
+ with open(ENVFILE) as f:
+ for line in f:
+ line = line.strip()
+ if line.startswith("WIKI_BOT_USER="):
+ user = line.split("=", 1)[1]
+ elif line.startswith("WIKI_BOT_PASS="):
+ password = line.split("=", 1)[1]
+ if not (user and password):
+ sys.exit("no credentials")
+ return user, password
+
+
+class Multipart:
+ def __init__(self):
+ self.boundary = uuid.uuid4().hex
+ self.parts = []
+
+ def field(self, name, value):
+ self.parts.append(
+ (
+ f"--{self.boundary}\r\n"
+ f'Content-Disposition: form-data; name="{name}"\r\n\r\n'
+ f"{value}\r\n"
+ ).encode()
+ )
+
+ def file(self, name, filename, data, ctype):
+ self.parts.append(
+ (
+ f"--{self.boundary}\r\n"
+ f'Content-Disposition: form-data; name="{name}"; filename="{filename}"\r\n'
+ f"Content-Type: {ctype}\r\n\r\n"
+ ).encode()
+ + data
+ + b"\r\n"
+ )
+
+ def body(self):
+ return b"".join(self.parts) + f"--{self.boundary}--\r\n".encode()
+
+ def content_type(self):
+ return f"multipart/form-data; boundary={self.boundary}"
+
+
+def main():
+ ap = argparse.ArgumentParser()
+ ap.add_argument("--file", required=True)
+ ap.add_argument("--name", required=True, help='wiki file title, e.g. "File:Example.png"')
+ ap.add_argument("--text", default="")
+ ap.add_argument("--summary", default="Upload image from tracked analysis")
+ args = ap.parse_args()
+
+ user, password = load_creds()
+ cj = http.cookiejar.CookieJar()
+ opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cj))
+
+ def api(params, post=False):
+ params.setdefault("format", "json")
+ qs = urllib.parse.urlencode(params)
+ if post:
+ req = urllib.request.Request(API, data=qs.encode(), headers={"User-Agent": UA})
+ else:
+ req = urllib.request.Request(API + "?" + qs, headers={"User-Agent": UA})
+ with opener.open(req, timeout=60) as r:
+ return json.loads(r.read())
+
+ tok = api({"action": "query", "meta": "tokens", "type": "login"})["query"]["tokens"]["logintoken"]
+ res = api({"action": "login", "lgname": user, "lgpassword": password, "lgtoken": tok}, post=True)
+ if res.get("login", {}).get("result") != "Success":
+ sys.exit(f"login failed: {res}")
+ csrf = api({"action": "query", "meta": "tokens", "type": "csrf"})["query"]["tokens"]["csrftoken"]
+
+ with open(args.file, "rb") as f:
+ data = f.read()
+ ctype = mimetypes.guess_type(args.file)[0] or "application/octet-stream"
+ mp = Multipart()
+ mp.field("action", "upload")
+ mp.field("format", "json")
+ mp.field("token", csrf)
+ mp.field("filename", args.name)
+ mp.field("comment", args.summary)
+ mp.field("text", args.text)
+ mp.field("ignorewarnings", "1")
+ mp.file("file", os.path.basename(args.file), data, ctype)
+ req = urllib.request.Request(
+ API,
+ data=mp.body(),
+ headers={"User-Agent": UA, "Content-Type": mp.content_type()},
+ )
+ with opener.open(req, timeout=120) as r:
+ raw = r.read()
+ try:
+ result = json.loads(raw)
+ except Exception:
+ print(raw[:2000])
+ raise
+ upload = result.get("upload", {})
+ if not upload:
+ print("RAW RESPONSE:", raw[:2000])
+ print(json.dumps(upload, indent=1))
+ if upload.get("result") != "Success":
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ main()