import discord from discord import app_commands from discord.ext import commands import asyncio import os from typing import Optional from dotenv import load_dotenv from ytdl import YTDLSource # Load environment variables load_dotenv() TOKEN = os.getenv('DISCORD_TOKEN') GUILD_ID = os.getenv('GUILD_ID') class MusicBot(commands.Cog): def __init__(self, bot): self.bot = bot self.queues = {} # guild_id -> list self.currents = {} # guild_id -> player self.volumes = {} # guild_id -> volume float self.loop_currents = {} # guild_id -> bool self.loop_playlists = {} # guild_id -> bool self.last_channels = {} # guild_id -> text channel self.history = {} # guild_id -> list of dicts self.queue_indices = {} # guild_id -> int self.jump_flag = {} # guild_id -> bool self.default_plays = self.get_default_plays() self.on_state_change_callback = None def load_history(self, guild_id: int): if guild_id in self.history: return self.history[guild_id] config_path = "config/history.json" if os.path.exists(config_path): try: import json with open(config_path, "r") as f: data = json.load(f) raw_history = data.get(str(guild_id), []) unique_history = [] seen = set() for item in raw_history: key = (item.get("title", ""), item.get("url", "")) if key not in seen: seen.add(key) unique_history.append(item) self.history[guild_id] = unique_history except Exception as e: print(f"Error loading history: {e}") self.history[guild_id] = [] else: self.history[guild_id] = [] return self.history[guild_id] def save_history(self, guild_id: int): config_path = "config/history.json" try: import json os.makedirs(os.path.dirname(config_path), exist_ok=True) data = {} if os.path.exists(config_path): with open(config_path, "r") as f: data = json.load(f) data[str(guild_id)] = self.history.get(guild_id, []) with open(config_path, "w") as f: json.dump(data, f, indent=4) except Exception as e: print(f"Error saving history: {e}") def add_to_history(self, guild_id: int, player): if not player or not player.title: return history = self.load_history(guild_id) entry = { "id": player.id, "title": player.title, "url": player.data.get("webpage_url") or player.url, "duration": player.duration, "is_local": player.data.get("is_local", False), "filepath": player.data.get("filepath") } history = [h for h in history if h.get("title") != entry["title"] and (not entry.get("url") or h.get("url") != entry["url"])] history.insert(0, entry) self.history[guild_id] = history[:50] self.save_history(guild_id) def get_queue(self, guild_id: int): if guild_id not in self.queues: self.queues[guild_id] = [] return self.queues[guild_id] def get_current(self, guild_id: int): return self.currents.get(guild_id) def set_current(self, guild_id: int, player): self.currents[guild_id] = player def get_volume(self, guild_id: int): return self.volumes.get(guild_id, 1.0) def set_volume(self, guild_id: int, volume: float): self.volumes[guild_id] = volume def get_loop_current(self, guild_id: int): return self.loop_currents.get(guild_id, False) def set_loop_current(self, guild_id: int, loop: bool): self.loop_currents[guild_id] = loop def get_loop_playlist(self, guild_id: int): return self.loop_playlists.get(guild_id, False) def set_loop_playlist(self, guild_id: int, loop: bool): self.loop_playlists[guild_id] = loop def get_queue_index(self, guild_id: int): return self.queue_indices.get(guild_id, -1) def set_queue_index(self, guild_id: int, index: int): self.queue_indices[guild_id] = index def clear_queue(self, guild_id: int): self.get_queue(guild_id).clear() self.set_current(guild_id, None) self.set_queue_index(guild_id, -1) async def get_fresh_player(self, track): if track.data.get('is_local'): new_player = await YTDLSource.from_file(track.data['filepath'], loop=self.bot.loop, title=track.title) else: url = track.data.get('webpage_url') or track.url new_player = await YTDLSource.from_url(url, loop=self.bot.loop, stream=True) new_player.id = track.id new_player.plays_left = getattr(track, 'plays_left', self.get_default_plays()) return new_player async def enqueue_track(self, guild_id: int, player, channel=None, from_web=False): if channel: self.last_channels[guild_id] = channel if getattr(player, 'plays_left', None) is None: player.plays_left = self.get_default_plays() queue = self.get_queue(guild_id) queue.append(player) voice_client = discord.utils.get(self.bot.voice_clients, guild__id=guild_id) is_playing = voice_client and (voice_client.is_playing() or voice_client.is_paused()) url = player.data.get('webpage_url') or player.url title_clean = discord.utils.escape_markdown(player.title or "Nieznany tytuł") is_http = url and str(url).startswith("http") if is_playing: embed = discord.Embed( title=f"🎵 {title_clean}" if not player.data.get('is_local') else f"📁 {title_clean}", url=url if is_http else None, description=f"[Kliknij tutaj, aby otworzyć link]({url})" if is_http else "Plik lokalny / brak linku", color=discord.Color.blue() ) if from_web: embed.set_author(name="Dodano do kolejki z panelu Web", icon_url="https://cdn-icons-png.flaticon.com/512/3844/3844724.png") else: embed.set_author(name="Dodano do kolejki", icon_url="https://cdn-icons-png.flaticon.com/512/3844/3844724.png") if player.duration: mins, secs = divmod(int(player.duration), 60) embed.add_field(name="Czas trwania", value=f"{mins}:{secs:02d}", inline=True) embed.set_footer(text=f"Pozycja w kolejce: {len(queue)}") await self.send_to_last_channel(guild_id, embed=embed) await self.trigger_state_change() return {"status": "queued", "title": player.title} else: await self.play_next(guild_id, advance=False) return {"status": "playing", "title": player.title} @commands.Cog.listener() async def on_voice_state_update(self, member, before, after): if member.id == self.bot.user.id and before.channel is not None and after.channel is None: guild_id = before.channel.guild.id self.clear_queue(guild_id) await self.trigger_state_change() async def trigger_state_change(self): if self.on_state_change_callback: try: if asyncio.iscoroutinefunction(self.on_state_change_callback): await self.on_state_change_callback() else: self.on_state_change_callback() except Exception as e: print(f"Error executing on_state_change_callback: {e}") def load_saved_channel_id(self, guild_id: int): config_path = "config/config.json" if os.path.exists(config_path): try: import json with open(config_path, "r") as f: data = json.load(f) val = data.get(str(guild_id)) return int(val) if val is not None else None except Exception as e: print(f"Error loading config: {e}") return None def save_channel_id(self, guild_id: int, channel_id: Optional[int]): config_path = "config/config.json" try: import json os.makedirs(os.path.dirname(config_path), exist_ok=True) data = {} if os.path.exists(config_path): with open(config_path, "r") as f: data = json.load(f) if channel_id is None: if str(guild_id) in data: del data[str(guild_id)] else: data[str(guild_id)] = channel_id with open(config_path, "w") as f: json.dump(data, f, indent=4) except Exception as e: print(f"Error saving config: {e}") def get_default_plays(self) -> int: config_path = "config/config.json" if os.path.exists(config_path): try: import json with open(config_path, "r") as f: data = json.load(f) val = data.get("default_plays") if val is not None: return int(val) except Exception as e: print(f"Error loading default_plays: {e}") return 5 def set_default_plays(self, val: int): config_path = "config/config.json" try: import json os.makedirs(os.path.dirname(config_path), exist_ok=True) data = {} if os.path.exists(config_path): with open(config_path, "r") as f: data = json.load(f) data["default_plays"] = int(val) with open(config_path, "w") as f: json.dump(data, f, indent=4) self.default_plays = int(val) except Exception as e: print(f"Error saving default_plays: {e}") def get_notification_channel(self, guild_id: int): if self.last_channels.get(guild_id): return self.last_channels[guild_id] saved_id = self.load_saved_channel_id(guild_id) if saved_id: channel = self.bot.get_channel(int(saved_id)) if channel: self.last_channels[guild_id] = channel return channel # Fallback to the first accessible channel in the guild guild = self.bot.get_guild(guild_id) if guild: for tc in guild.text_channels: if tc.permissions_for(guild.me).send_messages: self.last_channels[guild_id] = tc return tc return None async def ensure_voice(self, interaction): if not interaction.user.voice: await interaction.response.send_message( "Nie jesteś połączony z kanałem głosowym.", ephemeral=True ) return False if interaction.guild.voice_client is None: await interaction.user.voice.channel.connect() elif interaction.guild.voice_client.channel != interaction.user.voice.channel: await interaction.response.send_message( "Jestem już połączony z innym kanałem głosowym.", ephemeral=True ) return False return True @app_commands.command(name="play", description="Odtwórz utwór z YouTube") @app_commands.describe(query="Link YouTube lub zapytanie wyszukiwania") async def play(self, interaction: discord.Interaction, query: str): """Odtwórz utwór z YouTube""" if not await self.ensure_voice(interaction): return await interaction.response.defer() guild_id = interaction.guild.id self.last_channels[guild_id] = interaction.channel try: filepath = os.path.join("uploads", os.path.basename(query)) if os.path.exists(filepath) and not query.startswith("http"): player = await YTDLSource.from_file(filepath, loop=self.bot.loop) else: player = await YTDLSource.from_url(query, loop=self.bot.loop, stream=True) res = await self.enqueue_track(guild_id, player, channel=interaction.channel, from_web=False) if res["status"] == "queued": await interaction.followup.send( f"Dodano do kolejki: {discord.utils.escape_markdown(player.title)}", ephemeral=False ) else: await interaction.followup.send( f"Teraz odtwarzane: {discord.utils.escape_markdown(player.title)}", ephemeral=False ) except Exception as e: await interaction.followup.send( f"Wystąpił błąd: {e}", ephemeral=True ) async def uploads_autocomplete( self, interaction: discord.Interaction, current: str, ) -> list[app_commands.Choice[str]]: uploads_dir = "uploads" if not os.path.exists(uploads_dir): return [] files = [f for f in os.listdir(uploads_dir) if os.path.isfile(os.path.join(uploads_dir, f)) and not f.startswith(".")] filtered = [f for f in files if current.lower() in f.lower()][:25] return [app_commands.Choice(name=f, value=f) for f in filtered] @app_commands.command(name="playfile", description="Odtwórz plik przesłany na serwer") @app_commands.describe(filename="Wybierz plik z listy przesłanych") @app_commands.autocomplete(filename=uploads_autocomplete) async def playfile(self, interaction: discord.Interaction, filename: str): """Odtwórz plik przesłany na serwer""" if not await self.ensure_voice(interaction): return await interaction.response.defer() guild_id = interaction.guild.id self.last_channels[guild_id] = interaction.channel filepath = os.path.join("uploads", os.path.basename(filename)) if not os.path.exists(filepath): await interaction.followup.send("Nie znaleziono pliku na serwerze.", ephemeral=True) return try: player = await YTDLSource.from_file(filepath, loop=self.bot.loop) res = await self.enqueue_track(guild_id, player, channel=interaction.channel, from_web=False) if res["status"] == "queued": await interaction.followup.send( f"Dodano do kolejki: {discord.utils.escape_markdown(player.title)}", ephemeral=False ) else: await interaction.followup.send( f"Teraz odtwarzane: {discord.utils.escape_markdown(player.title)}", ephemeral=False ) except Exception as e: await interaction.followup.send( f"Wystąpił błąd: {e}", ephemeral=True ) async def play_next(self, guild_id: int, advance: bool = True): voice_client = discord.utils.get(self.bot.voice_clients, guild__id=guild_id) if not voice_client: self.set_current(guild_id, None) return if self.jump_flag.get(guild_id, False): self.jump_flag[guild_id] = False advance = False queue = self.get_queue(guild_id) if len(queue) == 0: self.set_queue_index(guild_id, -1) self.set_current(guild_id, None) return idx = self.get_queue_index(guild_id) if idx == -1 and len(queue) > 0: idx = 0 if not advance: if 0 <= idx < len(queue) and self.get_current(guild_id) is not None: next_idx = idx else: next_idx = len(queue) - 1 else: if self.get_loop_current(guild_id): next_idx = idx elif self.get_loop_playlist(guild_id): next_idx = (idx + 1) % len(queue) else: next_idx = idx + 1 if next_idx >= len(queue): self.set_queue_index(guild_id, next_idx) self.set_current(guild_id, None) await self.trigger_state_change() await self.send_to_last_channel(guild_id, "Kolejka zakończona.") return self.set_queue_index(guild_id, next_idx) track = queue[next_idx] if getattr(track, 'played', False): try: player = await self.get_fresh_player(track) except Exception as e: print(f"Error getting fresh player for track {track.title}: {e}") await self.send_to_last_channel(guild_id, f"⚠️ Nie udało się odtworzyć: {track.title} ({e}). Pomijam.") return await self.play_next(guild_id, advance=True) else: player = track player.played = True current_plays = getattr(player, 'plays_left', self.get_default_plays()) player.plays_left = current_plays - 1 if player.plays_left <= 0: queue.pop(next_idx) self.set_queue_index(guild_id, next_idx - 1) if self.get_loop_current(guild_id): self.set_loop_current(guild_id, False) else: queue[next_idx] = player self.set_current(guild_id, player) self.add_to_history(guild_id, player) await self.trigger_state_change() voice_client.play( player, after=lambda e: asyncio.run_coroutine_threadsafe( self.play_next(guild_id, advance=True), self.bot.loop ) ) url = player.data.get('webpage_url') or player.url title_clean = discord.utils.escape_markdown(player.title or "Nieznany tytuł") is_http = url and str(url).startswith("http") embed = discord.Embed( title=f"▶️ {title_clean}" if not player.data.get('is_local') else f"📁 {title_clean}", url=url if is_http else None, description=f"[Kliknij tutaj, aby otworzyć link]({url})" if is_http else "Plik lokalny / brak linku", color=discord.Color.green() ) if player.duration: mins, secs = divmod(int(player.duration), 60) embed.add_field(name="Czas trwania", value=f"{mins}:{secs:02d}", inline=True) embed.set_footer(text=f"Pozycja w kolejce: {next_idx + 1} / {len(queue)}") await self.send_to_last_channel(guild_id, embed=embed) async def send_to_last_channel(self, guild_id: int, text: str = None, embed: discord.Embed = None): channel = self.get_notification_channel(guild_id) if channel: try: await channel.send(content=text if text else None, embed=embed) except Exception as e: print(f"Failed to send message: {e}") @app_commands.command(name="queue", description="Pokaż aktualną kolejkę") async def show_queue(self, interaction: discord.Interaction): """Pokaż aktualną kolejkę""" guild_id = interaction.guild.id queue = self.get_queue(guild_id) if not queue: await interaction.response.send_message( "Kolejka jest pusta.", ephemeral=True ) return lines = [] current_idx = self.get_queue_index(guild_id) current_playing = self.get_current(guild_id) if current_playing and (current_idx < 0 or current_idx >= len(queue)): plays = getattr(current_playing, 'plays_left', 0) lines.append(f"▶️ **{discord.utils.escape_markdown(current_playing.title)}** (obecnie odtwarzane - ostatnie odtworzenie) [🔁 {plays}]\n---") for i, item in enumerate(queue): prefix = "▶️ **" if i == current_idx else f"{i + 1}. " suffix = "** (obecnie odtwarzane)" if i == current_idx else "" plays = getattr(item, 'plays_left', self.get_default_plays()) lines.append(f"{prefix}{discord.utils.escape_markdown(item.title)}{suffix} [🔁 {plays}]") queue_list = "\n".join(lines[:30]) if len(lines) > 30: queue_list += f"\n... i {len(lines) - 30} więcej." await interaction.response.send_message( f"Aktualna kolejka:\n{queue_list}", ephemeral=False ) async def history_autocomplete( self, interaction: discord.Interaction, current: str, ) -> list[app_commands.Choice[str]]: guild_id = interaction.guild.id history = self.load_history(guild_id) filtered = [h for h in history if current.lower() in h["title"].lower()][:25] return [app_commands.Choice(name=h["title"][:100], value=str(h["id"])) for h in filtered] @app_commands.command(name="history", description="Pokaż historię ostatnio odtwarzanych utworów") async def show_history(self, interaction: discord.Interaction): """Pokaż historię odtwarzania""" guild_id = interaction.guild.id history = self.load_history(guild_id) if not history: await interaction.response.send_message( "Historia odtwarzania jest pusta.", ephemeral=True ) return history_list = "\n".join([f"{i + 1}. {discord.utils.escape_markdown(item['title'])}" for i, item in enumerate(history[:15])]) await interaction.response.send_message( f"📜 Historia odtwarzania (ostatnie 15):\n{history_list}", ephemeral=False ) @app_commands.command(name="replay", description="Odtwórz ponownie utwór z historii") @app_commands.describe(track_id="Wybierz utwór z historii odtwarzania") @app_commands.autocomplete(track_id=history_autocomplete) async def replay(self, interaction: discord.Interaction, track_id: str): """Odtwórz z historii""" if not await self.ensure_voice(interaction): return await interaction.response.defer() guild_id = interaction.guild.id self.last_channels[guild_id] = interaction.channel history = self.load_history(guild_id) entry = next((item for item in history if str(item["id"]) == track_id), None) # If not found by ID, maybe user typed an index number 1..15 if not entry and track_id.isdigit(): idx = int(track_id) - 1 if 0 <= idx < len(history): entry = history[idx] if not entry: await interaction.followup.send("Nie znaleziono utworu w historii.", ephemeral=True) return try: if entry.get("is_local"): filepath = entry.get("filepath") or entry.get("url") if not os.path.exists(filepath): await interaction.followup.send("Plik lokalny z historii już nie istnieje na serwerze.", ephemeral=True) return player = await YTDLSource.from_file(filepath, loop=self.bot.loop, title=entry["title"]) else: player = await YTDLSource.from_url(entry["url"], loop=self.bot.loop, stream=True) res = await self.enqueue_track(guild_id, player, channel=interaction.channel, from_web=False) if res["status"] == "queued": await interaction.followup.send( f"Dodano do kolejki z historii: {discord.utils.escape_markdown(player.title)}", ephemeral=False ) else: await interaction.followup.send( f"Teraz odtwarzane z historii: {discord.utils.escape_markdown(player.title)}", ephemeral=False ) except Exception as e: await interaction.followup.send(f"Wystąpił błąd: {e}", ephemeral=True) @app_commands.command(name="skip", description="Pomiń aktualny utwór") async def skip(self, interaction: discord.Interaction): """Pomiń aktualny utwór""" guild_id = interaction.guild.id if self.get_loop_current(guild_id): self.set_loop_current(guild_id, False) if interaction.guild.voice_client and (interaction.guild.voice_client.is_playing() or interaction.guild.voice_client.is_paused()): interaction.guild.voice_client.stop() await self.trigger_state_change() await interaction.response.send_message( "Pominięto aktualny utwór.", ephemeral=False ) else: await interaction.response.send_message( "Nic nie jest odtwarzane.", ephemeral=True ) @app_commands.command(name="pause", description="Wstrzymaj odtwarzanie") async def pause(self, interaction: discord.Interaction): """Wstrzymaj odtwarzanie""" if interaction.guild.voice_client.is_playing(): interaction.guild.voice_client.pause() await self.trigger_state_change() await interaction.response.send_message( "Odtwarzanie wstrzymane.", ephemeral=False ) else: await interaction.response.send_message( "Nic nie jest odtwarzane.", ephemeral=True ) @app_commands.command(name="resume", description="Wznów odtwarzanie") async def resume(self, interaction: discord.Interaction): """Wznów odtwarzanie""" if interaction.guild.voice_client.is_paused(): interaction.guild.voice_client.resume() await self.trigger_state_change() await interaction.response.send_message( "Odtwarzanie wznowione.", ephemeral=False ) else: await interaction.response.send_message( "Odtwarzanie nie jest wstrzymane.", ephemeral=True ) @app_commands.command(name="stop", description="Zatrzymaj odtwarzanie i wyczyść kolejkę") async def stop(self, interaction: discord.Interaction): """Zatrzymaj odtwarzanie i wyczyść kolejkę""" guild_id = interaction.guild.id if interaction.guild.voice_client.is_playing(): interaction.guild.voice_client.stop() self.clear_queue(guild_id) await self.trigger_state_change() await interaction.response.send_message( "Zatrzymano odtwarzanie i wyczyszczono kolejkę.", ephemeral=False ) @app_commands.command(name="volume", description="Zmień głośność odtwarzacza") @app_commands.describe(volume="Poziom głośności (1-100)") async def volume(self, interaction: discord.Interaction, volume: int): """Zmień głośność odtwarzacza""" guild_id = interaction.guild.id if interaction.guild.voice_client is None: return await interaction.response.send_message( "Nie połączono z kanałem głosowym.", ephemeral=True ) if 0 < volume <= 100: self.set_volume(guild_id, volume / 100) interaction.guild.voice_client.source.volume = self.get_volume(guild_id) await self.trigger_state_change() await interaction.response.send_message( f"Zmieniono głośność na {volume}%", ephemeral=False ) return None else: await interaction.response.send_message( "Wprowadź wartość z przedziału od 1 do 100.", ephemeral=True ) return None @app_commands.command(name="nowplaying", description="Pokaż aktualnie odtwarzany utwór") async def now_playing(self, interaction: discord.Interaction): """Pokaż aktualnie odtwarzany utwór""" guild_id = interaction.guild.id current = self.get_current(guild_id) if current: await interaction.response.send_message( f"Teraz odtwarzane: {discord.utils.escape_markdown(current.title)}", ephemeral=False ) else: await interaction.response.send_message( "Nic nie jest obecnie odtwarzane.", ephemeral=True ) @app_commands.command(name="leave", description="Odłącz bota od kanału głosowego") async def leave(self, interaction: discord.Interaction): """Odłącz bota od kanału głosowego""" guild_id = interaction.guild.id if interaction.guild.voice_client is not None: await interaction.guild.voice_client.disconnect() self.clear_queue(guild_id) await interaction.response.send_message( "Rozłączono z kanału głosowego.", ephemeral=False ) else: await interaction.response.send_message( "Nie jestem na kanale głosowym.", ephemeral=True ) @app_commands.command(name="loop", description="Zmień tryb zapętlania (Wyłączone -> Cała kolejka -> Jeden utwór)") async def loop_command(self, interaction: discord.Interaction): """Zmień tryb zapętlania jak w YT Music/Spotify""" guild_id = interaction.guild.id loop_current = self.get_loop_current(guild_id) loop_pl = self.get_loop_playlist(guild_id) if not loop_current and not loop_pl: self.set_loop_playlist(guild_id, True) self.set_loop_current(guild_id, False) msg = "🔁 Tryb zapętlania: **Cała kolejka (playlista)**" elif loop_pl and not loop_current: self.set_loop_playlist(guild_id, False) self.set_loop_current(guild_id, True) msg = "🔂 Tryb zapętlania: **Jeden utwór**" else: self.set_loop_playlist(guild_id, False) self.set_loop_current(guild_id, False) msg = "➡️ Tryb zapętlania: **Wyłączone**" await interaction.response.send_message(msg, ephemeral=False) @app_commands.command(name="loopplaylist", description="Włącz/wyłącz zapętlanie całej kolejki (tryb playlisty)") async def loopplaylist_command(self, interaction: discord.Interaction): """Włącz/wyłącz zapętlanie całej kolejki""" guild_id = interaction.guild.id loop_pl = not self.get_loop_playlist(guild_id) self.set_loop_playlist(guild_id, loop_pl) if loop_pl: self.set_loop_current(guild_id, False) state = "włączone (utwory po zakończeniu wracają na koniec kolejki)" if loop_pl else "wyłączone" await interaction.response.send_message( f"🔁 Zapętlanie całej kolejki: **{state}**.", ephemeral=False ) @app_commands.command(name="defaultplays", description="Ustaw domyślną liczbę odtworzeń dla nowych utworów w kolejce") @app_commands.describe(count="Domyślna liczba odtworzeń (1-999)") async def default_plays_cmd(self, interaction: discord.Interaction, count: int): if count < 1 or count > 999: await interaction.response.send_message("❌ Liczba odtworzeń musi być z przedziału 1-999.", ephemeral=True) return self.set_default_plays(count) await self.trigger_state_change() await interaction.response.send_message(f"✅ Domyślna liczba odtworzeń dla nowych utworów została ustawiona na: **{count}**.") @app_commands.command(name="trackplays", description="Zmień liczbę odtworzeń dla utworu w kolejce") @app_commands.describe(index="Pozycja w kolejce (od 1)", count="Nowa liczba odtworzeń (1-999)") async def track_plays_cmd(self, interaction: discord.Interaction, index: int, count: int): guild_id = interaction.guild.id queue = self.get_queue(guild_id) if index < 1 or index > len(queue): await interaction.response.send_message("❌ Nieprawidłowy numer utworu w kolejce.", ephemeral=True) return if count < 1 or count > 999: await interaction.response.send_message("❌ Liczba odtworzeń musi być z przedziału 1-999.", ephemeral=True) return track = queue[index - 1] track.plays_left = count await self.trigger_state_change() await interaction.response.send_message(f"✅ Ustawiono liczbę odtworzeń dla utworu **{discord.utils.escape_markdown(track.title)}** na **{count}**.") class MyBot(commands.Bot): def __init__(self): intents = discord.Intents.default() intents.message_content = True intents.voice_states = True super().__init__(command_prefix="!", intents=intents) async def setup_hook(self): await self.add_cog(MusicBot(self)) if GUILD_ID: guild = discord.Object(id=int(GUILD_ID)) self.tree.copy_global_to(guild=guild) await self.tree.sync(guild=guild) print(f"Synced commands to guild {GUILD_ID}") else: await self.tree.sync() print("Synced commands globally") async def on_ready(self): print(f'Logged in as {self.user.name} ({self.user.id})') print('------') bot = MyBot()