diff options
author | Robin Jarry <robin@jarry.cc> | 2024-04-03 01:06:48 +0200 |
---|---|---|
committer | Robin Jarry <robin@jarry.cc> | 2024-06-28 23:33:12 +0200 |
commit | df06d6558622a089b04a3ac6315c950967c1a49d (patch) | |
tree | 5ffd5fefc9a64bde6c146e0c7a4d6ab60f3774ee /contrib/ircbot | |
parent | c15c265f7bcd6b0568b9d259673b7f302602f250 (diff) | |
download | aerc-df06d6558622a089b04a3ac6315c950967c1a49d.tar.gz |
ircbot: update webhook to handle applied patches
Register another webhook for all received emails and track the
X-Sourcehut-Patchset-Update header value. If it is APPLIED, then send
an IRC announce accordingly.
Use green for "applied" and light gr{e,a}y for "received".
Signed-off-by: Robin Jarry <robin@jarry.cc>
Tested-by: Bence Ferdinandy <bence@ferdinandy.com>
Diffstat (limited to 'contrib/ircbot')
-rw-r--r-- | contrib/ircbot/Sourcehut/plugin.py | 92 | ||||
-rwxr-xr-x | contrib/ircbot/install-webhook.sh | 33 |
2 files changed, 102 insertions, 23 deletions
diff --git a/contrib/ircbot/Sourcehut/plugin.py b/contrib/ircbot/Sourcehut/plugin.py index 33026ee1..593ad020 100644 --- a/contrib/ircbot/Sourcehut/plugin.py +++ b/contrib/ircbot/Sourcehut/plugin.py @@ -1,7 +1,12 @@ +import email.header +import email.utils import json +import mailbox +from urllib.parse import quote +from urllib.request import urlopen -from supybot import ircmsgs, callbacks, httpserver, log, world -from supybot.ircutils import bold, italic, underline +from supybot import callbacks, httpserver, ircmsgs, log, world +from supybot.ircutils import bold, italic, mircColor, underline class Sourcehut(callbacks.Plugin): @@ -28,6 +33,17 @@ class Sourcehut(callbacks.Plugin): libera.sendMsg(ircmsgs.notice(channel, message)) +def decode_header(header: str) -> str: + if not header: + return "" + text = "" + for chunk, encoding in email.header.decode_header(header): + if isinstance(chunk, bytes): + chunk = chunk.decode(encoding or "us-ascii") + text += chunk + return text + + class SourcehutServerCallback(httpserver.SupyHTTPServerCallback): name = "Sourcehut" defaultResponse = "Bad request\n" @@ -37,12 +53,55 @@ class SourcehutServerCallback(httpserver.SupyHTTPServerCallback): self.plugin = plugin SUBJECT = "[PATCH {prefix} v{version}] {subject}" - URL = "https://lists.sr.ht/{list[owner][canonicalName]}/{list[name]}/patches/{id}" + URL = "https://lists.sr.ht/{list[owner][canonicalName]}/{list[name]}" CHANS = { "#public-inbox": "##rjarry", "#aerc-devel": "#aerc", } + def announce_patch(self, patchset): + subject = self.SUBJECT.format(**patchset) + url = self.URL.format(**patchset) + if not url.startswith("https://lists.sr.ht/~rjarry/"): + raise ValueError("unknown list") + url += "/patches/{id}".format(**patchset) + channel = f"#{patchset['list']['name']}" + channel = self.CHANS.get(channel, channel) + try: + submitter = patchset["submitter"]["canonicalName"] + except KeyError: + try: + submitter = patchset["submitter"]["name"] + except KeyError: + submitter = patchset["submitter"]["address"] + msg = f"{mircColor('received', 'light gray')} {bold(subject)}" + msg += f" from {italic(submitter)}: {underline(url)}" + self.plugin.announce(channel, msg) + + def announce_apply(self, mail): + channel = f"#{mail['list']['name']}" + channel = self.CHANS.get(channel, channel) + refs = [] + for header in mail['references']: + refs += header.split() + for ref in refs: + url = self.URL.format(**mail) + quote(f"/{ref}") + print(f"GET {url}/raw") + with urlopen(f"{url}/raw") as u: + msg = mailbox.Message(u.read()) + subject = decode_header(msg["subject"]) + if not subject.startswith("[PATCH"): + continue + for name, addr in email.utils.getaddresses([decode_header(msg["from"])]): + if name: + submitter = name + else: + submitter = addr + msg = f"{bold(mircColor('applied', 'green'))} {bold(subject)}" + msg += f" from {italic(submitter)}: {underline(url)}" + self.plugin.announce(channel, msg) + return + def doPost(self, handler, path, form=None): if hasattr(form, "decode"): form = form.decode("utf-8") @@ -51,28 +110,21 @@ class SourcehutServerCallback(httpserver.SupyHTTPServerCallback): body = json.loads(form) hook = body["data"]["webhook"] if hook["event"] == "PATCHSET_RECEIVED": - patchset = hook["patchset"] - subject = self.SUBJECT.format(**patchset) - url = self.URL.format(**patchset) - if not url.startswith("https://lists.sr.ht/~rjarry/"): - raise ValueError("unknown list") - channel = f"#{patchset['list']['name']}" - channel = self.CHANS.get(channel, channel) - try: - submitter = patchset["submitter"]["canonicalName"] - except KeyError: - try: - submitter = patchset["submitter"]["name"] - except KeyError: - submitter = patchset["submitter"]["address"] - msg = f"received {bold(subject)} from {italic(submitter)}: {underline(url)}" - self.plugin.announce(channel, msg) + self.announce_patch(hook["patchset"]) + handler.send_response(200) + handler.end_headers() + handler.wfile.write(b"") + return + + if hook["event"] == "EMAIL_RECEIVED": + if hook["email"]["patchset_update"] == ["APPLIED"]: + self.announce_apply(hook["email"]) handler.send_response(200) handler.end_headers() handler.wfile.write(b"") return - raise ValueError("unsupported webhook: %r" % hook) + raise ValueError(f"unsupported webhook: {hook}") except Exception as e: print("ERROR", e) diff --git a/contrib/ircbot/install-webhook.sh b/contrib/ircbot/install-webhook.sh index 1f8f12dd..d4db101c 100755 --- a/contrib/ircbot/install-webhook.sh +++ b/contrib/ircbot/install-webhook.sh @@ -2,9 +2,10 @@ set -xe -hut lists webhook create "https://lists.sr.ht/~rjarry/aerc-devel" \ - --stdin -e patchset_received \ - -u https://bot.diabeteman.com/sourcehut/ <<EOF +list="${1:-https://lists.sr.ht/~rjarry/aerc-devel}" +url="${2:-https://bot.diabeteman.com/sourcehut/}" + +hut lists webhook create "$list" --stdin -e patchset_received -u "$url" <<EOF query { webhook { uuid @@ -38,3 +39,29 @@ query { } } EOF + +hut lists webhook create "$list" --stdin -e email_received -u "$url" <<EOF +query { + webhook { + uuid + event + date + ... on EmailEvent { + email { + id + subject + patchset_update: header(want: "X-Sourcehut-Patchset-Update") + references: header(want: "References") + list { + name + owner { + ... on User { + canonicalName + } + } + } + } + } + } +} +EOF |