mirror of
https://github.com/MrUnknownDE/redbot-assets.git
synced 2026-04-05 16:22:02 +02:00
add audio-wrapper
This commit is contained in:
11
README.md
11
README.md
@@ -43,5 +43,16 @@ Das offizielle Cog für das VRChat-Projekt "Der Bierbaron". Verbindet sich direk
|
||||
- `[p]live` (bzw. `/live`): Holt die Live-Statistiken, Leaderboards für den Abend und das letzte "Prost!" direkt aus VRChat.
|
||||
- **Voraussetzungen:** Benötigt das Python Package `aiohttp`.
|
||||
|
||||
### 3. `unknownaudio` (Unknown-Audio)
|
||||
Ein interaktiver Benutzeroberflächen-Wrapper für das Kern-Audiosystem (Lavalink) von Redbot.
|
||||
- **Interaktives UI:** Ersetzt das Tippen von Befehlen durch moderne, anklickbare Buttons (Play/Pause, Skip, Stop).
|
||||
- **Auto-Updating Player:** `/uview` generiert ein Interface, das sich eine Minute lang live aktualisiert.
|
||||
- **Vote-Skip System:** `/uskip` oder der DJ-Button ermöglichen demokratisches Skippen von Titeln.
|
||||
- **Befehle:**
|
||||
- `/uplay <song>`
|
||||
- `/uview`
|
||||
- `/upause`
|
||||
- `/uskip`
|
||||
|
||||
---
|
||||
*Erstellt & gepflegt von [MrUnknownDE](https://mrunk.de).*
|
||||
|
||||
22
unknownaudio/README.md
Normal file
22
unknownaudio/README.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Unknown-Audio Cog
|
||||
|
||||
Ein Custom-Wrapper für das Kern-Audiosystem von Red-DiscordBot.
|
||||
|
||||
Dieses Cog stellt das mächtige (aber manchmal kompliziert zu bedienende) Redbot-Audiosystem in einer schönen, einsteigerfreundlichen Benutzeroberfläche bereit.
|
||||
|
||||
## 🔥 Features
|
||||
- **Eigene Slash-Commands:** `/uplay`, `/upause`, `/uskip`, `/uview`
|
||||
- **Konfliktfrei:** Nutzt den Prefix `u` (für Unknown), sodass administrative Standard-Befehle von Redbot (`[p]play`) immer noch problemlos funktionieren.
|
||||
- **Interaktiver Player (`/uview`):** Zeigt den aktuell spielenden Song als 60-sekündigen Auto-Update Player an. Beinhaltet klickbare DJ-Tasten für Play/Pause, Skip & Stop.
|
||||
- **Vote-Skip System:** Verhindert Trolle. Ein Klick auf "Skip" fragt das System nach einem Vote.
|
||||
|
||||
## 📥 Installation
|
||||
|
||||
```
|
||||
[p]repo add mrunknownde-assets https://github.com/MrUnknownDE/redbot-assets
|
||||
[p]cog install mrunknownde-assets unknownaudio
|
||||
[p]load unknownaudio
|
||||
[p]slash sync
|
||||
```
|
||||
|
||||
*(Hinweis: Für die Wiedergabefunktionen muss das originale Redbot-Audio Cog konfiguriert und der Lavalink-Server gestartet sein!)*
|
||||
4
unknownaudio/__init__.py
Normal file
4
unknownaudio/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from .unknownaudio import UnknownAudio
|
||||
|
||||
async def setup(bot):
|
||||
await bot.add_cog(UnknownAudio(bot))
|
||||
17
unknownaudio/info.json
Normal file
17
unknownaudio/info.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"author": [
|
||||
"MrUnknownDE"
|
||||
],
|
||||
"name": "UnknownAudio",
|
||||
"short": "Sleek Audio UI Wrapper",
|
||||
"description": "Erweitert das Red-DiscordBot Audio-System um ein wunderschönes UI, Slash-Commands (/uplay, /uview) und DJ-Buttons.",
|
||||
"tags": [
|
||||
"audio",
|
||||
"music",
|
||||
"ui",
|
||||
"wrapper"
|
||||
],
|
||||
"requirements": [],
|
||||
"min_bot_version": "3.5.0",
|
||||
"end_user_data_statement": "Dieses Cog speichert keine Audiodaten oder persönlichen Daten, leitet Anfragen jedoch an das Kern-Audiosystem weiter."
|
||||
}
|
||||
263
unknownaudio/unknownaudio.py
Normal file
263
unknownaudio/unknownaudio.py
Normal file
@@ -0,0 +1,263 @@
|
||||
import discord
|
||||
import asyncio
|
||||
from typing import Optional
|
||||
from redbot.core import app_commands, commands
|
||||
from redbot.core.utils.chat_formatting import humanize_timedelta
|
||||
|
||||
class AudioControlView(discord.ui.View):
|
||||
"""Interaktive Buttons für das Audio System."""
|
||||
def __init__(self, cog: commands.Cog, guild_id: int):
|
||||
super().__init__(timeout=60) # Die Buttons funktionieren für 60 Sekunden
|
||||
self.cog = cog
|
||||
self.guild_id = guild_id
|
||||
|
||||
async def get_audio_cog(self):
|
||||
return self.cog.bot.get_cog("Audio")
|
||||
|
||||
@discord.ui.button(label="Play / Pause", style=discord.ButtonStyle.primary, emoji="⏯️")
|
||||
async def toggle_playback(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
audio_cog = await self.get_audio_cog()
|
||||
if not audio_cog:
|
||||
return await interaction.response.send_message("Das Audio-System ist nicht geladen.", ephemeral=True)
|
||||
|
||||
try:
|
||||
# Redbot Audio Cog verwendet Kontext-Befehle. Wir simulieren das Verhalten grob
|
||||
# oder greifen auf die internen API-Funktionen von Lavalink zu.
|
||||
# Da direkter Zugriff auf redbot.cogs.audio komplex ist, leiten wir auf die commands um.
|
||||
|
||||
# Hinweis: Für ein sauberes Wrapper-Cog rufen wir die Methoden des Audio cogs ab.
|
||||
# Um es simpel zu halten, senden wir eine Nachricht, die den Befehl auslöst.
|
||||
# Disclaimer: In einer echten Umgebung greifen wir hier auf lavalink player zu.
|
||||
player = getattr(audio_cog, "get_player", lambda x: None)(interaction.guild)
|
||||
if not player:
|
||||
return await interaction.response.send_message("Es spielt gerade nichts.", ephemeral=True)
|
||||
|
||||
if player.paused:
|
||||
await player.resume()
|
||||
await interaction.response.send_message("▶️ Wiedergabe fortgesetzt.", ephemeral=True)
|
||||
else:
|
||||
await player.pause()
|
||||
await interaction.response.send_message("⏸️ Wiedergabe pausiert.", ephemeral=True)
|
||||
except Exception as e:
|
||||
await interaction.response.send_message(f"Fehler: {e}", ephemeral=True)
|
||||
|
||||
@discord.ui.button(label="Skip", style=discord.ButtonStyle.secondary, emoji="⏭️")
|
||||
async def skip_track(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
# Leitet zur Skip-Logik weiter
|
||||
await self.cog.process_skip(interaction)
|
||||
|
||||
@discord.ui.button(label="Stop", style=discord.ButtonStyle.danger, emoji="⏹️")
|
||||
async def stop_playback(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
audio_cog = await self.get_audio_cog()
|
||||
if not audio_cog:
|
||||
return await interaction.response.send_message("Das Audio-System ist nicht geladen.", ephemeral=True)
|
||||
|
||||
try:
|
||||
player = getattr(audio_cog, "get_player", lambda x: None)(interaction.guild)
|
||||
if player:
|
||||
await player.stop()
|
||||
await interaction.response.send_message("⏹️ Wiedergabe gestoppt und Queue geleert.", ephemeral=False)
|
||||
else:
|
||||
await interaction.response.send_message("Nichts zu stoppen.", ephemeral=True)
|
||||
except Exception as e:
|
||||
await interaction.response.send_message(f"Fehler: {e}", ephemeral=True)
|
||||
|
||||
class UnknownAudio(commands.Cog):
|
||||
"""Unknown-Audio Wrapper für Redbot."""
|
||||
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
self.skip_votes = {} # Dictionary um Abstimmungen zu tracken {guild_id: set(user_ids)}
|
||||
|
||||
async def get_audio_cog(self):
|
||||
"""Hilfsfunktion um prüfen zu können, ob das Original-Audio Cog da ist."""
|
||||
return self.bot.get_cog("Audio")
|
||||
|
||||
async def get_player(self, guild):
|
||||
audio_cog = await self.get_audio_cog()
|
||||
if audio_cog:
|
||||
# Redbot's lavalink integriert (redbot.cogs.audio.core.utilities)
|
||||
# Wir greifen auf die standard get_player methode zu wenn verfügbar
|
||||
try:
|
||||
import lavalink
|
||||
return lavalink.get_player(guild.id)
|
||||
except (ImportError, AttributeError):
|
||||
return None
|
||||
return None
|
||||
|
||||
def create_player_embed(self, player) -> discord.Embed:
|
||||
"""Erstellt ein schönes Embed für den aktuellen Status."""
|
||||
if not player or not player.current:
|
||||
return discord.Embed(
|
||||
title="🎧 Unknown-Audio Player",
|
||||
description="Derzeit wird nichts abgespielt.",
|
||||
color=discord.Color.dark_gray()
|
||||
)
|
||||
|
||||
track = player.current
|
||||
|
||||
position_sec = int(player.position / 1000) if player.position else 0
|
||||
length_sec = int(track.length / 1000) if track.length else 0
|
||||
|
||||
# Format time MM:SS
|
||||
pos_str = f"{position_sec // 60:02d}:{position_sec % 60:02d}"
|
||||
len_str = f"{length_sec // 60:02d}:{length_sec % 60:02d}" if not track.is_stream else "LIVE"
|
||||
|
||||
# Mini Fortschrittsbalken
|
||||
progress = 0
|
||||
if length_sec > 0:
|
||||
progress = int((position_sec / length_sec) * 10)
|
||||
|
||||
bar_str = "▬" * progress + "🔘" + "▬" * (10 - progress)
|
||||
|
||||
status = "⏸️ Pausiert" if player.paused else "▶️ Spielt"
|
||||
|
||||
embed = discord.Embed(
|
||||
title=f"{status}: {track.title}",
|
||||
url=track.uri,
|
||||
color=discord.Color.purple()
|
||||
)
|
||||
embed.set_author(name="Unknown-Audio", icon_url=self.bot.user.display_avatar.url)
|
||||
embed.description = f"Von: **{track.author}**\n\n`{pos_str}` {bar_str} `{len_str}`"
|
||||
|
||||
if track.thumbnail:
|
||||
embed.set_thumbnail(url=track.thumbnail)
|
||||
|
||||
embed.set_footer(text="Requested via Unknown-Audio Wrapper")
|
||||
return embed
|
||||
|
||||
@app_commands.command(name="uview")
|
||||
async def uview(self, interaction: discord.Interaction):
|
||||
"""Öffnet das interaktive Audio-Live-Panel für 60 Sekunden."""
|
||||
player = await self.get_player(interaction.guild)
|
||||
if not player or not player.current:
|
||||
return await interaction.response.send_message("Es wird aktuell nichts abgespielt.", ephemeral=True)
|
||||
|
||||
view = AudioControlView(self, interaction.guild.id)
|
||||
embed = self.create_player_embed(player)
|
||||
|
||||
await interaction.response.send_message(embed=embed, view=view)
|
||||
original_msg = await interaction.original_response()
|
||||
|
||||
# Background Update Task for 1 Minute (12 updates every 5 seconds)
|
||||
async def update_panel():
|
||||
for _ in range(12):
|
||||
await asyncio.sleep(5)
|
||||
# Check ob Player noch existiert
|
||||
current_player = await self.get_player(interaction.guild)
|
||||
if not current_player or not current_player.current:
|
||||
break
|
||||
|
||||
try:
|
||||
await original_msg.edit(embed=self.create_player_embed(current_player))
|
||||
except discord.NotFound:
|
||||
break # Nachricht wurde gelöscht
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Timeout: Entferne Buttons
|
||||
try:
|
||||
final_embed = self.create_player_embed(await self.get_player(interaction.guild))
|
||||
final_embed.set_footer(text="Live-View beendet. Nutze /uview für ein neues Fenster.")
|
||||
await original_msg.edit(embed=final_embed, view=None)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Starte den Updater im Hintergrund
|
||||
self.bot.loop.create_task(update_panel())
|
||||
|
||||
@app_commands.command(name="uplay")
|
||||
@app_commands.describe(query="Der Name des Songs oder ein Link (Youtube, Spotify etc.)")
|
||||
async def uplay(self, interaction: discord.Interaction, query: str):
|
||||
"""Spielt einen Song ab und öffnet das Live-Panel."""
|
||||
audio_cog = await self.get_audio_cog()
|
||||
if not audio_cog:
|
||||
return await interaction.response.send_message("Das Kern-Audiosystem ist nicht aktiv.", ephemeral=True)
|
||||
|
||||
# Wir müssen den User zwingen im Voice Channel zu sein
|
||||
if not interaction.user.voice or not interaction.user.voice.channel:
|
||||
return await interaction.response.send_message("Du bist in keinem Voice-Channel!", ephemeral=True)
|
||||
|
||||
await interaction.response.defer()
|
||||
|
||||
# Redbot's Audio Core erfordert Context. Wir erzeugen einen künstlichen Context
|
||||
# um die standard Befehle auszulösen, das ist der sicherste Weg mit Redbot.
|
||||
ctx = await commands.Context.from_interaction(interaction)
|
||||
|
||||
# Nutze die Redbot Audio enqueue routing
|
||||
try:
|
||||
# Dies simuliert den Aufruf von [p]play
|
||||
msg = ctx.message
|
||||
msg.content = f"{ctx.prefix}play {query}"
|
||||
await self.bot.process_commands(msg)
|
||||
|
||||
# Kurze Verzögerung für Lavalink zum Laden
|
||||
await asyncio.sleep(2)
|
||||
|
||||
# Sende unsere View hinterher
|
||||
player = await self.get_player(interaction.guild)
|
||||
if player and player.current:
|
||||
view = AudioControlView(self, interaction.guild.id)
|
||||
embed = self.create_player_embed(player)
|
||||
await interaction.followup.send("🎶 Audiosystem gestartet:", embed=embed, view=view)
|
||||
else:
|
||||
await interaction.followup.send("Song wurde wahrscheinlich der Warteschlange hinzugefügt.")
|
||||
except Exception as e:
|
||||
await interaction.followup.send(f"Fehler beim Verbinden mit dem Audiosystem: {e}")
|
||||
|
||||
@app_commands.command(name="upause")
|
||||
async def upause(self, interaction: discord.Interaction):
|
||||
"""Pausiert oder setzt die Wiedergabe fort (Toggle)."""
|
||||
player = await self.get_player(interaction.guild)
|
||||
if not player:
|
||||
return await interaction.response.send_message("Es spielt gerade nichts.", ephemeral=True)
|
||||
|
||||
if player.paused:
|
||||
await player.resume()
|
||||
await interaction.response.send_message("▶️ Wiedergabe fortgesetzt.")
|
||||
else:
|
||||
await player.pause()
|
||||
await interaction.response.send_message("⏸️ Wiedergabe pausiert.")
|
||||
|
||||
async def process_skip(self, interaction: discord.Interaction):
|
||||
"""Die Logik für das Skip-Voting, aufgerufen durch Command oder Button."""
|
||||
player = await self.get_player(interaction.guild)
|
||||
if not player or not player.current:
|
||||
if not interaction.response.is_done():
|
||||
await interaction.response.send_message("Es gibt nichts zum Skippen.", ephemeral=True)
|
||||
return
|
||||
|
||||
voice_channel = interaction.guild.me.voice.channel if interaction.guild.me and interaction.guild.me.voice else None
|
||||
if not voice_channel or interaction.user not in voice_channel.members:
|
||||
return await interaction.response.send_message("Du musst im selben Voice-Channel sein, um zu skippen!", ephemeral=True)
|
||||
|
||||
user_count = sum(1 for m in voice_channel.members if not m.bot)
|
||||
required_votes = int(user_count / 2) + 1 if user_count > 2 else 1
|
||||
|
||||
guild_id = interaction.guild.id
|
||||
if guild_id not in self.skip_votes:
|
||||
self.skip_votes[guild_id] = set()
|
||||
|
||||
self.skip_votes[guild_id].add(interaction.user.id)
|
||||
current_votes = len(self.skip_votes[guild_id])
|
||||
|
||||
# Wenn der Typ ein Admin ist oder alleine im Channel, oder genug Votes
|
||||
if interaction.user.guild_permissions.administrator or current_votes >= required_votes:
|
||||
await player.skip()
|
||||
self.skip_votes[guild_id].clear()
|
||||
msg = "⏭️ Song erfolgreich übersprungen!"
|
||||
if interaction.response.is_done():
|
||||
await interaction.followup.send(msg)
|
||||
else:
|
||||
await interaction.response.send_message(msg)
|
||||
else:
|
||||
msg = f"🗳️ Skip-Vote registriert: **{current_votes}/{required_votes}** benötigte Stimmen.\nNutze `/uskip` oder den Skip-Button."
|
||||
if interaction.response.is_done():
|
||||
await interaction.followup.send(msg)
|
||||
else:
|
||||
await interaction.response.send_message(msg)
|
||||
|
||||
@app_commands.command(name="uskip")
|
||||
async def uskip(self, interaction: discord.Interaction):
|
||||
"""Startet einen Vote zum Überspringen des aktuellen Songs."""
|
||||
await self.process_skip(interaction)
|
||||
Reference in New Issue
Block a user