feat: add multi-guild support and text channel mapping persistence via Docker volume
This commit is contained in:
4 files changed
+250
-122
No files matched your search
@@ -15,52 +15,80 @@ GUILD_ID = os.getenv('GUILD_ID')
|
||||
class MusicBot(commands.Cog):
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
self.queue = []
|
||||
self.current = None
|
||||
self.volume = 1.0 # Domyślna głośność ustawiona na 100% (1.0)
|
||||
self.loop_current = False
|
||||
self.last_channel = None
|
||||
self.queues = {} # guild_id -> list
|
||||
self.currents = {} # guild_id -> player
|
||||
self.volumes = {} # guild_id -> volume float
|
||||
self.loop_currents = {} # guild_id -> bool
|
||||
self.last_channels = {} # guild_id -> text channel
|
||||
|
||||
def load_saved_channel_id(self):
|
||||
if os.path.exists("config.json"):
|
||||
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 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.json", "r") as f:
|
||||
with open(config_path, "r") as f:
|
||||
data = json.load(f)
|
||||
return data.get("text_channel_id")
|
||||
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, channel_id):
|
||||
def save_channel_id(self, guild_id: int, channel_id: 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.json"):
|
||||
with open("config.json", "r") as f:
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r") as f:
|
||||
data = json.load(f)
|
||||
data["text_channel_id"] = channel_id
|
||||
with open("config.json", "w") as f:
|
||||
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_notification_channel(self):
|
||||
if self.last_channel:
|
||||
return self.last_channel
|
||||
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()
|
||||
saved_id = self.load_saved_channel_id(guild_id)
|
||||
if saved_id:
|
||||
channel = self.bot.get_channel(int(saved_id))
|
||||
if channel:
|
||||
self.last_channel = channel
|
||||
self.last_channels[guild_id] = channel
|
||||
return channel
|
||||
|
||||
voice_client = self.bot.voice_clients[0] if self.bot.voice_clients else None
|
||||
if voice_client and voice_client.channel:
|
||||
for tc in voice_client.channel.guild.text_channels:
|
||||
if tc.permissions_for(voice_client.channel.guild.me).send_messages:
|
||||
self.last_channel = tc
|
||||
# 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
|
||||
@@ -92,23 +120,24 @@ class MusicBot(commands.Cog):
|
||||
return
|
||||
|
||||
await interaction.response.defer()
|
||||
self.last_channel = interaction.channel
|
||||
guild_id = interaction.guild.id
|
||||
self.last_channels[guild_id] = interaction.channel
|
||||
|
||||
try:
|
||||
player = await YTDLSource.from_url(query, loop=self.bot.loop, stream=True)
|
||||
|
||||
if interaction.guild.voice_client.is_playing():
|
||||
self.queue.append(player)
|
||||
self.get_queue(guild_id).append(player)
|
||||
await interaction.followup.send(
|
||||
f"Dodano do kolejki: {player.title}",
|
||||
ephemeral=False
|
||||
)
|
||||
else:
|
||||
self.current = player
|
||||
self.set_current(guild_id, player)
|
||||
interaction.guild.voice_client.play(
|
||||
player,
|
||||
after=lambda e: asyncio.run_coroutine_threadsafe(
|
||||
self.play_next(),
|
||||
self.play_next(guild_id),
|
||||
self.bot.loop
|
||||
)
|
||||
)
|
||||
@@ -122,45 +151,50 @@ class MusicBot(commands.Cog):
|
||||
ephemeral=True
|
||||
)
|
||||
|
||||
async def play_next(self):
|
||||
voice_client = self.bot.voice_clients[0] if self.bot.voice_clients else None
|
||||
async def play_next(self, guild_id: int):
|
||||
voice_client = discord.utils.get(self.bot.voice_clients, guild__id=guild_id)
|
||||
if not voice_client:
|
||||
self.current = None
|
||||
self.set_current(guild_id, None)
|
||||
return
|
||||
|
||||
if self.loop_current and self.current:
|
||||
loop_current = self.get_loop_current(guild_id)
|
||||
current = self.get_current(guild_id)
|
||||
|
||||
if loop_current and current:
|
||||
try:
|
||||
url = self.current.data.get('webpage_url') or self.current.url
|
||||
url = current.data.get('webpage_url') or current.url
|
||||
player = await YTDLSource.from_url(url, loop=self.bot.loop, stream=True)
|
||||
self.current = player
|
||||
self.set_current(guild_id, player)
|
||||
voice_client.play(
|
||||
self.current,
|
||||
player,
|
||||
after=lambda e: asyncio.run_coroutine_threadsafe(
|
||||
self.play_next(),
|
||||
self.play_next(guild_id),
|
||||
self.bot.loop
|
||||
)
|
||||
)
|
||||
await self.send_to_last_channel(f"Teraz odtwarzane (zapętlone): {self.current.title}")
|
||||
await self.send_to_last_channel(guild_id, f"Teraz odtwarzane (zapętlone): {player.title}")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"Error repeating song: {e}")
|
||||
|
||||
if len(self.queue) > 0:
|
||||
self.current = self.queue.pop(0)
|
||||
queue = self.get_queue(guild_id)
|
||||
if len(queue) > 0:
|
||||
next_player = queue.pop(0)
|
||||
self.set_current(guild_id, next_player)
|
||||
voice_client.play(
|
||||
self.current,
|
||||
next_player,
|
||||
after=lambda e: asyncio.run_coroutine_threadsafe(
|
||||
self.play_next(),
|
||||
self.play_next(guild_id),
|
||||
self.bot.loop
|
||||
)
|
||||
)
|
||||
await self.send_to_last_channel(f"Teraz odtwarzane: {self.current.title}")
|
||||
await self.send_to_last_channel(guild_id, f"Teraz odtwarzane: {next_player.title}")
|
||||
else:
|
||||
self.current = None
|
||||
await self.send_to_last_channel("Kolejka jest pusta.")
|
||||
self.set_current(guild_id, None)
|
||||
await self.send_to_last_channel(guild_id, "Kolejka jest pusta.")
|
||||
|
||||
async def send_to_last_channel(self, text):
|
||||
channel = self.get_notification_channel()
|
||||
async def send_to_last_channel(self, guild_id: int, text: str):
|
||||
channel = self.get_notification_channel(guild_id)
|
||||
if channel:
|
||||
try:
|
||||
await channel.send(text)
|
||||
@@ -170,14 +204,16 @@ class MusicBot(commands.Cog):
|
||||
@app_commands.command(name="queue", description="Pokaż aktualną kolejkę")
|
||||
async def show_queue(self, interaction: discord.Interaction):
|
||||
"""Pokaż aktualną kolejkę"""
|
||||
if not self.queue:
|
||||
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
|
||||
|
||||
queue_list = "\n".join([f"{i + 1}. {item.title}" for i, item in enumerate(self.queue)])
|
||||
queue_list = "\n".join([f"{i + 1}. {item.title}" for i, item in enumerate(queue)])
|
||||
await interaction.response.send_message(
|
||||
f"Aktualna kolejka:\n{queue_list}",
|
||||
ephemeral=False
|
||||
@@ -231,10 +267,11 @@ class MusicBot(commands.Cog):
|
||||
@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.queue.clear()
|
||||
self.current = None
|
||||
self.get_queue(guild_id).clear()
|
||||
self.set_current(guild_id, None)
|
||||
await interaction.response.send_message(
|
||||
"Zatrzymano odtwarzanie i wyczyszczono kolejkę.",
|
||||
ephemeral=False
|
||||
@@ -244,6 +281,7 @@ class MusicBot(commands.Cog):
|
||||
@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.",
|
||||
@@ -251,8 +289,8 @@ class MusicBot(commands.Cog):
|
||||
)
|
||||
|
||||
if 0 < volume <= 100:
|
||||
self.volume = volume / 100
|
||||
interaction.guild.voice_client.source.volume = self.volume
|
||||
self.set_volume(guild_id, volume / 100)
|
||||
interaction.guild.voice_client.source.volume = self.get_volume(guild_id)
|
||||
await interaction.response.send_message(
|
||||
f"Zmieniono głośność na {volume}%",
|
||||
ephemeral=False
|
||||
@@ -268,9 +306,11 @@ class MusicBot(commands.Cog):
|
||||
@app_commands.command(name="nowplaying", description="Pokaż aktualnie odtwarzany utwór")
|
||||
async def now_playing(self, interaction: discord.Interaction):
|
||||
"""Pokaż aktualnie odtwarzany utwór"""
|
||||
if self.current:
|
||||
guild_id = interaction.guild.id
|
||||
current = self.get_current(guild_id)
|
||||
if current:
|
||||
await interaction.response.send_message(
|
||||
f"Teraz odtwarzane: {self.current.title}",
|
||||
f"Teraz odtwarzane: {current.title}",
|
||||
ephemeral=False
|
||||
)
|
||||
else:
|
||||
@@ -282,10 +322,11 @@ class MusicBot(commands.Cog):
|
||||
@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.queue.clear()
|
||||
self.current = None
|
||||
self.get_queue(guild_id).clear()
|
||||
self.set_current(guild_id, None)
|
||||
await interaction.response.send_message(
|
||||
"Rozłączono z kanału głosowego.",
|
||||
ephemeral=False
|
||||
@@ -299,8 +340,10 @@ class MusicBot(commands.Cog):
|
||||
@app_commands.command(name="loop", description="Włącz/wyłącz zapętlanie aktualnego utworu")
|
||||
async def loop_command(self, interaction: discord.Interaction):
|
||||
"""Włącz/wyłącz zapętlanie aktualnego utworu"""
|
||||
self.loop_current = not self.loop_current
|
||||
state = "włączone" if self.loop_current else "wyłączone"
|
||||
guild_id = interaction.guild.id
|
||||
loop_current = not self.get_loop_current(guild_id)
|
||||
self.set_loop_current(guild_id, loop_current)
|
||||
state = "włączone" if loop_current else "wyłączone"
|
||||
await interaction.response.send_message(
|
||||
f"Zapętlanie aktualnego utworu zostało {state}.",
|
||||
ephemeral=False
|
||||
|
||||
Reference in new issue
Block a user