From 426d9b063dcece8e4e27b89a94fe9f95152be4cd Mon Sep 17 00:00:00 2001 From: MrUnknownDE Date: Sat, 7 Mar 2026 18:59:01 +0100 Subject: [PATCH] add live-embed --- bierbaron/bierbaron.py | 269 +++++++++++++++++++++++++++++++++-------- 1 file changed, 218 insertions(+), 51 deletions(-) diff --git a/bierbaron/bierbaron.py b/bierbaron/bierbaron.py index 49becbb..11fff51 100644 --- a/bierbaron/bierbaron.py +++ b/bierbaron/bierbaron.py @@ -1,45 +1,40 @@ import discord -from redbot.core import app_commands, commands import aiohttp -from datetime import datetime +import logging +from redbot.core import app_commands, commands, Config +from discord.ext import tasks + +log = logging.getLogger("red.bierbaron") + +API_URL = "https://live.der-bierbaron.de/api/vrchat.json" class Bierbaron(commands.Cog): """Bierbaron Commands and Stats.""" def __init__(self, bot): self.bot = bot - self.api_url = "https://live.der-bierbaron.de/api/vrchat.json" + self.config = Config.get_conf(self, identifier=9483726154, force_registration=True) + self.config.register_guild(live_channel=None, live_message=None) + self.live_update_task.start() - @app_commands.command(name="bierbaron") - async def info(self, interaction: discord.Interaction): - """Informationen ΓΌber den Bierbaron.""" - embed = discord.Embed( - title="der-Bierbaron", - description="Willkommen beim offiziellen Bierbaron Discord-System.\nHier dreht sich alles um das Trinken in VRChat.", - color=discord.Color.gold(), - url="https://der-bierbaron.de" - ) - embed.add_field(name="🌐 Webseite", value="[der-bierbaron.de](https://der-bierbaron.de)", inline=True) - embed.add_field(name="πŸ“‘ Live Stats", value="[live.der-bierbaron.de](https://live.der-bierbaron.de)", inline=True) - embed.set_footer(text="Bierbaron VRChat Projekt") - - await interaction.response.send_message(embed=embed) + def cog_unload(self): + self.live_update_task.cancel() - @app_commands.command(name="bierbaron-live") - async def live(self, interaction: discord.Interaction): - """Live-Informationen vom Server.""" - await interaction.response.defer() - + async def _fetch_api_data(self): + """Fetches data from the Bierbaron API. Returns None on failure.""" try: async with aiohttp.ClientSession() as session: - async with session.get(self.api_url) as response: + async with session.get(API_URL, timeout=aiohttp.ClientTimeout(total=10)) as response: if response.status != 200: - return await interaction.followup.send(f"Fehler! API gab Status-Code {response.status} zurΓΌck.") - data = await response.json() + log.warning(f"Bierbaron API returned {response.status}") + return None + return await response.json() except Exception as e: - return await interaction.followup.send(f"Fehler beim Abrufen der API: {e}") + log.error(f"Error fetching Bierbaron API: {e}") + return None - # Parse Data + def _build_live_embed(self, data: dict) -> discord.Embed: + """Builds the live stats embed from API data.""" server_info = data.get("server", {}) aggregate = data.get("aggregate", {}) users = data.get("users", []) @@ -50,34 +45,206 @@ class Bierbaron(commands.Cog): avg_fill = aggregate.get("averageFillLevel", 0) embed = discord.Embed( - title="🍺 der-Bierbaron Live Server", - color=discord.Color.from_rgb(255, 165, 0), # Orange/Gold fΓΌr Bier + title="🍺 der-Bierbaron – Live Server", + description=( + "Willkommen auf dem **Live-Server des BierbarΒ­ons**!\n" + "Hier ΓΌbertragen alle aktiven Teilnehmer ihren FΓΌllstand\n" + "fast in Echtzeit aus VRChat. Trinkt ihr mit?\n\n" + "[🌐 Webseite](https://der-bierbaron.de) Β· " + "[πŸ“‘ Live-Dashboard](https://live.der-bierbaron.de)" + ), + color=discord.Color.from_rgb(255, 140, 0), timestamp=discord.utils.utcnow() ) - - embed.add_field(name="πŸ‘₯ Online", value=f"{online_players} Spieler", inline=True) - embed.add_field(name="πŸ§ͺ Ø Volumen", value=f"{avg_vol:.1f} ml", inline=True) - embed.add_field(name="πŸ“Š Ø FΓΌllstand", value=f"{avg_fill:.1f}%", inline=True) + embed.set_footer(text="Aktualisiert alle 15 Sek. Β· der-Bierbaron Live") + # Server Stats + embed.add_field(name="πŸ‘₯ Spieler Online", value=f"**{online_players}**", inline=True) + embed.add_field(name="πŸ§ͺ Ø Volumen", value=f"**{avg_vol:.1f} ml**", inline=True) + embed.add_field(name="πŸ“Š Ø FΓΌllstand", value=f"**{avg_fill:.1f} %**", inline=True) + + # User table if users: - user_list = "" - # Wir sortieren nach der getrunkenen Menge heute absteigend und zeigen die Top 10 Online - for idx, u in enumerate(sorted(users, key=lambda x: x.get('drinkedToday_ml', 0), reverse=True)[:10]): + lines = [] + for idx, u in enumerate( + sorted(users, key=lambda x: x.get("drinkedToday_ml", 0), reverse=True)[:10] + ): name = u.get("username", "Unknown") - beverage = u.get("beverage", "N/A") + bev = u.get("beverage", "?") drank = u.get("drinkedToday_ml", 0) - filled = u.get("fillLevel", 0) - user_list += f"**{idx+1}. {name}**: {beverage} ({drank}ml getrunken, FΓΌllstand: {filled:.1f}%)\n" - - if user_list: - embed.add_field(name="Aktive Trinker Online", value=user_list, inline=False) - else: - embed.add_field(name="Aktive Trinker", value="Derzeit niemand am Trinken.", inline=False) - - if last_prost: - sender = last_prost.get("sender", "Jemand") - recipient = last_prost.get("recipient", "Jemandem") - message = last_prost.get("message", "Prost!") - embed.add_field(name="Letztes Prost 🍻", value=f"**{sender}** an **{recipient}**:\n*\"{message}\"*", inline=False) + fill = u.get("fillLevel", 0) - await interaction.followup.send(embed=embed) + # Simple fill bar (10 cells) + fill_pct = max(0, min(fill, 100)) + filled_cells = round(fill_pct / 10) + bar = "🟧" * filled_cells + "⬛" * (10 - filled_cells) + + lines.append(f"**{idx + 1}. {name}** – {bev}\n{bar} `{fill:.1f}%` Β· {drank} ml heute") + + embed.add_field( + name=f"πŸ† Trinker Online ({len(users)})", + value="\n".join(lines) or "–", + inline=False + ) + else: + embed.add_field(name="πŸ† Trinker Online", value="Derzeit niemand aktiv.", inline=False) + + # Last Prost + if last_prost: + sender = last_prost.get("sender", "?") + recipient = last_prost.get("recipient", "?") + msg = last_prost.get("message", "Prost!") + embed.add_field( + name="🍻 Letztes Prost", + value=f"**{sender}** trank auf **{recipient}**:\n*β€ž{msg}"*", + inline=False + ) + + return embed + + # ── Background task ────────────────────────────────────────────────────── + + @tasks.loop(seconds=15) + async def live_update_task(self): + """Updates all registered live embeds every 15 seconds.""" + data = await self._fetch_api_data() + if not data: + return + + embed = self._build_live_embed(data) + all_guilds = await self.config.all_guilds() + + for guild_id, cfg in all_guilds.items(): + channel_id = cfg.get("live_channel") + message_id = cfg.get("live_message") + if not channel_id or not message_id: + continue + + guild = self.bot.get_guild(guild_id) + if not guild: + continue + channel = guild.get_channel(channel_id) + if not channel: + continue + + try: + message = await channel.fetch_message(message_id) + await message.edit(embed=embed) + except discord.NotFound: + # Message deleted – clear config + await self.config.guild(guild).live_message.set(None) + await self.config.guild(guild).live_channel.set(None) + log.info(f"Live embed message not found in guild {guild_id}, cleared config.") + except discord.Forbidden: + log.warning(f"Missing permission to edit live embed in guild {guild_id}.") + except Exception as e: + log.error(f"Error updating live embed in guild {guild_id}: {e}") + + @live_update_task.before_loop + async def before_live_update_task(self): + await self.bot.wait_until_red_ready() + + # ── Slash Commands ──────────────────────────────────────────────────────── + + @app_commands.command(name="bierbaron") + async def info(self, interaction: discord.Interaction): + """Informationen ΓΌber den Bierbaron.""" + embed = discord.Embed( + title="der-Bierbaron", + description=( + "Willkommen beim offiziellen **Bierbaron** Discord-System.\n" + "Hier dreht sich alles ums Trinken in VRChat – GlΓ€ser, Scales und Echtzeit-Stats." + ), + color=discord.Color.gold(), + url="https://der-bierbaron.de" + ) + embed.add_field(name="🌐 Webseite", value="[der-bierbaron.de](https://der-bierbaron.de)", inline=True) + embed.add_field(name="πŸ“‘ Live Stats", value="[live.der-bierbaron.de](https://live.der-bierbaron.de)", inline=True) + embed.set_footer(text="Bierbaron VRChat Projekt") + await interaction.response.send_message(embed=embed) + + @app_commands.command(name="bierbaron-live") + async def live(self, interaction: discord.Interaction): + """Zeige einen einmaligen Live-Snapshot des Bierbaron-Servers.""" + await interaction.response.defer() + data = await self._fetch_api_data() + if data is None: + return await interaction.followup.send( + "❌ Fehler beim Abrufen der Bierbaron-API. Bitte spΓ€ter erneut versuchen." + ) + await interaction.followup.send(embed=self._build_live_embed(data)) + + # ── Bot-Owner-only management commands ─────────────────────────────────── + + @app_commands.command(name="bierbaron-setlive") + @app_commands.describe(channel="Kanal fΓΌr das automatisch aktualisierende Live-Embed.") + async def setlive(self, interaction: discord.Interaction, channel: discord.TextChannel): + """[Bot-Owner] Startet das permanente Live-Embed in einem Kanal (alle 15 Sek.).""" + if not await self.bot.is_owner(interaction.user): + return await interaction.response.send_message( + "β›” Nur der Bot-Owner kann diesen Befehl verwenden.", ephemeral=True + ) + + await interaction.response.defer(ephemeral=True) + + # Remove old embed if present + old_ch_id = await self.config.guild(interaction.guild).live_channel() + old_msg_id = await self.config.guild(interaction.guild).live_message() + if old_ch_id and old_msg_id: + old_ch = interaction.guild.get_channel(old_ch_id) + if old_ch: + try: + old_msg = await old_ch.fetch_message(old_msg_id) + await old_msg.delete() + except discord.NotFound: + pass + + data = await self._fetch_api_data() + if data is None: + return await interaction.followup.send("❌ Konnte die API nicht erreichen.") + + try: + msg = await channel.send(embed=self._build_live_embed(data)) + await self.config.guild(interaction.guild).live_channel.set(channel.id) + await self.config.guild(interaction.guild).live_message.set(msg.id) + await interaction.followup.send( + f"βœ… Live-Embed in {channel.mention} gestartet. Es wird alle **15 Sekunden** aktualisiert." + ) + except discord.Forbidden: + await interaction.followup.send("❌ Ich habe keine Berechtigung, in diesem Kanal zu schreiben.") + except Exception as e: + await interaction.followup.send(f"❌ Fehler: {e}") + + @app_commands.command(name="bierbaron-stoplive") + async def stoplive(self, interaction: discord.Interaction): + """[Bot-Owner] Stoppt das permanente Live-Embed auf diesem Server.""" + if not await self.bot.is_owner(interaction.user): + return await interaction.response.send_message( + "β›” Nur der Bot-Owner kann diesen Befehl verwenden.", ephemeral=True + ) + + await interaction.response.defer(ephemeral=True) + + channel_id = await self.config.guild(interaction.guild).live_channel() + message_id = await self.config.guild(interaction.guild).live_message() + + if not channel_id: + return await interaction.followup.send("ℹ️ Es lΓ€uft kein Live-Embed auf diesem Server.") + + # Try to delete the embed message + channel = interaction.guild.get_channel(channel_id) + if channel and message_id: + try: + msg = await channel.fetch_message(message_id) + await msg.delete() + except discord.NotFound: + pass + except discord.Forbidden: + await interaction.followup.send( + "⚠️ Konnte die Live-Embed-Nachricht nicht lΓΆschen (fehlende Berechtigung), " + "aber das Auto-Update wurde deaktiviert." + ) + + await self.config.guild(interaction.guild).live_channel.set(None) + await self.config.guild(interaction.guild).live_message.set(None) + await interaction.followup.send("βœ… Live-Embed wurde gestoppt und die Nachricht gelΓΆscht.")