Added customization for music controller buttons

This commit is contained in:
Choco
2025-05-12 17:48:49 +08:00
parent dadf4bd210
commit 164b4b10dd
4 changed files with 121 additions and 117 deletions

View File

@@ -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:

View File

@@ -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": {

View File

@@ -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"

View File

@@ -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)