mirror of
https://github.com/MrUnknownDE/redbot-assets.git
synced 2026-04-06 00:31:57 +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.
|
- `[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`.
|
- **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).*
|
*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