Files
redbot-assets/bierbaron/bierbaron.py
2026-03-07 19:11:48 +01:00

250 lines
11 KiB
Python

import discord
import aiohttp
import asyncio
import logging
from discord import app_commands
from redbot.core import commands, Config
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.config = Config.get_conf(self, identifier=9483726154, force_registration=True)
self.config.register_guild(live_channel=None, live_message=None)
self._live_task = None
async def cog_load(self):
"""Called by Redbot after the cog is fully loaded."""
self._live_task = asyncio.create_task(self._live_loop())
def cog_unload(self):
if self._live_task:
self._live_task.cancel()
async def _live_loop(self):
"""Background loop that updates all live embeds every 15 seconds."""
await self.bot.wait_until_red_ready()
while True:
await self._run_live_update()
await asyncio.sleep(15)
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(API_URL, timeout=aiohttp.ClientTimeout(total=10)) as response:
if response.status != 200:
log.warning(f"Bierbaron API returned {response.status}")
return None
return await response.json()
except Exception as e:
log.error(f"Error fetching Bierbaron API: {e}")
return None
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", [])
last_prost = data.get("lastProst", {})
online_players = server_info.get("onlinePlayers", 0)
avg_vol = aggregate.get("averageVolume_ml", 0)
avg_fill = aggregate.get("averageFillLevel", 0)
embed = discord.Embed(
title="Bierbaron - Live Server",
description=(
"Willkommen auf dem **Live-Server des Bierbar\u00f3ns**!\n"
"Alle aktiven Teilnehmer uebertragen ihren Fuellstand\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.set_footer(text="Aktualisiert alle 15 Sek. | der-Bierbaron Live")
embed.add_field(name="Spieler Online", value=f"**{online_players}**", inline=True)
embed.add_field(name="Durchschn. Volumen", value=f"**{avg_vol:.1f} ml**", inline=True)
embed.add_field(name="Durchschn. Fuellstand", value=f"**{avg_fill:.1f} %**", inline=True)
if users:
lines = []
for idx, u in enumerate(
sorted(users, key=lambda x: x.get("drinkedToday_ml", 0), reverse=True)[:10]
):
name = u.get("username", "Unknown")
bev = u.get("beverage", "?")
drank = u.get("drinkedToday_ml", 0)
fill = u.get("fillLevel", 0)
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)
if last_prost:
sender = last_prost.get("sender", "?")
recipient = last_prost.get("recipient", "?")
prost_msg = last_prost.get("message", "Prost!")
embed.add_field(
name="Letztes Prost",
value=f"**{sender}** trank auf **{recipient}**:\n\"{prost_msg}\"",
inline=False
)
return embed
async def _run_live_update(self):
"""Fetches API data and updates all registered live embeds."""
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:
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 asyncio.CancelledError:
raise
except Exception as e:
log.error(f"Error updating live embed in guild {guild_id}: {e}")
# ── Slash Commands ────────────────────────────────────────────────────────
@app_commands.command(name="bierbaron")
async def info(self, interaction: discord.Interaction):
"""Informationen ueber den Bierbaron."""
embed = discord.Embed(
title="der-Bierbaron",
description=(
"Willkommen beim offiziellen **Bierbaron** Discord-System.\n"
"Hier dreht sich alles ums Trinken in VRChat - Glaeser, 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 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 spaeter erneut versuchen."
)
await interaction.followup.send(embed=self._build_live_embed(data))
@app_commands.command(name="bierbaron-setlive")
@app_commands.describe(channel="Kanal fuer 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)
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 laeuft kein Live-Embed auf diesem Server.")
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 loeschen (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 geloescht.")