diff --git a/main.py b/commands/__init__.py old mode 100755 new mode 100644 similarity index 51% rename from main.py rename to commands/__init__.py index 6020168..9d7fa47 --- a/main.py +++ b/commands/__init__.py @@ -1,58 +1,41 @@ -#!/usr/bin/env python3 - import asyncio -import os -import sys -from os.path import splitext -from time import sleep import aiomqtt -import pytz -from discord import File, Intents -from discord.ext import commands -from discord_webhook import DiscordEmbed, DiscordWebhook +from discord_webhook import DiscordEmbed -import bottleclip as clip +import commands.bottleclip as bottleclip -mqtt_host = os.getenv("MQTT_HOST") -if not mqtt_host: - print("MQTT_HOST unset") - sys.exit(1) -token = os.getenv("DISCORD_TOKEN") -if not token: - print("DISCORD_TOKEN unset") - sys.exit(1) +_mqtt_host = None -webhook_url = os.getenv("DISCORD_WEBHOOK_URL") -if not webhook_url: - print("DISCORD_WEBHOOK_URL unset") - sys.exit(1) +def setup(bot, mqtt_host): + global _mqtt_host + _mqtt_host = mqtt_host -timezone = pytz.timezone("Europe/Amsterdam") - -# Discord bot stuff -intents = Intents.default() -intents.message_content = True -intents.members = True -HobbyBot = commands.Bot(command_prefix="!", description="Bitlair Bot", intents=intents) + # !state + bot.command(description="Bitlair Space State")(state) + # !co2 + bot.command(description="co2 levels")(co2) + # !temp + bot.command(description="Temperature")(temp) + # !humid + bot.command(description="Humidity")(humid) + # !np + bot.command(description="Now Playing")(np) + # !bottleclip + bot.command( + name="bottleclip", + description="Generate a bottle-clip STL file suitable for printing", + )(bottleclip.command) async def mqtt_get_one(topic, timeout=20): async with asyncio.timeout(timeout): - async with aiomqtt.Client(mqtt_host) as mq: + async with aiomqtt.Client(_mqtt_host) as mq: await mq.subscribe(topic) return await anext(mq.messages) -# Define bot commands -@HobbyBot.event -async def on_ready(): - print(f"Logged in as {HobbyBot.user} (ID: {HobbyBot.user.id})") - - -# !state -@HobbyBot.command(description="Bitlair Space State") async def state(ctx): async with ctx.typing(): try: @@ -67,8 +50,6 @@ async def state(ctx): raise err -# !co2 -@HobbyBot.command(description="co2 levels") async def co2(ctx, where="hoofdruimte"): async with ctx.typing(): try: @@ -79,8 +60,6 @@ async def co2(ctx, where="hoofdruimte"): raise err -# !temp -@HobbyBot.command(description="Temperature") async def temp(ctx, where="hoofdruimte"): async with ctx.typing(): try: @@ -91,8 +70,6 @@ async def temp(ctx, where="hoofdruimte"): raise err -# !humid -@HobbyBot.command(description="Humidity") async def humid(ctx, where="hoofdruimte"): async with ctx.typing(): try: @@ -103,39 +80,12 @@ async def humid(ctx, where="hoofdruimte"): raise err -# !np -@HobbyBot.command(description="Now Playing") async def np(ctx): async with ctx.typing(): await ctx.reply("Now playing: Darude - Sandstorm") -# !bottleclip -@HobbyBot.command(description="Generate a bottle-clip STL file suitable for printing") -async def bottleclip(ctx, icon: str = "", ears: bool = False): - icons = clip.list_icons() - if icon not in icons: - await ctx.reply( - f"usage: `!bottleclip []`\n* `icon` must be one of {', '.join(icons)}" - ) - return - - async with ctx.typing(): - label = ctx.author.nick or ctx.author.global_name - stl_file = clip.create_stl(label, icon, ears) - - icon_name, _ = splitext(icon) - with_ears = '_ears' if ears else '' - attach = File(stl_file.name, filename=f"{label}_{icon_name}{with_ears}.stl") - await ctx.reply("Ok! Hier is je flessenclip", file=attach) - - -def webhook_message(msg): - webhook = DiscordWebhook(url=webhook_url, rate_limit_retry=True, content=msg) - webhook.execute() - - -async def event_task(): +async def run_events(mqtt_host): retained = { "bitlair/alarm", "bitlair/photos", @@ -154,27 +104,15 @@ async def event_task(): payload = msg.payload.decode("ascii") if msg.topic.matches("bitlair/alarm"): - webhook_message(f"Alarm: {payload}") + yield f"Alarm: {payload}" elif msg.topic.matches("bitlair/state"): - webhook_message(f"Bitlair is now {payload.upper()}") + yield f"Bitlair is now {payload.upper()}" elif msg.topic.matches("bitlair/state/djo"): - webhook_message(f"DJO is now {payload.upper()}") + yield f"DJO is now {payload.upper()}" elif msg.topic.matches("bitlair/photos"): - webhook = DiscordWebhook(url=webhook_url, rate_limit_retry=True) embed = DiscordEmbed(title="WIP Cam", color="fc5d1d") embed.set_url(f"https://bitlair.nl/fotos/view/{payload}") embed.set_image(f"https://bitlair.nl/fotos/photos/{payload}") - webhook.add_embed(embed) - webhook.execute() + yield embed else: continue - sleep(1) # Prevent triggering rate limits. - - -async def main(): - t1 = asyncio.create_task(HobbyBot.start(token)) - t2 = asyncio.create_task(event_task()) - await asyncio.gather(t1, t2) - - -asyncio.run(main()) diff --git a/bottleclip.py b/commands/bottleclip.py similarity index 62% rename from bottleclip.py rename to commands/bottleclip.py index c6fc2e6..198a9fb 100644 --- a/bottleclip.py +++ b/commands/bottleclip.py @@ -1,9 +1,11 @@ import os import subprocess -from os.path import abspath, join +from os.path import abspath, join, splitext from tempfile import NamedTemporaryFile from typing import List +from discord import File + def resource_dir() -> str: p = os.getenv("BOTTLECLIP_RESOURCES") @@ -42,3 +44,21 @@ def create_stl(label: str, icon: str, ears: bool) -> NamedTemporaryFile: stl.seek(0) return stl + + +async def command(ctx, icon: str = "", ears: bool = False): + icons = list_icons() + if icon not in icons: + await ctx.reply( + f"usage: `!bottleclip []`\n* `icon` must be one of {', '.join(icons)}" + ) + return + + async with ctx.typing(): + label = ctx.author.nick or ctx.author.global_name + stl_file = create_stl(label, icon, ears) + + icon_name, _ = splitext(icon) + with_ears = "_ears" if ears else "" + attach = File(stl_file.name, filename=f"{label}_{icon_name}{with_ears}.stl") + await ctx.reply("Ok! Hier is je flessenclip", file=attach) diff --git a/discordbot.py b/discordbot.py new file mode 100755 index 0000000..c92eabe --- /dev/null +++ b/discordbot.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 + +import asyncio +import os +import sys + +import pytz +from discord import Intents +from discord.ext import commands +from discord_webhook import DiscordEmbed, DiscordWebhook + +import commands as botcommands + +mqtt_host = os.getenv("MQTT_HOST") +if not mqtt_host: + print("MQTT_HOST unset") + sys.exit(1) +token = os.getenv("DISCORD_TOKEN") +if not token: + print("DISCORD_TOKEN unset") + sys.exit(1) +webhook_url = os.getenv("DISCORD_WEBHOOK_URL") +if not webhook_url: + print("DISCORD_WEBHOOK_URL unset") + sys.exit(1) + +timezone = pytz.timezone("Europe/Amsterdam") + +intents = Intents.default() +intents.message_content = True +intents.members = True +bot = commands.Bot(command_prefix="!", description="Bitlair Bot", intents=intents) +botcommands.setup(bot, mqtt_host) + + +@bot.event +async def on_ready(): + print(f"Logged in as {bot.user} (ID: {bot.user.id})") + + +async def event_task(): + async for message in botcommands.run_events(mqtt_host): + webhook = DiscordWebhook(url=webhook_url, rate_limit_retry=True) + if type(message) is str: + webhook.content = message + elif type(message) is DiscordEmbed: + webhook.add_embed(message) + else: + print(f"invalid message type: {str(message)}") + continue + webhook.execute() + + +async def main(): + t1 = asyncio.create_task(bot.start(token)) + t2 = asyncio.create_task(event_task()) + await asyncio.gather(t1, t2) + + +if __name__ == "__main__": + asyncio.run(main())