diff --git a/skullbot.py b/skullbot.py index 0efb485..ff27180 100644 --- a/skullbot.py +++ b/skullbot.py @@ -148,6 +148,68 @@ async def hallo(ctx): """Simple hello command.""" await ctx.send(f"Hello {ctx.author.mention}! 👋") +# ===================== +# PLAYLIST QUEUE +# ===================== +playlist_queue = [] # Liste from Dicts: { 'url':..., 'title':..., 'data':... } +is_playing = False + +async def play_next(ctx): + global is_playing + if playlist_queue: + next_track = playlist_queue.pop(0) + player = await YTDLSource.from_url(next_track['url'], loop=bot.loop, stream=True) + ctx.voice_client.play( + player, + after=lambda e: asyncio.run_coroutine_threadsafe(play_next(ctx), bot.loop) + ) + await ctx.send(f"🎶 Now playing: **{player.title}**") + is_playing = True + else: + is_playing = False + await ctx.send("✅ Playlist finished.") + +@bot.command(name="add") +async def add(ctx, *, url): + """ + Fügt einen Song oder eine komplette YouTube-Playlist zur Warteschlange hinzu. + """ + # Playlist oder Einzelvideo abrufen + data = await asyncio.get_event_loop().run_in_executor( + None, lambda: ytdl.extract_info(url, download=False) + ) + + added_titles = [] + + if 'entries' in data: + for entry in data['entries']: + playlist_queue.append({'url': entry['url'], 'title': entry['title'], 'data': entry}) + added_titles.append(entry['title']) + else: + playlist_queue.append({'url': data['url'], 'title': data['title'], 'data': data}) + added_titles.append(data['title']) + + await ctx.send(f"✅ Added {len(added_titles)} track(s) to the playlist:\n" + + "\n".join(f"- {t}" for t in added_titles[:10]) + + (f"\n… +{len(added_titles)-10} more" if len(added_titles) > 10 else "")) + + if not ctx.voice_client and ctx.author.voice: + await ctx.author.voice.channel.connect() + + if ctx.voice_client and not ctx.voice_client.is_playing(): + await play_next(ctx) + +@bot.command(name="playlist") +async def playlist_cmd(ctx): + """ + Zeigt die aktuelle Playlist/Warteschlange an. + """ + if not playlist_queue: + await ctx.send("🎵 Die Playlist ist leer.") + else: + text = "\n".join([f"{idx+1}. {track['title']}" for idx, track in enumerate(playlist_queue[:20])]) + await ctx.send(f"🎵 **Aktuelle Playlist:**\n{text}") + # ===================== # YTDL / FFMPEG SETUP # ===================== @@ -165,10 +227,6 @@ ffmpeg_options = { ytdl = youtube_dl.YoutubeDL(ytdl_format_options) class YTDLSource(discord.PCMVolumeTransformer): - """ - Handles downloading and streaming audio from YouTube. - """ - def __init__(self, source, *, data, volume=0.5): super().__init__(source, volume) self.data = data @@ -177,13 +235,19 @@ class YTDLSource(discord.PCMVolumeTransformer): @classmethod async def from_url(cls, url, *, loop=None, stream=False): loop = loop or asyncio.get_event_loop() - data = await loop.run_in_executor( - None, lambda: ytdl.extract_info(url, download=not stream) - ) + try: + data = await loop.run_in_executor( + None, lambda: ytdl.extract_info(url, download=not stream) + ) + except Exception as e: + return None, e + if "entries" in data: data = data["entries"][0] + filename = data["url"] if stream else ytdl.prepare_filename(data) - return cls(discord.FFmpegPCMAudio(filename, **ffmpeg_options), data=data) + return cls(discord.FFmpegPCMAudio(filename, **ffmpeg_options), data=data), None + # ===================== # VOICE / MUSIC COMMANDS @@ -208,24 +272,23 @@ async def leave(ctx): @bot.command(name="play") async def play(ctx, *, url): - """ - Streams audio from a given YouTube URL to the connected voice channel. - Adds interactive buttons to control playback. - """ - # Connect to voice channel if not already connected + """Streams Audio von einer YouTube URL""" if not ctx.voice_client: if ctx.author.voice: await ctx.author.voice.channel.connect() else: - await ctx.send("You are not in a voice channel!") + await ctx.send("Du bist in keinem Sprachkanal!") return - # Start streaming - player = await YTDLSource.from_url(url, loop=bot.loop, stream=True) + player, error = await YTDLSource.from_url(url, loop=bot.loop, stream=True) + if error or not player: + await ctx.send("⚠️ Konnte den Titel nicht abspielen. " + "YouTube Restrictions blockieren möglicherweise den Zugriff.") + return + ctx.voice_client.stop() ctx.voice_client.play(player) - # Create interactive buttons stop_button = Button(label="Stop", style=discord.ButtonStyle.red) pause_button = Button(label="Pause", style=discord.ButtonStyle.gray) resume_button = Button(label="Resume", style=discord.ButtonStyle.green) @@ -245,12 +308,10 @@ async def play(ctx, *, url): ctx.voice_client.resume() await interaction.response.defer() - # Attach callbacks stop_button.callback = stop_callback pause_button.callback = pause_callback resume_button.callback = resume_callback - # Build and send view view = View() view.add_item(stop_button) view.add_item(pause_button)