From df06d6558622a089b04a3ac6315c950967c1a49d Mon Sep 17 00:00:00 2001 From: Robin Jarry Date: Wed, 3 Apr 2024 01:06:48 +0200 Subject: 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 Tested-by: Bence Ferdinandy --- contrib/ircbot/Sourcehut/plugin.py | 92 +++++++++++++++++++++++++++++--------- 1 file changed, 72 insertions(+), 20 deletions(-) (limited to 'contrib/ircbot/Sourcehut/plugin.py') 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) -- cgit