#!/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()