diff options
Diffstat (limited to 'tools/site/upload_file.py')
| -rw-r--r-- | tools/site/upload_file.py | 132 |
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() |
