feat: Add vote functionality.

This commit is contained in:
Lemon4ksan
2025-01-22 19:30:37 +03:00
parent 18ff925236
commit 859d60de35
8 changed files with 278 additions and 152 deletions

View File

@@ -12,9 +12,9 @@ from yandex_music import Track, Album, Artist, Playlist
from MusicBot.database import BaseUsersDatabase, BaseGuildsDatabase from MusicBot.database import BaseUsersDatabase, BaseGuildsDatabase
from MusicBot.cogs.utils.find import ( from MusicBot.cogs.utils.find import (
process_album, process_track, process_artist, process_playlist, process_album, process_track, process_artist, process_playlist,
ListenAlbum, ListenTrack, ListenArtist, ListenPlaylist ListenAlbum, ListenTrack, ListenArtist, ListenPlaylist, ListenLikesPlaylist
) )
from MusicBot.cogs.utils.misc import MyPlalists, ListenLikesPlaylist, generate_playlist_embed, generate_likes_embed from MusicBot.cogs.utils.misc import MyPlaylists, generate_playlist_embed, generate_likes_embed
def setup(bot): def setup(bot):
bot.add_cog(General(bot)) bot.add_cog(General(bot))
@@ -161,7 +161,7 @@ class General(Cog):
] ]
self.users_db.update(ctx.user.id, {'playlists': playlists, 'playlists_page': 0}) self.users_db.update(ctx.user.id, {'playlists': playlists, 'playlists_page': 0})
embed = generate_playlist_embed(0, playlists) embed = generate_playlist_embed(0, playlists)
await ctx.respond(embed=embed, view=MyPlalists(ctx), ephemeral=True) await ctx.respond(embed=embed, view=MyPlaylists(ctx), ephemeral=True)
@discord.slash_command(description="Найти контент и отправить информацию о нём. Возвращается лучшее совпадение.") @discord.slash_command(description="Найти контент и отправить информацию о нём. Возвращается лучшее совпадение.")
@discord.option( @discord.option(

View File

@@ -23,7 +23,7 @@ class Settings(Cog):
embed = discord.Embed(title="Настройки бота", color=0xfed42b) embed = discord.Embed(title="Настройки бота", color=0xfed42b)
explicit = "✅ - Разрешены" if guild['allow_explicit'] else "❌ - Запрещены" explicit = "✅ - Разрешены" if guild['allow_explicit'] else "❌ - Запрещены"
menu = "✅ - Всегда доступно" if guild['always_allow_menu'] else "❌ - Если в канале 1 пользователь." menu = "✅ - Всегда доступно" if guild['always_allow_menu'] else "❌ - Если в канале 1 человек."
vote = "✅ - Переключение" if guild['vote_next_track'] else "❌ - Переключение" vote = "✅ - Переключение" if guild['vote_next_track'] else "❌ - Переключение"
vote += "\n✅ - Добавление треков" if guild['vote_add_track'] else "\n❌ - Добавление треков" vote += "\n✅ - Добавление треков" if guild['vote_add_track'] else "\n❌ - Добавление треков"
@@ -32,7 +32,7 @@ class Settings(Cog):
vote += "\n✅ - Добавление плейлистов" if guild['vote_add_playlist'] else "\n❌ - Добавление плейлистов" vote += "\n✅ - Добавление плейлистов" if guild['vote_add_playlist'] else "\n❌ - Добавление плейлистов"
embed.add_field(name="__Explicit треки__", value=explicit, inline=False) embed.add_field(name="__Explicit треки__", value=explicit, inline=False)
embed.add_field(name="__Проигрыватель__", value=menu, inline=False) embed.add_field(name="__Меню проигрывателя__", value=menu, inline=False)
embed.add_field(name="__Голосование__", value=vote, inline=False) embed.add_field(name="__Голосование__", value=vote, inline=False)
await ctx.respond(embed=embed, ephemeral=True) await ctx.respond(embed=embed, ephemeral=True)
@@ -64,8 +64,8 @@ class Settings(Cog):
"vote_type", "vote_type",
description="Тип голосования.", description="Тип голосования.",
type=discord.SlashCommandOptionType.string, type=discord.SlashCommandOptionType.string,
choices=['+Всё', '-Всё', 'Переключение', '+Трек', '+Альбом', '+Плейлист'], choices=['+Всё', '-Всё', 'Переключение', 'Трек', 'Альбом', 'Плейлист'],
default='Всё' default='+Всё'
) )
async def vote(self, ctx: discord.ApplicationContext, vote_type: Literal['+Всё', '-Всё', 'Переключение', 'Трек', 'Альбом', 'Плейлист']) -> None: async def vote(self, ctx: discord.ApplicationContext, vote_type: Literal['+Всё', '-Всё', 'Переключение', 'Трек', 'Альбом', 'Плейлист']) -> None:
member = cast(discord.Member, ctx.author) member = cast(discord.Member, ctx.author)
@@ -97,18 +97,18 @@ class Settings(Cog):
response_message = "Голосование включено." response_message = "Голосование включено."
elif vote_type == 'Переключение': elif vote_type == 'Переключение':
self.db.update(ctx.guild.id, {'vote_next_track': not guild['vote_next_track']}) self.db.update(ctx.guild.id, {'vote_next_track': not guild['vote_next_track']})
response_message = "Голосование за переключение трека " + ("включено." if guild['vote_next_track'] else "выключено.") response_message = "Голосование за переключение трека " + ("выключено." if guild['vote_next_track'] else "включено.")
elif vote_type == 'Трек': elif vote_type == 'Трек':
self.db.update(ctx.guild.id, {'vote_add_track': not guild['vote_add_track']}) self.db.update(ctx.guild.id, {'vote_add_track': not guild['vote_add_track']})
response_message = "Голосование за добавление трека " + ("включено." if guild['vote_add_track'] else "выключено.") response_message = "Голосование за добавление трека " + ("выключено." if guild['vote_add_track'] else "включено.")
elif vote_type == 'Альбом': elif vote_type == 'Альбом':
self.db.update(ctx.guild.id, {'vote_add_album': not guild['vote_add_album']}) self.db.update(ctx.guild.id, {'vote_add_album': not guild['vote_add_album']})
response_message = "Голосование за добавление альбома " + ("включено." if guild['vote_add_album'] else "выключено.") response_message = "Голосование за добавление альбома " + ("выключено." if guild['vote_add_album'] else "включено.")
elif vote_type == 'Артист': elif vote_type == 'Артист':
self.db.update(ctx.guild.id, {'vote_add_artist': not guild['vote_add_artist']}) self.db.update(ctx.guild.id, {'vote_add_artist': not guild['vote_add_artist']})
response_message = "Голосование за добавление артиста " + ("включено." if guild['vote_add_artist'] else "выключено.") response_message = "Голосование за добавление артиста " + ("выключено." if guild['vote_add_artist'] else "включено.")
elif vote_type == 'Плейлист': elif vote_type == 'Плейлист':
self.db.update(ctx.guild.id, {'vote_add_playlist': not guild['vote_add_playlist']}) self.db.update(ctx.guild.id, {'vote_add_playlist': not guild['vote_add_playlist']})
response_message = "Голосование за добавление плейлиста " + ("включено." if guild['vote_add_playlist'] else "выключено.") response_message = "Голосование за добавление плейлиста " + ("выключено." if guild['vote_add_playlist'] else "включено.")
await ctx.respond(response_message, delete_after=15, ephemeral=True) await ctx.respond(response_message, delete_after=15, ephemeral=True)

View File

@@ -23,7 +23,26 @@ class PlayTrackButton(Button, VoiceExtension):
gid = interaction.guild.id gid = interaction.guild.id
guild = self.db.get_guild(gid) guild = self.db.get_guild(gid)
channel = cast(discord.VoiceChannel, interaction.channel)
member = cast(discord.Member, interaction.user)
if guild['vote_add_track'] and len(channel.members) > 2 and not member.guild_permissions.manage_channels:
message = cast(discord.Interaction, await interaction.respond(f"{member.mention} хочет добавить трек **{self.track.title}** в очередь.\n\n Голосуйте за добавление.", delete_after=30))
response = await message.original_response()
await response.add_reaction('')
await response.add_reaction('')
self.db.update_vote(
gid,
response.id,
{
'positive_votes': list(),
'negative_votes': list(),
'total_members': len(channel.members),
'action': 'add_track',
'vote_content': self.track.to_dict()
}
)
else:
if guild['current_track']: if guild['current_track']:
self.db.modify_track(gid, self.track, 'next', 'append') self.db.modify_track(gid, self.track, 'next', 'append')
response_message = f"Трек **{self.track.title}** был добавлен в очередь." response_message = f"Трек **{self.track.title}** был добавлен в очередь."
@@ -53,9 +72,28 @@ class PlayAlbumButton(Button, VoiceExtension):
gid = interaction.guild.id gid = interaction.guild.id
guild = self.db.get_guild(gid) guild = self.db.get_guild(gid)
channel = cast(discord.VoiceChannel, interaction.channel)
member = cast(discord.Member, interaction.user)
tracks: list[Track] = [track for volume in album.volumes for track in volume] tracks: list[Track] = [track for volume in album.volumes for track in volume]
if guild['vote_add_album'] and len(channel.members) > 2 and not member.guild_permissions.manage_channels:
message = cast(discord.Interaction, await interaction.respond(f"{member.mention} хочет добавить альбом **{self.album.title}** в очередь.\n\n Голосуйте за добавление.", delete_after=30))
response = await message.original_response()
await response.add_reaction('')
await response.add_reaction('')
self.db.update_vote(
gid,
response.id,
{
'positive_votes': list(),
'negative_votes': list(),
'total_members': len(channel.members),
'action': 'add_album',
'vote_content': [track.to_dict() for track in tracks]
}
)
else:
if guild['current_track'] is not None: if guild['current_track'] is not None:
self.db.modify_track(gid, tracks, 'next', 'extend') self.db.modify_track(gid, tracks, 'next', 'extend')
response_message = f"Альбом **{album.title}** был добавлен в очередь." response_message = f"Альбом **{album.title}** был добавлен в очередь."
@@ -86,9 +124,28 @@ class PlayArtistButton(Button, VoiceExtension):
gid = interaction.guild.id gid = interaction.guild.id
guild = self.db.get_guild(gid) guild = self.db.get_guild(gid)
channel = cast(discord.VoiceChannel, interaction.channel)
member = cast(discord.Member, interaction.user)
tracks: list[Track] = artist_tracks.tracks.copy() tracks: list[Track] = artist_tracks.tracks.copy()
if guild['vote_add_artist'] and len(channel.members) > 2 and not member.guild_permissions.manage_channels:
message = cast(discord.Interaction, await interaction.respond(f"{member.mention} хочет добавить треки от **{self.artist.name}** в очередь.\n\n Голосуйте за добавление.", delete_after=30))
response = await message.original_response()
await response.add_reaction('')
await response.add_reaction('')
self.db.update_vote(
gid,
response.id,
{
'positive_votes': list(),
'negative_votes': list(),
'total_members': len(channel.members),
'action': 'add_album',
'vote_content': [track.to_dict() for track in tracks]
}
)
else:
if guild['current_track'] is not None: if guild['current_track'] is not None:
self.db.modify_track(gid, tracks, 'next', 'extend') self.db.modify_track(gid, tracks, 'next', 'extend')
response_message = f"Песни артиста **{self.artist.name}** были добавлены в очередь." response_message = f"Песни артиста **{self.artist.name}** были добавлены в очередь."
@@ -104,6 +161,7 @@ class PlayArtistButton(Button, VoiceExtension):
await interaction.respond(response_message, delete_after=15) await interaction.respond(response_message, delete_after=15)
class PlayPlaylistButton(Button, VoiceExtension): class PlayPlaylistButton(Button, VoiceExtension):
def __init__(self, playlist: Playlist, **kwargs): def __init__(self, playlist: Playlist, **kwargs):
Button.__init__(self, **kwargs) Button.__init__(self, **kwargs)
VoiceExtension.__init__(self, None) VoiceExtension.__init__(self, None)
@@ -120,9 +178,28 @@ class PlayPlaylistButton(Button, VoiceExtension):
gid = interaction.guild.id gid = interaction.guild.id
guild = self.db.get_guild(gid) guild = self.db.get_guild(gid)
channel = cast(discord.VoiceChannel, interaction.channel)
member = cast(discord.Member, interaction.user)
tracks: list[Track] = [cast(Track, short_track.track) for short_track in short_tracks] tracks: list[Track] = [cast(Track, short_track.track) for short_track in short_tracks]
if guild['vote_add_playlist'] and len(channel.members) > 2 and not member.guild_permissions.manage_channels:
message = cast(discord.Interaction, await interaction.respond(f"{member.mention} хочет добавить плейлист **{self.playlist.title}** в очередь.\n\n Голосуйте за добавление.", delete_after=30))
response = await message.original_response()
await response.add_reaction('')
await response.add_reaction('')
self.db.update_vote(
gid,
response.id,
{
'positive_votes': list(),
'negative_votes': list(),
'total_members': len(channel.members),
'action': 'add_playlist',
'vote_content': [track.to_dict() for track in tracks]
}
)
else:
if guild['current_track'] is not None: if guild['current_track'] is not None:
self.db.modify_track(gid, tracks, 'next', 'extend') self.db.modify_track(gid, tracks, 'next', 'extend')
response_message = f"Плейлист **{self.playlist.title}** был добавлен в очередь." response_message = f"Плейлист **{self.playlist.title}** был добавлен в очередь."
@@ -137,6 +214,58 @@ class PlayPlaylistButton(Button, VoiceExtension):
await interaction.respond(response_message, delete_after=15) await interaction.respond(response_message, delete_after=15)
class PlayLikesButton(Button, VoiceExtension):
def __init__(self, playlist: list[Track], **kwargs):
Button.__init__(self, **kwargs)
VoiceExtension.__init__(self, None)
self.playlist = playlist
async def callback(self, interaction: Interaction):
if not interaction.guild or not await self.voice_check(interaction):
return
playlist = self.playlist.copy()
gid = interaction.guild.id
guild = self.db.get_guild(gid)
channel = cast(discord.VoiceChannel, interaction.channel)
member = cast(discord.Member, interaction.user)
if guild['vote_add_playlist'] and len(channel.members) > 2 and not member.guild_permissions.manage_channels:
message = cast(discord.Interaction, await interaction.respond(f"{member.mention} хочет добавить плейлист **«Мне нравится»** в очередь.\n\n Голосуйте за добавление.", delete_after=30))
response = await message.original_response()
await response.add_reaction('')
await response.add_reaction('')
self.db.update_vote(
gid,
response.id,
{
'positive_votes': list(),
'negative_votes': list(),
'total_members': len(channel.members),
'action': 'add_playlist',
'vote_content': [track.to_dict() for track in playlist]
}
)
else:
if guild['current_track'] is not None:
self.db.modify_track(gid, playlist, 'next', 'extend')
response_message = f"Плейлист **«Мне нравится»** был добавлен в очередь."
else:
track = playlist.pop(0)
self.db.modify_track(gid, playlist, 'next', 'extend')
await self.play_track(interaction, track)
response_message = f"Сейчас играет: **{track.title}**!"
if guild['current_player'] is not None and interaction.message:
await interaction.message.delete()
await interaction.respond(response_message, delete_after=15)
class ListenLikesPlaylist(View):
def __init__(self, playlist: list[Track], *items: Item, timeout: float | None = None, disable_on_timeout: bool = False):
super().__init__(*items, timeout=timeout, disable_on_timeout=disable_on_timeout)
self.add_item(PlayLikesButton(playlist, label="Слушать в голосовом канале", style=ButtonStyle.gray))
class ListenTrack(View): class ListenTrack(View):
def __init__(self, track: Track, *items: Item, timeout: float | None = None, disable_on_timeout: bool = False): def __init__(self, track: Track, *items: Item, timeout: float | None = None, disable_on_timeout: bool = False):

View File

@@ -62,39 +62,6 @@ def generate_queue_embed(page: int, tracks_list: list[dict[str, Any]]) -> Embed:
embed.add_field(name=f"{i} - {track['title']} - {duration_m}:{duration_s:02d}", value="", inline=False) embed.add_field(name=f"{i} - {track['title']} - {duration_m}:{duration_s:02d}", value="", inline=False)
return embed return embed
class PlayLikesButton(Button, VoiceExtension):
def __init__(self, playlist: list[Track], **kwargs):
Button.__init__(self, **kwargs)
VoiceExtension.__init__(self, None)
self.playlist = playlist
async def callback(self, interaction: Interaction):
if not interaction.guild or not await self.voice_check(interaction):
return
playlist = self.playlist.copy()
gid = interaction.guild.id
guild = self.db.get_guild(gid)
if guild['current_track'] is not None:
self.db.modify_track(gid, playlist, 'next', 'extend')
response_message = f"Плейлист **«Мне нравится»** был добавлен в очередь."
else:
track = playlist.pop(0)
self.db.modify_track(gid, playlist, 'next', 'extend')
await self.play_track(interaction, track)
response_message = f"Сейчас играет плейлист **{track.title}**!"
if guild['current_player'] is not None and interaction.message:
await interaction.message.delete()
await interaction.respond(response_message, delete_after=15)
class ListenLikesPlaylist(View):
def __init__(self, playlist: list[Track], *items: Item, timeout: float | None = None, disable_on_timeout: bool = False):
super().__init__(*items, timeout=timeout, disable_on_timeout=disable_on_timeout)
self.add_item(PlayLikesButton(playlist, label="Слушать в голосовом канале", style=ButtonStyle.gray))
class MPNextButton(Button, VoiceExtension): class MPNextButton(Button, VoiceExtension):
def __init__(self, **kwargs): def __init__(self, **kwargs):
Button.__init__(self, **kwargs) Button.__init__(self, **kwargs)
@@ -107,7 +74,7 @@ class MPNextButton(Button, VoiceExtension):
page = user['playlists_page'] + 1 page = user['playlists_page'] + 1
self.users_db.update(interaction.user.id, {'playlists_page': page}) self.users_db.update(interaction.user.id, {'playlists_page': page})
embed = generate_playlist_embed(page, user['playlists']) embed = generate_playlist_embed(page, user['playlists'])
await interaction.edit(embed=embed, view=MyPlalists(interaction)) await interaction.edit(embed=embed, view=MyPlaylists(interaction))
class MPPrevButton(Button, VoiceExtension): class MPPrevButton(Button, VoiceExtension):
def __init__(self, **kwargs): def __init__(self, **kwargs):
@@ -121,9 +88,9 @@ class MPPrevButton(Button, VoiceExtension):
page = user['playlists_page'] - 1 page = user['playlists_page'] - 1
self.users_db.update(interaction.user.id, {'playlists_page': page}) self.users_db.update(interaction.user.id, {'playlists_page': page})
embed = generate_playlist_embed(page, user['playlists']) embed = generate_playlist_embed(page, user['playlists'])
await interaction.edit(embed=embed, view=MyPlalists(interaction)) await interaction.edit(embed=embed, view=MyPlaylists(interaction))
class MyPlalists(View, VoiceExtension): class MyPlaylists(View, VoiceExtension):
def __init__(self, ctx: ApplicationContext | Interaction, *items: Item, timeout: float | None = 3600, disable_on_timeout: bool = True): def __init__(self, ctx: ApplicationContext | Interaction, *items: Item, timeout: float | None = 3600, disable_on_timeout: bool = True):
View.__init__(self, *items, timeout=timeout, disable_on_timeout=disable_on_timeout) View.__init__(self, *items, timeout=timeout, disable_on_timeout=disable_on_timeout)
VoiceExtension.__init__(self, None) VoiceExtension.__init__(self, None)

View File

@@ -1,4 +1,4 @@
from typing import cast, TypedDict, Literal from typing import cast
import discord import discord
from discord.ext.commands import Cog from discord.ext.commands import Cog
@@ -21,32 +21,26 @@ class Voice(Cog, VoiceExtension):
def __init__(self, bot: discord.Bot): def __init__(self, bot: discord.Bot):
VoiceExtension.__init__(self, bot) VoiceExtension.__init__(self, bot)
self.bot = bot self.bot = bot
MessageVotes = TypedDict('MessageVotes', {'positive_votes': set[int], 'negative_votes': set[int], 'total_members': int, 'action': Literal['next']})
self.vote_messages: dict[int, dict[int, MessageVotes]] = {}
@Cog.listener() @Cog.listener()
async def on_voice_state_update(self, member: discord.Member, before: discord.VoiceState, after: discord.VoiceState) -> None: async def on_voice_state_update(self, member: discord.Member, before: discord.VoiceState, after: discord.VoiceState) -> None:
gid = member.guild.id gid = member.guild.id
guild = self.db.get_guild(gid) guild = self.db.get_guild(gid)
if after.channel: channel = after.channel or before.channel
channel = cast(discord.VoiceChannel, after.channel)
else:
channel = cast(discord.VoiceChannel, before.channel)
if not channel: if not channel:
return return
discord_guild = await self.bot.fetch_guild(gid) discord_guild = await self.bot.fetch_guild(gid)
vc = cast((discord.VoiceClient | None), discord.utils.get(self.bot.voice_clients, guild=discord_guild)) vc = cast(discord.VoiceClient | None, discord.utils.get(self.bot.voice_clients, guild=discord_guild))
if len(channel.members) == 1 and vc: if len(channel.members) == 1 and vc:
self.db.clear_history(gid) self.db.clear_history(gid)
self.db.update(gid, {'current_track': None, 'is_stopped': True}) self.db.update(gid, {'current_track': None, 'is_stopped': True})
vc.stop() vc.stop()
if len(channel.members) > 2 and not guild['always_allow_menu']: elif len(channel.members) > 2 and not guild['always_allow_menu']:
current_player = self.db.get_current_player(gid) current_player = self.db.get_current_player(gid)
if current_player is not None: if current_player:
self.db.update(gid, {'current_player': None, 'repeat': False, 'shuffle': False}) self.db.update(gid, {'current_player': None, 'repeat': False, 'shuffle': False})
try: try:
message = await channel.fetch_message(current_player) message = await channel.fetch_message(current_player)
@@ -78,42 +72,58 @@ class Voice(Cog, VoiceExtension):
return return
guild_id = payload.guild_id guild_id = payload.guild_id
if guild_id not in self.vote_messages: if not guild_id:
return return
guild = self.db.get_guild(guild_id)
votes = guild['votes']
if payload.message_id not in self.vote_messages[guild_id]: vote_data = votes[str(payload.message_id)]
return
vote_data = self.vote_messages[guild_id][payload.message_id]
if payload.emoji.name == '': if payload.emoji.name == '':
vote_data['positive_votes'].add(payload.user_id) vote_data['positive_votes'].append(payload.user_id)
elif payload.emoji.name == '': elif payload.emoji.name == '':
vote_data['negative_votes'].add(payload.user_id) vote_data['negative_votes'].append(payload.user_id)
total_members = len(channel.members) total_members = len(channel.members)
if total_members <= 5: required_votes = 2 if total_members <= 5 else 4 if total_members <= 10 else 6 if total_members <= 15 else 9
required_votes = 2
elif total_members <= 10:
required_votes = 4
elif total_members <= 15:
required_votes = 6
else:
required_votes = 9
if len(vote_data['positive_votes']) >= required_votes: if len(vote_data['positive_votes']) >= required_votes:
if vote_data['action'] == 'next': if vote_data['action'] == 'next':
self.db.update(guild_id, {'is_stopped': False}) self.db.update(guild_id, {'is_stopped': False})
title = await self.next_track(payload) title = await self.next_track(payload)
await message.clear_reactions() await message.clear_reactions()
if title is not None:
await message.edit(content=f"Сейчас играет: **{title}**!", delete_after=15) await message.edit(content=f"Сейчас играет: **{title}**!", delete_after=15)
del self.vote_messages[guild_id][payload.message_id] del votes[str(payload.message_id)]
elif vote_data['action'] == 'add_track':
await message.clear_reactions()
track = vote_data['vote_content']
if not track:
return
self.db.update(guild_id, {'is_stopped': False})
self.db.modify_track(guild_id, track, 'next', 'append')
if guild['current_track']:
await message.edit(content=f"Трек был добавлен в очередь!", delete_after=15)
else:
title = await self.next_track(payload)
await message.edit(content=f"Сейчас играет: **{title}**!", delete_after=15)
del votes[str(payload.message_id)]
elif vote_data['action'] in ('add_album', 'add_artist', 'add_playlist'):
tracks = vote_data['vote_content']
await message.clear_reactions()
if not tracks:
return
self.db.update(guild_id, {'is_stopped': False})
self.db.modify_track(guild_id, tracks, 'next', 'extend')
if guild['current_track']:
await message.edit(content=f"Контент был добавлен в очередь!", delete_after=15)
else:
title = await self.next_track(payload)
await message.edit(content=f"Сейчас играет: **{title}**!", delete_after=15)
del votes[str(payload.message_id)]
elif len(vote_data['negative_votes']) >= required_votes: elif len(vote_data['negative_votes']) >= required_votes:
channel = cast(discord.VoiceChannel, self.bot.get_channel(payload.channel_id))
message = await channel.fetch_message(payload.message_id)
await message.clear_reactions() await message.clear_reactions()
await message.edit(content='Запрос был отклонён.', delete_after=15) await message.edit(content='Запрос был отклонён.', delete_after=15)
del self.vote_messages[guild_id][payload.message_id] del votes[str(payload.message_id)]
self.db.update(guild_id, {'votes': votes})
@Cog.listener() @Cog.listener()
async def on_raw_reaction_remove(self, payload: discord.RawReactionActionEvent) -> None: async def on_raw_reaction_remove(self, payload: discord.RawReactionActionEvent) -> None:
@@ -121,11 +131,10 @@ class Voice(Cog, VoiceExtension):
return return
guild_id = payload.guild_id guild_id = payload.guild_id
if guild_id not in self.vote_messages: if not guild_id:
return
if payload.message_id not in self.vote_messages[guild_id]:
return return
guild = self.db.get_guild(guild_id)
votes = guild['votes']
channel = cast(discord.VoiceChannel, self.bot.get_channel(payload.channel_id)) channel = cast(discord.VoiceChannel, self.bot.get_channel(payload.channel_id))
if not channel: if not channel:
@@ -135,11 +144,13 @@ class Voice(Cog, VoiceExtension):
if not message or message.author.id != self.bot.user.id: if not message or message.author.id != self.bot.user.id:
return return
vote_data = self.vote_messages[guild_id][payload.message_id] vote_data = votes[str(payload.message_id)]
if payload.emoji.name == '✔️': if payload.emoji.name == '✔️':
vote_data['positive_votes'].discard(payload.user_id) del vote_data['positive_votes'][payload.user_id]
elif payload.emoji.name == '': elif payload.emoji.name == '':
vote_data['negative_votes'].discard(payload.user_id) del vote_data['negative_votes'][payload.user_id]
self.db.update(guild_id, {'votes': votes})
@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:
@@ -280,33 +291,33 @@ class Voice(Cog, VoiceExtension):
if not await self.voice_check(ctx): if not await self.voice_check(ctx):
return return
gid = ctx.guild.id gid = ctx.guild.id
tracks_list = self.db.get_tracks_list(gid, 'next') guild = self.db.get_guild(gid)
if not tracks_list: if not guild['next_tracks']:
await ctx.respond("❌ Нет песенен в очереди.", delete_after=15, ephemeral=True) await ctx.respond("❌ Нет песенен в очереди.", delete_after=15, ephemeral=True)
return return
member = cast(discord.Member, ctx.author) member = cast(discord.Member, ctx.author)
channel = cast(discord.VoiceChannel, ctx.channel) channel = cast(discord.VoiceChannel, ctx.channel)
if self.db.get_track(gid, 'current') and len(channel.members) > 2 and not member.guild_permissions.manage_channels: if guild['vote_next_track'] and len(channel.members) > 2 and not member.guild_permissions.manage_channels:
message = cast(discord.Interaction, await ctx.respond(f"{ctx.user.mention} хочет пропустить текущий трек.\n\nВыполнить переход?", delete_after=30)) message = cast(discord.Interaction, await ctx.respond(f"{member.mention} хочет пропустить текущий трек.\n\nВыполнить переход?", delete_after=30))
response = await message.original_response() response = await message.original_response()
await response.add_reaction('') await response.add_reaction('')
await response.add_reaction('') await response.add_reaction('')
self.vote_messages[ctx.guild.id] = { self.db.update_vote(
response.id: { gid,
'positive_votes': set(), response.id,
'negative_votes': set(), {
'positive_votes': list(),
'negative_votes': list(),
'total_members': len(channel.members), 'total_members': len(channel.members),
'action': 'next' 'action': 'next',
} 'vote_content': None
} }
)
else: else:
self.db.update(gid, {'is_stopped': False}) self.db.update(gid, {'is_stopped': False})
title = await self.next_track(ctx) title = await self.next_track(ctx)
if title is not None:
await ctx.respond(f"Сейчас играет: **{title}**!", delete_after=15) await ctx.respond(f"Сейчас играет: **{title}**!", delete_after=15)
else:
await ctx.respond(f"Нет треков в очереди.", delete_after=15, ephemeral=True)
@track.command(description="Добавить трек в избранное или убрать, если он уже там.") @track.command(description="Добавить трек в избранное или убрать, если он уже там.")
async def like(self, ctx: discord.ApplicationContext) -> None: async def like(self, ctx: discord.ApplicationContext) -> None:

View File

@@ -2,7 +2,7 @@ from .base import BaseGuildsDatabase, BaseUsersDatabase
from .extensions import VoiceGuildsDatabase from .extensions import VoiceGuildsDatabase
from .user import User, ExplicitUser from .user import User, ExplicitUser
from .guild import Guild, ExplicitGuild from .guild import Guild, ExplicitGuild, MessageVotes
__all__ = [ __all__ = [
'BaseGuildsDatabase', 'BaseGuildsDatabase',
@@ -12,4 +12,5 @@ __all__ = [
'ExplicitUser', 'ExplicitUser',
'Guild', 'Guild',
'ExplicitGuild', 'ExplicitGuild',
'MessageVotes'
] ]

View File

@@ -5,8 +5,8 @@ from typing import cast
from pymongo import MongoClient from pymongo import MongoClient
from pymongo.collection import Collection from pymongo.collection import Collection
from MusicBot.database.user import User, ExplicitUser from .user import User, ExplicitUser
from MusicBot.database.guild import Guild, ExplicitGuild from .guild import Guild, ExplicitGuild, MessageVotes
client: MongoClient = MongoClient("mongodb://localhost:27017/") client: MongoClient = MongoClient("mongodb://localhost:27017/")
users: Collection[ExplicitUser] = client.YandexMusicBot.users users: Collection[ExplicitUser] = client.YandexMusicBot.users
@@ -91,14 +91,14 @@ class BaseGuildsDatabase:
is_stopped=True, is_stopped=True,
allow_explicit=True, allow_explicit=True,
always_allow_menu=False, always_allow_menu=False,
vote_add=True,
vote_next_track=True, vote_next_track=True,
vote_add_track=True, vote_add_track=True,
vote_add_album=True, vote_add_album=True,
vote_add_artist=True, vote_add_artist=True,
vote_add_playlist=True, vote_add_playlist=True,
shuffle=False, shuffle=False,
repeat=False repeat=False,
votes={}
)) ))
def update(self, gid: int, data: Guild) -> None: def update(self, gid: int, data: Guild) -> None:
@@ -135,14 +135,14 @@ class BaseGuildsDatabase:
is_stopped=True, is_stopped=True,
allow_explicit=True, allow_explicit=True,
always_allow_menu=False, always_allow_menu=False,
vote_add=True,
vote_next_track=True, vote_next_track=True,
vote_add_track=True, vote_add_track=True,
vote_add_album=True, vote_add_album=True,
vote_add_artist=True, vote_add_artist=True,
vote_add_playlist=True, vote_add_playlist=True,
shuffle=False, shuffle=False,
repeat=False repeat=False,
votes={}
) )
for field, default_value in fields.items(): for field, default_value in fields.items():
if field not in existing_fields and field != '_id': if field not in existing_fields and field != '_id':
@@ -151,3 +151,14 @@ class BaseGuildsDatabase:
return guild return guild
def update_vote(self, gid: int, mid: int, data: MessageVotes) -> None:
"""Update vote for a message in a guild.
Args:
gid (int): Guild id.
mid (int): Message id.
vote (bool): Vote value.
"""
guild = self.get_guild(gid)
guild['votes'][str(mid)] = data
guilds.update_one({'_id': gid}, {"$set": {'votes': guild['votes']}})

View File

@@ -1,4 +1,11 @@
from typing import TypedDict, Any from typing import TypedDict, Literal, Any
class MessageVotes(TypedDict):
positive_votes: list[int]
negative_votes: list[int]
total_members: int
action: Literal['next', 'add_track', 'add_album', 'add_artist', 'add_playlist']
vote_content: dict[str, Any] | list[dict[str, Any]] | None
class Guild(TypedDict, total=False): class Guild(TypedDict, total=False):
next_tracks: list[dict[str, Any]] next_tracks: list[dict[str, Any]]
@@ -8,7 +15,6 @@ class Guild(TypedDict, total=False):
is_stopped: bool is_stopped: bool
allow_explicit: bool allow_explicit: bool
always_allow_menu: bool always_allow_menu: bool
vote_add: bool
vote_next_track: bool vote_next_track: bool
vote_add_track: bool vote_add_track: bool
vote_add_album: bool vote_add_album: bool
@@ -16,6 +22,7 @@ class Guild(TypedDict, total=False):
vote_add_playlist: bool vote_add_playlist: bool
shuffle: bool shuffle: bool
repeat: bool repeat: bool
votes: dict[str, MessageVotes]
class ExplicitGuild(TypedDict): class ExplicitGuild(TypedDict):
_id: int _id: int
@@ -26,7 +33,6 @@ class ExplicitGuild(TypedDict):
is_stopped: bool # Prevents the `after` callback of play_track is_stopped: bool # Prevents the `after` callback of play_track
allow_explicit: bool allow_explicit: bool
always_allow_menu: bool always_allow_menu: bool
vote_add: bool
vote_next_track: bool vote_next_track: bool
vote_add_track: bool vote_add_track: bool
vote_add_album: bool vote_add_album: bool
@@ -34,3 +40,4 @@ class ExplicitGuild(TypedDict):
vote_add_playlist: bool vote_add_playlist: bool
shuffle: bool shuffle: bool
repeat: bool repeat: bool
votes: dict[str, MessageVotes]