feat: add multi-guild support and text channel mapping persistence via Docker volume
This commit is contained in:
4 files changed
+237
-109
No files matched your search
@@ -3,3 +3,4 @@ __pycache__/
|
|||||||
*.pyc
|
*.pyc
|
||||||
downloads/
|
downloads/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
config/
|
||||||
@@ -15,52 +15,80 @@ GUILD_ID = os.getenv('GUILD_ID')
|
|||||||
class MusicBot(commands.Cog):
|
class MusicBot(commands.Cog):
|
||||||
def __init__(self, bot):
|
def __init__(self, bot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.queue = []
|
self.queues = {} # guild_id -> list
|
||||||
self.current = None
|
self.currents = {} # guild_id -> player
|
||||||
self.volume = 1.0 # Domyślna głośność ustawiona na 100% (1.0)
|
self.volumes = {} # guild_id -> volume float
|
||||||
self.loop_current = False
|
self.loop_currents = {} # guild_id -> bool
|
||||||
self.last_channel = None
|
self.last_channels = {} # guild_id -> text channel
|
||||||
|
|
||||||
def load_saved_channel_id(self):
|
def get_queue(self, guild_id: int):
|
||||||
if os.path.exists("config.json"):
|
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:
|
try:
|
||||||
import json
|
import json
|
||||||
with open("config.json", "r") as f:
|
with open(config_path, "r") as f:
|
||||||
data = json.load(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:
|
except Exception as e:
|
||||||
print(f"Error loading config: {e}")
|
print(f"Error loading config: {e}")
|
||||||
return None
|
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:
|
try:
|
||||||
import json
|
import json
|
||||||
|
os.makedirs(os.path.dirname(config_path), exist_ok=True)
|
||||||
data = {}
|
data = {}
|
||||||
if os.path.exists("config.json"):
|
if os.path.exists(config_path):
|
||||||
with open("config.json", "r") as f:
|
with open(config_path, "r") as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
data["text_channel_id"] = channel_id
|
data[str(guild_id)] = channel_id
|
||||||
with open("config.json", "w") as f:
|
with open(config_path, "w") as f:
|
||||||
json.dump(data, f, indent=4)
|
json.dump(data, f, indent=4)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error saving config: {e}")
|
print(f"Error saving config: {e}")
|
||||||
|
|
||||||
def get_notification_channel(self):
|
def get_notification_channel(self, guild_id: int):
|
||||||
if self.last_channel:
|
if self.last_channels.get(guild_id):
|
||||||
return self.last_channel
|
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:
|
if saved_id:
|
||||||
channel = self.bot.get_channel(int(saved_id))
|
channel = self.bot.get_channel(int(saved_id))
|
||||||
if channel:
|
if channel:
|
||||||
self.last_channel = channel
|
self.last_channels[guild_id] = channel
|
||||||
return channel
|
return channel
|
||||||
|
|
||||||
voice_client = self.bot.voice_clients[0] if self.bot.voice_clients else None
|
# Fallback to the first accessible channel in the guild
|
||||||
if voice_client and voice_client.channel:
|
guild = self.bot.get_guild(guild_id)
|
||||||
for tc in voice_client.channel.guild.text_channels:
|
if guild:
|
||||||
if tc.permissions_for(voice_client.channel.guild.me).send_messages:
|
for tc in guild.text_channels:
|
||||||
self.last_channel = tc
|
if tc.permissions_for(guild.me).send_messages:
|
||||||
|
self.last_channels[guild_id] = tc
|
||||||
return tc
|
return tc
|
||||||
|
|
||||||
return None
|
return None
|
||||||
@@ -92,23 +120,24 @@ class MusicBot(commands.Cog):
|
|||||||
return
|
return
|
||||||
|
|
||||||
await interaction.response.defer()
|
await interaction.response.defer()
|
||||||
self.last_channel = interaction.channel
|
guild_id = interaction.guild.id
|
||||||
|
self.last_channels[guild_id] = interaction.channel
|
||||||
|
|
||||||
try:
|
try:
|
||||||
player = await YTDLSource.from_url(query, loop=self.bot.loop, stream=True)
|
player = await YTDLSource.from_url(query, loop=self.bot.loop, stream=True)
|
||||||
|
|
||||||
if interaction.guild.voice_client.is_playing():
|
if interaction.guild.voice_client.is_playing():
|
||||||
self.queue.append(player)
|
self.get_queue(guild_id).append(player)
|
||||||
await interaction.followup.send(
|
await interaction.followup.send(
|
||||||
f"Dodano do kolejki: {player.title}",
|
f"Dodano do kolejki: {player.title}",
|
||||||
ephemeral=False
|
ephemeral=False
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.current = player
|
self.set_current(guild_id, player)
|
||||||
interaction.guild.voice_client.play(
|
interaction.guild.voice_client.play(
|
||||||
player,
|
player,
|
||||||
after=lambda e: asyncio.run_coroutine_threadsafe(
|
after=lambda e: asyncio.run_coroutine_threadsafe(
|
||||||
self.play_next(),
|
self.play_next(guild_id),
|
||||||
self.bot.loop
|
self.bot.loop
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -122,45 +151,50 @@ class MusicBot(commands.Cog):
|
|||||||
ephemeral=True
|
ephemeral=True
|
||||||
)
|
)
|
||||||
|
|
||||||
async def play_next(self):
|
async def play_next(self, guild_id: int):
|
||||||
voice_client = self.bot.voice_clients[0] if self.bot.voice_clients else None
|
voice_client = discord.utils.get(self.bot.voice_clients, guild__id=guild_id)
|
||||||
if not voice_client:
|
if not voice_client:
|
||||||
self.current = None
|
self.set_current(guild_id, None)
|
||||||
return
|
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:
|
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)
|
player = await YTDLSource.from_url(url, loop=self.bot.loop, stream=True)
|
||||||
self.current = player
|
self.set_current(guild_id, player)
|
||||||
voice_client.play(
|
voice_client.play(
|
||||||
self.current,
|
player,
|
||||||
after=lambda e: asyncio.run_coroutine_threadsafe(
|
after=lambda e: asyncio.run_coroutine_threadsafe(
|
||||||
self.play_next(),
|
self.play_next(guild_id),
|
||||||
self.bot.loop
|
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
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error repeating song: {e}")
|
print(f"Error repeating song: {e}")
|
||||||
|
|
||||||
if len(self.queue) > 0:
|
queue = self.get_queue(guild_id)
|
||||||
self.current = self.queue.pop(0)
|
if len(queue) > 0:
|
||||||
|
next_player = queue.pop(0)
|
||||||
|
self.set_current(guild_id, next_player)
|
||||||
voice_client.play(
|
voice_client.play(
|
||||||
self.current,
|
next_player,
|
||||||
after=lambda e: asyncio.run_coroutine_threadsafe(
|
after=lambda e: asyncio.run_coroutine_threadsafe(
|
||||||
self.play_next(),
|
self.play_next(guild_id),
|
||||||
self.bot.loop
|
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:
|
else:
|
||||||
self.current = None
|
self.set_current(guild_id, None)
|
||||||
await self.send_to_last_channel("Kolejka jest pusta.")
|
await self.send_to_last_channel(guild_id, "Kolejka jest pusta.")
|
||||||
|
|
||||||
async def send_to_last_channel(self, text):
|
async def send_to_last_channel(self, guild_id: int, text: str):
|
||||||
channel = self.get_notification_channel()
|
channel = self.get_notification_channel(guild_id)
|
||||||
if channel:
|
if channel:
|
||||||
try:
|
try:
|
||||||
await channel.send(text)
|
await channel.send(text)
|
||||||
@@ -170,14 +204,16 @@ class MusicBot(commands.Cog):
|
|||||||
@app_commands.command(name="queue", description="Pokaż aktualną kolejkę")
|
@app_commands.command(name="queue", description="Pokaż aktualną kolejkę")
|
||||||
async def show_queue(self, interaction: discord.Interaction):
|
async def show_queue(self, interaction: discord.Interaction):
|
||||||
"""Pokaż aktualną kolejkę"""
|
"""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(
|
await interaction.response.send_message(
|
||||||
"Kolejka jest pusta.",
|
"Kolejka jest pusta.",
|
||||||
ephemeral=True
|
ephemeral=True
|
||||||
)
|
)
|
||||||
return
|
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(
|
await interaction.response.send_message(
|
||||||
f"Aktualna kolejka:\n{queue_list}",
|
f"Aktualna kolejka:\n{queue_list}",
|
||||||
ephemeral=False
|
ephemeral=False
|
||||||
@@ -231,10 +267,11 @@ class MusicBot(commands.Cog):
|
|||||||
@app_commands.command(name="stop", description="Zatrzymaj odtwarzanie i wyczyść kolejkę")
|
@app_commands.command(name="stop", description="Zatrzymaj odtwarzanie i wyczyść kolejkę")
|
||||||
async def stop(self, interaction: discord.Interaction):
|
async def stop(self, interaction: discord.Interaction):
|
||||||
"""Zatrzymaj odtwarzanie i wyczyść kolejkę"""
|
"""Zatrzymaj odtwarzanie i wyczyść kolejkę"""
|
||||||
|
guild_id = interaction.guild.id
|
||||||
if interaction.guild.voice_client.is_playing():
|
if interaction.guild.voice_client.is_playing():
|
||||||
interaction.guild.voice_client.stop()
|
interaction.guild.voice_client.stop()
|
||||||
self.queue.clear()
|
self.get_queue(guild_id).clear()
|
||||||
self.current = None
|
self.set_current(guild_id, None)
|
||||||
await interaction.response.send_message(
|
await interaction.response.send_message(
|
||||||
"Zatrzymano odtwarzanie i wyczyszczono kolejkę.",
|
"Zatrzymano odtwarzanie i wyczyszczono kolejkę.",
|
||||||
ephemeral=False
|
ephemeral=False
|
||||||
@@ -244,6 +281,7 @@ class MusicBot(commands.Cog):
|
|||||||
@app_commands.describe(volume="Poziom głośności (1-100)")
|
@app_commands.describe(volume="Poziom głośności (1-100)")
|
||||||
async def volume(self, interaction: discord.Interaction, volume: int):
|
async def volume(self, interaction: discord.Interaction, volume: int):
|
||||||
"""Zmień głośność odtwarzacza"""
|
"""Zmień głośność odtwarzacza"""
|
||||||
|
guild_id = interaction.guild.id
|
||||||
if interaction.guild.voice_client is None:
|
if interaction.guild.voice_client is None:
|
||||||
return await interaction.response.send_message(
|
return await interaction.response.send_message(
|
||||||
"Nie połączono z kanałem głosowym.",
|
"Nie połączono z kanałem głosowym.",
|
||||||
@@ -251,8 +289,8 @@ class MusicBot(commands.Cog):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if 0 < volume <= 100:
|
if 0 < volume <= 100:
|
||||||
self.volume = volume / 100
|
self.set_volume(guild_id, volume / 100)
|
||||||
interaction.guild.voice_client.source.volume = self.volume
|
interaction.guild.voice_client.source.volume = self.get_volume(guild_id)
|
||||||
await interaction.response.send_message(
|
await interaction.response.send_message(
|
||||||
f"Zmieniono głośność na {volume}%",
|
f"Zmieniono głośność na {volume}%",
|
||||||
ephemeral=False
|
ephemeral=False
|
||||||
@@ -268,9 +306,11 @@ class MusicBot(commands.Cog):
|
|||||||
@app_commands.command(name="nowplaying", description="Pokaż aktualnie odtwarzany utwór")
|
@app_commands.command(name="nowplaying", description="Pokaż aktualnie odtwarzany utwór")
|
||||||
async def now_playing(self, interaction: discord.Interaction):
|
async def now_playing(self, interaction: discord.Interaction):
|
||||||
"""Pokaż aktualnie odtwarzany utwór"""
|
"""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(
|
await interaction.response.send_message(
|
||||||
f"Teraz odtwarzane: {self.current.title}",
|
f"Teraz odtwarzane: {current.title}",
|
||||||
ephemeral=False
|
ephemeral=False
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@@ -282,10 +322,11 @@ class MusicBot(commands.Cog):
|
|||||||
@app_commands.command(name="leave", description="Odłącz bota od kanału głosowego")
|
@app_commands.command(name="leave", description="Odłącz bota od kanału głosowego")
|
||||||
async def leave(self, interaction: discord.Interaction):
|
async def leave(self, interaction: discord.Interaction):
|
||||||
"""Odłącz bota od kanału głosowego"""
|
"""Odłącz bota od kanału głosowego"""
|
||||||
|
guild_id = interaction.guild.id
|
||||||
if interaction.guild.voice_client is not None:
|
if interaction.guild.voice_client is not None:
|
||||||
await interaction.guild.voice_client.disconnect()
|
await interaction.guild.voice_client.disconnect()
|
||||||
self.queue.clear()
|
self.get_queue(guild_id).clear()
|
||||||
self.current = None
|
self.set_current(guild_id, None)
|
||||||
await interaction.response.send_message(
|
await interaction.response.send_message(
|
||||||
"Rozłączono z kanału głosowego.",
|
"Rozłączono z kanału głosowego.",
|
||||||
ephemeral=False
|
ephemeral=False
|
||||||
@@ -299,8 +340,10 @@ class MusicBot(commands.Cog):
|
|||||||
@app_commands.command(name="loop", description="Włącz/wyłącz zapętlanie aktualnego utworu")
|
@app_commands.command(name="loop", description="Włącz/wyłącz zapętlanie aktualnego utworu")
|
||||||
async def loop_command(self, interaction: discord.Interaction):
|
async def loop_command(self, interaction: discord.Interaction):
|
||||||
"""Włącz/wyłącz zapętlanie aktualnego utworu"""
|
"""Włącz/wyłącz zapętlanie aktualnego utworu"""
|
||||||
self.loop_current = not self.loop_current
|
guild_id = interaction.guild.id
|
||||||
state = "włączone" if self.loop_current else "wyłączone"
|
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(
|
await interaction.response.send_message(
|
||||||
f"Zapętlanie aktualnego utworu zostało {state}.",
|
f"Zapętlanie aktualnego utworu zostało {state}.",
|
||||||
ephemeral=False
|
ephemeral=False
|
||||||
|
|||||||
@@ -8,4 +8,6 @@ services:
|
|||||||
- "80:8000"
|
- "80:8000"
|
||||||
environment:
|
environment:
|
||||||
- DISCORD_TOKEN=${DISCORD_TOKEN}
|
- DISCORD_TOKEN=${DISCORD_TOKEN}
|
||||||
|
volumes:
|
||||||
|
- ./config:/app/config
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -3,6 +3,7 @@ from fastapi.staticfiles import StaticFiles
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import discord
|
||||||
|
|
||||||
# Import bot instance and YTDL class
|
# Import bot instance and YTDL class
|
||||||
from bot import bot
|
from bot import bot
|
||||||
@@ -33,6 +34,15 @@ class ConnectRequest(BaseModel):
|
|||||||
class TextChannelRequest(BaseModel):
|
class TextChannelRequest(BaseModel):
|
||||||
channel_id: str
|
channel_id: str
|
||||||
|
|
||||||
|
# Helper to determine active guild
|
||||||
|
def get_active_guild():
|
||||||
|
voice_client = bot.voice_clients[0] if bot.voice_clients else None
|
||||||
|
if voice_client:
|
||||||
|
return voice_client.guild
|
||||||
|
elif bot.guilds:
|
||||||
|
return bot.guilds[0]
|
||||||
|
return None
|
||||||
|
|
||||||
# FastAPI Endpoints
|
# FastAPI Endpoints
|
||||||
@app.get("/api/status")
|
@app.get("/api/status")
|
||||||
async def get_status():
|
async def get_status():
|
||||||
@@ -40,7 +50,25 @@ async def get_status():
|
|||||||
if not music_cog:
|
if not music_cog:
|
||||||
return {"error": "MusicBot cog not loaded"}
|
return {"error": "MusicBot cog not loaded"}
|
||||||
|
|
||||||
voice_client = bot.voice_clients[0] if bot.voice_clients else None
|
guild = get_active_guild()
|
||||||
|
if not guild:
|
||||||
|
return {
|
||||||
|
"playing": False,
|
||||||
|
"paused": False,
|
||||||
|
"loop_single": False,
|
||||||
|
"volume": 100,
|
||||||
|
"current": None,
|
||||||
|
"queue": [],
|
||||||
|
"voice_channels": [],
|
||||||
|
"active_voice_channel": None,
|
||||||
|
"text_channels": [],
|
||||||
|
"active_text_channel_id": None
|
||||||
|
}
|
||||||
|
|
||||||
|
guild_id = guild.id
|
||||||
|
|
||||||
|
# Active voice channel
|
||||||
|
voice_client = discord.utils.get(bot.voice_clients, guild__id=guild_id)
|
||||||
active_vc = None
|
active_vc = None
|
||||||
if voice_client and voice_client.channel:
|
if voice_client and voice_client.channel:
|
||||||
active_vc = {
|
active_vc = {
|
||||||
@@ -50,9 +78,8 @@ async def get_status():
|
|||||||
"guild_id": str(voice_client.channel.guild.id)
|
"guild_id": str(voice_client.channel.guild.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
# Gather voice channels available to the bot across all guilds
|
# Gather voice channels available to the bot on this active guild
|
||||||
channels = []
|
channels = []
|
||||||
for guild in bot.guilds:
|
|
||||||
for channel in guild.voice_channels:
|
for channel in guild.voice_channels:
|
||||||
channels.append({
|
channels.append({
|
||||||
"guild_name": guild.name,
|
"guild_name": guild.name,
|
||||||
@@ -61,9 +88,8 @@ async def get_status():
|
|||||||
"channel_id": str(channel.id)
|
"channel_id": str(channel.id)
|
||||||
})
|
})
|
||||||
|
|
||||||
# Gather text channels available to the bot across all guilds where it has send permissions
|
# Gather text channels available to the bot on this active guild where it can write
|
||||||
text_channels = []
|
text_channels = []
|
||||||
for guild in bot.guilds:
|
|
||||||
for channel in guild.text_channels:
|
for channel in guild.text_channels:
|
||||||
if channel.permissions_for(guild.me).send_messages:
|
if channel.permissions_for(guild.me).send_messages:
|
||||||
text_channels.append({
|
text_channels.append({
|
||||||
@@ -74,29 +100,32 @@ async def get_status():
|
|||||||
})
|
})
|
||||||
|
|
||||||
active_tc_id = None
|
active_tc_id = None
|
||||||
active_tc = music_cog.get_notification_channel()
|
active_tc = music_cog.get_notification_channel(guild_id)
|
||||||
if active_tc:
|
if active_tc:
|
||||||
active_tc_id = str(active_tc.id)
|
active_tc_id = str(active_tc.id)
|
||||||
|
|
||||||
playing = voice_client.is_playing() if voice_client else False
|
playing = voice_client.is_playing() if voice_client else False
|
||||||
paused = voice_client.is_paused() if voice_client else False
|
paused = voice_client.is_paused() if voice_client else False
|
||||||
|
|
||||||
|
current_track = music_cog.get_current(guild_id)
|
||||||
|
queue = music_cog.get_queue(guild_id)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"playing": playing,
|
"playing": playing,
|
||||||
"paused": paused,
|
"paused": paused,
|
||||||
"loop_single": music_cog.loop_current,
|
"loop_single": music_cog.get_loop_current(guild_id),
|
||||||
"volume": int(music_cog.volume * 100),
|
"volume": int(music_cog.get_volume(guild_id) * 100),
|
||||||
"current": {
|
"current": {
|
||||||
"title": music_cog.current.title,
|
"title": current_track.title,
|
||||||
"url": music_cog.current.data.get("webpage_url") or music_cog.current.url,
|
"url": current_track.data.get("webpage_url") or current_track.url,
|
||||||
"id": music_cog.current.id
|
"id": current_track.id
|
||||||
} if music_cog.current else None,
|
} if current_track else None,
|
||||||
"queue": [
|
"queue": [
|
||||||
{
|
{
|
||||||
"title": item.title,
|
"title": item.title,
|
||||||
"url": item.data.get("webpage_url") or item.url,
|
"url": item.data.get("webpage_url") or item.url,
|
||||||
"id": item.id
|
"id": item.id
|
||||||
} for item in music_cog.queue
|
} for item in queue
|
||||||
],
|
],
|
||||||
"voice_channels": channels,
|
"voice_channels": channels,
|
||||||
"active_voice_channel": active_vc,
|
"active_voice_channel": active_vc,
|
||||||
@@ -110,54 +139,71 @@ async def api_play(req: PlayRequest):
|
|||||||
if not music_cog:
|
if not music_cog:
|
||||||
raise HTTPException(status_code=500, detail="MusicBot cog not loaded")
|
raise HTTPException(status_code=500, detail="MusicBot cog not loaded")
|
||||||
|
|
||||||
voice_client = bot.voice_clients[0] if bot.voice_clients else None
|
guild = get_active_guild()
|
||||||
|
if not guild:
|
||||||
|
raise HTTPException(status_code=400, detail="Bot has no active server context")
|
||||||
|
|
||||||
|
guild_id = guild.id
|
||||||
|
voice_client = discord.utils.get(bot.voice_clients, guild__id=guild_id)
|
||||||
if not voice_client:
|
if not voice_client:
|
||||||
raise HTTPException(status_code=400, detail="Bot is not connected to a voice channel")
|
raise HTTPException(status_code=400, detail="Bot is not connected to a voice channel on this server")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
player = await YTDLSource.from_url(req.query, loop=bot.loop, stream=True)
|
player = await YTDLSource.from_url(req.query, loop=bot.loop, stream=True)
|
||||||
|
|
||||||
if voice_client.is_playing() or voice_client.is_paused():
|
if voice_client.is_playing() or voice_client.is_paused():
|
||||||
music_cog.queue.append(player)
|
music_cog.get_queue(guild_id).append(player)
|
||||||
await music_cog.send_to_last_channel(f"Dodano do kolejki (Web): {player.title}")
|
await music_cog.send_to_last_channel(guild_id, f"Dodano do kolejki (Web): {player.title}")
|
||||||
return {"status": "queued", "title": player.title}
|
return {"status": "queued", "title": player.title}
|
||||||
else:
|
else:
|
||||||
music_cog.current = player
|
music_cog.set_current(guild_id, player)
|
||||||
voice_client.play(
|
voice_client.play(
|
||||||
player,
|
player,
|
||||||
after=lambda e: asyncio.run_coroutine_threadsafe(
|
after=lambda e: asyncio.run_coroutine_threadsafe(
|
||||||
music_cog.play_next(),
|
music_cog.play_next(guild_id),
|
||||||
bot.loop
|
bot.loop
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
await music_cog.send_to_last_channel(f"Teraz odtwarzane (Web): {player.title}")
|
await music_cog.send_to_last_channel(guild_id, f"Teraz odtwarzane (Web): {player.title}")
|
||||||
return {"status": "playing", "title": player.title}
|
return {"status": "playing", "title": player.title}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=500, detail=str(e))
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
@app.post("/api/pause")
|
@app.post("/api/pause")
|
||||||
async def api_pause():
|
async def api_pause():
|
||||||
voice_client = bot.voice_clients[0] if bot.voice_clients else None
|
guild = get_active_guild()
|
||||||
|
if not guild:
|
||||||
|
raise HTTPException(status_code=400, detail="Bot has no active server context")
|
||||||
|
|
||||||
|
voice_client = discord.utils.get(bot.voice_clients, guild__id=guild.id)
|
||||||
if voice_client and voice_client.is_playing():
|
if voice_client and voice_client.is_playing():
|
||||||
voice_client.pause()
|
voice_client.pause()
|
||||||
return {"status": "paused"}
|
return {"status": "paused"}
|
||||||
raise HTTPException(status_code=400, detail="Bot is not playing")
|
raise HTTPException(status_code=400, detail="Bot is not playing on this server")
|
||||||
|
|
||||||
@app.post("/api/resume")
|
@app.post("/api/resume")
|
||||||
async def api_resume():
|
async def api_resume():
|
||||||
voice_client = bot.voice_clients[0] if bot.voice_clients else None
|
guild = get_active_guild()
|
||||||
|
if not guild:
|
||||||
|
raise HTTPException(status_code=400, detail="Bot has no active server context")
|
||||||
|
|
||||||
|
voice_client = discord.utils.get(bot.voice_clients, guild__id=guild.id)
|
||||||
if voice_client and voice_client.is_paused():
|
if voice_client and voice_client.is_paused():
|
||||||
voice_client.resume()
|
voice_client.resume()
|
||||||
return {"status": "resumed"}
|
return {"status": "resumed"}
|
||||||
raise HTTPException(status_code=400, detail="Bot is not paused")
|
raise HTTPException(status_code=400, detail="Bot is not paused on this server")
|
||||||
|
|
||||||
@app.post("/api/skip")
|
@app.post("/api/skip")
|
||||||
async def api_skip():
|
async def api_skip():
|
||||||
voice_client = bot.voice_clients[0] if bot.voice_clients else None
|
guild = get_active_guild()
|
||||||
|
if not guild:
|
||||||
|
raise HTTPException(status_code=400, detail="Bot has no active server context")
|
||||||
|
|
||||||
|
voice_client = discord.utils.get(bot.voice_clients, guild__id=guild.id)
|
||||||
if voice_client and (voice_client.is_playing() or voice_client.is_paused()):
|
if voice_client and (voice_client.is_playing() or voice_client.is_paused()):
|
||||||
voice_client.stop()
|
voice_client.stop()
|
||||||
return {"status": "skipped"}
|
return {"status": "skipped"}
|
||||||
raise HTTPException(status_code=400, detail="Bot is not playing anything")
|
raise HTTPException(status_code=400, detail="Bot is not playing anything on this server")
|
||||||
|
|
||||||
@app.post("/api/stop")
|
@app.post("/api/stop")
|
||||||
async def api_stop():
|
async def api_stop():
|
||||||
@@ -165,12 +211,17 @@ async def api_stop():
|
|||||||
if not music_cog:
|
if not music_cog:
|
||||||
raise HTTPException(status_code=500, detail="MusicBot cog not loaded")
|
raise HTTPException(status_code=500, detail="MusicBot cog not loaded")
|
||||||
|
|
||||||
voice_client = bot.voice_clients[0] if bot.voice_clients else None
|
guild = get_active_guild()
|
||||||
|
if not guild:
|
||||||
|
return {"status": "stopped"}
|
||||||
|
|
||||||
|
guild_id = guild.id
|
||||||
|
voice_client = discord.utils.get(bot.voice_clients, guild__id=guild_id)
|
||||||
if voice_client and (voice_client.is_playing() or voice_client.is_paused()):
|
if voice_client and (voice_client.is_playing() or voice_client.is_paused()):
|
||||||
voice_client.stop()
|
voice_client.stop()
|
||||||
|
|
||||||
music_cog.queue.clear()
|
music_cog.get_queue(guild_id).clear()
|
||||||
music_cog.current = None
|
music_cog.set_current(guild_id, None)
|
||||||
return {"status": "stopped"}
|
return {"status": "stopped"}
|
||||||
|
|
||||||
@app.post("/api/volume")
|
@app.post("/api/volume")
|
||||||
@@ -179,11 +230,16 @@ async def api_volume(req: VolumeRequest):
|
|||||||
if not music_cog:
|
if not music_cog:
|
||||||
raise HTTPException(status_code=500, detail="MusicBot cog not loaded")
|
raise HTTPException(status_code=500, detail="MusicBot cog not loaded")
|
||||||
|
|
||||||
|
guild = get_active_guild()
|
||||||
|
if not guild:
|
||||||
|
raise HTTPException(status_code=400, detail="Bot has no active server context")
|
||||||
|
|
||||||
|
guild_id = guild.id
|
||||||
if 0 <= req.volume <= 100:
|
if 0 <= req.volume <= 100:
|
||||||
music_cog.volume = req.volume / 100
|
music_cog.set_volume(guild_id, req.volume / 100)
|
||||||
voice_client = bot.voice_clients[0] if bot.voice_clients else None
|
voice_client = discord.utils.get(bot.voice_clients, guild__id=guild_id)
|
||||||
if voice_client and voice_client.source:
|
if voice_client and voice_client.source:
|
||||||
voice_client.source.volume = music_cog.volume
|
voice_client.source.volume = music_cog.get_volume(guild_id)
|
||||||
return {"status": "success", "volume": req.volume}
|
return {"status": "success", "volume": req.volume}
|
||||||
raise HTTPException(status_code=400, detail="Volume must be between 0 and 100")
|
raise HTTPException(status_code=400, detail="Volume must be between 0 and 100")
|
||||||
|
|
||||||
@@ -193,7 +249,11 @@ async def api_loop(req: LoopRequest):
|
|||||||
if not music_cog:
|
if not music_cog:
|
||||||
raise HTTPException(status_code=500, detail="MusicBot cog not loaded")
|
raise HTTPException(status_code=500, detail="MusicBot cog not loaded")
|
||||||
|
|
||||||
music_cog.loop_current = req.loop
|
guild = get_active_guild()
|
||||||
|
if not guild:
|
||||||
|
raise HTTPException(status_code=400, detail="Bot has no active server context")
|
||||||
|
|
||||||
|
music_cog.set_loop_current(guild.id, req.loop)
|
||||||
return {"status": "success", "loop": req.loop}
|
return {"status": "success", "loop": req.loop}
|
||||||
|
|
||||||
@app.post("/api/remove")
|
@app.post("/api/remove")
|
||||||
@@ -202,10 +262,17 @@ async def api_remove(req: RemoveRequest):
|
|||||||
if not music_cog:
|
if not music_cog:
|
||||||
raise HTTPException(status_code=500, detail="MusicBot cog not loaded")
|
raise HTTPException(status_code=500, detail="MusicBot cog not loaded")
|
||||||
|
|
||||||
initial_len = len(music_cog.queue)
|
guild = get_active_guild()
|
||||||
music_cog.queue = [item for item in music_cog.queue if item.id != req.id]
|
if not guild:
|
||||||
|
raise HTTPException(status_code=400, detail="Bot has no active server context")
|
||||||
|
|
||||||
if len(music_cog.queue) < initial_len:
|
guild_id = guild.id
|
||||||
|
queue = music_cog.get_queue(guild_id)
|
||||||
|
initial_len = len(queue)
|
||||||
|
new_queue = [item for item in queue if item.id != req.id]
|
||||||
|
music_cog.queues[guild_id] = new_queue
|
||||||
|
|
||||||
|
if len(new_queue) < initial_len:
|
||||||
return {"status": "removed"}
|
return {"status": "removed"}
|
||||||
raise HTTPException(status_code=404, detail="Track not found in queue")
|
raise HTTPException(status_code=404, detail="Track not found in queue")
|
||||||
|
|
||||||
@@ -215,7 +282,13 @@ async def api_reorder(req: ReorderRequest):
|
|||||||
if not music_cog:
|
if not music_cog:
|
||||||
raise HTTPException(status_code=500, detail="MusicBot cog not loaded")
|
raise HTTPException(status_code=500, detail="MusicBot cog not loaded")
|
||||||
|
|
||||||
queue_map = {item.id: item for item in music_cog.queue}
|
guild = get_active_guild()
|
||||||
|
if not guild:
|
||||||
|
raise HTTPException(status_code=400, detail="Bot has no active server context")
|
||||||
|
|
||||||
|
guild_id = guild.id
|
||||||
|
queue = music_cog.get_queue(guild_id)
|
||||||
|
queue_map = {item.id: item for item in queue}
|
||||||
|
|
||||||
new_queue = []
|
new_queue = []
|
||||||
for track_id in req.queue_ids:
|
for track_id in req.queue_ids:
|
||||||
@@ -223,11 +296,11 @@ async def api_reorder(req: ReorderRequest):
|
|||||||
new_queue.append(queue_map[track_id])
|
new_queue.append(queue_map[track_id])
|
||||||
|
|
||||||
# Retain any items not present in the reordered IDs list
|
# Retain any items not present in the reordered IDs list
|
||||||
for item in music_cog.queue:
|
for item in queue:
|
||||||
if item.id not in req.queue_ids:
|
if item.id not in req.queue_ids:
|
||||||
new_queue.append(item)
|
new_queue.append(item)
|
||||||
|
|
||||||
music_cog.queue = new_queue
|
music_cog.queues[guild_id] = new_queue
|
||||||
return {"status": "success"}
|
return {"status": "success"}
|
||||||
|
|
||||||
@app.post("/api/connect")
|
@app.post("/api/connect")
|
||||||
@@ -241,35 +314,43 @@ async def api_connect(req: ConnectRequest):
|
|||||||
if not channel:
|
if not channel:
|
||||||
raise HTTPException(status_code=404, detail="Voice channel not found")
|
raise HTTPException(status_code=404, detail="Voice channel not found")
|
||||||
|
|
||||||
if bot.voice_clients:
|
guild_id = channel.guild.id
|
||||||
|
|
||||||
|
# Only disconnect voice clients from the SAME guild!
|
||||||
for vc in bot.voice_clients:
|
for vc in bot.voice_clients:
|
||||||
|
if vc.guild.id == guild_id:
|
||||||
await vc.disconnect()
|
await vc.disconnect()
|
||||||
|
|
||||||
await channel.connect()
|
await channel.connect()
|
||||||
|
|
||||||
music_cog = bot.get_cog("MusicBot")
|
music_cog = bot.get_cog("MusicBot")
|
||||||
if music_cog and not music_cog.last_channel:
|
if music_cog and not music_cog.get_notification_channel(guild_id):
|
||||||
# Save first accessible text channel to send updates
|
# Save first accessible text channel to send updates
|
||||||
for tc in channel.guild.text_channels:
|
for tc in channel.guild.text_channels:
|
||||||
if tc.permissions_for(channel.guild.me).send_messages:
|
if tc.permissions_for(channel.guild.me).send_messages:
|
||||||
music_cog.last_channel = tc
|
music_cog.last_channels[guild_id] = tc
|
||||||
break
|
break
|
||||||
|
|
||||||
return {"status": "connected", "channel": channel.name}
|
return {"status": "connected", "channel": channel.name}
|
||||||
|
|
||||||
@app.post("/api/disconnect")
|
@app.post("/api/disconnect")
|
||||||
async def api_disconnect():
|
async def api_disconnect():
|
||||||
if bot.voice_clients:
|
guild = get_active_guild()
|
||||||
for vc in bot.voice_clients:
|
if not guild:
|
||||||
await vc.disconnect()
|
raise HTTPException(status_code=400, detail="Bot has no active server context")
|
||||||
|
|
||||||
|
guild_id = guild.id
|
||||||
|
voice_client = discord.utils.get(bot.voice_clients, guild__id=guild_id)
|
||||||
|
if voice_client:
|
||||||
|
await voice_client.disconnect()
|
||||||
|
|
||||||
music_cog = bot.get_cog("MusicBot")
|
music_cog = bot.get_cog("MusicBot")
|
||||||
if music_cog:
|
if music_cog:
|
||||||
music_cog.queue.clear()
|
music_cog.get_queue(guild_id).clear()
|
||||||
music_cog.current = None
|
music_cog.set_current(guild_id, None)
|
||||||
|
|
||||||
return {"status": "disconnected"}
|
return {"status": "disconnected"}
|
||||||
raise HTTPException(status_code=400, detail="Bot not connected to voice")
|
raise HTTPException(status_code=400, detail="Bot not connected to voice on this server")
|
||||||
|
|
||||||
@app.post("/api/settings/text_channel")
|
@app.post("/api/settings/text_channel")
|
||||||
async def api_set_text_channel(req: TextChannelRequest):
|
async def api_set_text_channel(req: TextChannelRequest):
|
||||||
@@ -281,10 +362,11 @@ async def api_set_text_channel(req: TextChannelRequest):
|
|||||||
if not channel:
|
if not channel:
|
||||||
raise HTTPException(status_code=404, detail="Text channel not found")
|
raise HTTPException(status_code=404, detail="Text channel not found")
|
||||||
|
|
||||||
music_cog.last_channel = channel
|
guild_id = channel.guild.id
|
||||||
music_cog.save_channel_id(int(req.channel_id))
|
music_cog.last_channels[guild_id] = channel
|
||||||
|
music_cog.save_channel_id(guild_id, int(req.channel_id))
|
||||||
|
|
||||||
# Send test message to confirm in Polish with new bot name
|
# Send test message to confirm
|
||||||
try:
|
try:
|
||||||
await channel.send(f"🔔 Ten kanał został ustawiony jako główny kanał powiadomień dla ŻyrarBOT z panelu webowego.")
|
await channel.send(f"🔔 Ten kanał został ustawiony jako główny kanał powiadomień dla ŻyrarBOT z panelu webowego.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
Reference in new issue
Block a user