Separate Discord API and command implementations
This commit is contained in:
parent
677d4f1259
commit
78f30b19fe
3 changed files with 109 additions and 90 deletions
116
main.py → commands/__init__.py
Executable file → Normal file
116
main.py → commands/__init__.py
Executable file → Normal file
|
@ -1,58 +1,41 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
from os.path import splitext
|
|
||||||
from time import sleep
|
|
||||||
|
|
||||||
import aiomqtt
|
import aiomqtt
|
||||||
import pytz
|
from discord_webhook import DiscordEmbed
|
||||||
from discord import File, Intents
|
|
||||||
from discord.ext import commands
|
|
||||||
from discord_webhook import DiscordEmbed, DiscordWebhook
|
|
||||||
|
|
||||||
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")
|
_mqtt_host = None
|
||||||
if not token:
|
|
||||||
print("DISCORD_TOKEN unset")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
webhook_url = os.getenv("DISCORD_WEBHOOK_URL")
|
def setup(bot, mqtt_host):
|
||||||
if not webhook_url:
|
global _mqtt_host
|
||||||
print("DISCORD_WEBHOOK_URL unset")
|
_mqtt_host = mqtt_host
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
timezone = pytz.timezone("Europe/Amsterdam")
|
# !state
|
||||||
|
bot.command(description="Bitlair Space State")(state)
|
||||||
# Discord bot stuff
|
# !co2
|
||||||
intents = Intents.default()
|
bot.command(description="co2 levels")(co2)
|
||||||
intents.message_content = True
|
# !temp
|
||||||
intents.members = True
|
bot.command(description="Temperature")(temp)
|
||||||
HobbyBot = commands.Bot(command_prefix="!", description="Bitlair Bot", intents=intents)
|
# !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 def mqtt_get_one(topic, timeout=20):
|
||||||
async with asyncio.timeout(timeout):
|
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)
|
await mq.subscribe(topic)
|
||||||
return await anext(mq.messages)
|
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 def state(ctx):
|
||||||
async with ctx.typing():
|
async with ctx.typing():
|
||||||
try:
|
try:
|
||||||
|
@ -67,8 +50,6 @@ async def state(ctx):
|
||||||
raise err
|
raise err
|
||||||
|
|
||||||
|
|
||||||
# !co2
|
|
||||||
@HobbyBot.command(description="co2 levels")
|
|
||||||
async def co2(ctx, where="hoofdruimte"):
|
async def co2(ctx, where="hoofdruimte"):
|
||||||
async with ctx.typing():
|
async with ctx.typing():
|
||||||
try:
|
try:
|
||||||
|
@ -79,8 +60,6 @@ async def co2(ctx, where="hoofdruimte"):
|
||||||
raise err
|
raise err
|
||||||
|
|
||||||
|
|
||||||
# !temp
|
|
||||||
@HobbyBot.command(description="Temperature")
|
|
||||||
async def temp(ctx, where="hoofdruimte"):
|
async def temp(ctx, where="hoofdruimte"):
|
||||||
async with ctx.typing():
|
async with ctx.typing():
|
||||||
try:
|
try:
|
||||||
|
@ -91,8 +70,6 @@ async def temp(ctx, where="hoofdruimte"):
|
||||||
raise err
|
raise err
|
||||||
|
|
||||||
|
|
||||||
# !humid
|
|
||||||
@HobbyBot.command(description="Humidity")
|
|
||||||
async def humid(ctx, where="hoofdruimte"):
|
async def humid(ctx, where="hoofdruimte"):
|
||||||
async with ctx.typing():
|
async with ctx.typing():
|
||||||
try:
|
try:
|
||||||
|
@ -103,39 +80,12 @@ async def humid(ctx, where="hoofdruimte"):
|
||||||
raise err
|
raise err
|
||||||
|
|
||||||
|
|
||||||
# !np
|
|
||||||
@HobbyBot.command(description="Now Playing")
|
|
||||||
async def np(ctx):
|
async def np(ctx):
|
||||||
async with ctx.typing():
|
async with ctx.typing():
|
||||||
await ctx.reply("Now playing: Darude - Sandstorm")
|
await ctx.reply("Now playing: Darude - Sandstorm")
|
||||||
|
|
||||||
|
|
||||||
# !bottleclip
|
async def run_events(mqtt_host):
|
||||||
@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 <icon> [<ears y|n>]`\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():
|
|
||||||
retained = {
|
retained = {
|
||||||
"bitlair/alarm",
|
"bitlair/alarm",
|
||||||
"bitlair/photos",
|
"bitlair/photos",
|
||||||
|
@ -154,27 +104,15 @@ async def event_task():
|
||||||
payload = msg.payload.decode("ascii")
|
payload = msg.payload.decode("ascii")
|
||||||
|
|
||||||
if msg.topic.matches("bitlair/alarm"):
|
if msg.topic.matches("bitlair/alarm"):
|
||||||
webhook_message(f"Alarm: {payload}")
|
yield f"Alarm: {payload}"
|
||||||
elif msg.topic.matches("bitlair/state"):
|
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"):
|
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"):
|
elif msg.topic.matches("bitlair/photos"):
|
||||||
webhook = DiscordWebhook(url=webhook_url, rate_limit_retry=True)
|
|
||||||
embed = DiscordEmbed(title="WIP Cam", color="fc5d1d")
|
embed = DiscordEmbed(title="WIP Cam", color="fc5d1d")
|
||||||
embed.set_url(f"https://bitlair.nl/fotos/view/{payload}")
|
embed.set_url(f"https://bitlair.nl/fotos/view/{payload}")
|
||||||
embed.set_image(f"https://bitlair.nl/fotos/photos/{payload}")
|
embed.set_image(f"https://bitlair.nl/fotos/photos/{payload}")
|
||||||
webhook.add_embed(embed)
|
yield embed
|
||||||
webhook.execute()
|
|
||||||
else:
|
else:
|
||||||
continue
|
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())
|
|
|
@ -1,9 +1,11 @@
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
from os.path import abspath, join
|
from os.path import abspath, join, splitext
|
||||||
from tempfile import NamedTemporaryFile
|
from tempfile import NamedTemporaryFile
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
|
from discord import File
|
||||||
|
|
||||||
|
|
||||||
def resource_dir() -> str:
|
def resource_dir() -> str:
|
||||||
p = os.getenv("BOTTLECLIP_RESOURCES")
|
p = os.getenv("BOTTLECLIP_RESOURCES")
|
||||||
|
@ -42,3 +44,21 @@ def create_stl(label: str, icon: str, ears: bool) -> NamedTemporaryFile:
|
||||||
stl.seek(0)
|
stl.seek(0)
|
||||||
|
|
||||||
return stl
|
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 <icon> [<ears y|n>]`\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)
|
61
discordbot.py
Executable file
61
discordbot.py
Executable file
|
@ -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())
|
Loading…
Add table
Add a link
Reference in a new issue