Merge pull request #5 from deadcxap/main

Добавлен докер и докер-композ
This commit is contained in:
Bananchiki
2025-03-10 09:56:14 +03:00
committed by GitHub
12 changed files with 225 additions and 40 deletions

20
.dockerignore Normal file
View File

@@ -0,0 +1,20 @@
.git
.gitignore
venv/
env/
ENV/
__pycache__/
*.py[cod]
*$py.class
build/
dist/
.vscode/
.idea/
.DS_Store
.env

14
Dockerfile Normal file
View File

@@ -0,0 +1,14 @@
FROM python:3.13-slim
RUN apt-get update && apt-get install -y ffmpeg && apt-get clean && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY MusicBot /app/MusicBot
ENV PYTHONPATH=/app
CMD ["python", "./MusicBot/main.py"]

View File

@@ -176,7 +176,7 @@ class General(Cog):
@account.command(description="Ввести токен Яндекс Музыки.") @account.command(description="Ввести токен Яндекс Музыки.")
@discord.option("token", type=discord.SlashCommandOptionType.string, description="Токен.") @discord.option("token", type=discord.SlashCommandOptionType.string, description="Токен.")
async def login(self, ctx: discord.ApplicationContext, token: str) -> None: async def login(self, ctx: discord.ApplicationContext, token: str) -> None:
logging.info(f"[GENERAL] Login command invoked by user {ctx.author.id} in guild {ctx.guild.id}") logging.info(f"[GENERAL] Login command invoked by user {ctx.author.id} in guild {ctx.guild_id}")
try: try:
client = await YMClient(token).init() client = await YMClient(token).init()
except UnauthorizedError: except UnauthorizedError:
@@ -196,7 +196,7 @@ class General(Cog):
@account.command(description="Удалить токен из базы данных бота.") @account.command(description="Удалить токен из базы данных бота.")
async def remove(self, ctx: discord.ApplicationContext) -> None: async def remove(self, ctx: discord.ApplicationContext) -> None:
logging.info(f"[GENERAL] Remove command invoked by user {ctx.author.id} in guild {ctx.guild.id}") logging.info(f"[GENERAL] Remove command invoked by user {ctx.author.id} in guild {ctx.guild_id}")
if not await self.users_db.get_ym_token(ctx.user.id): if not await self.users_db.get_ym_token(ctx.user.id):
logging.info(f"[GENERAL] No token found for user {ctx.author.id}") logging.info(f"[GENERAL] No token found for user {ctx.author.id}")
await ctx.respond('❌ Токен не указан.', delete_after=15, ephemeral=True) await ctx.respond('❌ Токен не указан.', delete_after=15, ephemeral=True)
@@ -208,7 +208,7 @@ class General(Cog):
@account.command(description="Получить плейлист «Мне нравится»") @account.command(description="Получить плейлист «Мне нравится»")
async def likes(self, ctx: discord.ApplicationContext) -> None: async def likes(self, ctx: discord.ApplicationContext) -> None:
logging.info(f"[GENERAL] Likes command invoked by user {ctx.author.id} in guild {ctx.guild.id}") logging.info(f"[GENERAL] Likes command invoked by user {ctx.author.id} in guild {ctx.guild_id}")
token = await self.users_db.get_ym_token(ctx.user.id) token = await self.users_db.get_ym_token(ctx.user.id)
if not token: if not token:
@@ -223,7 +223,13 @@ class General(Cog):
await ctx.respond('❌ Неверный токен. Укажите новый через /account login.', delete_after=15, ephemeral=True) await ctx.respond('❌ Неверный токен. Укажите новый через /account login.', delete_after=15, ephemeral=True)
return return
likes = await client.users_likes_tracks() try:
likes = await client.users_likes_tracks()
except UnauthorizedError:
logging.warning(f"[GENERAL] Unknown token error for user {ctx.user.id}")
await ctx.respond("❌ Произошла неизвестная ошибка при попытке получения лайков. Пожалуйста, сообщите об этом разработчику.", delete_after=15, ephemeral=True)
return
if likes is None: if likes is None:
logging.info(f"[GENERAL] Failed to fetch likes for user {ctx.user.id}") logging.info(f"[GENERAL] Failed to fetch likes for user {ctx.user.id}")
await ctx.respond('❌ Что-то пошло не так. Повторите попытку позже.', delete_after=15, ephemeral=True) await ctx.respond('❌ Что-то пошло не так. Повторите попытку позже.', delete_after=15, ephemeral=True)

View File

@@ -1,3 +1,4 @@
import logging
from typing import Literal, cast from typing import Literal, cast
import discord import discord
@@ -19,7 +20,12 @@ class Settings(Cog):
@settings.command(name="show", description="Показать текущие настройки бота.") @settings.command(name="show", description="Показать текущие настройки бота.")
async def show(self, ctx: discord.ApplicationContext) -> None: async def show(self, ctx: discord.ApplicationContext) -> None:
guild = await self.db.get_guild(ctx.guild.id, projection={'allow_change_connect': 1, 'vote_switch_track': 1, 'vote_add': 1}) if not ctx.guild_id:
logging.warning("[SETTINGS] Show command invoked without guild_id")
await ctx.respond("❌ Эта команда может быть использована только на сервере.", ephemeral=True)
return
guild = await self.db.get_guild(ctx.guild_id, projection={'allow_change_connect': 1, 'vote_switch_track': 1, 'vote_add': 1})
vote = "✅ - Переключение" if guild['vote_switch_track'] else "❌ - Переключение" vote = "✅ - Переключение" if guild['vote_switch_track'] else "❌ - Переключение"
vote += "\n✅ - Добавление в очередь" if guild['vote_add'] else "\n❌ - Добавление в очередь" vote += "\n✅ - Добавление в очередь" if guild['vote_add'] else "\n❌ - Добавление в очередь"
@@ -49,20 +55,25 @@ class Settings(Cog):
if not member.guild_permissions.manage_channels: if not member.guild_permissions.manage_channels:
await ctx.respond("У вас нет прав для выполнения этой команды.", delete_after=15, ephemeral=True) await ctx.respond("У вас нет прав для выполнения этой команды.", delete_after=15, ephemeral=True)
return return
if not ctx.guild_id:
logging.warning("[SETTINGS] Toggle command invoked without guild_id")
await ctx.respond("❌ Эта команда может быть использована только на сервере.", ephemeral=True)
return
guild = await self.db.get_guild(ctx.guild.id, projection={ guild = await self.db.get_guild(ctx.guild_id, projection={
'vote_switch_track': 1, 'vote_add': 1, 'allow_change_connect': 1}) 'vote_switch_track': 1, 'vote_add': 1, 'allow_change_connect': 1})
if vote_type == 'Переключение': if vote_type == 'Переключение':
await self.db.update(ctx.guild.id, {'vote_switch_track': not guild['vote_switch_track']}) await self.db.update(ctx.guild_id, {'vote_switch_track': not guild['vote_switch_track']})
response_message = "Голосование за переключение трека " + ("❌ выключено." if guild['vote_switch_track'] else "✅ включено.") response_message = "Голосование за переключение трека " + ("❌ выключено." if guild['vote_switch_track'] else "✅ включено.")
elif vote_type == 'Добавление в очередь': elif vote_type == 'Добавление в очередь':
await self.db.update(ctx.guild.id, {'vote_add': not guild['vote_add']}) await self.db.update(ctx.guild_id, {'vote_add': not guild['vote_add']})
response_message = "Голосование за добавление в очередь " + ("❌ выключено." if guild['vote_add'] else "✅ включено.") response_message = "Голосование за добавление в очередь " + ("❌ выключено." if guild['vote_add'] else "✅ включено.")
elif vote_type == 'Добавление/Отключение бота': elif vote_type == 'Добавление/Отключение бота':
await self.db.update(ctx.guild.id, {'allow_change_connect': not guild['allow_change_connect']}) await self.db.update(ctx.guild_id, {'allow_change_connect': not guild['allow_change_connect']})
response_message = f"Добавление/Отключение бота от канала теперь {'✅ разрешено' if not guild['allow_change_connect'] else '❌ запрещено'} участникам без прав управления каналом." response_message = f"Добавление/Отключение бота от канала теперь {'✅ разрешено' if not guild['allow_change_connect'] else '❌ запрещено'} участникам без прав управления каналом."
else: else:

View File

@@ -352,8 +352,8 @@ class VoiceExtension:
Returns: Returns:
bool: Check result. bool: Check result.
""" """
if not ctx.user or not ctx.guild: if not ctx.user or not ctx.guild_id:
logging.warning("[VC_EXT] User or guild not found in context inside 'voice_check'") logging.warning("[VC_EXT] User or guild id not found in context inside 'voice_check'")
await ctx.respond("❌ Что-то пошло не так. Попробуйте еще раз.", delete_after=15, ephemeral=True) await ctx.respond("❌ Что-то пошло не так. Попробуйте еще раз.", delete_after=15, ephemeral=True)
return False return False
@@ -379,7 +379,7 @@ class VoiceExtension:
return False return False
if check_vibe_privilage: if check_vibe_privilage:
guild = await self.db.get_guild(ctx.guild.id, projection={'current_viber_id': 1, 'vibing': 1}) guild = await self.db.get_guild(ctx.guild_id, projection={'current_viber_id': 1, 'vibing': 1})
if guild['vibing'] and ctx.user.id != guild['current_viber_id']: if guild['vibing'] and ctx.user.id != guild['current_viber_id']:
logging.debug("[VIBE] Context user is not the current viber") logging.debug("[VIBE] Context user is not the current viber")
await ctx.respond("❌ Вы не можете взаимодействовать с чужой волной!", delete_after=15, ephemeral=True) await ctx.respond("❌ Вы не можете взаимодействовать с чужой волной!", delete_after=15, ephemeral=True)

View File

@@ -203,16 +203,21 @@ class Voice(Cog, VoiceExtension):
@voice.command(name="menu", description="Создать или обновить меню проигрывателя.") @voice.command(name="menu", description="Создать или обновить меню проигрывателя.")
async def menu(self, ctx: discord.ApplicationContext) -> None: async def menu(self, ctx: discord.ApplicationContext) -> None:
logging.info(f"[VOICE] Menu command invoked by user {ctx.author.id} in guild {ctx.guild.id}") logging.info(f"[VOICE] Menu command invoked by user {ctx.author.id} in guild {ctx.guild_id}")
if await self.voice_check(ctx) and not await self.send_menu_message(ctx): if await self.voice_check(ctx) and not await self.send_menu_message(ctx):
await ctx.respond("Не удалось создать меню.", ephemeral=True) await ctx.respond("Не удалось создать меню.", ephemeral=True)
@voice.command(name="join", description="Подключиться к голосовому каналу, в котором вы сейчас находитесь.") @voice.command(name="join", description="Подключиться к голосовому каналу, в котором вы сейчас находитесь.")
async def join(self, ctx: discord.ApplicationContext) -> None: async def join(self, ctx: discord.ApplicationContext) -> None:
logging.info(f"[VOICE] Join command invoked by user {ctx.author.id} in guild {ctx.guild.id}") logging.info(f"[VOICE] Join command invoked by user {ctx.author.id} in guild {ctx.guild_id}")
if not ctx.guild_id:
logging.warning("[VOICE] Join command invoked without guild_id")
await ctx.respond("❌ Эта команда может быть использована только на сервере.", ephemeral=True)
return
member = cast(discord.Member, ctx.author) member = cast(discord.Member, ctx.author)
guild = await self.db.get_guild(ctx.guild.id, projection={'allow_change_connect': 1}) guild = await self.db.get_guild(ctx.guild_id, projection={'allow_change_connect': 1})
await ctx.defer(ephemeral=True) await ctx.defer(ephemeral=True)
if not member.guild_permissions.manage_channels and not guild['allow_change_connect']: if not member.guild_permissions.manage_channels and not guild['allow_change_connect']:
@@ -234,13 +239,18 @@ class Voice(Cog, VoiceExtension):
@voice.command(description="Заставить бота покинуть голосовой канал.") @voice.command(description="Заставить бота покинуть голосовой канал.")
async def leave(self, ctx: discord.ApplicationContext) -> None: async def leave(self, ctx: discord.ApplicationContext) -> None:
logging.info(f"[VOICE] Leave command invoked by user {ctx.author.id} in guild {ctx.guild.id}") logging.info(f"[VOICE] Leave command invoked by user {ctx.author.id} in guild {ctx.guild_id}")
if not ctx.guild_id:
logging.warning("[VOICE] Leave command invoked without guild_id")
await ctx.respond("❌ Эта команда может быть использована только на сервере.", ephemeral=True)
return
member = cast(discord.Member, ctx.author) member = cast(discord.Member, ctx.author)
guild = await self.db.get_guild(ctx.guild.id, projection={'allow_change_connect': 1}) guild = await self.db.get_guild(ctx.guild_id, projection={'allow_change_connect': 1})
if not member.guild_permissions.manage_channels and not guild['allow_change_connect']: if not member.guild_permissions.manage_channels and not guild['allow_change_connect']:
logging.info(f"[VOICE] User {ctx.author.id} does not have permissions to execute leave command in guild {ctx.guild.id}") logging.info(f"[VOICE] User {ctx.author.id} does not have permissions to execute leave command in guild {ctx.guild_id}")
await ctx.respond("У вас нет прав для выполнения этой команды.", delete_after=15, ephemeral=True) await ctx.respond("У вас нет прав для выполнения этой команды.", delete_after=15, ephemeral=True)
return return
@@ -252,13 +262,18 @@ class Voice(Cog, VoiceExtension):
await vc.disconnect(force=True) await vc.disconnect(force=True)
await ctx.respond("✅ Отключение успешно!", delete_after=15, ephemeral=True) await ctx.respond("✅ Отключение успешно!", delete_after=15, ephemeral=True)
logging.info(f"[VOICE] Successfully disconnected from voice channel in guild {ctx.guild.id}") logging.info(f"[VOICE] Successfully disconnected from voice channel in guild {ctx.guild_id}")
else: else:
await ctx.respond("❌ Бот не подключен к голосовому каналу.", delete_after=15, ephemeral=True) await ctx.respond("❌ Бот не подключен к голосовому каналу.", delete_after=15, ephemeral=True)
@queue.command(description="Очистить очередь треков и историю прослушивания.") @queue.command(description="Очистить очередь треков и историю прослушивания.")
async def clear(self, ctx: discord.ApplicationContext) -> None: async def clear(self, ctx: discord.ApplicationContext) -> None:
logging.info(f"[VOICE] Clear queue command invoked by user {ctx.author.id} in guild {ctx.guild.id}") logging.info(f"[VOICE] Clear queue command invoked by user {ctx.author.id} in guild {ctx.guild_id}")
if not ctx.guild_id:
logging.warning("[VOICE] Clear command invoked without guild_id")
await ctx.respond("❌ Эта команда может быть использована только на сервере.", ephemeral=True)
return
if not await self.voice_check(ctx): if not await self.voice_check(ctx):
return return
@@ -267,7 +282,7 @@ class Voice(Cog, VoiceExtension):
channel = cast(discord.VoiceChannel, ctx.channel) channel = cast(discord.VoiceChannel, ctx.channel)
if len(channel.members) > 2 and not member.guild_permissions.manage_channels: if len(channel.members) > 2 and not member.guild_permissions.manage_channels:
logging.info(f"Starting vote for clearing queue in guild {ctx.guild.id}") logging.info(f"Starting vote for clearing queue in guild {ctx.guild_id}")
response_message = f"{member.mention} хочет очистить историю прослушивания и очередь треков.\n\n Выполнить действие?." response_message = f"{member.mention} хочет очистить историю прослушивания и очередь треков.\n\n Выполнить действие?."
message = cast(discord.Interaction, await ctx.respond(response_message, delete_after=60)) message = cast(discord.Interaction, await ctx.respond(response_message, delete_after=60))
@@ -289,19 +304,24 @@ class Voice(Cog, VoiceExtension):
) )
return return
await self.db.update(ctx.guild.id, {'previous_tracks': [], 'next_tracks': []}) await self.db.update(ctx.guild_id, {'previous_tracks': [], 'next_tracks': []})
await ctx.respond("✅ Очередь и история сброшены.", delete_after=15, ephemeral=True) await ctx.respond("✅ Очередь и история сброшены.", delete_after=15, ephemeral=True)
logging.info(f"[VOICE] Queue and history cleared in guild {ctx.guild.id}") logging.info(f"[VOICE] Queue and history cleared in guild {ctx.guild_id}")
@queue.command(description="Получить очередь треков.") @queue.command(description="Получить очередь треков.")
async def get(self, ctx: discord.ApplicationContext) -> None: async def get(self, ctx: discord.ApplicationContext) -> None:
logging.info(f"[VOICE] Get queue command invoked by user {ctx.author.id} in guild {ctx.guild.id}") logging.info(f"[VOICE] Get queue command invoked by user {ctx.author.id} in guild {ctx.guild_id}")
if not ctx.guild_id:
logging.warning("[VOICE] Get command invoked without guild_id")
await ctx.respond("❌ Эта команда может быть использована только на сервере.", ephemeral=True)
return
if not await self.voice_check(ctx): if not await self.voice_check(ctx):
return return
await self.users_db.update(ctx.user.id, {'queue_page': 0}) await self.users_db.update(ctx.user.id, {'queue_page': 0})
tracks = await self.db.get_tracks_list(ctx.guild.id, 'next') tracks = await self.db.get_tracks_list(ctx.guild_id, 'next')
if len(tracks) == 0: if len(tracks) == 0:
await ctx.respond("❌ Очередь пуста.", ephemeral=True) await ctx.respond("❌ Очередь пуста.", ephemeral=True)
return return
@@ -309,11 +329,16 @@ class Voice(Cog, VoiceExtension):
embed = generate_queue_embed(0, tracks) embed = generate_queue_embed(0, tracks)
await ctx.respond(embed=embed, view=await QueueView(ctx).init(), ephemeral=True) await ctx.respond(embed=embed, view=await QueueView(ctx).init(), ephemeral=True)
logging.info(f"[VOICE] Queue embed sent to user {ctx.author.id} in guild {ctx.guild.id}") logging.info(f"[VOICE] Queue embed sent to user {ctx.author.id} in guild {ctx.guild_id}")
@voice.command(description="Прервать проигрывание, удалить историю, очередь и текущий плеер.") @voice.command(description="Прервать проигрывание, удалить историю, очередь и текущий плеер.")
async def stop(self, ctx: discord.ApplicationContext) -> None: async def stop(self, ctx: discord.ApplicationContext) -> None:
logging.info(f"[VOICE] Stop command invoked by user {ctx.author.id} in guild {ctx.guild.id}") logging.info(f"[VOICE] Stop command invoked by user {ctx.author.id} in guild {ctx.guild_id}")
if not ctx.guild_id:
logging.warning("[VOICE] Stop command invoked without guild_id")
await ctx.respond("❌ Эта команда может быть использована только на сервере.", ephemeral=True)
return
if not await self.voice_check(ctx): if not await self.voice_check(ctx):
return return
@@ -322,7 +347,7 @@ class Voice(Cog, VoiceExtension):
channel = cast(discord.VoiceChannel, ctx.channel) channel = cast(discord.VoiceChannel, ctx.channel)
if len(channel.members) > 2 and not member.guild_permissions.manage_channels: if len(channel.members) > 2 and not member.guild_permissions.manage_channels:
logging.info(f"Starting vote for stopping playback in guild {ctx.guild.id}") logging.info(f"Starting vote for stopping playback in guild {ctx.guild_id}")
response_message = f"{member.mention} хочет полностью остановить проигрывание.\n\n Выполнить действие?." response_message = f"{member.mention} хочет полностью остановить проигрывание.\n\n Выполнить действие?."
message = cast(discord.Interaction, await ctx.respond(response_message, delete_after=60)) message = cast(discord.Interaction, await ctx.respond(response_message, delete_after=60))
@@ -351,7 +376,7 @@ class Voice(Cog, VoiceExtension):
else: else:
await ctx.respond("❌ Произошла ошибка при остановке воспроизведения.", delete_after=15, ephemeral=True) await ctx.respond("❌ Произошла ошибка при остановке воспроизведения.", delete_after=15, ephemeral=True)
@voice.command(name='vibe', description="Запустить Мою Волну.") @voice.command(description="Запустить Мою Волну.")
@discord.option( @discord.option(
"запрос", "запрос",
parameter_name='name', parameter_name='name',
@@ -360,15 +385,20 @@ class Voice(Cog, VoiceExtension):
autocomplete=discord.utils.basic_autocomplete(get_vibe_stations_suggestions), autocomplete=discord.utils.basic_autocomplete(get_vibe_stations_suggestions),
required=False required=False
) )
async def user_vibe(self, ctx: discord.ApplicationContext, name: str | None = None) -> None: async def vibe(self, ctx: discord.ApplicationContext, name: str | None = None) -> None:
logging.info(f"[VOICE] Vibe (user) command invoked by user {ctx.user.id} in guild {ctx.guild_id}") logging.info(f"[VOICE] Vibe (user) command invoked by user {ctx.user.id} in guild {ctx.guild_id}")
if not await self.voice_check(ctx): if not await self.voice_check(ctx):
return return
if not ctx.guild_id:
logging.warning("[VOICE] Vibe command invoked without guild_id")
await ctx.respond("❌ Эта команда может быть использована только на сервере.", ephemeral=True)
return
guild = await self.db.get_guild(ctx.guild.id, projection={'current_menu': 1, 'vibing': 1}) guild = await self.db.get_guild(ctx.guild_id, projection={'current_menu': 1, 'vibing': 1})
if guild['vibing']: if guild['vibing']:
logging.info(f"[VOICE] Action declined: vibing is already enabled in guild {ctx.guild.id}") logging.info(f"[VOICE] Action declined: vibing is already enabled in guild {ctx.guild_id}")
await ctx.respond("❌ Моя Волна уже включена. Используйте /voice stop, чтобы остановить воспроизведение.", delete_after=15, ephemeral=True) await ctx.respond("❌ Моя Волна уже включена. Используйте /voice stop, чтобы остановить воспроизведение.", delete_after=15, ephemeral=True)
return return
@@ -411,7 +441,7 @@ class Voice(Cog, VoiceExtension):
channel = cast(discord.VoiceChannel, ctx.channel) channel = cast(discord.VoiceChannel, ctx.channel)
if len(channel.members) > 2 and not member.guild_permissions.manage_channels: if len(channel.members) > 2 and not member.guild_permissions.manage_channels:
logging.info(f"Starting vote for starting vibe in guild {ctx.guild.id}") logging.info(f"Starting vote for starting vibe in guild {ctx.guild_id}")
if _type == 'user' and _id == 'onyourwave': if _type == 'user' and _id == 'onyourwave':
station = "Моя Волна" station = "Моя Волна"

View File

@@ -1,3 +1,4 @@
import os
from typing import Iterable, Any, cast from typing import Iterable, Any, cast
from pymongo import AsyncMongoClient, ReturnDocument, UpdateOne from pymongo import AsyncMongoClient, ReturnDocument, UpdateOne
from pymongo.asynchronous.collection import AsyncCollection from pymongo.asynchronous.collection import AsyncCollection
@@ -6,7 +7,8 @@ from pymongo.results import UpdateResult
from .user import User, ExplicitUser from .user import User, ExplicitUser
from .guild import Guild, ExplicitGuild, MessageVotes from .guild import Guild, ExplicitGuild, MessageVotes
client: AsyncMongoClient = AsyncMongoClient("mongodb://localhost:27017/") mongo_server = os.getenv('MONGO_URI')
client: AsyncMongoClient = AsyncMongoClient(mongo_server)
db = client.YandexMusicBot db = client.YandexMusicBot
users: AsyncCollection[ExplicitUser] = db.users users: AsyncCollection[ExplicitUser] = db.users

View File

@@ -18,7 +18,7 @@ class PlayButton(Button, VoiceExtension):
async def callback(self, interaction: Interaction) -> None: async def callback(self, interaction: Interaction) -> None:
logging.debug(f"[FIND] Callback triggered for type: '{type(self.item).__name__}'") logging.debug(f"[FIND] Callback triggered for type: '{type(self.item).__name__}'")
if not interaction.guild: if not interaction.guild_id:
logging.info("[FIND] No guild found in PlayButton callback") logging.info("[FIND] No guild found in PlayButton callback")
await interaction.respond("❌ Эта команда доступна только на серверах.", ephemeral=True, delete_after=15) await interaction.respond("❌ Эта команда доступна только на серверах.", ephemeral=True, delete_after=15)
return return
@@ -26,7 +26,7 @@ class PlayButton(Button, VoiceExtension):
if not await self.voice_check(interaction): if not await self.voice_check(interaction):
return return
guild = await self.db.get_guild(interaction.guild.id, projection={'current_track': 1, 'current_menu': 1, 'vote_add': 1, 'vibing': 1}) guild = await self.db.get_guild(interaction.guild_id, projection={'current_track': 1, 'current_menu': 1, 'vote_add': 1, 'vibing': 1})
if guild['vibing']: if guild['vibing']:
await interaction.respond("❌ Нельзя добавлять треки в очередь, пока запущена волна.", ephemeral=True, delete_after=15) await interaction.respond("❌ Нельзя добавлять треки в очередь, пока запущена волна.", ephemeral=True, delete_after=15)
return return
@@ -100,7 +100,7 @@ class PlayButton(Button, VoiceExtension):
await response.add_reaction('') await response.add_reaction('')
await self.db.update_vote( await self.db.update_vote(
interaction.guild.id, interaction.guild_id,
response.id, response.id,
{ {
'positive_votes': list(), 'positive_votes': list(),
@@ -119,11 +119,11 @@ class PlayButton(Button, VoiceExtension):
if guild['current_track']: if guild['current_track']:
logging.debug(f"[FIND] Adding tracks to queue") logging.debug(f"[FIND] Adding tracks to queue")
await self.db.modify_track(interaction.guild.id, tracks, 'next', 'extend') await self.db.modify_track(interaction.guild_id, tracks, 'next', 'extend')
else: else:
logging.debug(f"[FIND] Playing track") logging.debug(f"[FIND] Playing track")
track = tracks.pop(0) track = tracks.pop(0)
await self.db.modify_track(interaction.guild.id, tracks, 'next', 'extend') await self.db.modify_track(interaction.guild_id, tracks, 'next', 'extend')
if not await self.play_track(interaction, track): if not await self.play_track(interaction, track):
await interaction.respond('Не удалось воспроизвести трек.', ephemeral=True, delete_after=15) await interaction.respond('Не удалось воспроизвести трек.', ephemeral=True, delete_after=15)

View File

@@ -593,7 +593,7 @@ class MenuView(View, VoiceExtension):
if not self.ctx.guild_id: if not self.ctx.guild_id:
return self return self
self.guild = await self.db.get_guild(self.ctx.guild_id, projection={'repeat': 1, 'shuffle': 1, 'current_track': 1, 'vibing': 1}) self.guild = await self.db.get_guild(self.ctx.guild_id, projection={'repeat': 1, 'shuffle': 1, 'current_track': 1, 'current_menu': 1, 'vibing': 1})
if self.guild['repeat']: if self.guild['repeat']:
self.repeat_button.style = ButtonStyle.success self.repeat_button.style = ButtonStyle.success

View File

@@ -77,6 +77,72 @@ DEBUG='False' # Включение DEBUG логов (True/False)
Запустите бота (`python ./MusicBot/main.py`). Запустите бота (`python ./MusicBot/main.py`).
## Запуск в Docker
Возможен запуск как из командной строки, так и с помощью docker-compose.
### docker cli
>[!NOTE]
>При этом методе запуска вам необходимо самостоятельно установить MongoDB и указать адресс сервера в команде запуска.
```bash
docker run -d \
--name yandex-music-discord-bot \
--restart unless-stopped \
-e TOKEN=XXXXXX \
-e EXPLICIT_EID=1325879701117472869 \
-e DEBUG=False \
-e MONGO_URI="mongodb://mongodb:27017" \
deadcxap/yandexmusicdiscordbot:latest
```
### docker-compose (рекомендованный)
>[!NOTE]
>При первом запуске БД и коллекции будут созданы автоматически.
```yaml
---
services:
app:
container_name: yandex-music-discord-bot
image: deadcxap/yandexmusicdiscordbot:latest
restart: unless-stopped
depends_on:
- mongodb
env_file:
- .env
environment:
MONGO_URI: "mongodb://ymdb-mongodb:27017"
networks:
- ymdb_network
mongodb:
container_name: ymdb-mongodb
image: mongo:latest
restart: unless-stopped
volumes:
- mongodb_data:/data/db
- ./init-mongodb.js:/docker-entrypoint-initdb.d/init-mongodb.js:ro
networks:
- ymdb_network
healthcheck:
test: echo 'db.runCommand("ping").ok' | mongo localhost:27017 --quiet
interval: 30s
timeout: 10s
retries: 5
volumes:
mongodb_data:
networks:
ymdb_network:
```
```bash
docker-compose up -d
```
## Настройка бота ## Настройка бота
Так должны выглядить настройки бота: Так должны выглядить настройки бота:

33
docker-compose.yml Normal file
View File

@@ -0,0 +1,33 @@
services:
app:
container_name: yandex-music-discord-bot
image: deadcxap/yandexmusicdiscordbot:latest
restart: unless-stopped
depends_on:
- mongodb
env_file:
- .env
environment:
MONGO_URI: "mongodb://ymdb-mongodb:27017"
networks:
- ymdb_network
mongodb:
container_name: ymdb-mongodb
image: mongo:latest
restart: unless-stopped
volumes:
- mongodb_data:/data/db
- ./init-mongodb.js:/docker-entrypoint-initdb.d/init-mongodb.js:ro
networks:
- ymdb_network
healthcheck:
test: echo 'db.runCommand("ping").ok' | mongo localhost:27017 --quiet
interval: 30s
timeout: 10s
retries: 5
volumes:
mongodb_data:
networks:
ymdb_network:

3
init-mongodb.js Normal file
View File

@@ -0,0 +1,3 @@
db = db.getSiblingDB('YandexMusicBot');
db.createCollection('guilds');
db.createCollection('users');