aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/ircbot
diff options
context:
space:
mode:
authorRobin Jarry <robin@jarry.cc>2024-04-03 01:06:48 +0200
committerRobin Jarry <robin@jarry.cc>2024-06-28 23:33:12 +0200
commitdf06d6558622a089b04a3ac6315c950967c1a49d (patch)
tree5ffd5fefc9a64bde6c146e0c7a4d6ab60f3774ee /contrib/ircbot
parentc15c265f7bcd6b0568b9d259673b7f302602f250 (diff)
downloadaerc-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.py92
-rwxr-xr-xcontrib/ircbot/install-webhook.sh33
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