"""Datoka Hooks HTTP example. Python 3.10+, standard library only. Usage: python send-http.py ACCESS_FILE STABLE_EVENT_ID PAYLOAD_FILE Retry manually with the same event ID and unchanged payload file. """ import json import re import sys from pathlib import Path from urllib.error import HTTPError from urllib.request import HTTPRedirectHandler, Request, build_opener ORIGIN = "https://datoka-hooks-test.bhazarstudio.workers.dev" class NoRedirect(HTTPRedirectHandler): def redirect_request(self, req, fp, code, msg, headers, newurl): return None def inputs(args): if len(args) != 3: raise ValueError("usage") access_file, event_id, payload_file = args access = json.loads(Path(access_file).read_text(encoding="utf-8-sig")) if not isinstance(access, dict) or access.get("origin", ORIGIN) != ORIGIN: raise ValueError("invalid_access") if not re.fullmatch(r"pub-[a-f0-9]{32}", access.get("hookId", "")): raise ValueError("invalid_hook") if not re.fullmatch(r"[a-f0-9]{64}", access.get("token", "")): raise ValueError("invalid_token") if not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._:-]{0,127}", event_id): raise ValueError("invalid_event_id") with Path(payload_file).open("rb") as source: body = source.read(65537) if len(body) > 65536: raise ValueError("body_too_large") json.loads(body.decode("utf-8")) return access, event_id, body def send_http(access, event_id, body, opener=None): # Preserve the original bytes. Never follow redirects with the bearer token. request = Request( ORIGIN + "/v1/hooks/" + access["hookId"] + "/events", data=body, method="POST", headers={"Authorization": "Bearer " + access["token"], "Content-Type": "application/json", "X-Datoka-Event-Id": event_id}) client = opener if opener is not None else build_opener(NoRedirect()) try: with client.open(request, timeout=20) as response: if response.status != 202: raise ValueError("unexpected_status") result = json.loads(response.read(1048577)) except HTTPError as error: # Do not print raw errors, URLs, response bodies or credentials. status = error.code error.close() raise ValueError("hooks_http_" + str(status)) from None return {key: result.get(key) for key in ("deliveryId", "state", "attempts")} def main(args=None): try: result = send_http(*inputs(sys.argv[1:] if args is None else args)) print(json.dumps(result, indent=2)) return 0 except Exception: print("Submission not confirmed. Check access, receiver and quota. " "Retry with the SAME event ID and unchanged JSON file. " "HTTP 202 means accepted, not delivered.", file=sys.stderr) return 1 if __name__ == "__main__": sys.exit(main())