#!/usr/bin/env python3 # Bitlair HobbyBot from time import sleep from discord import Intents from discord.ext import commands from discord_webhook import DiscordWebhook, DiscordEmbed import pytz import paho.mqtt.client as mqtt import paho.mqtt.subscribe as subscribe import os import sys 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") # Discord bot stuff intents = Intents.default() intents.message_content = True intents.members = True HobbyBot = commands.Bot(command_prefix="!", description="Bitlair Bot", intents=intents) def mqtt_get_one(topic): try: msg = subscribe.simple(topic, hostname=mqtt_host, keepalive=10) return msg.payload.decode() except Exception as err: print(err) return "" # 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(): spaceState = mqtt_get_one("bitlair/state") if spaceState == "open": await ctx.send("Bitlair is OPEN! :sunglasses:") elif spaceState == "closed": await ctx.send("Bitlair is closed :pensive:") # !co2 @HobbyBot.command(description="co2 levels") async def co2(ctx): async with ctx.typing(): hoofdruimte = mqtt_get_one("bitlair/climate/hoofdruimte_ingang/co2_ppm") await ctx.send("Hoofdruimte: %s ppm\n" % hoofdruimte) # !temp @HobbyBot.command(description="Temperature") async def temp(ctx): async with ctx.typing(): hoofdruimte = mqtt_get_one("bitlair/climate/hoofdruimte_ingang/temperature_c") await ctx.send("Hoofdruimte: %s °C\n" % hoofdruimte) # !humid @HobbyBot.command(description="Humidity") async def humid(ctx): async with ctx.typing(): hoofdruimte = mqtt_get_one("bitlair/climate/hoofdruimte_ingang/humidity_pct") await ctx.send("Hoofdruimte: %s pct\n" % hoofdruimte) # !np @HobbyBot.command(description="Now Playing") async def np(ctx): async with ctx.typing(): await ctx.send("Now playing: Darude - Sandstorm") # define mqtt client stuff # # subscribe to topics def on_connect(client, userdata, flags, rc): client.subscribe("bitlair/alarm") client.subscribe("bitlair/state") client.subscribe("bitlair/state/djo") client.subscribe("bitlair/photos") def webhook_message(msg): webhook = DiscordWebhook(url=webhook_url, rate_limit_retry=True, content=msg) webhook.execute() retained = { "bitlair/alarm", "bitlair/photos", "bitlair/state", "bitlair/state/djo", } # post to mqtt discord channel when state changes def on_message(client, userdata, msg): try: topic = msg.topic msg = msg.payload.decode() # Retained messages trigger an initial message on connecting. Prevent relaying them to # Discord on startup. if topic in retained: retained.remove(topic) return if topic == "bitlair/alarm": webhook_message("Alarm: %s" % msg) elif topic == "bitlair/state": webhook_message("Bitlair is now %s" % msg.upper()) elif topic == "bitlair/state/djo": webhook_message("DJO is now %s" % msg.upper()) elif topic == "bitlair/photos": webhook = DiscordWebhook(url=webhook_url, rate_limit_retry=True) embed = DiscordEmbed(title="WIP Cam", color="fc5d1d") embed.set_url("https://bitlair.nl/fotos/view/" + msg) embed.set_image("https://bitlair.nl/fotos/photos/" + msg) webhook.add_embed(embed) webhook.execute() else: return sleep(1) # Prevent triggering rate limits. except Exception as e: print(e) client = mqtt.Client() client.on_connect = on_connect client.on_message = on_message client.connect(mqtt_host, 1883, 60) # Start mqtt loop and discord bot client.loop_start() HobbyBot.run(token) # Exit when bot crashes client.loop_stop(force=True)