1#!/usr/bin/env hy23(import os sys argparse json hmac4 [http.server [*]]5 [github [Github]])6(require [hy.contrib.walk [let]])78(setv GITHUB-TOKEN "GITHUB_ACCESS_TOKEN") ;; ENV for API access token9(setv GITHUB-SECRET "GITHUB_WEBHOOK_SECRET") ;; ENV for webhook secret1011(defclass WebhookException [Exception]12 (defn --init-- [self msg &optional [code 500]]13 (setv self.code code)14 (.--init-- (super WebhookException self) msg))1516 (defn get-status-code [self]17 (. self code)))1819(defclass GithubWebhookHandler [BaseHTTPRequestHandler]20 (defn handle-pr [self dict]21 (let [action (get dict "action")]22 (if (or (= action "opened")23 (and handle-reopened? (= action "reopened")))24 (let [name (get (get dict "repository") "full_name")25 repo (.get-repo github-api name)26 pr (.get-issue repo :number (get dict "number"))]27 (.create-comment pr comment-text)28 (when close-issue?29 (.edit pr :state "closed"))))))3031 (defn dispatch-event [self body]32 (try33 (let [body-json (json.loads body)34 event (.get self.headers "X-GitHub-Event")]35 (cond36 [(= event "pull_request")37 (.handle-pr self body-json)]38 [(not (= event "ping"))39 (raise (WebhookException "Unsupported webhook event" 500))])40 (.send-response self 200)41 (.end-headers self))42 (except [e WebhookException]43 (.send-error self (.get-status-code e) (str e)))44 (except [[json.decoder.JSONDecodeError KeyError]]45 (.send-error self 400 "Received invalid JSON document"))))4647 (defn from-github? [self header data]48 (let [hmac-obj (hmac.new github-secret :msg data :digestmod "sha1")49 digest (.hex (.digest hmac-obj))]50 (= (.format "sha1={}" digest) header)))5152 (defn do-POST [self]53 (if (not (= "application/json" (.get self.headers "Content-Type")))54 (.send-error self 400 "Expected Content-Type application/json")55 (let [con-len (int (.get self.headers "Content-Length" :failobj "0"))]56 (let [body (.read self.rfile con-len)]57 (if (.from-github? self (.get self.headers "X-Hub-Signature") body)58 (.dispatch-event self body)59 (.send-error self 403 "HMAC digest validation failed"))))))6061 (defn handle [self]62 (try63 (.handle (super GithubWebhookHandler self))6465 ;; Flush stderr, containing log messages created by http.server66 (finally (sys.stderr.flush)))))6768(defmacro setg [name value]69 `(do70 (global ~name)71 (setv ~name ~value)))7273(defn get-env [name]74 (let [value (os.getenv name)]75 (when (is None value)76 (print :file sys.stderr77 (.format "Environment variable '{}' is not set but required" name))78 (sys.exit 1))79 value))8081(defmain [&rest args]82 (let [parser (argparse.ArgumentParser)83 token (get-env GITHUB-TOKEN)84 secret (get-env GITHUB-SECRET)]85 (parser.add-argument "PATH" :type string86 :help "Path to markdown file containing comment text")87 (parser.add-argument "-p" :type int :metavar "PORT"88 :default 80 :help "TCP port used by the webhook HTTP server")89 (parser.add-argument "-c" :action "store_true"90 :help "Apart from adding a comment, also close the PR")91 (parser.add-argument "-r" :action "store_true"92 :help "Also handle reopened pull requests")93 (parser.add-argument "-a" :type string :metavar "ADDR"94 :default "localhost" :help "Address the webhook HTTP server binds to")9596 (let [args (parser.parse-args)]97 (setg close-issue? args.c)98 (setg handle-reopened? args.r)99 (setg github-api (Github token))100 (setg github-secret (.encode secret))101102 (with [f (open args.PATH)]103 (setg comment-text (.rstrip (.read f))))104105 (.serve-forever106 (HTTPServer (, args.a args.p) GithubWebhookHandler)))))