diff --git a/function.py b/function.py index e8fcae5..da2ba00 100644 --- a/function.py +++ b/function.py @@ -166,8 +166,8 @@ def get_lang_non_async(guild_id: int, *keys) -> Union[list[str], str]: LANGS[lang] = open_json(os.path.join("langs", f"{lang}.json")) if len(keys) == 1: - return LANGS.get(lang, {}).get(keys[0], "Language pack not found!") - return [LANGS.get(lang, {}).get(key, "Language pack not found!") for key in keys] + return LANGS.get(lang, {}).get(keys[0], "Not found!") + return [LANGS.get(lang, {}).get(key, "Not found!") for key in keys] def format_bytes(bytes: int, unit: bool = False): if bytes <= 1_000_000_000: diff --git a/settings Example.json b/settings Example.json index ed29928..6a2021f 100644 --- a/settings Example.json +++ b/settings Example.json @@ -117,11 +117,44 @@ "color": "@@default_embed_color@@" } }, - "default_buttons": [ - ["back", "resume", "skip", {"stop": "red"}, "add"], - ["tracks"] - ], - "disableButtonText": false + "buttons": [ + { + "back": { + "emoji": "⏮️", + "label": "@@buttonBack@@" + }, + "play-pause": { + "states": { + "pause": { + "emoji": "⏸️", + "label": "@@buttonPause@@" + }, + "resume": { + "emoji": "▶️", + "label": "@@buttonResume@@" + } + } + }, + "skip": { + "emoji": "⏭️", + "label": "@@buttonSkip@@" + }, + "stop": { + "emoji": "⏹️", + "label": "@@buttonLeave@@", + "style": "red" + }, + "add-fav": { + "emoji": "❤️", + "label": "" + } + }, + { + "tracks": { + "label": "@@playerDropdown@@" + } + } + ] }, "default_voice_status_template": "{{@@track_name@@ != 'None' ?? @@track_source_emoji@@ Now Playing: @@track_name@@ // Waiting for song requests}}", "cooldowns": { diff --git a/update.py b/update.py index 1560d4b..cf537be 100644 --- a/update.py +++ b/update.py @@ -31,11 +31,10 @@ import subprocess from io import BytesIO ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) -__version__ = "v2.7.1" +__version__ = "v2.7.2b1" # URLs for update and migration PYTHON_CMD_NAME = os.path.basename(sys.executable) -print(PYTHON_CMD_NAME) GITHUB_API_URL = "https://api.github.com/repos/ChocoMeow/Vocard/releases/latest" VOCARD_URL = "https://github.com/ChocoMeow/Vocard/archive/" MIGRATION_SCRIPT_URL = f"https://raw.githubusercontent.com/ChocoMeow/Vocard-Magration/main/{__version__}.py" diff --git a/views/controller.py b/views/controller.py index d403e83..28993c6 100644 --- a/views/controller.py +++ b/views/controller.py @@ -29,23 +29,64 @@ import views import function as func from discord.ext import commands -from typing import Dict, Type +from typing import Optional, Dict, Type, Union, Any def key(interaction: discord.Interaction): return interaction.user +def replace_translations(player: "voicelink.Player", msg: str) -> str: + """Replace placeholders in the message with translated texts.""" + keys = re.findall(r'@@(.*?)@@', msg) + translated_texts = player.get_msg(*keys) + if not isinstance(translated_texts, list): + translated_texts = [translated_texts] + + for key, value in zip(keys, translated_texts): + if value: + msg = msg.replace(f"@@{key}@@", str(value)) + return msg + class ControlButton(discord.ui.Button): def __init__( self, player, - label: str = None, + btn_data: Dict[str, Any], + default_states: Optional[str] = None, **kwargs ): self.player: voicelink.Player = player - - self.disable_button_text: bool = func.settings.controller.get("disableButtonText", False) - super().__init__(label=self.player.get_msg(label) if label and not self.disable_button_text else None, **kwargs) + self.btn_data: Dict[str, Any] = btn_data + btn_config = self._get_button_config(default_states) + emoji = btn_config.get("emoji", btn_data.get("emoji")) + style = self._get_button_style(btn_config.get("style")) + label = replace_translations(self.player, btn_config.get("label")) + + super().__init__(emoji=emoji, style=style, label=label, **kwargs) + + def _get_button_config(self, states: Optional[str]) -> Dict[str, Any]: + """Retrieve button configuration based on states.""" + if states and "states" in self.btn_data: + return self.btn_data["states"].get(states, {}) + return self.btn_data + + def _get_button_style(self, style_name: Optional[str]) -> discord.ButtonStyle: + """Retrieve the corresponding ButtonStyle based on the provided style name.""" + if style_name: + for name, btn_style in discord.ButtonStyle.__members__.items(): + if name.lower() == style_name.lower(): + return btn_style + + return discord.ButtonStyle.gray + + def change_states(self, states: str) -> None: + """Change the button's emoji and label based on the provided state.""" + state_config = self._get_button_config(states) + if state_config: + self.emoji = state_config.get("emoji", self.emoji) + self.style = self._get_button_style(state_config.get("style")) + self.label = replace_translations(self.player, state_config.get("label", self.label)) + async def send(self, interaction: discord.Interaction, key: str, *params, view: discord.ui.View = None, ephemeral: bool = False) -> None: stay = self.player.settings.get("controller_msg", True) return await func.send( @@ -58,8 +99,6 @@ class ControlButton(discord.ui.Button): class Back(ControlButton): def __init__(self, **kwargs): super().__init__( - emoji="⏮️", - label="buttonBack", disabled=False if kwargs["player"].queue.history() or not kwargs["player"].current else True, **kwargs ) @@ -87,21 +126,20 @@ class Back(ControlButton): if self.player.queue._repeat.mode == voicelink.LoopType.TRACK: await self.player.set_repeat(voicelink.LoopType.OFF) -class Resume(ControlButton): +class PlayPause(ControlButton): def __init__(self, **kwargs): + self.playing_status = lambda player, reverse=False: "pause" if (player.is_paused and not reverse) or (not player.is_paused and reverse) else "resume" + super().__init__( - emoji="⏸️", - label="buttonPause", + default_states="pause", disabled=kwargs["player"].current is None, **kwargs ) async def callback(self, interaction: discord.Interaction): is_paused = not self.player.is_paused - vote_type = "pause" if is_paused else "resume" + vote_type = self.playing_status(self.player, True) votes = getattr(self.player, f"{vote_type}_votes") - emoji = "▶️" if is_paused else "⏸️" - button = "buttonResume" if is_paused else "buttonPause" if not self.player.is_privileged(interaction.user): if interaction.user in votes: @@ -111,19 +149,13 @@ class Resume(ControlButton): if len(votes) < (required := self.player.required()): return await self.send(interaction, f"{vote_type}Vote", interaction.user, len(votes), required) - self.emoji = emoji - if not self.disable_button_text: - self.label = await func.get_lang(interaction.guild.id, button) + self.change_states(self.playing_status(self.player)) await self.player.set_pause(is_paused, interaction.user) await interaction.response.edit_message(view=self.view) class Skip(ControlButton): def __init__(self, **kwargs): - super().__init__( - emoji="⏭️", - label="buttonSkip", - **kwargs - ) + super().__init__(**kwargs) async def callback(self, interaction: discord.Interaction): if not self.player.is_playing: @@ -148,11 +180,7 @@ class Skip(ControlButton): class Stop(ControlButton): def __init__(self, **kwargs): - super().__init__( - emoji="⏹️", - label="buttonLeave", - **kwargs - ) + super().__init__(**kwargs) async def callback(self, interaction: discord.Interaction): if not self.player.is_privileged(interaction.user): @@ -168,13 +196,9 @@ class Stop(ControlButton): await self.send(interaction, "left", interaction.user) await self.player.teardown() -class Add(ControlButton): +class AddFav(ControlButton): def __init__(self, **kwargs): - super().__init__( - emoji="❤️", - disabled=kwargs["player"].current is None, - **kwargs - ) + super().__init__(disabled=kwargs["player"].current is None, **kwargs) async def callback(self, interaction: discord.Interaction): track = self.player.current @@ -196,43 +220,25 @@ class Add(ControlButton): await self.send(interaction, "playlistAddError2", ephemeral=True) class Loop(ControlButton): - def __init__(self, **kwargs): - self.btn_emojis: dict[str, str] = { - "off": "🔁", - "track": "🔂", - "queue": "🔁" - } - + def __init__(self, **kwargs): super().__init__( - emoji=self.get_next_loop_emoji(kwargs["player"]), - label="buttonLoop", + default_states=kwargs["player"].queue.repeat.lower(), **kwargs ) - def get_next_loop_emoji(self, player) -> str: - current_repeat_mode = player.queue.repeat.lower() - if current_repeat_mode not in self.btn_emojis: - raise ValueError(f"Invalid repeat mode: {current_repeat_mode}") - - return self.btn_emojis[current_repeat_mode] - async def callback(self, interaction: discord.Interaction): if not self.player.is_privileged(interaction.user): return await self.send(interaction, 'missingPerms_mode', ephemeral=True) mode = await self.player.set_repeat(requester=interaction.user) - self.emoji = self.get_next_loop_emoji(self.player) - + self.change_states(mode) + await interaction.response.edit_message(view=self.view) await self.send(interaction, 'repeat', mode.name.capitalize()) class VolumeUp(ControlButton): def __init__(self, **kwargs): - super().__init__( - emoji="🔊", - label="buttonVolumeUp", - **kwargs - ) + super().__init__(**kwargs) async def callback(self, interaction: discord.Interaction): if not self.player.is_privileged(interaction.user): @@ -245,11 +251,7 @@ class VolumeUp(ControlButton): class VolumeDown(ControlButton): def __init__(self, **kwargs): - super().__init__( - emoji="🔉", - label="buttonVolumeDown", - **kwargs - ) + super().__init__(**kwargs) async def callback(self, interaction: discord.Interaction): if not self.player.is_privileged(interaction.user): @@ -263,8 +265,7 @@ class VolumeDown(ControlButton): class VolumeMute(ControlButton): def __init__(self, **kwargs): super().__init__( - emoji="🔇" if kwargs["player"].volume else "🔈", - label="buttonVolumeMute" if kwargs["player"].volume else "buttonVolumeUnmute", + default_states="muted" if kwargs["player"].volume else "mute" **kwargs ) @@ -274,20 +275,13 @@ class VolumeMute(ControlButton): is_muted = self.player.volume != 0 value = 0 if is_muted else self.player.settings.get("volume", 100) - self.emoji = "🔈" if is_muted else "🔇" - if not self.disable_button_text: - self.label = await func.get_lang(interaction.guild_id, "buttonVolumeUnmute" if is_muted else "buttonVolumeMute") - + self.change_states("muted" if value else "mute") await self.player.set_volume(value, interaction.user) await interaction.response.edit_message(view=self.view) class AutoPlay(ControlButton): def __init__(self, **kwargs): - super().__init__( - emoji="💡", - label="buttonAutoPlay", - **kwargs - ) + super().__init__(**kwargs) async def callback(self, interaction: discord.Interaction): if not self.player.is_privileged(interaction.user): @@ -302,11 +296,7 @@ class AutoPlay(ControlButton): class Shuffle(ControlButton): def __init__(self, **kwargs): - super().__init__( - emoji="🔀", - label="buttonShuffle", - **kwargs - ) + super().__init__(**kwargs) async def callback(self, interaction: discord.Interaction): if not self.player.is_privileged(interaction.user): @@ -325,8 +315,6 @@ class Shuffle(ControlButton): class Forward(ControlButton): def __init__(self, **kwargs): super().__init__( - emoji="⏩", - label="buttonForward", disabled=kwargs["player"].current is None, **kwargs ) @@ -346,8 +334,6 @@ class Forward(ControlButton): class Rewind(ControlButton): def __init__(self, **kwargs): super().__init__( - emoji="⏪", - label="buttonRewind", disabled=kwargs["player"].current is None, **kwargs ) @@ -367,8 +353,6 @@ class Rewind(ControlButton): class Lyrics(ControlButton): def __init__(self, **kwargs): super().__init__( - emoji="📜", - label="buttonLyrics", disabled=kwargs["player"].current is None, **kwargs ) @@ -390,8 +374,7 @@ class Lyrics(ControlButton): view.response = await self.send(interaction, view.build_embed(), view=view, ephemeral=True) class Tracks(discord.ui.Select): - def __init__(self, player, style, row): - + def __init__(self, player, btn_data, **kwargs): self.player: voicelink.Player = player options = [] @@ -401,9 +384,9 @@ class Tracks(discord.ui.Select): options.append(discord.SelectOption(label=f"{index}. {track.title[:40]}", description=f"{track.author[:30]} · " + ("Live" if track.is_stream else track.formatted_length), emoji=track.emoji)) super().__init__( - placeholder=self.player.get_msg("playerDropdown"), + placeholder=replace_translations(player, btn_data.get("label")), options=options, - row=row + **kwargs ) async def callback(self, interaction: discord.Interaction): @@ -417,7 +400,7 @@ class Tracks(discord.ui.Select): await func.send(interaction, "skipped", interaction.user) class Effects(discord.ui.Select): - def __init__(self, player, style, row): + def __init__(self, player, btn_data, row): self.player: voicelink.Player = player @@ -426,7 +409,7 @@ class Effects(discord.ui.Select): options.append(discord.SelectOption(label=name.capitalize(), value=name)) super().__init__( - placeholder=self.player.get_msg("playerFilter"), + placeholder=replace_translations(player, btn_data.get("label")), options=options, row=row ) @@ -448,12 +431,12 @@ class Effects(discord.ui.Select): await self.player.add_filter(selected_filter, requester=interaction.user) await func.send(interaction, "addEffect", selected_filter.tag) -BUTTON_TYPE: Dict[str, Type[ControlButton]] = { +BUTTON_TYPE: Dict[str, Type[Union[ControlButton, discord.ui.Select]]] = { "back": Back, - "resume": Resume, + "play-pause": PlayPause, "skip": Skip, "stop": Stop, - "add": Add, + "add-fav": AddFav, "loop": Loop, "volumeup": VolumeUp, "volumedown": VolumeDown, @@ -467,29 +450,18 @@ BUTTON_TYPE: Dict[str, Type[ControlButton]] = { "effects": Effects } -BUTTON_COLORS: Dict[str, discord.ButtonStyle] = { - "blue": discord.ButtonStyle.primary, - "grey": discord.ButtonStyle.secondary, - "red": discord.ButtonStyle.danger, - "green": discord.ButtonStyle.success -} - class InteractiveController(discord.ui.View): def __init__(self, player): super().__init__(timeout=None) self.player: voicelink.Player = player - for row, btnRow in enumerate(func.settings.controller.get("default_buttons")): - for btn in btnRow: - color = "" - if isinstance(btn, Dict): - color = list(btn.values())[0] - btn = list(btn.keys())[0] - btnClass = BUTTON_TYPE.get(btn.lower()) - style = BUTTON_COLORS.get(color.lower(), BUTTON_COLORS["grey"]) - if not btnClass or (self.player.queue.is_empty and btn == "tracks"): + for row_num, btn_row in enumerate(func.settings.controller.get("buttons")): + for btn_name, btn_data in btn_row.items(): + btn_class = BUTTON_TYPE.get(btn_name.lower()) + if not btn_class or (btn_name.lower() == "tracks" and player.queue.is_empty): continue - self.add_item(btnClass(player=player, style=style, row=row)) + + self.add_item(btn_class(player=player, btn_data=btn_data, row=row_num)) self.cooldown = commands.CooldownMapping.from_cooldown(2.0, 10.0, key)