~rjarry/public-inbox

aerc: contrib: add irc bot stuff v1 PROPOSED

Robin Jarry: 1
 contrib: add irc bot stuff

 10 files changed, 251 insertions(+), 0 deletions(-)
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~rjarry/public-inbox/patches/42164/mbox | git am -3
Learn more about email & git

[PATCH aerc] contrib: add irc bot stuff Export this patch

Add a small script to install a sourcehut webhook that triggers on
patchset reception.

Add a limnoria (supybot fork) plugin to receive the webhook requests and
send IRC NOTICE messages on the proper channels.

Signed-off-by: Robin Jarry <robin@jarry.cc>
---
 contrib/ircbot/Sourcehut/README.md         |  1 +
 contrib/ircbot/Sourcehut/__init__.py       | 21 ++++++
 contrib/ircbot/Sourcehut/config.py         | 14 ++++
 contrib/ircbot/Sourcehut/local/__init__.py |  1 +
 contrib/ircbot/Sourcehut/plugin.py         | 80 ++++++++++++++++++++++
 contrib/ircbot/Sourcehut/setup.py          |  3 +
 contrib/ircbot/install-webhook.sh          | 36 ++++++++++
 contrib/ircbot/nginx.conf                  | 36 ++++++++++
 contrib/ircbot/supybot.conf                | 40 +++++++++++
 contrib/ircbot/supybot.service             | 19 +++++
 10 files changed, 251 insertions(+)
 create mode 100644 contrib/ircbot/Sourcehut/README.md
 create mode 100644 contrib/ircbot/Sourcehut/__init__.py
 create mode 100644 contrib/ircbot/Sourcehut/config.py
 create mode 100644 contrib/ircbot/Sourcehut/local/__init__.py
 create mode 100644 contrib/ircbot/Sourcehut/plugin.py
 create mode 100644 contrib/ircbot/Sourcehut/setup.py
 create mode 100755 contrib/ircbot/install-webhook.sh
 create mode 100644 contrib/ircbot/nginx.conf
 create mode 100644 contrib/ircbot/supybot.conf
 create mode 100644 contrib/ircbot/supybot.service

diff --git a/contrib/ircbot/Sourcehut/README.md b/contrib/ircbot/Sourcehut/README.md
new file mode 100644
index 000000000000..dbc4311d6488
--- /dev/null
+++ b/contrib/ircbot/Sourcehut/README.md
@@ -0,0 +1 @@
Supybot plugin to receive Sourcehut webhooks
diff --git a/contrib/ircbot/Sourcehut/__init__.py b/contrib/ircbot/Sourcehut/__init__.py
new file mode 100644
index 000000000000..39a9beef142e
--- /dev/null
+++ b/contrib/ircbot/Sourcehut/__init__.py
@@ -0,0 +1,21 @@
"""
Sourcehut: Supybot plugin to receive Sourcehut webhooks
"""

import sys
import supybot

__version__ = "0.1"
__author__ = supybot.authors.unknown
__contributors__ = {}
__url__ = ''

from . import config
from . import plugin
from importlib import reload

reload(config)
reload(plugin)

Class = plugin.Class
configure = config.configure
diff --git a/contrib/ircbot/Sourcehut/config.py b/contrib/ircbot/Sourcehut/config.py
new file mode 100644
index 000000000000..38e554275d81
--- /dev/null
+++ b/contrib/ircbot/Sourcehut/config.py
@@ -0,0 +1,14 @@
from supybot import conf, registry
try:
    from supybot.i18n import PluginInternationalization
    _ = PluginInternationalization('Sourcehut')
except:
    _ = lambda x: x


def configure(advanced):
    from supybot.questions import expect, anything, something, yn
    conf.registerPlugin('Sourcehut', True)


Sourcehut = conf.registerPlugin('Sourcehut')
diff --git a/contrib/ircbot/Sourcehut/local/__init__.py b/contrib/ircbot/Sourcehut/local/__init__.py
new file mode 100644
index 000000000000..e86e97b86ebb
--- /dev/null
+++ b/contrib/ircbot/Sourcehut/local/__init__.py
@@ -0,0 +1 @@
# Stub so local is a module, used for third-party modules
diff --git a/contrib/ircbot/Sourcehut/plugin.py b/contrib/ircbot/Sourcehut/plugin.py
new file mode 100644
index 000000000000..d66c10299d02
--- /dev/null
+++ b/contrib/ircbot/Sourcehut/plugin.py
@@ -0,0 +1,80 @@
import json

from supybot import ircmsgs, callbacks, httpserver, log, world
from supybot.ircutils import bold, italic, underline


class Sourcehut(callbacks.Plugin):
    """
    Supybot plugin to receive Sourcehut webhooks
    """
    def __init__(self, irc):
        super().__init__(irc)
        httpserver.hook("sourcehut", SourcehutServerCallback(self))

    def die(self):
        httpserver.unhook("sourcehut")
        super().die()

    def announce(self, channel, message):
        libera = world.getIrc("libera")
        if libera is None:
            print("error: no irc libera")
            return
        if channel not in libera.state.channels:
            print(f"error: not in {channel} channel")
            return
        libera.sendMsg(ircmsgs.notice(channel, message))


class SourcehutServerCallback(httpserver.SupyHTTPServerCallback):
    name = "Sourcehut"
    defaultResponse = "Bad request\n"

    def __init__(self, plugin: Sourcehut):
        super().__init__()
        self.plugin = plugin

    SUBJECT = "[PATCH {prefix} v{version}] {subject}"
    URL = "https://lists.sr.ht/{list[owner][canonicalName]}/{list[name]}/patches/{id}"
    CHANS = {
        "#public-inbox": "##rjarry",
        "#aerc-devel": "#aerc",
    }

    def doPost(self, handler, path, form=None):
        if hasattr(form, "decode"):
            form = form.decode("utf-8")
        print(f"POST {path} {form}")
        try:
            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)
                submitter = patchset["submitter"]["canonicalName"]
                msg = f"received {bold(subject)} from {italic(submitter)}: {underline(url)}"
                self.plugin.announce(channel, msg)
                handler.send_response(200)
                handler.end_headers()
                handler.wfile.write(b"")
                return

            raise ValueError("unsupported webhook: %r" % hook)

        except Exception as e:
            print("ERROR", e)
            handler.send_response(400)
            handler.end_headers()
            handler.wfile.write(b"Bad request\n")

    def log_message(self, format, *args):
        pass


Class = Sourcehut
diff --git a/contrib/ircbot/Sourcehut/setup.py b/contrib/ircbot/Sourcehut/setup.py
new file mode 100644
index 000000000000..11ba8772bb1f
--- /dev/null
+++ b/contrib/ircbot/Sourcehut/setup.py
@@ -0,0 +1,3 @@
from supybot.setup import plugin_setup

plugin_setup('Sourcehut')
diff --git a/contrib/ircbot/install-webhook.sh b/contrib/ircbot/install-webhook.sh
new file mode 100755
index 000000000000..4d737f5bd5c2
--- /dev/null
+++ b/contrib/ircbot/install-webhook.sh
@@ -0,0 +1,36 @@
#!/bin/sh

set -xe

hut lists webhook create "https://lists.sr.ht/~rjarry/aerc-devel" \
	--stdin -e patchset_received \
	-u https://bot.diabeteman.com/sourcehut/ <<EOF
query {
	webhook {
		uuid
		event
		date
		... on PatchsetEvent {
			patchset {
				id
				subject
				version
				prefix
				list {
					name
					owner {
						... on User {
							canonicalName
						}
					}
				}
				submitter {
					... on User {
						canonicalName
					}
				}
			}
		}
	}
}
EOF
diff --git a/contrib/ircbot/nginx.conf b/contrib/ircbot/nginx.conf
new file mode 100644
index 000000000000..b68f860a7233
--- /dev/null
+++ b/contrib/ircbot/nginx.conf
@@ -0,0 +1,36 @@
limit_req_zone $binary_remote_addr zone=aercbot:1m rate=1r/s;

server {
        listen 443 ssl;
        listen [::]:443 ssl;
        server_name bot.diabeteman.com;

        ssl_certificate /etc/dehydrated/certs/diabeteman.com/fullchain.pem;
        ssl_certificate_key /etc/dehydrated/certs/diabeteman.com/privkey.pem;

        client_max_body_size 150K;
        limit_req zone=aercbot burst=10 nodelay;

        location / {
                allow 173.195.146.144;
                allow 2604:bf00:710:0:5054:ff:fec4:6bfb;
                allow 2a01:cb00:f8b:9700:84d0:c0d6:72c3:677c;
                deny all;
                proxy_http_version 1.1;
                proxy_set_header Host $http_host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
                proxy_redirect off;
                proxy_buffering off;
                proxy_request_buffering off;
                proxy_pass http://127.0.0.1:7777;
        }
}

server {
        listen 80;
        listen [::]:80;
        server_name bot.diabeteman.com;
        return 301 https://$host$request_uri;
}
diff --git a/contrib/ircbot/supybot.conf b/contrib/ircbot/supybot.conf
new file mode 100644
index 000000000000..f7cff21dbf6d
--- /dev/null
+++ b/contrib/ircbot/supybot.conf
@@ -0,0 +1,40 @@
supybot.commands.allowShell = False
supybot.ident = aercbot
supybot.log.format = %(name)s: %(message)s
supybot.log.plugins.format = %(message)s
supybot.log.stdout.colorized = False
supybot.log.stdout.format = %(message)s
supybot.log.stdout.level = INFO
supybot.log.stdout.wrap = False
supybot.log.stdout = True
supybot.networks.libera.channels = #aerc
supybot.networks.libera.requireStarttls = False
supybot.networks.libera.sasl.password = ********************
supybot.networks.libera.sasl.required = True
supybot.networks.libera.sasl.username = aercbot
supybot.networks.libera.servers = irc.libera.chat:6697
supybot.networks.libera.ssl = True
supybot.networks = libera
supybot.nick.alternates = %s` %s_
supybot.nick = aercbot
supybot.plugins.Channel.partMsg = KTHXBYE
supybot.plugins.Karma.allowSelfRating = False
supybot.plugins.Karma.allowUnaddressedKarma = True
supybot.plugins.Karma.decrementChars = --
supybot.plugins.Karma.incrementChars = ++
supybot.plugins.Karma.mostDisplay = 25
supybot.plugins.Karma.onlyNicks = False
supybot.plugins.Karma.public = True
supybot.plugins.Karma.rankingDisplay = 3
supybot.plugins.Karma.response = True
supybot.plugins.Karma.simpleOutput = True
supybot.plugins.Karma = True
supybot.plugins.Sourcehut.public = False
supybot.plugins.Sourcehut = True
supybot.servers.http.hosts4 = 127.0.0.1
supybot.servers.http.hosts6 = ::1
supybot.servers.http.keepAlive = False
supybot.servers.http.port = 7777
supybot.servers.http.publicUrl = https://bot.diabeteman.com/
supybot.servers.http.singleStack = True
supybot.user = aerc's IRC bot
diff --git a/contrib/ircbot/supybot.service b/contrib/ircbot/supybot.service
new file mode 100644
index 000000000000..dd1278509649
--- /dev/null
+++ b/contrib/ircbot/supybot.service
@@ -0,0 +1,19 @@
[Unit]
Description=IRC bot
After=network.target auditd.service

[Service]
ExecStart=/usr/bin/supybot /var/lib/supybot/supybot.conf
User=supybot
Group=supybot
WorkingDirectory=/var/lib/supybot
ProtectHome=true
ProtectSystem=strict
ReadWritePaths=/var/lib/supybot /tmp
PrivateTmp=true
SyslogIdentifier=supybot
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
-- 
2.41.0