Add a IRC wrapper for Discord command implementations

This commit is contained in:
polyfloyd 2025-06-01 16:41:04 +02:00
parent 78f30b19fe
commit 5dccc2d4bb
5 changed files with 145 additions and 4 deletions

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
/.ruff_cache /.ruff_cache
/.venv /.venv
__pycache__

115
ircbot.py Executable file
View file

@ -0,0 +1,115 @@
import asyncio
import os
import sys
import pydle
from discord_webhook import DiscordEmbed
import commands as botcommands
class DiscordAuthor:
def __init__(self, nick):
self.nick = nick
self.global_name = nick
class DiscordContext:
def __init__(self, bot, target, source, message):
self._bot = bot
self._target = target
self._source = source
self._message = message
self.author = DiscordAuthor(source)
def typing(self):
class NilTyping:
async def __aenter__(self):
return self
async def __aexit__(self, *exc):
return None
return NilTyping()
async def reply(self, m):
lines = m.strip().split("\n")
lines = [f"{self._source}: {line}" for line in lines]
await self._bot.message(self._target, "\n".join(lines))
class DiscordImplBot(pydle.Client):
def __init__(self, channel, nickname, *, push_messages=None, prefix="!"):
super().__init__(nickname, realname=nickname)
self._channel = channel
self._cmd_prefix = prefix
self._cmds = {}
self._push_messages = push_messages
self._push_messages_task = None
async def on_connect(self):
await self.join(self._channel)
if self._push_messages is not None:
self._push_messages_task = asyncio.create_task(self._handle_push_messages())
async def on_disconnect(self):
if self._push_messages_task is not None:
self._push_messages_task.cancel()
self._push_messages_task = None
async def _handle_push_messages(self):
async for message in self._push_messages():
if isinstance(message, DiscordEmbed):
await self.message(self._channel, f"{message.title} {message.url}")
else:
await self.message(self._channel, message)
async def on_message(self, target, source, message):
# Don't respond to our own messages, as this leads to a positive feedback loop.
if source == self.nickname:
return
if not message.startswith(self._cmd_prefix):
return
cmd_fn = self._cmds.get(message.removeprefix(self._cmd_prefix))
if not cmd_fn:
return
ctx = DiscordContext(self, target, source, message)
await cmd_fn(ctx)
# Discord API: Register a new command
def command(self, *, name=None, description=None):
def _reg_cmd(handler):
nonlocal name
name = name or handler.__name__
self._cmds[name] = handler
return _reg_cmd
def main(*, server, channel, nick, mqtt_host):
def push_messages():
return botcommands.run_events(mqtt_host)
bot = DiscordImplBot(
channel,
nick,
push_messages=push_messages,
)
botcommands.setup(bot, mqtt_host)
bot.run(server, tls=True)
if __name__ == "__main__":
mqtt_host = os.getenv("MQTT_HOST")
if not mqtt_host:
print("MQTT_HOST unset")
sys.exit(1)
main(
server="irc.libera.chat",
channel="#bitlair-bot-test",
nick="Bitlair",
mqtt_host=mqtt_host,
)

View file

@ -8,5 +8,9 @@ dependencies = [
"aiomqtt>=2.4.0", "aiomqtt>=2.4.0",
"discord-py>=2.5.2", "discord-py>=2.5.2",
"discord-webhook>=1.4.1", "discord-webhook>=1.4.1",
"pydle[sasl]",
"pytz>=2025.2", "pytz>=2025.2",
] ]
[tool.uv.sources]
pydle = { git = "https://github.com/TheHolyRoger/pydle", branch = "fix-py10" }

View file

@ -1,5 +1,5 @@
# This file was autogenerated by uv via the following command: # This file was autogenerated by uv via the following command:
# uv export --format requirements-txt # uv export --format=requirements.txt
aiohappyeyeballs==2.6.1 \ aiohappyeyeballs==2.6.1 \
--hash=sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558 \ --hash=sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558 \
--hash=sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8 --hash=sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8
@ -38,9 +38,7 @@ aiohttp==3.11.18 \
--hash=sha256:ea4cf2488156e0f281f93cc2fd365025efcba3e2d217cbe3df2840f8c73db261 \ --hash=sha256:ea4cf2488156e0f281f93cc2fd365025efcba3e2d217cbe3df2840f8c73db261 \
--hash=sha256:f8af2ef3b4b652ff109f98087242e2ab974b2b2b496304063585e3d78de0b000 \ --hash=sha256:f8af2ef3b4b652ff109f98087242e2ab974b2b2b496304063585e3d78de0b000 \
--hash=sha256:f9e6710ebebfce2ba21cee6d91e7452d1125100f41b906fb5af3da8c78b764c1 --hash=sha256:f9e6710ebebfce2ba21cee6d91e7452d1125100f41b906fb5af3da8c78b764c1
# via # via discord-py
# discord-bot
# discord-py
aiomqtt==2.4.0 \ aiomqtt==2.4.0 \
--hash=sha256:721296e2b79df5f6c7c4dfc91700ae0166953a4127735c92637859619dbd84e4 \ --hash=sha256:721296e2b79df5f6c7c4dfc91700ae0166953a4127735c92637859619dbd84e4 \
--hash=sha256:ab0f18fc5b7ffaa57451c407417d674db837b00a9c7d953cccd02be64f046c17 --hash=sha256:ab0f18fc5b7ffaa57451c407417d674db837b00a9c7d953cccd02be64f046c17
@ -308,6 +306,11 @@ propcache==0.3.1 \
# via # via
# aiohttp # aiohttp
# yarl # yarl
pure-sasl==0.6.2 \
--hash=sha256:53c1355f5da95e2b85b2cc9a6af435518edc20c81193faa0eea65fdc835138f4
# via pydle
pydle @ git+https://github.com/TheHolyRoger/pydle@befd10fb340a1934fbdbc9c8ccaca68620cba0f6
# via discord-bot
pytz==2025.2 \ pytz==2025.2 \
--hash=sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3 \ --hash=sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3 \
--hash=sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00 --hash=sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00

18
uv.lock generated
View file

@ -189,6 +189,7 @@ dependencies = [
{ name = "aiomqtt" }, { name = "aiomqtt" },
{ name = "discord-py" }, { name = "discord-py" },
{ name = "discord-webhook" }, { name = "discord-webhook" },
{ name = "pydle", extra = ["sasl"] },
{ name = "pytz" }, { name = "pytz" },
] ]
@ -197,6 +198,7 @@ requires-dist = [
{ name = "aiomqtt", specifier = ">=2.4.0" }, { name = "aiomqtt", specifier = ">=2.4.0" },
{ name = "discord-py", specifier = ">=2.5.2" }, { name = "discord-py", specifier = ">=2.5.2" },
{ name = "discord-webhook", specifier = ">=1.4.1" }, { name = "discord-webhook", specifier = ">=1.4.1" },
{ name = "pydle", extras = ["sasl"], git = "https://github.com/TheHolyRoger/pydle?branch=fix-py10" },
{ name = "pytz", specifier = ">=2025.2" }, { name = "pytz", specifier = ">=2025.2" },
] ]
@ -420,6 +422,22 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b8/d3/c3cb8f1d6ae3b37f83e1de806713a9b3642c5895f0215a62e1a4bd6e5e34/propcache-0.3.1-py3-none-any.whl", hash = "sha256:9a8ecf38de50a7f518c21568c80f985e776397b902f1ce0b01f799aba1608b40", size = 12376, upload-time = "2025-03-26T03:06:10.5Z" }, { url = "https://files.pythonhosted.org/packages/b8/d3/c3cb8f1d6ae3b37f83e1de806713a9b3642c5895f0215a62e1a4bd6e5e34/propcache-0.3.1-py3-none-any.whl", hash = "sha256:9a8ecf38de50a7f518c21568c80f985e776397b902f1ce0b01f799aba1608b40", size = 12376, upload-time = "2025-03-26T03:06:10.5Z" },
] ]
[[package]]
name = "pure-sasl"
version = "0.6.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/83/b7/a0d688f86c869073cc28c0640899394a1cf68a6d87ee78a09565e9037da6/pure-sasl-0.6.2.tar.gz", hash = "sha256:53c1355f5da95e2b85b2cc9a6af435518edc20c81193faa0eea65fdc835138f4", size = 11617, upload-time = "2019-10-14T21:43:57.13Z" }
[[package]]
name = "pydle"
version = "1.0.1"
source = { git = "https://github.com/TheHolyRoger/pydle?branch=fix-py10#befd10fb340a1934fbdbc9c8ccaca68620cba0f6" }
[package.optional-dependencies]
sasl = [
{ name = "pure-sasl" },
]
[[package]] [[package]]
name = "pytz" name = "pytz"
version = "2025.2" version = "2025.2"