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.notice(self._channel, f"{message.title} {message.url}") else: await self.notice(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, )