From a8561066ef1c7f62a4bf116ce13351c0bbec3fb7 Mon Sep 17 00:00:00 2001 From: Choco Date: Sun, 11 May 2025 22:50:49 +0800 Subject: [PATCH 01/22] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index afe10e3..55a5870 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Vocard is a highly customizable Discord music bot, designed to deliver a user-fr * [Lavalink Server (Requires 4.0.0+)](https://github.com/freyacodes/Lavalink) ## Setup -Please see the [Setup Page](https://docs.vocard.xyz) in the docs to run this bot yourself! +Please see the [Setup Page](https://docs.vocard.xyz/latest/bot/setup) in the docs to run this bot yourself! ## Need Help? Join the [Vocard Support Discord](https://discord.gg/wRCgB7vBQv) for help or questions. From 032954467b840b73cd80346e68f46b73a14a6f77 Mon Sep 17 00:00:00 2001 From: Arnaud <80671351+Buco7854@users.noreply.github.com> Date: Sun, 11 May 2025 19:57:15 +0200 Subject: [PATCH 02/22] Create FR.json Add french translation --- langs/FR.json | 194 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 langs/FR.json diff --git a/langs/FR.json b/langs/FR.json new file mode 100644 index 0000000..3ae2e63 --- /dev/null +++ b/langs/FR.json @@ -0,0 +1,194 @@ +{ + "unknownException": "⚠️ Une erreur est survenue lors de l'exécution de la commande ! Veuillez réessayer plus tard ou rejoignez notre serveur Discord pour plus d'assistance.", + "enabled": "activé", + "disabled": "désactivé", + + "nodeReconnect": "Veuillez réessayer après la reconnexion du node.", + "noChannel": "Aucun salon vocal pour se connecter. Veuillez en fournir un ou rejoindre un salon vocal.", + "alreadyConnected": "Déjà connecté à un salon vocal.", + "noPermission": "Désolé ! Je n'ai pas les permissions pour rejoindre ou parler dans votre salon vocal.", + "noCreatePermission": "Désolé ! Je n'ai pas les permissions pour créer un salon de demande de chansons.", + "noPlaySource": "Impossible de trouver une source à lire !", + "noPlayer": "Aucun lecteur n'a été trouvé sur ce serveur.", + "notVote": "Cette commande nécessite votre vote ! Tapez `/vote` pour en savoir plus.", + "missingIntents": "Désolé, cette commande ne peut pas être exécutée car le bot n'a pas l'intent requis : `({0})`.", + "languageNotFound": "Aucun pack de langue trouvé ! Veuillez sélectionner un pack de langue existant.", + "changedLanguage": "Changement réussi vers le pack de langue `{0}`.", + "setPrefix": "C'est fait ! Mon préfixe dans votre serveur est désormais `{0}`. Essayez de lancer `{1}ping` pour tester.", + "setDJ": "Le DJ a été défini sur {0}.", + "setqueue": "Le mode file d'attente est défini sur `{0}`.", + "247": "Le mode 24/7 est maintenant `{0}`.", + "bypassVote": "Vous avez le système de vote `{0}`.", + "setVolume": "Le volume est réglé à `{0}`%", + "togglecontroller": "Vous avez maintenant `{0}` le contrôleur de musique.", + "toggleDuplicateTrack": "Vous avez maintenant `{0}` la prévention des morceaux en double.", + "toggleControllerMsg": "Vous avez maintenant `{0}` les messages du contrôleur musical.", + "settingsMenu": "Paramètres du serveur | {0}", + "settingsTitle": "❤️ Infos de base :", + "settingsValue": "```Préfixe : {0}\nLangue : {1}\nContrôleur musique : {2}\nRôle DJ : @{3}\nVote Bypass : {4}\n24/7 : {5}\nVolume par défaut : {6}%\nTemps de lecture : {7}```", + "settingsTitle2": "🔗 Infos sur la file :", + "settingsValue2": "```Mode de file : {0}\nMaximum de chansons : {1}\nPiste dupliquée : {2}```", + "settingsTitle3": "🎤 Infos sur l'état vocal :", + "settingsPermTitle": "✨ Permissions :", + "settingsPermValue": "```{0} Administrateur\n{1} Gérer_serveur\n{2} Gérer_salons\n{3} Gérer_messages```", + "pingTitle1": "Infos Bot :", + "pingTitle2": "Infos Lecteur :", + "pingfield1": "```Shard ID : {0}/{1}\nLatence Shard : {2:.3f}s {3}\nRégion : {4}```", + "pingfield2": "```Node : {0} - {1:.3f}s\nLecteurs : {2}\nRégion vocale : {3}```", + "addEffect": "Effet `{0}` appliqué.", + "clearEffect": "Les effets sonores ont été supprimés !", + "FilterTagAlreadyInUse": "Cet effet sonore est déjà utilisé ! Veuillez utiliser /cleareffect pour le retirer.", + + "playlistViewTitle": "📜 Toutes les playlists de {0}", + "playlistViewHeaders": "ID:,Durée:,Nom:,Morceaux:", + "playlistFooter": "Tapez /playlist play [playlist] pour ajouter une playlist à la file.", + "playlistNotFound": "Playlist [`{0}`] introuvable. Tapez /playlist view pour voir toutes vos playlists.", + "playlistNotAccess": "Désolé ! Vous n'êtes pas autorisé à accéder à cette playlist !", + "playlistNoTrack": "Désolé ! Il n'y a aucun morceau dans la playlist [`{0}`].", + "playlistNotAllow": "Cette commande n'est pas autorisée sur les playlists liées ou partagées.", + "playlistPlay": "La playlist [`{0}`] avec `{1}` chansons a été ajoutée à la file.", + "playlistOverText": "Désolé ! Le nom de la playlist ne peut pas dépasser 10 caractères.", + "playlistSameName": "Désolé ! Veuillez choisir un nouveau nom pour la playlist.", + "playlistDeleteError": "Vous n'êtes pas autorisé à supprimer la playlist par défaut.", + "playlistRemove": "Vous avez supprimé la playlist [`{0}`].", + "playlistSendErrorPlayer": "Désolé ! Vous ne pouvez pas vous envoyer une invitation à vous-même.", + "playlistSendErrorBot": "Désolé ! Vous ne pouvez pas envoyer une invitation à un bot.", + "playlistBelongs": "Désolé ! Cette playlist appartient à <@{0}>.", + "playlistShare": "Désolé ! Cette playlist a été partagée avec {0}.", + "playlistSent": "Désolé ! Vous avez déjà envoyé une invitation à cet utilisateur auparavant.", + "noPlaylistAcc": "{0} n'a pas créé de compte playlist.", + "overPlaylistCreation": "Vous ne pouvez pas créer plus de `{0}` playlists !", + "playlistExists": "La playlist [`{0}`] existe déjà.", + "playlistNotInvalidUrl": "Veuillez entrer un lien valide de playlist publique Spotify ou YouTube.", + "playlistCreated": "Vous avez créé `{0}` playlists. Tapez /playlist view pour plus d'infos.", + "playlistRenamed": "Vous avez renommé `{0}` en `{1}`.", + "playlistLimitTrack": "Vous avez atteint la limite ! Vous ne pouvez ajouter que `{0}` chansons à votre playlist.", + "playlistPlaylistLink": "Vous n'êtes pas autorisé à utiliser des liens de playlist.", + "playlistStream": "Vous n'êtes pas autorisé à ajouter des vidéos en streaming à votre playlist.", + "playlistPositionNotFound": "Impossible de trouver la position `{0}` dans votre playlist [`{1}`] !", + "playlistRemoved": "👋 **{0}** a été retiré de la playlist [`{2}`] de {1}.", + "playlistClear": "Vous avez vidé avec succès votre playlist [`{0}`].", + "playlistView": "Visualiseur de playlist", + "playlistViewDesc": "```Nom | ID : {0} | {1}\nTotal Morceaux : {2}\nPropriétaire : {3}\nType : {4}\n```", + "playlistViewPermsValue": "📖 Lecture : ✓ ✍🏽 Écriture : {0} 🗑️ Suppression : {1}", + "playlistViewPermsValue2": "📖 Lecture : {0}", + "playlistViewTrack": "Morceaux", + "playlistViewPage": "Page : {0}/{1} | Durée totale : {2}", + "inboxFull": "Désolé ! La boîte de réception de {0} est pleine.", + "inboxNoMsg": "Aucun message dans votre boîte de réception.", + "invitationSent": "Invitation envoyée à {0}.", + + "notInChannel": "{0}, vous devez être dans {1} pour utiliser les commandes vocales. Veuillez rejoindre à nouveau si vous êtes déjà en vocal !", + "noTrackPlaying": "Aucune chanson n'est en cours de lecture pour le moment", + "noTrackFound": "Aucune chanson trouvée pour cette requête ! Veuillez fournir une URL valide.", + "noLinkSupport": "La commande de recherche ne prend pas en charge les liens !", + "voted": "Vous avez voté !", + "missingPerms_pos": "Seul le DJ ou les admins peuvent changer la position.", + "missingPerms_mode": "Seul le DJ ou les admins peuvent changer le mode de boucle.", + "missingPerms_queue": "Seul le DJ ou les admins peuvent retirer un morceau de la file.", + "missingPerms_autoplay": "Seul le DJ ou les admins peuvent activer/désactiver l'autoplay !", + "missingPerms_function": "Seul le DJ ou un admin peut utiliser cette fonction.", + "timeFormatError": "Format de temps incorrect. Exemple : 2:42 ou 12:39:31", + "lyricsNotFound": "Paroles non trouvées. Tapez /lyrics pour chercher les paroles.", + "missingTrackInfo": "Certaines informations sur le morceau sont manquantes.", + "noVoiceChannel": "Aucun salon vocal trouvé !", + + "playlistAddError": "Vous n'êtes pas autorisé à ajouter des vidéos en streaming à votre playlist !", + "playlistAddError2": "Problème lors de l'ajout de morceaux à la playlist !", + "playlistlimited": "Vous avez atteint la limite ! Vous ne pouvez ajouter que {0} chansons à votre playlist.", + "playlistrepeated": "Ce morceau est déjà dans votre playlist !", + "playlistAdded": "❤️ **{0}** ajouté à la playlist [`{2}`] de {1} !", + + "playerDropdown": "Sélectionnez une chanson vers laquelle passer...", + "playerFilter": "Sélectionnez un filtre à appliquer...", + + "buttonBack": "Précédent", + "buttonPause": "Pause", + "buttonResume": "Reprendre", + "buttonSkip": "Passer", + "buttonLeave": "Quitter", + "buttonLoop": "Boucle", + "buttonVolumeUp": "Volume +", + "buttonVolumeDown": "Volume -", + "buttonVolumeMute": "Muet", + "buttonVolumeUnmute": "Son activé", + "buttonAutoPlay": "Autoplay", + "buttonShuffle": "Mélanger", + "buttonForward": "Avancer", + "buttonRewind": "Rembobiner", + "buttonLyrics": "Paroles", + + "nowplayingDesc": "**Lecture en cours :**\n```{0}```", + "nowplayingField": "À suivre :", + "nowplayingLink": "Écouter sur {0}", + + "connect": "Connecté à {0}", + + "live": "EN DIRECT", + "playlistLoad": " 🎶 La playlist **{0}** avec `{1}` chansons a été ajoutée à la file.", + "trackLoad": "**[{0}](<{1}>)** de **{2}** (`{3}`) a été ajoutée et commencera à jouer.\n", + "trackLoad_pos": "**[{0}](<{1}>)** de **{2}** (`{3}`) a été ajoutée à la file à la position **{4}**\n", + + "searchTitle": "Recherche : {0}", + "searchDesc": "➥ Plateforme : {0} **{1}**\n➥ Résultats : **{2}**\n\n{3}", + "searchWait": "Choisissez la chanson à ajouter à la file.", + "searchTimeout": "Recherche expirée. Veuillez réessayer plus tard.", + "searchSuccess": "Chanson ajoutée à la file.", + + "queueTitle": "File à venir :", + "historyTitle": "Historique :", + "viewTitle": "File de musiques", + "viewDesc": "**Lecture en cours : [Cliquez ici]({0}) ⮯**\n{1}", + "viewFooter": "Page : {0}/{1} | Durée totale : {2}", + + "pauseError": "Le lecteur est déjà en pause.", + "pauseVote": "{0} a voté pour mettre la chanson en pause. [{1}/{2}]", + "paused": "`{0}` a mis le lecteur en pause.", + "resumeError": "Le lecteur n'est pas en pause.", + "resumeVote": "{0} a voté pour reprendre la chanson. [{1}/{2}]", + "resumed": "`{0}` a repris le lecteur.", + "shuffleError": "Ajoutez plus de chansons à la file avant de mélanger.", + "shuffleVote": "{0} a voté pour mélanger la file. [{1}/{2}]", + "shuffled": "La file a été mélangée.", + "skipError": "Aucune chanson vers laquelle passer.", + "skipVote": "{0} a voté pour passer la chanson. [{1}/{2}]", + "skipped": "`{0}` a passé la chanson.", + + "backVote": "{0} a voté pour passer à la chanson précédente. [{1}/{2}]", + "backed": "`{0}` est en train de passer à la chanson précédente.", + + "leaveVote": "{0} a voté pour arrêter le lecteur. [{1}/{2}]", + "left": "`{0}` a arrêté le lecteur.", + + "seek": "Le lecteur a été placé à **{0}**", + "repeat": "Le mode de répétition a été réglé sur `{0}`", + "cleared": "Tous les morceaux dans `{0}` ont été supprimés", + "removed": "`{0}` morceaux ont été retirés de la file.", + "forward": "Le lecteur a avancé à **{0}**", + "rewind": "Le lecteur a été rembobiné à **{0}**", + "replay": "Relecture de la chanson actuelle.", + "swapped": "`{0}` et `{1}` ont été échangés", + "moved": "`{0}` a été déplacé vers `{1}`", + "autoplay": "Le mode autoplay est maintenant **{0}**", + + "notdj": "Vous n'êtes pas DJ. Le DJ actuel est {0}.", + "djToMe": "Impossible de transférer le rôle de DJ à vous-même ou à un bot.", + "djnotinchannel": "`{0}` n'est pas dans le salon vocal.", + "djswap": "Vous avez transféré le rôle de DJ à `{0}`.", + + "chaptersDropdown": "Sélectionnez un chapitre vers lequel passer ...", + "noChaptersFound": "Aucun chapitre trouvé !", + "chatpersNotSupport": "Cette commande ne supporte que les vidéos YouTube !", + + "voicelinkQueueFull": "Désolé, vous avez atteint le maximum de `{0}` morceaux dans la file !", + "voicelinkOutofList": "Veuillez fournir un index de morceau valide !", + "voicelinkDuplicateTrack": "Désolé, ce morceau est déjà dans la file.", + + "deocdeError": "Une erreur s'est produite lors du décodage du fichier !", + "invalidStartTime": "Heure de début invalide, doit être entre `00:00` et `{0}`", + "invalidEndTime": "Heure de fin invalide, doit être entre `00:00` et `{0}`", + "invalidTimeOrder": "L'heure de fin ne peut pas être inférieure ou égale à celle de début", + + "setStageAnnounceTemplate": "C'est fait ! Désormais, le statut vocal tel que celui dans lequel vous êtes sera nommé selon votre modèle. Vous devriez le voir se mettre à jour dans quelques secondes.", + "createSongRequestChannel": "Un salon de demande de chansons ({0}) a été créé ! Vous pouvez commencer à demander n'importe quelle chanson par nom ou URL dans ce salon, sans besoin d'utiliser le préfixe du bot." +} From 27f7f64fc0c1959c5e78b04524ae239644cb1c42 Mon Sep 17 00:00:00 2001 From: Azarath7 <158289825+Azarath7@users.noreply.github.com> Date: Mon, 12 May 2025 09:37:31 +0300 Subject: [PATCH 03/22] Update application.yml to use the latest youtube source version + added comments --- lavalink/application.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lavalink/application.yml b/lavalink/application.yml index 9c17b12..40196f1 100644 --- a/lavalink/application.yml +++ b/lavalink/application.yml @@ -100,9 +100,9 @@ plugins: recommendationsLoadLimit: 10 # Number of tracks lavalink: plugins: - - dependency: "dev.lavalink.youtube:youtube-plugin:1.12.0" + - dependency: "dev.lavalink.youtube:youtube-plugin:1.13.2" # Please check the latest version at https://github.com/lavalink-devs/youtube-source/releases snapshot: false - - dependency: "com.github.topi314.lavasrc:lavasrc-plugin:4.6.0" + - dependency: "com.github.topi314.lavasrc:lavasrc-plugin:4.6.0" # Please check the latest version at https://github.com/topi314/LavaSrc/releases snapshot: false # - dependency: "com.github.example:example-plugin:1.0.0" # required, the coordinates of your plugin # repository: "https://maven.example.com/releases" # optional, defaults to the Lavalink releases repository by default @@ -192,4 +192,4 @@ logging: logback: rollingpolicy: max-file-size: 1GB - max-history: 30 \ No newline at end of file + max-history: 30 From 164b4b10dd08c55a075947a38bd6f37def3aa552 Mon Sep 17 00:00:00 2001 From: Choco Date: Mon, 12 May 2025 17:48:49 +0800 Subject: [PATCH 04/22] Added customization for music controller buttons --- function.py | 4 +- settings Example.json | 43 ++++++++-- update.py | 3 +- views/controller.py | 188 ++++++++++++++++++------------------------ 4 files changed, 121 insertions(+), 117 deletions(-) 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) From 2219820e550db79a6e6e7568f0528b5102b63436 Mon Sep 17 00:00:00 2001 From: Choco Date: Tue, 13 May 2025 16:52:47 +0800 Subject: [PATCH 05/22] Supported local languages in placeholder --- voicelink/placeholders.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/voicelink/placeholders.py b/voicelink/placeholders.py index b1f361e..771a4cd 100644 --- a/voicelink/placeholders.py +++ b/voicelink/placeholders.py @@ -70,6 +70,10 @@ class Placeholders: "server_invite_link": func.settings.invite_link, "invite_link": f"https://discord.com/oauth2/authorize?client_id={self.bot.user.id}&permissions=2184260928&scope=bot%20applications.commands" } + + self.regexes = { + "@@t_(.*?)@@": self.translation + } def get_current(self) -> Track: return self.player.current if self.player else None @@ -152,11 +156,14 @@ class Placeholders: def bot_icon(self) -> str: return self.bot.user.display_avatar.url if self.player else "https://i.imgur.com/dIFBwU7.png" + + def translation(self, text: str) -> str: + return self.player.get_msg(text) def replace(self, text: str, variables: dict[str, str]) -> str: if not text or text.isspace(): return - pattern = r"\{\{(.*?)\}\}" - matches: list[str] = re.findall(pattern, text) + + matches: list[str] = re.findall(r"\{\{(.*?)\}\}", text) for match in matches: parts: list[str] = match.split("??") @@ -185,6 +192,8 @@ class Placeholders: except: text = text.replace("{{" + match + "}}", "") + for regex_pattern, func in self.regexes.items(): + text = re.sub(regex_pattern, lambda m: func(m.group(1)), text) text = re.sub(r'@@(.*?)@@', lambda x: str(variables.get(x.group(1), '')), text) return text From 47972a4eb5ba5835d90cc52b98624689a55fae1c Mon Sep 17 00:00:00 2001 From: Choco Date: Tue, 13 May 2025 16:53:07 +0800 Subject: [PATCH 06/22] Added peek next in loop type --- voicelink/queue.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/voicelink/queue.py b/voicelink/queue.py index 10beee0..cb5bd3b 100644 --- a/voicelink/queue.py +++ b/voicelink/queue.py @@ -38,6 +38,13 @@ class LoopTypeCycle: self.current = next(self._cycle) return self.current + def peek_next(self) -> LoopType: + temp_cycle = cycle(LoopType) + while next(temp_cycle) != self.current: + pass + + return next(temp_cycle) + def set_mode(self, value: LoopType) -> LoopType: while next(self._cycle) != value: pass From 5b721c59fc4b1e5c26d58d320f6e1eb2fac930e9 Mon Sep 17 00:00:00 2001 From: Choco Date: Tue, 13 May 2025 16:53:56 +0800 Subject: [PATCH 07/22] Fixed an error when passing delete_after into Webhook --- function.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/function.py b/function.py index da2ba00..f6fea93 100644 --- a/function.py +++ b/function.py @@ -212,8 +212,8 @@ async def send( # Determine the sending function send_func = ( ctx.send if isinstance(ctx, commands.Context) else - ctx.response.send_message if not ctx.response.is_done() else - ctx.followup.send + ctx.followup.send if ctx.response.is_done() else + ctx.response.send_message ) # Check settings for delete_after duration @@ -221,10 +221,21 @@ async def send( if settings and ctx.channel.id == settings.get("music_request_channel", {}).get("text_channel_id"): delete_after = 10 - # Send the message or embed + send_kwargs = { + "content": text, + "embed": embed, + "ephemeral": ephemeral, + "allowed_mentions": ALLOWED_MENTIONS + } + + if hasattr(send_func, "delete_after"): + send_kwargs["delete_after"] = delete_after + if view: - return await send_func(text, embed=embed, view=view, delete_after=delete_after, ephemeral=ephemeral, allowed_mentions=ALLOWED_MENTIONS) - return await send_func(text, embed=embed, delete_after=delete_after, ephemeral=ephemeral, allowed_mentions=ALLOWED_MENTIONS) + send_kwargs["view"] = view + + # Send the message or embed + return await send_func(**send_kwargs) async def update_db(db: AsyncIOMotorCollection, tempStore: dict, filter: dict, data: dict) -> bool: for mode, action in data.items(): From 60451b294b3626ced3f3df21c47c2cc664aa498d Mon Sep 17 00:00:00 2001 From: Choco Date: Tue, 13 May 2025 16:58:04 +0800 Subject: [PATCH 08/22] Supported placeholder in music controller button --- settings Example.json | 13 +++++---- views/controller.py | 68 +++++++++++++++++++------------------------ 2 files changed, 37 insertions(+), 44 deletions(-) diff --git a/settings Example.json b/settings Example.json index 6a2021f..3a359b6 100644 --- a/settings Example.json +++ b/settings Example.json @@ -121,27 +121,27 @@ { "back": { "emoji": "⏮️", - "label": "@@buttonBack@@" + "label": "@@t_buttonBack@@" }, "play-pause": { "states": { "pause": { "emoji": "⏸️", - "label": "@@buttonPause@@" + "label": "@@t_buttonPause@@" }, "resume": { "emoji": "▶️", - "label": "@@buttonResume@@" + "label": "@@t_buttonResume@@" } } }, "skip": { "emoji": "⏭️", - "label": "@@buttonSkip@@" + "label": "@@t_buttonSkip@@" }, "stop": { "emoji": "⏹️", - "label": "@@buttonLeave@@", + "label": "@@t_buttonLeave@@", "style": "red" }, "add-fav": { @@ -151,7 +151,8 @@ }, { "tracks": { - "label": "@@playerDropdown@@" + "label": "@@t_playerDropdown@@", + "max_options": 10 } } ] diff --git a/views/controller.py b/views/controller.py index 28993c6..28a83b7 100644 --- a/views/controller.py +++ b/views/controller.py @@ -33,36 +33,20 @@ 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, + player: "voicelink.Player", btn_data: Dict[str, Any], default_states: Optional[str] = None, **kwargs ): + super().__init__(**kwargs) + self.player: voicelink.Player = player 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) + self.change_states(default_states) def _get_button_config(self, states: Optional[str]) -> Dict[str, Any]: """Retrieve button configuration based on states.""" @@ -81,11 +65,12 @@ class ControlButton(discord.ui.Button): def change_states(self, states: str) -> None: """Change the button's emoji and label based on the provided state.""" + states = states.lower() if states else None state_config = self._get_button_config(states) if state_config: - self.emoji = state_config.get("emoji", self.emoji) + self.emoji = state_config.get("emoji") or None self.style = self._get_button_style(state_config.get("style")) - self.label = replace_translations(self.player, state_config.get("label", self.label)) + self.label = self.player._ph.replace(state_config.get("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) @@ -220,9 +205,9 @@ class AddFav(ControlButton): await self.send(interaction, "playlistAddError2", ephemeral=True) class Loop(ControlButton): - def __init__(self, **kwargs): + def __init__(self, **kwargs): super().__init__( - default_states=kwargs["player"].queue.repeat.lower(), + default_states=kwargs["player"].queue._repeat.peek_next().name, **kwargs ) @@ -231,7 +216,7 @@ class Loop(ControlButton): return await self.send(interaction, 'missingPerms_mode', ephemeral=True) mode = await self.player.set_repeat(requester=interaction.user) - self.change_states(mode) + self.change_states(self.player.queue._repeat.peek_next().name) await interaction.response.edit_message(view=self.view) await self.send(interaction, 'repeat', mode.name.capitalize()) @@ -265,7 +250,7 @@ class VolumeDown(ControlButton): class VolumeMute(ControlButton): def __init__(self, **kwargs): super().__init__( - default_states="muted" if kwargs["player"].volume else "mute" + default_states="muted" if kwargs["player"].volume else "mute", **kwargs ) @@ -374,21 +359,25 @@ 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, btn_data, **kwargs): + def __init__(self, player: "voicelink.Player", btn_data, **kwargs): self.player: voicelink.Player = player + if player.queue.is_empty: + raise ValueError("Player queue is empty, cannot create Tracks row instance.") + options = [] for index, track in enumerate(self.player.queue.tracks(), start=1): - if index > 10: + if index > min(max(btn_data.get("max_options", 10), 1), 25): break 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=replace_translations(player, btn_data.get("label")), + placeholder=self.player._ph.replace(btn_data.get("label"), {}), options=options, + disabled=player.queue.is_empty, **kwargs ) - + async def callback(self, interaction: discord.Interaction): if not self.player.is_privileged(interaction.user): return await func.send(interaction, "missingPerms_function", ephemeral=True) @@ -400,7 +389,7 @@ class Tracks(discord.ui.Select): await func.send(interaction, "skipped", interaction.user) class Effects(discord.ui.Select): - def __init__(self, player, btn_data, row): + def __init__(self, player: "voicelink.Player", btn_data, row): self.player: voicelink.Player = player @@ -409,11 +398,11 @@ class Effects(discord.ui.Select): options.append(discord.SelectOption(label=name.capitalize(), value=name)) super().__init__( - placeholder=replace_translations(player, btn_data.get("label")), + placeholder=self.player._ph.replace(btn_data.get("label"), {}), options=options, row=row ) - + async def callback(self, interaction: discord.Interaction): if not self.player.is_privileged(interaction.user): return await func.send(interaction, "missingPerms_function", ephemeral=True) @@ -458,11 +447,14 @@ class InteractiveController(discord.ui.View): 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): + if not btn_class: continue - - self.add_item(btn_class(player=player, btn_data=btn_data, row=row_num)) - + + try: + self.add_item(btn_class(player=player, btn_data=btn_data, row=row_num)) + except ValueError: + pass + self.cooldown = commands.CooldownMapping.from_cooldown(2.0, 10.0, key) async def interaction_check(self, interaction: discord.Interaction): @@ -482,7 +474,7 @@ class InteractiveController(discord.ui.View): await func.send(interaction, "notInChannel", interaction.user.mention, self.player.channel.mention, ephemeral=True) return False - async def on_error(self, interaction: discord.Interaction, error: Exception, item: discord.ui.Item): + async def on_error(self, interaction: discord.Interaction, error: Exception, item: discord.ui.Item) -> None: if isinstance(error, views.ButtonOnCooldown): sec = int(error.retry_after) await interaction.response.send_message(f"You're on cooldown for {sec} second{'' if sec == 1 else 's'}!", ephemeral=True) From f581980dd8cc7ff0be91516c5c9037bad4304a33 Mon Sep 17 00:00:00 2001 From: Choco Date: Tue, 13 May 2025 17:23:14 +0800 Subject: [PATCH 09/22] Fixed a bug --- function.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/function.py b/function.py index f6fea93..4f2d66f 100644 --- a/function.py +++ b/function.py @@ -228,7 +228,7 @@ async def send( "allowed_mentions": ALLOWED_MENTIONS } - if hasattr(send_func, "delete_after"): + if "delete_after" in send_func.__code__.co_varnames: send_kwargs["delete_after"] = delete_after if view: From 4009832240d13c3823b316bff2565415fb8a3f54 Mon Sep 17 00:00:00 2001 From: Choco Date: Tue, 13 May 2025 17:28:25 +0800 Subject: [PATCH 10/22] Update controller.py --- views/controller.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/views/controller.py b/views/controller.py index 28a83b7..a832614 100644 --- a/views/controller.py +++ b/views/controller.py @@ -215,11 +215,10 @@ class Loop(ControlButton): 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) + await self.player.set_repeat(requester=interaction.user) self.change_states(self.player.queue._repeat.peek_next().name) await interaction.response.edit_message(view=self.view) - await self.send(interaction, 'repeat', mode.name.capitalize()) class VolumeUp(ControlButton): def __init__(self, **kwargs): From 8b81c05d98a210df7e421ee9fc98e84e32e93f17 Mon Sep 17 00:00:00 2001 From: Choco Date: Wed, 14 May 2025 09:37:33 +0800 Subject: [PATCH 11/22] Disable yt ratelimit if there are no tokens provided --- voicelink/pool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/voicelink/pool.py b/voicelink/pool.py index ec625d0..abe75d2 100644 --- a/voicelink/pool.py +++ b/voicelink/pool.py @@ -113,7 +113,7 @@ class Node: self._players: Dict[int, Player] = {} self._info: Optional[NodeInfo] = None - self.yt_ratelimit: Optional[YTRatelimit] = STRATEGY.get(yt_ratelimit.get("strategy"))(self, yt_ratelimit) if yt_ratelimit else None + self.yt_ratelimit: Optional[YTRatelimit] = STRATEGY.get(yt_ratelimit.get("strategy"))(self, yt_ratelimit) if yt_ratelimit and yt_ratelimit.get("tokens") else None self._bot.add_listener(self._update_handler, "on_socket_response") From 5b84959908415f7bf35d8417ed4b0caa5517ca4d Mon Sep 17 00:00:00 2001 From: Choco Date: Wed, 28 May 2025 17:55:18 +0800 Subject: [PATCH 12/22] Update methods.py --- ipc/methods.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipc/methods.py b/ipc/methods.py index 8365b2b..a3c3cca 100644 --- a/ipc/methods.py +++ b/ipc/methods.py @@ -314,7 +314,7 @@ async def toggleAutoplay(player: Player, member: Member, data: Dict) -> Dict: return { "op": "toggleAutoplay", "status": check, - "guildId": player.guild.id, + "guildId": str(player.guild.id), "requesterId": str(member.id) } From 026b262752ca135d22585bd014008172c4b4ce7f Mon Sep 17 00:00:00 2001 From: Choco Date: Wed, 11 Jun 2025 14:34:24 +0800 Subject: [PATCH 13/22] return empty list if track not found --- voicelink/pool.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/voicelink/pool.py b/voicelink/pool.py index abe75d2..427fcfd 100644 --- a/voicelink/pool.py +++ b/voicelink/pool.py @@ -399,6 +399,9 @@ class Node: return [] tracks = await self.get_tracks(query=query, requester=self.bot.user) + if not track: + return [] + if isinstance(tracks, Playlist): tracks = tracks.tracks From 15d0ac3fc80185b78414bc6373bd64f0698bf865 Mon Sep 17 00:00:00 2001 From: Choco Date: Sun, 15 Jun 2025 10:44:12 +0800 Subject: [PATCH 14/22] Dump lavalink and plugin version --- docker-compose.yml | 1 - lavalink/Dockerfile-lavalink | 2 +- lavalink/application.yml | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 6a74b4d..7d9d039 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -101,7 +101,6 @@ services: # - 8000:8000 # networks: # - vocard - # - web vocard: container_name: vocard diff --git a/lavalink/Dockerfile-lavalink b/lavalink/Dockerfile-lavalink index 48804af..c5809ce 100644 --- a/lavalink/Dockerfile-lavalink +++ b/lavalink/Dockerfile-lavalink @@ -5,7 +5,7 @@ RUN groupadd -g 322 lavalink && \ WORKDIR /opt/Lavalink -ADD --chown=lavalink:lavalink https://github.com/lavalink-devs/Lavalink/releases/download/4.0.8/Lavalink.jar . +ADD --chown=lavalink:lavalink https://github.com/lavalink-devs/Lavalink/releases/download/4.1.1/Lavalink.jar . RUN apt-get update && \ apt-get install -y --no-install-recommends netcat-openbsd && \ diff --git a/lavalink/application.yml b/lavalink/application.yml index 40196f1..19b7cb0 100644 --- a/lavalink/application.yml +++ b/lavalink/application.yml @@ -100,9 +100,9 @@ plugins: recommendationsLoadLimit: 10 # Number of tracks lavalink: plugins: - - dependency: "dev.lavalink.youtube:youtube-plugin:1.13.2" # Please check the latest version at https://github.com/lavalink-devs/youtube-source/releases + - dependency: "dev.lavalink.youtube:youtube-plugin:1.13.3" # Please check the latest version at https://github.com/lavalink-devs/youtube-source/releases snapshot: false - - dependency: "com.github.topi314.lavasrc:lavasrc-plugin:4.6.0" # Please check the latest version at https://github.com/topi314/LavaSrc/releases + - dependency: "com.github.topi314.lavasrc:lavasrc-plugin:4.7.0" # Please check the latest version at https://github.com/topi314/LavaSrc/releases snapshot: false # - dependency: "com.github.example:example-plugin:1.0.0" # required, the coordinates of your plugin # repository: "https://maven.example.com/releases" # optional, defaults to the Lavalink releases repository by default From c3c1f2e3e78b9071e8439ae7ade0849dfbb13b32 Mon Sep 17 00:00:00 2001 From: Choco Date: Mon, 16 Jun 2025 10:08:49 +0800 Subject: [PATCH 15/22] Update docker-image.yml --- .github/workflows/docker-image.yml | 42 +++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 53b4f72..9e05fa9 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -2,9 +2,13 @@ name: Docker Image CI on: push: - branches: [ "main" ] + tags: + - '*' # Trigger on any tag + branches: + - main # Trigger on push to main branch + - beta # Trigger on push to beta branch paths-ignore: - - '**.md' + - '**.md' permissions: packages: write @@ -14,16 +18,28 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - name: Build the Docker image - run: docker build . --file Dockerfile --tag vocard:latest + - uses: actions/checkout@v4 - - name: Log in to GitHub Docker Registry - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + - name: Build the Docker image + run: | + if [[ $GITHUB_REF == refs/tags/* ]]; then + TAG=${GITHUB_REF#refs/tags/} + IMAGE_NAME="ghcr.io/chocomeow/vocard:${TAG}" + elif [[ $GITHUB_REF == refs/heads/beta ]]; then + IMAGE_NAME="ghcr.io/chocomeow/vocard:beta" + else + IMAGE_NAME="ghcr.io/chocomeow/vocard:latest" + fi + + echo "Building image: $IMAGE_NAME" + docker build . --file Dockerfile --tag $IMAGE_NAME + + - name: Log in to GitHub Docker Registry + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin - - name: Push the Docker image - run: | - docker tag vocard:latest ghcr.io/chocomeow/vocard:latest # Ensure lowercase - docker push ghcr.io/chocomeow/vocard:latest # Ensure lowercase \ No newline at end of file + - name: Push the Docker image + run: | + echo "Pushing image: $IMAGE_NAME" + docker push $IMAGE_NAME \ No newline at end of file From c23aa5c11e8bcbfd11be4c19cff0ad6828b88db6 Mon Sep 17 00:00:00 2001 From: Choco Date: Mon, 16 Jun 2025 10:15:07 +0800 Subject: [PATCH 16/22] Update docker-image.yml --- .github/workflows/docker-image.yml | 40 ++++++++++++++++++------------ 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 9e05fa9..3ffbf59 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -2,7 +2,7 @@ name: Docker Image CI on: push: - tags: + tags: - '*' # Trigger on any tag branches: - main # Trigger on push to main branch @@ -19,21 +19,28 @@ jobs: steps: - uses: actions/checkout@v4 - + + - name: Determine Image Tag + id: image_tag + run: | + if [ "${{ github.ref_type }}" == "tag" ]; then + IMAGE_NAME="ghcr.io/chocomeow/vocard:${{ github.ref_name }}" + elif [ "${{ github.ref }}" == "refs/heads/beta" ]; then + IMAGE_NAME="ghcr.io/chocomeow/vocard:beta" + elif [ "${{ github.ref }}" == "refs/heads/main" ]; then + IMAGE_NAME="ghcr.io/chocomeow/vocard:latest" + else + echo "Error: Triggered on an unexpected ref: ${{ github.ref }}" + exit 1 + fi + echo "IMAGE_NAME=${IMAGE_NAME}" >> $GITHUB_ENV + echo "Set image name to: ${IMAGE_NAME}" + - name: Build the Docker image run: | - if [[ $GITHUB_REF == refs/tags/* ]]; then - TAG=${GITHUB_REF#refs/tags/} - IMAGE_NAME="ghcr.io/chocomeow/vocard:${TAG}" - elif [[ $GITHUB_REF == refs/heads/beta ]]; then - IMAGE_NAME="ghcr.io/chocomeow/vocard:beta" - else - IMAGE_NAME="ghcr.io/chocomeow/vocard:latest" - fi - - echo "Building image: $IMAGE_NAME" - docker build . --file Dockerfile --tag $IMAGE_NAME - + echo "Building image: ${{ env.IMAGE_NAME }}" + docker build . --file Dockerfile --tag ${{ env.IMAGE_NAME }} + - name: Log in to GitHub Docker Registry env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -41,5 +48,6 @@ jobs: - name: Push the Docker image run: | - echo "Pushing image: $IMAGE_NAME" - docker push $IMAGE_NAME \ No newline at end of file + echo "Pushing image: ${{ env.IMAGE_NAME }}" + docker push ${{ env.IMAGE_NAME }} + echo "Successfully pushed ${{ env.IMAGE_NAME }}" \ No newline at end of file From 7c5caa8d6d944317dfdf3d097e2fcc695ea2a0cf Mon Sep 17 00:00:00 2001 From: Choco Date: Thu, 3 Jul 2025 16:10:41 +0800 Subject: [PATCH 17/22] Updated the search platform from spotify to youtube --- cogs/basic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cogs/basic.py b/cogs/basic.py index 9eb6d36..a7d4730 100644 --- a/cogs/basic.py +++ b/cogs/basic.py @@ -93,7 +93,7 @@ class Basic(commands.Cog): node = voicelink.NodePool.get_node() if not node: return [] - tracks: list[voicelink.Track] = await node.get_tracks(current, requester=interaction.user, search_type=SearchType.SPOTIFY) + tracks: list[voicelink.Track] = await node.get_tracks(current, requester=interaction.user) return [app_commands.Choice(name=truncate_string(f"🎵 {track.author} - {track.title}", 100), value=truncate_string(f"{track.author} - {track.title}", 100)) for track in tracks] if tracks else [] history = {track["identifier"]: track for track_id in reversed(await get_user(interaction.user.id, "history")) if (track := voicelink.decode(track_id))["uri"]} From c7d19b938368c2def56c3f3a69b534d8000b5d32 Mon Sep 17 00:00:00 2001 From: Choco Date: Thu, 3 Jul 2025 16:11:01 +0800 Subject: [PATCH 18/22] Change variable name --- voicelink/placeholders.py | 18 +++++++++--------- voicelink/player.py | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/voicelink/placeholders.py b/voicelink/placeholders.py index 771a4cd..407225c 100644 --- a/voicelink/placeholders.py +++ b/voicelink/placeholders.py @@ -197,39 +197,39 @@ class Placeholders: text = re.sub(r'@@(.*?)@@', lambda x: str(variables.get(x.group(1), '')), text) return text -def build_embed(raw: dict[str, dict], placeholder: Placeholders) -> Embed: +def build_embed(embed_form: dict[str, dict], placeholder: Placeholders) -> Embed: embed = Embed() try: rv = {key: func() if callable(func) else func for key, func in placeholder.variables.items()} - if author := raw.get("author"): + if author := embed_form.get("author"): embed.set_author( name = placeholder.replace(author.get("name"), rv), url = placeholder.replace(author.get("url"), rv), icon_url = placeholder.replace(author.get("icon_url"), rv) ) - if title := raw.get("title"): + if title := embed_form.get("title"): embed.title = placeholder.replace(title.get("name"), rv) embed.url = placeholder.replace(title.get("url"), rv) - if fields := raw.get("fields", []): + if fields := embed_form.get("fields", []): for f in fields: embed.add_field(name=placeholder.replace(f.get("name"), rv), value=placeholder.replace(f.get("value", ""), rv), inline=f.get("inline", False)) - if footer := raw.get("footer"): + if footer := embed_form.get("footer"): embed.set_footer( text = placeholder.replace(footer.get("text"), rv), icon_url = placeholder.replace(footer.get("icon_url"), rv) ) - if thumbnail := raw.get("thumbnail"): + if thumbnail := embed_form.get("thumbnail"): embed.set_thumbnail(url = placeholder.replace(thumbnail, rv)) - if image := raw.get("image"): + if image := embed_form.get("image"): embed.set_image(url = placeholder.replace(image, rv)) - embed.description = placeholder.replace(raw.get("description"), rv) - embed.color = int(placeholder.replace(raw.get("color"), rv)) + embed.description = placeholder.replace(embed_form.get("description"), rv) + embed.color = int(placeholder.replace(embed_form.get("color"), rv)) except: pass diff --git a/voicelink/player.py b/voicelink/player.py index 81148e6..e53dc1d 100644 --- a/voicelink/player.py +++ b/voicelink/player.py @@ -302,9 +302,9 @@ class Player(VoiceProtocol): def build_embed(self, current_track: Track = None): """Builds an embed based on the current track state.""" controller = self.settings.get("default_controller", func.settings.controller).get("embeds", {}) - raw = controller.get("active" if current_track else "inactive", {}) + embed_form = controller.get("active" if current_track else "inactive", {}) - return build_embed(raw, self._ph) + return build_embed(embed_form, self._ph) async def send(self, method: RequestMethod, query: str = None, data: Union[Dict, str] = {}) -> Dict: """Sends an HTTP request to the node with the given method, query, and data.""" From 955d7168c5075ceea4a12bfa31b089b6cfe8ca66 Mon Sep 17 00:00:00 2001 From: Choco Date: Mon, 7 Jul 2025 14:39:39 +0800 Subject: [PATCH 19/22] Added musixmatch lyrics --- addons/lyrics.py | 147 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 139 insertions(+), 8 deletions(-) diff --git a/addons/lyrics.py b/addons/lyrics.py index 40b8e1d..e05168a 100644 --- a/addons/lyrics.py +++ b/addons/lyrics.py @@ -21,9 +21,17 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import aiohttp, random, bs4, re +import aiohttp +import random +import re +import hmac +import hashlib +import base64 +import json +import urllib.parse import function as func +from datetime import datetime from abc import ABC, abstractmethod from urllib.parse import quote from math import floor @@ -71,9 +79,6 @@ Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_6; en-US) AppleWebKit/530.9 (KHTM Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_6; en-US) AppleWebKit/530.6 (KHTML, like Gecko) Chrome/ Safari/530.6 Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_6; en-US) AppleWebKit/530.5 (KHTML, like Gecko) Chrome/ Safari/530.5''' -LYRIST_ENDPOINT = "https://lyrist.vercel.app/api/" -LRCLIB_ENDPOINT = "https://lrclib.net/api/" - class LyricsPlatform(ABC): @abstractmethod async def get_lyrics(self, title: str, artist: str) -> Optional[dict[str, str]]: @@ -207,9 +212,12 @@ class Genius(LyricsPlatform): return {"default": song.lyrics} class Lyrist(LyricsPlatform): + def __init__(self): + self.base_url: str = "https://lyrist.vercel.app/api/" + async def get_lyrics(self, title: str, artist: str) -> Optional[dict[str, str]]: try: - request_url = LYRIST_ENDPOINT + title + "/" + artist + request_url = self.base_url + title + "/" + artist async with aiohttp.ClientSession() as session: resp = await session.get(url=request_url, headers={'User-Agent': random.choice(userAgents)}) if resp.status != 200: @@ -221,6 +229,9 @@ class Lyrist(LyricsPlatform): return None class Lrclib(LyricsPlatform): + def __init__(self): + self.base_url: str = "https://lrclib.net/api/" + async def get(self, url, params: dict = None) -> list[dict]: try: async with aiohttp.ClientSession() as session: @@ -231,15 +242,135 @@ class Lrclib(LyricsPlatform): except: return [] - async def get_lyrics(self, title, artist): + async def get_lyrics(self, title: str, artist: str) -> Optional[dict[str, str]]: params = {"q": title} - result = await self.get(LRCLIB_ENDPOINT + "search", params) + result = await self.get(self.base_url + "search", params) if result: return {"default": result[0].get("plainLyrics", "")} +""" +Strvm/musicxmatch-api: a reverse engineered API wrapper for MusicXMatch +Copyright (c) 2025 Strvm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +class MusixMatch(LyricsPlatform): + def __init__(self): + self.base_url = "https://www.musixmatch.com/ws/1.1/" + self.headers = {'User-Agent': random.choice(userAgents)} + self.secret: Optional[str] = None + + async def search_tracks(self, track_query: str, page: int = 1) -> dict: + url = f"track.search?app_id=web-desktop-app-v1.0&format=json&q={urllib.parse.quote(track_query)}&f_has_lyrics=true&page_size=5&page={page}" + return await self.make_request(url) + + async def get_track_lyrics(self, track_id: Optional[str] = None, track_isrc: Optional[str] = None) -> dict: + if not (track_id or track_isrc): + raise ValueError("Either track_id or track_isrc must be provided.") + param = f"track_id={track_id}" if track_id else f"track_isrc={track_isrc}" + url = f"track.lyrics.get?app_id=web-desktop-app-v1.0&format=json&{param}" + return await self.make_request(url) + + async def get_latest_app(self): + url = "https://www.musixmatch.com/search" + + async with aiohttp.ClientSession() as session: + async with session.get(url, headers={**self.headers, "Cookie": "mxm_bab=AB"}) as response: + html_content = await response.text() + pattern = r'src="([^"]*/_next/static/chunks/pages/_app-[^"]+\.js)"' + matches = re.findall(pattern, html_content) + + if not matches: + raise Exception("_app URL not found in the HTML content.") + + return matches[-1] + + async def get_secret(self) -> str: + async with aiohttp.ClientSession() as session: + async with session.get(await self.get_latest_app(), headers=self.headers, timeout=5) as response: + javascript_code = await response.text() + + pattern = r'from\(\s*"(.*?)"\s*\.split' + match = re.search(pattern, javascript_code) + + if match: + encoded_string = match.group(1) + reversed_string = encoded_string[::-1] + + decoded_bytes = base64.b64decode(reversed_string) + return decoded_bytes.decode("utf-8") + else: + raise Exception("Encoded string not found in the JavaScript code.") + + async def generate_signature(self, url: str) -> str: + current_date = datetime.now() + date_str = f"{current_date.year}{str(current_date.month).zfill(2)}{str(current_date.day).zfill(2)}" + message = (url + date_str).encode() + + if not self.secret: + self.secret = await self.get_secret() + + key = self.secret.encode() + hash_output = hmac.new(key, message, hashlib.sha256).digest() + signature = ( + "&signature=" + + urllib.parse.quote(base64.b64encode(hash_output).decode()) + + "&signature_protocol=sha256" + ) + return signature + + async def make_request(self, url: str) -> dict: + url = url.replace("%20", "+").replace(" ", "+") + url = self.base_url + url + signed_url = url + await self.generate_signature(url) + + async with aiohttp.ClientSession() as session: + async with session.get(signed_url, headers=self.headers, timeout=5) as response: + if response.status != 200: + raise Exception(f"HTTP Error: {response.status} for URL: {signed_url}") + + try: + text_data = await response.text() + return json.loads(text_data) + except json.JSONDecodeError: + raise Exception(f"Failed to parse JSON. Response: {text_data}") + + async def get_lyrics(self, title: str, artist: str) -> Optional[dict[str, str]]: + results = await self.search_tracks(track_query=f"{artist} {title}" if artist else title) + + track_list = results.get("message", {}).get("body", {}).get("track_list") + if not track_list: + return None + + track_id = track_list[0]["track"]["track_id"] + lyric = await self.get_track_lyrics(track_id=track_id) + + lyrics_body = lyric.get("message", {}).get("body", {}).get("lyrics", {}).get("lyrics_body", "") + if not lyrics_body: + return None + + return {"default": lyrics_body} + LYRICS_PLATFORMS: dict[str, Type[LyricsPlatform]] = { "a_zlyrics": A_ZLyrics, "genius": Genius, "lyrist": Lyrist, - "lrclib": Lrclib + "lrclib": Lrclib, + "musixmatch": MusixMatch } \ No newline at end of file From c8fa4e561af6ca5a2fd7310f6f892abb387020ee Mon Sep 17 00:00:00 2001 From: Choco Date: Wed, 23 Jul 2025 10:46:52 +0800 Subject: [PATCH 20/22] Added silent messaging feature --- .DS_Store | Bin 8196 -> 8196 bytes cogs/settings.py | 11 +++++++++++ function.py | 8 ++++---- langs/CH.json | 21 +-------------------- langs/DE.json | 21 +-------------------- langs/EN.json | 23 ++--------------------- langs/ES.json | 21 +-------------------- langs/FR.json | 23 ++--------------------- langs/JA.json | 21 +-------------------- langs/KO.json | 21 +-------------------- langs/PL.json | 23 ++--------------------- langs/RU.json | 22 ++-------------------- langs/UA.json | 20 +------------------- local_langs/zh-TW.json | 2 ++ update.py | 2 +- 15 files changed, 32 insertions(+), 207 deletions(-) diff --git a/.DS_Store b/.DS_Store index ef029df8cfcba5f4b10348e86a115d53b424e756..9c1109aed6080e8cf42445c6e8b4d0955b6fd086 100644 GIT binary patch delta 44 zcmZp1XmOa}&nUVvU^hRb=w=>)-OQ5(MXWa0igYngY^d4HF7b_JbERk>)5L~K07@qh AV*mgE delta 118 zcmZp1XmOa}&nUhzU^hRb_+}n~-OSda48;to3?)FA$&km84&*5?xH42QBr@bO6foof z<@6Y`fTH;fd7e4>$w@i+Nem1O0t^g{KPT@H_T=ScCS0K*a;)c^nh diff --git a/cogs/settings.py b/cogs/settings.py index ed47037..3e662a6 100644 --- a/cogs/settings.py +++ b/cogs/settings.py @@ -241,6 +241,17 @@ class Settings(commands.Cog, name="settings"): await update_settings(ctx.guild.id, {"$set": {'controller_msg': toggle}}) await send(ctx, 'toggleControllerMsg', await get_lang(ctx.guild.id, "enabled" if toggle else "disabled")) + + @settings.command(name="silentmsg", aliases=get_aliases("silentmsg")) + @commands.has_permissions(manage_guild=True) + @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) + async def silentmsg(self, ctx: commands.Context): + "Toggle silent messaging to send discreet messages without alerting recipients." + settings = await get_settings(ctx.guild.id) + toggle = not settings.get('silentMsg', False) + + await update_settings(ctx.guild.id, {"$set": {'silentMsg': toggle}}) + await send(ctx, 'toggleSilentMsg', await get_lang(ctx.guild.id, "enabled" if toggle else "disabled")) @settings.command(name="stageannounce", aliases=get_aliases("stageannounce")) @commands.has_permissions(manage_guild=True) diff --git a/function.py b/function.py index 4f2d66f..7e1029a 100644 --- a/function.py +++ b/function.py @@ -218,17 +218,17 @@ async def send( # Check settings for delete_after duration settings = await get_settings(ctx.guild.id) - if settings and ctx.channel.id == settings.get("music_request_channel", {}).get("text_channel_id"): - delete_after = 10 - send_kwargs = { "content": text, "embed": embed, "ephemeral": ephemeral, - "allowed_mentions": ALLOWED_MENTIONS + "allowed_mentions": ALLOWED_MENTIONS, + "silent": settings.get("silentMsg", False), } if "delete_after" in send_func.__code__.co_varnames: + if settings and ctx.channel.id == settings.get("music_request_channel", {}).get("text_channel_id"): + delete_after = 10 send_kwargs["delete_after"] = delete_after if view: diff --git a/langs/CH.json b/langs/CH.json index 0d21c97..e89990c 100644 --- a/langs/CH.json +++ b/langs/CH.json @@ -2,7 +2,6 @@ "unknownException": "⚠️ 執行命令時出現問題!請稍後再試,或加入我們的 Discord 伺服器獲得進一步支援。", "enabled": "已啟用", "disabled": "已停用", - "nodeReconnect": "請稍後再試!在節點重新連接後再嘗試。", "noChannel": "沒有語音頻道可供連接。請提供一個語音頻道或加入一個語音頻道。", "alreadyConnected": "已經連接到語音頻道。", @@ -23,6 +22,7 @@ "togglecontroller": "現在您已 `{0}` 音樂控制器。", "toggleDuplicateTrack": "現在您已 `{0}` 防止隊列中存在重複曲目。", "toggleControllerMsg": "現在您已從音樂控制器 `{0}` 消息。", + "toggleSilentMsg": "你{0}靜默消息。", "settingsMenu": "伺服器設置 | {0}", "settingsTitle": "❤️ 基本資訊:", "settingsValue": "```前綴:{0}\n語言:{1}\n音樂控制器:{2}\nDJ 角色:@{3}\n投票繞過:{4}\n24/7:{5}\n默認音量:{6}%\n播放時間:{7}```", @@ -38,7 +38,6 @@ "addEffect": "套用音效`{0}`濾鏡。", "clearEffect": "聲音效果已清除!", "FilterTagAlreadyInUse": "此聲音效果已在使用中!請使用 /cleareffect 移除它。", - "playlistViewTitle": "📜 所有 {0} 的播放清單", "playlistViewHeaders": "ID:,時間:,名稱:,曲目數:", "playlistFooter": "輸入 /playlist play [播放清單] 加入此播放清單至隊列中。", @@ -77,7 +76,6 @@ "inboxFull": "抱歉!{0} 的收件匣已滿。", "inboxNoMsg": "您的收件匣中沒有任何訊息。", "invitationSent": "已發送邀請給 {0}。", - "notInChannel": "{0},您必須在 {1} 中使用語音指令。如果您已在語音頻道中,請重新加入!", "noTrackPlaying": "現在沒有歌曲正在播放", "noTrackFound": "找不到符合該查詢的歌曲!請提供有效的網址。", @@ -92,16 +90,13 @@ "lyricsNotFound": "找不到歌詞。輸入 /lyrics <歌曲名稱> <作者> 查找歌詞。", "missingTrackInfo": "有些音軌資訊缺失。", "noVoiceChannel": "找不到語音頻道!", - "playlistAddError": "您無權將串流視訊添加到播放清單中!", "playlistAddError2": "添加音軌到播放清單時發生問題!", "playlistlimited": "您已達到上限!您只能將 {0} 首歌曲添加到播放清單中。", "playlistrepeated": "您的播放清單中已經存在相同的音軌!", "playlistAdded": "❤️ 已將 **{0}** 添加到 {1} 的播放清單中 [`{2}`]!", - "playerDropdown": "選擇要跳轉到的音軌...", "playerFilter": "選擇要套用的篩選器...", - "buttonBack": "返回", "buttonPause": "暫停", "buttonResume": "繼續", @@ -117,30 +112,24 @@ "buttonForward": "前進", "buttonRewind": "後退", "buttonLyrics": "歌詞", - "nowplayingDesc": "**現在播放:**\n```{0}```", "nowplayingField": "接下來播放:", "nowplayingLink": "在 {0} 上收聽", - "connect": "已連接至 {0}", - "live": "直播", "playlistLoad": " 🎶 已添加播放清單 **{0}**,共 `{1}` 首歌曲至隊列。", "trackLoad": "已添加 **[{0}](<{1}>)**,由 **{2}** (`{3}`) 開始播放。\n", "trackLoad_pos": "已將 **[{0}](<{1}>)**,由 **{2}** (`{3}`) 添加到隊列中位置 **{4}**\n", - "searchTitle": "搜索查詢: {0}", "searchDesc": "➥ 平台: {0} **{1}**\n➥ 結果: **{2}**\n\n{3}", "searchWait": "選擇您想要添加到隊列中的歌曲。", "searchTimeout": "搜索超時。請稍後再試。", "searchSuccess": "已將歌曲添加到隊列中。", - "queueTitle": "即將播放的隊列:", "historyTitle": "歷史隊列:", "viewTitle": "音樂隊列", "viewDesc": "**現正播放:[點擊我]({0}) ⮯**\n{1}", "viewFooter": "頁數:{0}/{1} | 總長度:{2}", - "pauseError": "播放器已經暫停。", "pauseVote": "{0} 已投票暫停歌曲。[{1}/{2}]", "paused": "播放器已被 `{0}` 暫停。", @@ -153,13 +142,10 @@ "skipError": "沒有歌曲可以跳過。", "skipVote": "{0} 已投票跳過歌曲。[{1}/{2}]", "skipped": "播放器已被 `{0}` 跳過歌曲。", - "backVote": "{0} 已投票跳到上一首歌曲。[{1}/{2}]", "backed": "播放器已被 `{0}` 跳到上一首歌曲。", - "leaveVote": "{0} 已投票停止播放器。[{1}/{2}]", "left": "播放器已被 `{0}` 停止。", - "seek": "將播放器設置到 **{0}**。", "repeat": "重複模式已設置為 `{0}`。", "cleared": "清除了 `{0}` 中的所有歌曲。", @@ -170,25 +156,20 @@ "swapped": "已交換 `{0}` 和 `{1}`。", "moved": "已將 `{0}` 移動到 `{1}`。", "autoplay": "自動播放模式現在為 **{0}**。", - "notdj": "您不是DJ,當前DJ為 {0}。", "djToMe": "您無法將DJ權限轉移給自己或機器人。", "djnotinchannel": "`{0}` 不在語音頻道中。", "djswap": "您已將DJ權限轉移給 `{0}`。", - "chaptersDropdown": "選擇要跳轉到的章節...", "noChaptersFound": "找不到任何章節!", "chatpersNotSupport": "此命令僅支持 YouTube 影片!", - "voicelinkQueueFull": "抱歉,您已達到隊列中 `{0}` 首歌曲的最大數量!", "voicelinkOutofList": "請提供有效的歌曲索引!", "voicelinkDuplicateTrack": "抱歉,此歌曲已在隊列中。", - "deocdeError": "解碼文件時出現問題!", "invalidStartTime": "無效的開始時間! 時間必須在 `00:00` 和 `{0}` 之間。", "invalidEndTime": "無效的結束時間! 時間必須在 `00:00` 和 `{0}` 之間。", "invalidTimeOrder": "結束時間不能小於或等於開始時間。", - "setStageAnnounceTemplate": "完成!從現在開始,像您現在的語音狀態將根據您的模板命名。您應該在幾秒鐘內看到它更新。", "createSongRequestChannel": "一個歌曲請求頻道 ({0}) 已建立!您可以在該頻道中透過歌曲名稱或 URL 開始要求任何歌曲,而無需使用機器人前綴。" } \ No newline at end of file diff --git a/langs/DE.json b/langs/DE.json index 155b281..482d428 100644 --- a/langs/DE.json +++ b/langs/DE.json @@ -2,7 +2,6 @@ "unknownException": "⚠️ Beim Ausführen des Befehls ist etwas schiefgelaufen! Bitte versuche es später erneut oder tritt unserem Discord-Server bei, um weiteren Support zu erhalten.", "enabled": "aktiviert", "disabled": "deaktiviert", - "nodeReconnect": "Bitte versuche es erneut, nachdem sich die Node wieder verbunden hat.", "noChannel": "Kein Sprachkanal zum Verbinden gefunden. Bitte stelle entweder einen zur Verfügung oder trete einem bei.", "alreadyConnected": "Bereits mit einem Sprachkanal verbunden.", @@ -23,6 +22,7 @@ "togglecontroller": "Der Musikcontroller wurde erfolgreich `{0}`", "toggleDuplicateTrack": "Du hast das hinzufügen von doppelten Tracks in der Warteschlange `{0}`", "toggleControllerMsg": "Nachrichten vom Musik-Controller wurden erfolgreich `{0}`", + "toggleSilentMsg": "Sie haben {0} stille Nachrichten.", "settingsMenu": "Servereinstellungen | {0}", "settingsTitle": "❤️ Grundlegende Informationen:", "settingsValue": "```Präfix: {0}`\nSprache: {1}\nMusik-Controller: {2}\nDJ-Rolle: @{3}\nAbstimmungsumgehung: {4}\n24/7 Play: {5}\nStandard Lautstärke: {6}%\nSpielzeit: {7}```", @@ -38,7 +38,6 @@ "addEffect": "Wende den Effekt `{0}` Filter an.", "clearEffect": "Die Soundeffekte wurden gelöscht!", "FilterTagAlreadyInUse": "Diese Soundeffekte sind bereits im Einsatz! Bitte verwende /cleareffect , um sie zu entfernen.", - "playlistViewTitle": "📜 Alle Playlists von {0}", "playlistViewHeaders": "ID:,Zeit:,Name:,Tracks:", "playlistFooter": "Gebe /playlist play [playlist] ein, um die Playlist in die Warteschlange einzufügen.", @@ -77,7 +76,6 @@ "inboxFull": "Es tut mir leid! Der Posteingang von {0} ist voll.", "inboxNoMsg": "Es sind keine Nachrichten in Deinem Posteingang.", "invitationSent": "Einladung an {0} gesendet.", - "notInChannel": "{0}, Du musst in {1} sein, um Sprachbefehle nutzen zu können. Bitte betrete den Sprachkanal erneut bei, wenn Du dich im Voice Channel befindest!", "noTrackPlaying": "Es werden derzeit keine Songs abgespielt", "noTrackFound": "Es wurden keine Songs mit dieser Abfrage gefunden! Bitte gib eine gültige URL an.", @@ -92,16 +90,13 @@ "lyricsNotFound": "Es wurden keine Songtexte gefunden. Gebe /lyrics ein, um die Songtexte zu finden.", "missingTrackInfo": "Einige Track-Informationen fehlen.", "noVoiceChannel": "Dieser Sprachkanal wurde nicht gefunden!", - "playlistAddError": "Du darfst Deiner Wiedergabenliste keine aktiven Streams hinzufügen!", "playlistAddError2": "Es gab ein Problem beim Hinzufügen von Tracks zur Wiedergabenliste!", "playlistlimited": "Du hast das Limit erreicht! Du kannst nur noch {0} Songs zu Deiner Wiedergabenliste hinzufügen.", "playlistrepeated": "In Deiner Wiedergabenliste gibt es bereits den gleichen Track!", "playlistAdded": "❤️ **{0}** wurde in der Wiedergabenliste [`{2}`] von {1} hinzugefügt.", - "playerDropdown": "Wähle einen Song aus, um zu überspringen ...", "playerFilter": "Wähle einen Filter aus, um ihn anzuwenden ...", - "buttonBack": "Zurück", "buttonPause": "Pause", "buttonResume": "Weiter", @@ -117,30 +112,24 @@ "buttonForward": "Vorspulen", "buttonRewind": "Zurückspulen", "buttonLyrics": "Liedtexte", - "nowplayingDesc": "**Jetzt wird abgespielt:**\n```{0}```", "nowplayingField": "Als nächstes:", "nowplayingLink": "Auf {0} anhören", - "connect": "Verbunden mit {0}", - "live": "LIVE", "playlistLoad": " 🎶 Die Wiedergabeliste **{0}** mit `{1}` Songs wurde zur Warteschlange hinzugefügt.", "trackLoad": "**[{0}](<{1}>)** von **{2}** (`{3}`) wurde zum Abspielen hinzugefügt.\n", "trackLoad_pos": "**[{0}](<{1}>)** von **{2}** (`{3}`) wurde in der Warteschlange zu Position **{4}** hinzugefügt.\n", - "searchTitle": "Suchabfrage: {0}", "searchDesc": "➥ Plattform: {0} **{1}**\n➥ Ergebnisse: **{2}**\n\n{3}", "searchWait": "Wähle den Song aus, den Du zur Warteschlange hinzufügen möchtest.", "searchTimeout": "Die Suche wurde abgebrochen. Bitte versuche es später erneut.", "searchSuccess": "Der Song wurde zur Warteschlange hinzugefügt.", - "queueTitle": "Kommende Warteschlange:", "historyTitle": "Vorherige Warteschlange:", "viewTitle": "Musik-Warteschlange", "viewDesc": "**Jetzt wird abgespielt: [Hier klicken]({0}) ⮯**\n{1}", "viewFooter": "Seite: {0}/{1} | Gesamtdauer: {2}", - "pauseError": "Der Player ist bereits pausiert.", "pauseVote": "{0} hat für eine Pause des Songs abgestimmt. [{1}/{2}]", "paused": "`{0}` hat den Player pausiert.", @@ -153,13 +142,10 @@ "skipError": "Es gibt keine Songs, die übersprungen werden können.", "skipVote": "{0} hat für das Überspringen des Songs abgestimmt. [{1}/{2}]", "skipped": "`{0}` hat den Song übersprungen.", - "backVote": "{0} hat für das Überspringen zum vorherigen Song gestimmt. [{1}/{2}]", "backed": "{0} hat zum vorherigen Song übersprungen.", - "leaveVote": "{0} hat für das Anhalten des Players gestimmt. [{1}/{2}]", "left": "`{0}` hat den Player angehalten.", - "seek": "Der Player wurde auf **{0}** gesetzt.", "repeat": "Der Wiederholungsmodus wurde auf `{0}` gesetzt", "cleared": "Alle Tracks in `{0}` wurden gelöscht", @@ -170,25 +156,20 @@ "swapped": "`{0}` und `{1}` wurden ausgetauscht.", "moved": "`{0}` zu `{1}` verschoben", "autoplay": "Der Autoplay-Modus ist jetzt **{0}**", - "notdj": "Du bist kein DJ, der aktuelle DJ ist {0}.", "djToMe": "Du kannst den DJ nicht an dich selbst oder einer App übertragen.", "djnotinchannel": "`{0}` ist nicht im Sprachkanal.", "djswap": "Du hast die DJ-Rolle auf `{0}` übertragen.", - "chaptersDropdown": "Wähle ein Kapitel zum Überspringen aus...", "noChaptersFound": "Es wurden keine Kapitel gefunden!", "chatpersNotSupport": "Dieser Befehl unterstützt nur YouTube-Videos!", - "voicelinkQueueFull": "Entschuldigung, Du hast das Maximum von `{0}` Tracks in der Warteschlange erreicht.", "voicelinkOutofList": "Bitte gib einen gültigen Track-Index an!", "voicelinkDuplicateTrack": "Entschuldigung, dieser Track ist bereits in der Warteschlange.", - "deocdeError": "Beim Dekodieren der Datei ist etwas schief gelaufen!", "invalidStartTime": "Ungültige Startzeit! Die Zeit muss innerhalb von `00:00` und `{0}` liegen.", "invalidEndTime": "Ungültige Endzeit! Die Zeit muss innerhalb von `00:00` und `{0}` liegen.", "invalidTimeOrder": "Die Endzeit darf nicht kleiner oder gleich dem Startzeit sein.", - "setStageAnnounceTemplate": "Fertig! Ab sofort wird der Sprachstatus wie der, in dem Du Dich gerade befindest, gemäß Deiner Vorlage benannt. Du solltest in wenigen Sekunden eine Aktualisierung sehen.", "createSongRequestChannel": "Der Song Request Channel ({0}) wurde erstellt! Du kannst jeden Song nach Namen oder URL in diesem Kanal anfordern, ohne den Bot-Präfix verwenden zu müssen." } \ No newline at end of file diff --git a/langs/EN.json b/langs/EN.json index d305005..0f073f5 100644 --- a/langs/EN.json +++ b/langs/EN.json @@ -2,7 +2,6 @@ "unknownException": "⚠️ Something wrong while running the command! Please try again later or join our discord server for further support.", "enabled": "enabled", "disabled": "disabled", - "nodeReconnect": "Please try again! After the node reconnects.", "noChannel": "No voice channel to connect. Please either provide one or join one.", "alreadyConnected": "Already connected to a voice channel.", @@ -23,6 +22,7 @@ "togglecontroller": "You now have `{0}` the music controller.", "toggleDuplicateTrack": "You have now `{0}` duplicate track prevention.", "toggleControllerMsg": "You now have `{0}` messages from the music controller.", + "toggleSilentMsg": "You have {0} silent messaging.", "settingsMenu": "Server Settings | {0}", "settingsTitle": "❤️ Basic Info:", "settingsValue": "```Prefix: {0}\nLanguage: {1}\nMusic Controller: {2}\nDJ Role: @{3}\nVote Bypass: {4}\n24/7: {5}\nDefault Volume: {6}%\nPlay Time: {7}```", @@ -38,7 +38,6 @@ "addEffect": "Applied the `{0}` effect.", "clearEffect": "The sound effects have been cleared!", "FilterTagAlreadyInUse": "This sound effect is already in use! Please use /cleareffect to remove it.", - "playlistViewTitle": "📜 All of {0}'s Playlists", "playlistViewHeaders": "ID:,Time:,Name:,Tracks:", "playlistFooter": "Type /playlist play [playlist] to add a playlist the into the queue.", @@ -77,7 +76,6 @@ "inboxFull": "Sorry! {0}'s inbox is full.", "inboxNoMsg": "There are no messages in your inbox.", "invitationSent": "Invitation sent to {0}.", - "notInChannel": "{0}, you must be in {1} to use voice commands. Please rejoin if you are in voice!", "noTrackPlaying": "There are no songs playing right now", "noTrackFound": "No songs were found with that query! Please provide a valid url.", @@ -92,16 +90,13 @@ "lyricsNotFound": "Lyrics not found. Type /lyrics to find the lyrics.", "missingTrackInfo": "Some track details are missing.", "noVoiceChannel": "Voice Channel Not Found!", - "playlistAddError": "You are not allowed to add streaming videos to your playlist!", "playlistAddError2": "There was a problem adding tracks to the playlist!", "playlistlimited": "You have reached the limit! You can only add {0} songs to your playlist.", "playlistrepeated": "This track is already in your playlist!", "playlistAdded": "❤️ Added **{0}** into {1}'s playlist [`{2}`]!", - "playerDropdown": "Select a song to skip to ...", "playerFilter": "Select a filter to apply ...", - "buttonBack": "Back", "buttonPause": "Pause", "buttonResume": "Resume", @@ -117,30 +112,24 @@ "buttonForward": "Forward", "buttonRewind": "Rewind", "buttonLyrics": "Lyrics", - "nowplayingDesc": "**Now Playing:**\n```{0}```", "nowplayingField": "Up Next:", "nowplayingLink": "Listen on {0}", - "connect": "Connected to {0}", - "live": "LIVE", "playlistLoad": " 🎶 Added the playlist **{0}** with `{1}` songs to the queue.", "trackLoad": "Added **[{0}](<{1}>)** by **{2}** (`{3}`) to begin playing.\n", "trackLoad_pos": "Added **[{0}](<{1}>)** by **{2}** (`{3}`) to the queue at position **{4}**\n", - "searchTitle": "Search Query: {0}", "searchDesc": "➥ Platform: {0} **{1}**\n➥ Results: **{2}**\n\n{3}", "searchWait": "Select the song you want to add to the queue.", "searchTimeout": "Search timed out. Please try again later.", "searchSuccess": "Song added to the queue.", - "queueTitle": "Upcoming Queue:", "historyTitle": "History Queue:", "viewTitle": "Music Queue", "viewDesc": "**Now Playing: [Click Me]({0}) ⮯**\n{1}", "viewFooter": "Page: {0}/{1} | Total Duration: {2}", - "pauseError": "The player is already paused.", "pauseVote": "{0} has voted to pause the song. [{1}/{2}]", "paused": "`{0}` has paused the player.", @@ -153,13 +142,10 @@ "skipError": "There are no songs too skip to.", "skipVote": "{0} has voted to skip the song. [{1}/{2}]", "skipped": "`{0}` has skipped the song.", - "backVote": "{0} has voted to skip to the previous song. [{1}/{2}]", "backed": "`{0}` has skipping to previous song.", - "leaveVote": "{0} has voted to stop the player. [{1}/{2}]", "left": "`{0}` has stopped the player.", - "seek": "Set the player to **{0}**", "repeat": "The repeat mode has been set to `{0}`", "cleared": "Cleared all tracks in the `{0}`", @@ -170,25 +156,20 @@ "swapped": "`{0}` and `{1}` have been swapped", "moved": "Moved `{0}` to `{1}`", "autoplay": "Autoplay mode is now **{0}**", - "notdj": "You are not DJ. The current DJ is {0}.", "djToMe": "You cannot transfer role of DJ to yourself or a bot.", "djnotinchannel": "`{0}` is not in the voice channel.", "djswap": "You have transferred the role of dj to `{0}`.", - "chaptersDropdown": "Select a chapter to skip to ...", "noChaptersFound": "No chapters has been found!", "chatpersNotSupport": "This command only supports Youtube videos!", - "voicelinkQueueFull": "Sorry, you have reached the maximum of `{0}` tracks in the queue!", "voicelinkOutofList": "Please provide a valid track index!", "voicelinkDuplicateTrack": "Sorry, this track is already in the queue.", - "deocdeError": "Something went wrong while decoding the file!", "invalidStartTime": "Invalid start time, it must be between `00:00` and `{0}`", "invalidEndTime": "Invalid end time, it must be between `00:00` and `{0}`", "invalidTimeOrder": "End time cannot be less than or equal to start time", - "setStageAnnounceTemplate": "Done! From now on, voice status like the one you're in now will be named according to your template. You should see it update in a few seconds.", "createSongRequestChannel": "A song request channel ({0}) has been created! You can start requesting any song by name or URL in that channel, without needing to use the bot prefix." -} +} \ No newline at end of file diff --git a/langs/ES.json b/langs/ES.json index 3ee7877..6bd17cc 100644 --- a/langs/ES.json +++ b/langs/ES.json @@ -2,7 +2,6 @@ "unknownException": "⚠️ ¡Algo salió mal al ejecutar el comando! Por favor, intente de nuevo más tarde o únase a nuestro servidor de Discord para obtener más soporte.", "enabled": "habilitado", "disabled": "deshabilitado", - "nodeReconnect": "¡Por favor, inténtelo de nuevo! Después de que el nodo se reconecte.", "noChannel": "No hay canal de voz al que conectarse. Por favor, proporcione uno o únase a uno.", "alreadyConnected": "Ya conectado a un canal de voz.", @@ -23,6 +22,7 @@ "togglecontroller": "Ahora tienes `{0}` el controlador de música.", "toggleDuplicateTrack": "Ahora tienes `{0}` para evitar que se agreguen pistas duplicadas a la cola.", "toggleControllerMsg": "Ahora tiene `{0}` mensajes del controlador de música.", + "toggleSilentMsg": "Tienes {0} mensajes silenciosos.", "settingsMenu": "Configuración del servidor | {0}", "settingsTitle": "❤️ Información básica:", "settingsValue": "```Prefijo: {0}\nIdioma: {1}\nHabilitar controlador de música: {2}\nRol de DJ: @{3}\nBypass de votación: {4}\n24/7: {5}\nVolumen predeterminado: {6}%\nTiempo de reproducción: {7}```", @@ -38,7 +38,6 @@ "addEffect": "plica el efecto `{0}` filtro.", "clearEffect": "¡Los efectos de sonido se han borrado!", "FilterTagAlreadyInUse": "¡Este efecto de sonido ya está en uso! Utilice /cleareffect para eliminarlo.", - "playlistViewTitle": "📜 Todas las listas de reproducción de {0}", "playlistViewHeaders": "ID:,Tiempo:,Nombre:,Pistas:", "playlistFooter": "Escriba /playlist play [playlist] para agregar la lista de reproducción a la cola.", @@ -77,7 +76,6 @@ "inboxFull": "¡Lo siento! La bandeja de entrada de {0} está llena.", "inboxNoMsg": "No hay mensajes en su bandeja de entrada.", "invitationSent": "Se ha enviado una invitación a {0}.", - "notInChannel": "{0}, debes estar en {1} para usar comandos de voz. ¡Vuelve a unirte si estás en voz!", "noTrackPlaying": "Actualmente no hay canciones reproduciéndose.", "noTrackFound": "No se encontraron canciones con esa consulta. Proporcione una URL válida.", @@ -92,16 +90,13 @@ "lyricsNotFound": "No se encontraron letras. Escriba /lyrics para buscar las letras.", "missingTrackInfo": "Falta información de la canción.", "noVoiceChannel": "¡Canal de voz no encontrado!", - "playlistAddError": "No está autorizado para agregar videos de transmisión a su lista de reproducción.", "playlistAddError2": "Hubo un problema al agregar canciones a la lista de reproducción.", "playlistlimited": "¡Ha alcanzado el límite! Solo puede agregar {0} canciones a su lista de reproducción.", "playlistrepeated": "¡Ya hay una canción igual en su lista de reproducción!", "playlistAdded": "❤️ Agregado **{0}** a la lista de reproducción de {1} [`{2}`]!", - "playerDropdown": "Seleccione una canción para saltar a ...", "playerFilter": "Seleccione un filtro para aplicar ...", - "buttonBack": "Atrás", "buttonPause": "Pausa", "buttonResume": "Reanudar", @@ -117,30 +112,24 @@ "buttonForward": "Adelante", "buttonRewind": "Atrás", "buttonLyrics": "Letras", - "nowplayingDesc": "**Reproduciendo ahora:**\n```{0}```", "nowplayingField": "A continuación:", "nowplayingLink": "Escucha en {0}", - "connect": "Conectado a {0}", - "live": "EN VIVO", "playlistLoad": " 🎶 Se agregó la lista de reproducción {0} con {1} canciones a la cola.", "trackLoad": "Se agregó **[{0}](<{1}>)** de {2} ({3}) para comenzar a reproducir.\n", "trackLoad_pos": "Se agregó **[{0}](<{1}>)** de {2} ({3}) a la cola en la posición {4}\n", - "searchTitle": "Consulta de búsqueda: {0}", "searchDesc": "➥ Plataforma: {0} **{1}**\n➥ Resultados: **{2}**\n\n{3}", "searchWait": "Seleccione la canción que desea agregar a la cola.", "searchTimeout": "La búsqueda ha expirado. Por favor, inténtelo de nuevo más tarde.", "searchSuccess": "Canción agregada a la cola.", - "queueTitle": "Cola de próximas canciones:", "historyTitle": "Cola de historial:", "viewTitle": "Cola de música", "viewDesc": "**Reproduciendo ahora: [Haga clic aquí]({0}) ⮯**\n{1}", "viewFooter": "Página: {0}/{1} | Duración total: {2}", - "pauseError": "El reproductor ya está en pausa.", "pauseVote": "{0} ha votado para pausar la canción. [{1}/{2}]", "paused": "`{0}` ha pausado el reproductor.", @@ -153,13 +142,10 @@ "skipError": "No hay canciones para saltar.", "skipVote": "{0} ha votado para saltar la canción. [{1}/{2}]", "skipped": "`{0}` ha saltado la canción.", - "backVote": "{0} ha votado para volver a la canción anterior. [{1}/{2}]", "backed": "`{0}` ha vuelto a la canción anterior.", - "leaveVote": "{0} ha votado para detener el reproductor. [{1}/{2}]", "left": "`{0}` ha detenido el reproductor.", - "seek": "El reproductor se estableció en **{0}**", "repeat": "El modo de repetición se ha establecido en `{0}`", "cleared": "Se borraron todas las canciones de `{0}`", @@ -170,25 +156,20 @@ "swapped": "Se intercambiaron `{0}` y `{1}`", "moved": "Se movió `{0}` a `{1}`", "autoplay": "El modo de reproducción automática es ahora **{0}**", - "notdj": "No eres el DJ. El DJ actual es {0}.", "djToMe": "No puedes transferir el rol de DJ a ti mismo o a un bot.", "djnotinchannel": "`{0}` no está en el canal de voz.", "djswap": "Has transferido el rol de DJ a `{0}`.", - "chaptersDropdown": "Selecciona un capítulo al que saltar ...", "noChaptersFound": "¡No se encontraron capítulos!", "chatpersNotSupport": "¡Este comando solo es compatible con videos de Youtube!", - "voicelinkQueueFull": "Lo siento, ¡ha alcanzado el máximo de `{0}` canciones en la cola!", "voicelinkOutofList": "¡Proporcione un índice de pista válido!", "voicelinkDuplicateTrack": "Lo siento, esta canción ya está en la cola.", - "deocdeError": "¡Algo salió mal al decodificar el archivo!", "invalidStartTime": "Tiempo de inicio inválido! El tiempo debe estar entre `00:00` y `{0}`.", "invalidEndTime": "Tiempo de finalización inválido! El tiempo debe estar entre `00:00` y `{0}`.", "invalidTimeOrder": "El tiempo final no puede ser menor o igual que el tiempo de inicio.", - "setStageAnnounceTemplate": "¡Hecho! A partir de ahora, el estado de voz como el que tienes ahora se nombrará según tu plantilla. Deberías verlo actualizarse en unos segundos.", "createSongRequestChannel": "¡Se ha creado un canal de solicitudes de canciones ({0})! Puedes empezar a solicitar cualquier canción por nombre o URL en ese canal, sin necesidad de usar el prefijo del bot." } \ No newline at end of file diff --git a/langs/FR.json b/langs/FR.json index 3ae2e63..de8f982 100644 --- a/langs/FR.json +++ b/langs/FR.json @@ -2,7 +2,6 @@ "unknownException": "⚠️ Une erreur est survenue lors de l'exécution de la commande ! Veuillez réessayer plus tard ou rejoignez notre serveur Discord pour plus d'assistance.", "enabled": "activé", "disabled": "désactivé", - "nodeReconnect": "Veuillez réessayer après la reconnexion du node.", "noChannel": "Aucun salon vocal pour se connecter. Veuillez en fournir un ou rejoindre un salon vocal.", "alreadyConnected": "Déjà connecté à un salon vocal.", @@ -23,6 +22,7 @@ "togglecontroller": "Vous avez maintenant `{0}` le contrôleur de musique.", "toggleDuplicateTrack": "Vous avez maintenant `{0}` la prévention des morceaux en double.", "toggleControllerMsg": "Vous avez maintenant `{0}` les messages du contrôleur musical.", + "toggleSilentMsg": "Vous avez {0} messages silencieux.", "settingsMenu": "Paramètres du serveur | {0}", "settingsTitle": "❤️ Infos de base :", "settingsValue": "```Préfixe : {0}\nLangue : {1}\nContrôleur musique : {2}\nRôle DJ : @{3}\nVote Bypass : {4}\n24/7 : {5}\nVolume par défaut : {6}%\nTemps de lecture : {7}```", @@ -38,7 +38,6 @@ "addEffect": "Effet `{0}` appliqué.", "clearEffect": "Les effets sonores ont été supprimés !", "FilterTagAlreadyInUse": "Cet effet sonore est déjà utilisé ! Veuillez utiliser /cleareffect pour le retirer.", - "playlistViewTitle": "📜 Toutes les playlists de {0}", "playlistViewHeaders": "ID:,Durée:,Nom:,Morceaux:", "playlistFooter": "Tapez /playlist play [playlist] pour ajouter une playlist à la file.", @@ -77,7 +76,6 @@ "inboxFull": "Désolé ! La boîte de réception de {0} est pleine.", "inboxNoMsg": "Aucun message dans votre boîte de réception.", "invitationSent": "Invitation envoyée à {0}.", - "notInChannel": "{0}, vous devez être dans {1} pour utiliser les commandes vocales. Veuillez rejoindre à nouveau si vous êtes déjà en vocal !", "noTrackPlaying": "Aucune chanson n'est en cours de lecture pour le moment", "noTrackFound": "Aucune chanson trouvée pour cette requête ! Veuillez fournir une URL valide.", @@ -92,16 +90,13 @@ "lyricsNotFound": "Paroles non trouvées. Tapez /lyrics pour chercher les paroles.", "missingTrackInfo": "Certaines informations sur le morceau sont manquantes.", "noVoiceChannel": "Aucun salon vocal trouvé !", - "playlistAddError": "Vous n'êtes pas autorisé à ajouter des vidéos en streaming à votre playlist !", "playlistAddError2": "Problème lors de l'ajout de morceaux à la playlist !", "playlistlimited": "Vous avez atteint la limite ! Vous ne pouvez ajouter que {0} chansons à votre playlist.", "playlistrepeated": "Ce morceau est déjà dans votre playlist !", "playlistAdded": "❤️ **{0}** ajouté à la playlist [`{2}`] de {1} !", - "playerDropdown": "Sélectionnez une chanson vers laquelle passer...", "playerFilter": "Sélectionnez un filtre à appliquer...", - "buttonBack": "Précédent", "buttonPause": "Pause", "buttonResume": "Reprendre", @@ -117,30 +112,24 @@ "buttonForward": "Avancer", "buttonRewind": "Rembobiner", "buttonLyrics": "Paroles", - "nowplayingDesc": "**Lecture en cours :**\n```{0}```", "nowplayingField": "À suivre :", "nowplayingLink": "Écouter sur {0}", - "connect": "Connecté à {0}", - "live": "EN DIRECT", "playlistLoad": " 🎶 La playlist **{0}** avec `{1}` chansons a été ajoutée à la file.", "trackLoad": "**[{0}](<{1}>)** de **{2}** (`{3}`) a été ajoutée et commencera à jouer.\n", "trackLoad_pos": "**[{0}](<{1}>)** de **{2}** (`{3}`) a été ajoutée à la file à la position **{4}**\n", - "searchTitle": "Recherche : {0}", "searchDesc": "➥ Plateforme : {0} **{1}**\n➥ Résultats : **{2}**\n\n{3}", "searchWait": "Choisissez la chanson à ajouter à la file.", "searchTimeout": "Recherche expirée. Veuillez réessayer plus tard.", "searchSuccess": "Chanson ajoutée à la file.", - "queueTitle": "File à venir :", "historyTitle": "Historique :", "viewTitle": "File de musiques", "viewDesc": "**Lecture en cours : [Cliquez ici]({0}) ⮯**\n{1}", "viewFooter": "Page : {0}/{1} | Durée totale : {2}", - "pauseError": "Le lecteur est déjà en pause.", "pauseVote": "{0} a voté pour mettre la chanson en pause. [{1}/{2}]", "paused": "`{0}` a mis le lecteur en pause.", @@ -153,13 +142,10 @@ "skipError": "Aucune chanson vers laquelle passer.", "skipVote": "{0} a voté pour passer la chanson. [{1}/{2}]", "skipped": "`{0}` a passé la chanson.", - "backVote": "{0} a voté pour passer à la chanson précédente. [{1}/{2}]", "backed": "`{0}` est en train de passer à la chanson précédente.", - "leaveVote": "{0} a voté pour arrêter le lecteur. [{1}/{2}]", "left": "`{0}` a arrêté le lecteur.", - "seek": "Le lecteur a été placé à **{0}**", "repeat": "Le mode de répétition a été réglé sur `{0}`", "cleared": "Tous les morceaux dans `{0}` ont été supprimés", @@ -170,25 +156,20 @@ "swapped": "`{0}` et `{1}` ont été échangés", "moved": "`{0}` a été déplacé vers `{1}`", "autoplay": "Le mode autoplay est maintenant **{0}**", - "notdj": "Vous n'êtes pas DJ. Le DJ actuel est {0}.", "djToMe": "Impossible de transférer le rôle de DJ à vous-même ou à un bot.", "djnotinchannel": "`{0}` n'est pas dans le salon vocal.", "djswap": "Vous avez transféré le rôle de DJ à `{0}`.", - "chaptersDropdown": "Sélectionnez un chapitre vers lequel passer ...", "noChaptersFound": "Aucun chapitre trouvé !", "chatpersNotSupport": "Cette commande ne supporte que les vidéos YouTube !", - "voicelinkQueueFull": "Désolé, vous avez atteint le maximum de `{0}` morceaux dans la file !", "voicelinkOutofList": "Veuillez fournir un index de morceau valide !", "voicelinkDuplicateTrack": "Désolé, ce morceau est déjà dans la file.", - "deocdeError": "Une erreur s'est produite lors du décodage du fichier !", "invalidStartTime": "Heure de début invalide, doit être entre `00:00` et `{0}`", "invalidEndTime": "Heure de fin invalide, doit être entre `00:00` et `{0}`", "invalidTimeOrder": "L'heure de fin ne peut pas être inférieure ou égale à celle de début", - "setStageAnnounceTemplate": "C'est fait ! Désormais, le statut vocal tel que celui dans lequel vous êtes sera nommé selon votre modèle. Vous devriez le voir se mettre à jour dans quelques secondes.", "createSongRequestChannel": "Un salon de demande de chansons ({0}) a été créé ! Vous pouvez commencer à demander n'importe quelle chanson par nom ou URL dans ce salon, sans besoin d'utiliser le préfixe du bot." -} +} \ No newline at end of file diff --git a/langs/JA.json b/langs/JA.json index b8d9ebd..6a47f8e 100644 --- a/langs/JA.json +++ b/langs/JA.json @@ -2,7 +2,6 @@ "unknownException": "⚠️ コマンドの実行中に何かが間違っています!後でもう一度試してください。または、より詳しいサポートを求めるために当社のDiscordサーバーに参加してください。", "enabled": "有効化", "disabled": "無効化", - "nodeReconnect": "再試行してください!ノードが再接続した後。", "noChannel": "接続する音声チャンネルがありません。提供するか、参加してください。", "alreadyConnected": "すでに音声チャンネルに接続しています。", @@ -23,6 +22,7 @@ "togglecontroller": "今、音楽コントローラーは「{0}」です。", "toggleDuplicateTrack": "今、キュー内の重複トラックを防止するための設定は「{0}」です。", "toggleControllerMsg": "現在、音楽コントローラーから `{0}` 件のメッセージがあります。", + "toggleSilentMsg": "あなたは{0}サイレントメッセージを持っています。", "settingsMenu": "サーバー設定| {0}", "settingsTitle": "❤️ 基本情報:", "settingsValue": "```プレフィックス:{0}\n言語:{1}\n音楽コントローラーを有効にする:{2}\nDJロール:@{3}\n投票バイパス:{4}\n24/7:{5}\nデフォルトボリューム:{6}%\n再生時間:{7}```", @@ -38,7 +38,6 @@ "addEffect": "`{0}` フィルターを適用します。", "clearEffect": "効果音がクリアされました!", "FilterTagAlreadyInUse": "このサウンドエフェクトはすでに使用されています!削除するには/cleareffect を使用してください。", - "playlistViewTitle": "📜 {0}のすべてのプレイリスト", "playlistViewHeaders": "ID:,時間:,名前:,トラック:", "playlistFooter": "プレイリストをキューに追加するには、/playlist play [playlist]を入力してください。", @@ -77,7 +76,6 @@ "inboxFull": "申し訳ありません!{0}さんの受信トレイはいっぱいです。", "inboxNoMsg": "受信トレイにメッセージはありません。", "invitationSent": "{0}さんに招待状を送信しました。", - "notInChannel": "{0}さん、音声コマンドを使用するには{1}にいる必要があります。音声に接続している場合は再接続してください!", "noTrackPlaying": "現在再生中の曲はありません", "noTrackFound": "そのクエリで曲が見つかりませんでした!有効なURLを入力してください。", @@ -92,16 +90,13 @@ "lyricsNotFound": "歌詞が見つかりませんでした。/lyrics <曲名> <アーティスト>と入力して歌詞を検索してください。", "missingTrackInfo": "一部のトラック情報が欠落しています。", "noVoiceChannel": "音声チャンネルが見つかりません!", - "playlistAddError": "ストリーミングビデオをプレイリストに追加することはできません!", "playlistAddError2": "トラックをプレイリストに追加する際に問題が発生しました!", "playlistlimited": "制限に達しました!プレイリストには{0}曲しか追加できません。", "playlistrepeated": "すでにプレイリストに同じトラックがあります!", "playlistAdded": "❤️ **{0}**を{1}のプレイリスト[`{2}`]に追加しました!", - "playerDropdown": "スキップする曲を選択してください...", "playerFilter": "適用するフィルターを選択してください...", - "buttonBack": "戻る", "buttonPause": "一時停止", "buttonResume": "再開", @@ -117,30 +112,24 @@ "buttonForward": "進む", "buttonRewind": "戻る", "buttonLyrics": "歌詞", - "nowplayingDesc": "**現在再生中:**\n```{0}```", "nowplayingField": "次に再生する曲:", "nowplayingLink": "{0}で聴く", - "connect": "{0}に接続しました", - "live": "ライブ", "playlistLoad": " 🎶 プレイリスト**{0}**をキューに`{1}`曲追加しました。", "trackLoad": "**{2}**の**[{0}](<{1}>)** (`{3}`)を再生を開始するために追加しました。\n", "trackLoad_pos": "**{2}**の**[{0}](<{1}>)** (`{3}`)をキューの位置**{4}**に追加しました。\n", - "searchTitle": "検索クエリ:{0}", "searchDesc": "➥ プラットフォーム:{0} **{1}**\n➥ 結果:**{2}**\n\n{3}", "searchWait": "キューに追加したい曲を選択してください。", "searchTimeout": "検索がタイムアウトしました。後でもう一度お試しください。", "searchSuccess": "曲がキューに追加されました。", - "queueTitle": "キューに追加された曲:", "historyTitle": "再生履歴:", "viewTitle": "音楽キュー", "viewDesc": "**現在再生中:[ここをクリックして聴く]({0}) ⮯**\n{1}", "viewFooter": "ページ:{0}/{1} | 合計再生時間:{2}", - "pauseError": "プレイヤーはすでに一時停止しています。", "pauseVote": "{0}が曲を一時停止することに賛成しました。[{1}/{2}]", "paused": "{0}がプレイヤーを一時停止しました。", @@ -153,13 +142,10 @@ "skipError": "スキップする曲はありません。", "skipVote": "{0}が曲をスキップすることに賛成しました。[{1}/{2}]", "skipped": "{0}が曲をスキップしました。", - "backVote": "{0}が前の曲にスキップすることに賛成しました。[{1}/{2}]", "backed": "`{0}`が前の曲にスキップしました。", - "leaveVote": "{0}がプレイヤーを停止することに賛成しました。[{1}/{2}]", "left": "`{0}`がプレイヤーを停止しました。", - "seek": "プレイヤーを **{0}** に設定しました。", "repeat": "リピートモードが `{0}` に設定されました。", "cleared": "`{0}`のすべてのトラックをクリアしました。", @@ -170,25 +156,20 @@ "swapped": "`{0}`と`{1}`が交換されました。", "moved": "`{0}`を`{1}`に移動しました。", "autoplay": "オートプレイモードが **{0}** に設定されました。", - "notdj": "DJではありません。現在のDJは{0}です。", "djToMe": "自分自身またはボットにDJ権限を移行することはできません。", "djnotinchannel": "`{0}`はボイスチャンネルにいません。", "djswap": "DJの役割を`{0}`に移行しました。", - "chaptersDropdown": "スキップする章を選択してください...", "noChaptersFound": "章が見つかりませんでした!", "chatpersNotSupport": "このコマンドはYouTubeの動画のみサポートしています!", - "voicelinkQueueFull": "申し訳ありませんが、キュー内の曲数が最大値の`{0}`に達しました!", "voicelinkOutofList": "有効なトラックインデックスを指定してください!", "voicelinkDuplicateTrack": "申し訳ありませんが、このトラックは既にキューに存在します。", - "deocdeError": "ファイルのデコード中に問題が発生しました!", "invalidStartTime": "無効な開始時間!時間は `00:00` と `{0}` の間に設定する必要があります。", "invalidEndTime": "無効な終了時間!時間は `00:00` と `{0}` の間に設定する必要があります。", "invalidTimeOrder": "終了時間は開始時間より大きくない必要があります。", - "setStageAnnounceTemplate": "完了!これからは、今いるボイスステータスがあなたのテンプレートに従って名前が付けられます。数秒以内に更新されるのを見ることができるはずです。", "createSongRequestChannel": "曲のリクエストチャンネル ({0}) が作成されました!そのチャンネルで、曲名または URL を使用して任意の曲をリクエストできます。ボットのプレフィックスは必要ありません。" } \ No newline at end of file diff --git a/langs/KO.json b/langs/KO.json index 5582649..c491e96 100644 --- a/langs/KO.json +++ b/langs/KO.json @@ -2,7 +2,6 @@ "unknownException": "⚠️ 명령어 실행 중 오류가 발생했습니다! 나중에 다시 시도하거나 추가 지원을 위해 디스코드 서버에 참여하십시오.", "enabled": "활성화됨", "disabled": "비활성화됨", - "nodeReconnect": "다시 시도하세요! 노드가 다시 연결될 때까지 기다려주세요.", "noChannel": "연결할 음성 채널이 없습니다. 하나를 제공하거나 참여하십시오.", "alreadyConnected": "이미 음성 채널에 연결되어 있습니다.", @@ -23,6 +22,7 @@ "togglecontroller": "이제 음악 컨트롤러가 `{0}`(으)로 설정되었습니다.", "toggleDuplicateTrack": "이제 대기열에서 중복된 트랙을 방지하기 위해 `{0}`(으)로 설정되었습니다.", "toggleControllerMsg": "이제 음악 컨트롤러로부터 `{0}` 메시지가 있습니다.", + "toggleSilentMsg": "당신은 {0} 침묵 메시지를 사용하고 있습니다.", "settingsMenu": "서버 설정 | {0}", "settingsTitle": "❤️ 기본 정보:", "settingsValue": "```접두사: {0}\n언어: {1}\n음악 컨트롤러 사용: {2}\nDJ 역할: @{3}\n투표 우회: {4}\n24/7 모드: {5}\n기본 볼륨: {6}%\n재생 시간: {7}```", @@ -38,7 +38,6 @@ "addEffect": "`{0}` 필터를 적용하세요.", "clearEffect": "효과가 삭제되었습니다!", "FilterTagAlreadyInUse": "이 필터는 이미 사용 중입니다! 삭제하려면 /cleareffect 를 사용하십시오.", - "playlistViewTitle": "📜 {0}의 모든 재생 목록", "playlistViewHeaders": "ID:,시간:,이름:,트랙:", "playlistFooter": "/playlist play [재생 목록]을 입력하여 재생 목록을 대기열에 추가하세요.", @@ -77,7 +76,6 @@ "inboxFull": "죄송합니다! {0}님의 받은 편지함이 가득 찼습니다.", "inboxNoMsg": "받은 편지함에 메시지가 없습니다.", "invitationSent": "{0}님에게 초대장을 보냈습니다.", - "notInChannel": "{0}, 음성 명령을 사용하려면 {1}에 있어야합니다. 음성 채널에 있지 않으면 다시 참여하세요!", "noTrackPlaying": "현재 재생중인 곡이 없습니다.", "noTrackFound": "해당 쿼리로 곡을 찾을 수 없습니다. 유효한 URL을 제공해주세요.", @@ -92,16 +90,13 @@ "lyricsNotFound": "가사를 찾을 수 없습니다. 가사를 찾으려면 /lyrics <노래 제목> <작곡가>를 입력하세요.", "missingTrackInfo": "일부 트랙 정보가 누락되었습니다.", "noVoiceChannel": "음성 채널을 찾을 수 없습니다!", - "playlistAddError": "스트리밍 비디오를 재생목록에 추가할 수 없습니다!", "playlistAddError2": "재생목록에 곡을 추가하는 중 문제가 발생했습니다!", "playlistlimited": "한 재생목록에 최대 {0}곡까지 추가 가능합니다. 이제 한도에 도달했습니다!", "playlistrepeated": "재생목록에 이미 같은 곡이 있습니다!", "playlistAdded": "❤️ **{0}**을(를) {1}의 재생목록 [`{2}`] 에 추가했습니다!", - "playerDropdown": "건너뛰기 할 노래를 선택하세요...", "playerFilter": "적용할 필터를 선택하세요...", - "buttonBack": "이전", "buttonPause": "일시정지", "buttonResume": "재개", @@ -117,30 +112,24 @@ "buttonForward": "앞으로", "buttonRewind": "뒤로", "buttonLyrics": "가사", - "nowplayingDesc": "**현재 재생중인 곡:**\n```{0}```", "nowplayingField": "다음 곡:", "nowplayingLink": "{0}에서 듣기", - "connect": "{0}에 연결되었습니다.", - "live": "라이브", "playlistLoad": "재생목록 **{0}**을(를) 대기열에 `{1}`개의 곡과 함께 추가했습니다.", "trackLoad": "**{2}**의 **[{0}](<{1}>)** (`{3}`)를 재생목록에 추가하고 재생을 시작합니다.\n", "trackLoad_pos": "**{2}**의 **[{0}](<{1}>)** (`{3}`)를 대기열의 **{4}**번째로 추가합니다.\n", - "searchTitle": "검색 쿼리: {0}", "searchDesc": "➥ 플랫폼: {0} **{1}**\n➥ 결과: **{2}**\n\n{3}", "searchWait": "대기열에 추가할 곡을 선택하세요.", "searchTimeout": "검색이 시간 초과되었습니다. 나중에 다시 시도해주세요.", "searchSuccess": "곡이 대기열에 추가되었습니다.", - "queueTitle": "다음 대기열:", "historyTitle": "이전 곡 대기열:", "viewTitle": "음악 대기열", "viewDesc": "**현재 재생중: [여기를 클릭하여 듣기]({0}) ⮯**\n{1}", "viewFooter": "페이지: {0}/{1} | 총 재생 시간: {2}", - "pauseError": "플레이어가 이미 일시정지되었습니다.", "pauseVote": "{0}님이 노래 일시정지 투표를 했습니다. [{1}/{2}]", "paused": "{0}님이 플레이어를 일시정지했습니다.", @@ -153,13 +142,10 @@ "skipError": "건너뛸 노래가 없습니다.", "skipVote": "{0}님이 노래 건너뛰기 투표를 했습니다. [{1}/{2}]", "skipped": "{0}님이 노래를 건너뛰었습니다.", - "backVote": "{0}님이 이전 노래로 건너뛰기 투표를 했습니다. [{1}/{2}]", "backed": "`{0}`님이 이전 노래로 건너뛰었습니다.", - "leaveVote": "{0}님이 플레이어를 중지하기 투표를 했습니다. [{1}/{2}]", "left": "`{0}`님이 플레이어를 중지했습니다.", - "seek": "플레이어가 **{0}**로 설정되었습니다.", "repeat": "반복 모드가 `{0}`(으)로 설정되었습니다.", "cleared": " `{0}`의 모든 트랙이 삭제되었습니다.", @@ -170,25 +156,20 @@ "swapped": "`{0}`와 `{1}`이(가) 스왑되었습니다.", "moved": "`{0}`을(를) `{1}`로 이동했습니다.", "autoplay": "자동 재생 모드가 **{0}**(으)로 변경되었습니다.", - "notdj": "당신은 DJ가 아닙니다. 현재 DJ는 {0}입니다.", "djToMe": "자신이나 봇에게 DJ를 이전할 수 없습니다.", "djnotinchannel": "`{0}`님이 음성 채널에 없습니다.", "djswap": "당신은 DJ 권한을 `{0}`님에게 이전했습니다.", - "chaptersDropdown": "건너뛸 챕터를 선택해주세요.", "noChaptersFound": "챕터를 찾을 수 없습니다!", "chatpersNotSupport": "이 명령어는 유튜브 비디오만 지원합니다!", - "voicelinkQueueFull": "죄송합니다. 큐에 `{0}`개의 트랙을 모두 추가하셨습니다!", "voicelinkOutofList": "유효한 트랙 인덱스를 제공해주세요!", "voicelinkDuplicateTrack": "죄송합니다. 이 트랙은 이미 큐에 있습니다.", - "deocdeError": "파일 디코딩 중 문제가 발생했습니다!", "invalidStartTime": "효력 없는 시작 시간! 시간은 `00:00` 과 `{0}` 사이에 설정해야 합니다.", "invalidEndTime": "효력 없는 종료 시간! 시간은 `00:00` 과 `{0}` 사이에 설정해야 합니다.", "invalidTimeOrder": "종료 시간은 시작 시간보다 클수 있어야 합니다.", - "setStageAnnounceTemplate": "완료! 이제부터 지금 있는 음성 상태는 귀하의 템플릿에 따라 이름이 지정됩니다. 몇 초 후에 업데이트되는 것을 볼 수 있을 것입니다.", "createSongRequestChannel": "노래 요청 채널 ({0})이 생성되었습니다! 해당 채널에서 노래 제목이나 URL로 원하는 노래를 요청할 수 있으며, 봇 접두사를 사용할 필요가 없습니다." } \ No newline at end of file diff --git a/langs/PL.json b/langs/PL.json index e2d8d40..517fd28 100644 --- a/langs/PL.json +++ b/langs/PL.json @@ -1,8 +1,7 @@ - { +{ "unknownException": "⚠️ Coś poszło nie tak podczas wykonywania polecenia. Spróbuj ponownie później lub dołąćz do serwera discord w celu uzyskania pomocy.", "enabled": "włączony", "disabled": "wyłączony", - "nodeReconnect": "Spróbuj ponownie! Trwa ponowne łącznie z node'm.", "noChannel": "Brak kanału głosowego do dołącznia. Dołącz do jakiegoś, lub wskaż kanał komendą /connect", "alreadyConnected": "Już połączono z kanałem głosowym.", @@ -23,6 +22,7 @@ "togglecontroller": "Kontroler muzyki: `{0}`", "toggleDuplicateTrack": "Unikanie duplikowania pozycji w kolejce: `{0}`", "toggleControllerMsg": "Wiadomości od kontrolera muzyki: `{0}`", + "toggleSilentMsg": "Masz {0} ciche wiadomości.", "settingsMenu": "Ustawnienia serwera | {0}", "settingsTitle": "❤️ Podstawowe informacje:", "settingsValue": "```Prefix: {0}\nJęzyk: {1}\nKontroler muzyki: {2}\nRola DJ: @{3}\nOminięcie głosowania: {4}\n24/7: {5}\nDomyślna głośność: {6}%\nCzas odtwarzania: {7}```", @@ -38,7 +38,6 @@ "addEffect": "Nałożono efekt: `{0}`", "clearEffect": "Efekty dźwiękowe zostały wyczyszczone.", "FilterTagAlreadyInUse": "Ten efekt dźwiękowy jest już w używany! Użyj /cleareffect aby go wyłączyć.", - "playlistViewTitle": "📜 Playlisty użytkownika {0}:", "playlistViewHeaders": "ID:,Czas:,Nazwa:,Ilość pozycji:", "playlistFooter": "Użyj /playlist play [nazwa_playlisty] by dodać playlistę do kolejki.", @@ -77,7 +76,6 @@ "inboxFull": "Błąd! Skrzynka odbiorcza {0} jest pełna.", "inboxNoMsg": "W twojej skrzynce odbiorczej nie ma wiadomości.", "invitationSent": "Zaproszenie wysłane do {0}.", - "notInChannel": "{0}, musisz być na kanale {1} by używać komend głosowych. Jeżeli jesteś na kanale połącz się ponownie!", "noTrackPlaying": "Aktualnie nic nie jest odtwarzane", "noTrackFound": "Nie znaleziono utworu! Podaj poprawny adres URL.", @@ -92,16 +90,13 @@ "lyricsNotFound": "Tekst nie został znaleziony. Użyj /lyrics by znaleźć tekst piosenki.", "missingTrackInfo": "Brakuje niektórych informacji o utworze.", "noVoiceChannel": "Kanał nie został znaleziony", - "playlistAddError": "Niemożesz dodawać transmisji na żywo do playlisty!", "playlistAddError2": "Podczas dodawania utworu do playlisty wystąpił błąd!", "playlistlimited": "Osiągnąłeś limit! Możesz dodać maksymalnie `{0}` pozycji do playlisty.", "playlistrepeated": "Ten utwór znajduje sie już na playliście!", "playlistAdded": "❤️ Dodano **{0}** do playlisty [`{2}`] użytkownika {1}!", - "playerDropdown": "Wybierz pozycję do której pominąć ...", "playerFilter": "Wybierz efekt który chesz nałożyć ...", - "buttonBack": "Cofnij", "buttonPause": "Pauza", "buttonResume": "Wznów", @@ -117,30 +112,24 @@ "buttonForward": "Przewiń do przodu", "buttonRewind": "Przewiń do tyłu", "buttonLyrics": "Tekst utworu", - "nowplayingDesc": "**Aktualnie odtwarzane:**\n```{0}```", "nowplayingField": "Następne:", "nowplayingLink": "Słuchaj na: {0}", - "connect": "Połączono do {0}", - "live": "LIVE", "playlistLoad": " 🎶 Dodano playlistę **{0}** z `{1}` pozycjami do kolejki.", "trackLoad": "Rozpoczęto odtwarzanie **[{0}](<{1}>)** autorstwa **{2}** (`{3}`).\n", "trackLoad_pos": "Dodano **[{0}](<{1}>)** autorstwa **{2}** (`{3}`) do kolejki na pozycji **{4}**\n", - "searchTitle": "Wyszukiwana fraza: {0}", "searchDesc": "➥ Platforma: {0} **{1}**\n➥ Wyniki: **{2}**\n\n{3}", "searchWait": "Wybierz utwór, który chcesz dodać do playlisty.", "searchTimeout": "Upłynął czas wyszukiwania. Spróbuj ponownie później.", "searchSuccess": "Dodano utwór do kolejki.", - "queueTitle": "Nadchodząca kolejka:", "historyTitle": "Historia kolejki:", "viewTitle": "Kolejka utworów", "viewDesc": "**Aktualnie odtwarzane: [Kliknij mnie]({0}) ⮯**\n{1}", "viewFooter": "Strona: {0}/{1} | Całkowity czas trwania: {2}", - "pauseError": "Odtwarzacz jest już zapauzowany.", "pauseVote": "{0} zagłosował nad zapauzowaniem odtwarzania. [{1}/{2}]", "paused": "`{0}` zapauzował odtwarznaie.", @@ -153,13 +142,10 @@ "skipError": "Nie ma utworu, do którego można by pominąć.", "skipVote": "{0} zagłosował za pominięciem tego utworu. [{1}/{2}]", "skipped": "`{0}` pominął utwór.", - "backVote": "{0} zagłosował za przejściem do poprzedniego utworu. [{1}/{2}]", "backed": "`{0}` cofnął do poprzedniego utworu.", - "leaveVote": "{0} zagłosował nad zatrzyamniem odtwarzaza. [{1}/{2}]", "left": "`{0}` zatrzymał odtwarzacz.", - "seek": "Ustawiono odtwarzacz na **{0}**", "repeat": "Tryb pętli został ustawiony na: `{0}`", "cleared": "Wyczyszczono wszystkie utwory w: `{0}`", @@ -170,25 +156,20 @@ "swapped": "`{0}` oraz `{1}` są zamienione", "moved": "Przeniesiono `{0}` do `{1}`", "autoplay": "Tryb autoodwarzania jest teraz **{0}**", - "notdj": "Nie jesteś DJ'em. Obecnym DJ'em jest {0}.", "djToMe": "Nie możesz przekazać roli DJ'a sobie lub botu.", "djnotinchannel": "`{0}` nie jest na kanale głosowym.", "djswap": "Przekazałeś rolę DJ'a użytkownikowi `{0}`.", - "chaptersDropdown": "Wybierz rozdział do którego przeskoczyć ...", "noChaptersFound": "Nie znaleziono żadnych rozdziałów!", "chatpersNotSupport": "Ta komenda obsługuje jedynie utwory z YouTube'a!", - "voicelinkQueueFull": "Przepraszamy osiągnięto limit `{0}` pozycji w kolejce!", "voicelinkOutofList": "Podaj właściwy indeks utworu!", "voicelinkDuplicateTrack": "Błąd! Ten utwór znajduje się już w kolejce.", - "deocdeError": "Coś poszło nie tak podczas dekodowania pliku!", "invalidStartTime": "Niepoprawny czas startu, musi on mieścić się pomiędzy `00:00` a `{0}`", "invalidEndTime": "Niepoprawny czas końca, musi on mieścić się pomiędzy `00:00` a `{0}`", "invalidTimeOrder": "Czas zakończenia odtwarzania nie może być mniejszy lub równy czasowi startu", - "setStageAnnounceTemplate": "Gotowe! Od tego momentu status kanału w którym znajduje się bot będzie wyglądał tak jak podany szablon. Aktualizacja powinna nastąpić za kilka sekund.", "createSongRequestChannel": "Utworzono kanał do obsługi bota - ({0})! Możesz w nim dodawać utwory po nazwie lub adresie URL bez dodawnia prefixu" } \ No newline at end of file diff --git a/langs/RU.json b/langs/RU.json index d0293d0..d7944a2 100644 --- a/langs/RU.json +++ b/langs/RU.json @@ -2,7 +2,6 @@ "unknownException": "⚠️ Что-то пошло не так при выполнении команды! Пожалуйста, попробуйте позже или присоединитесь к нашему серверу Discord для получения дополнительной поддержки.", "enabled": "вкл", "disabled": "выкл", - "nodeReconnect": "Пожалуйста, попробуйте чуть позже!", "noChannel": "Нет голосового канала для подключения. Пожалуйста, укажите или присоединитесь к одному.", "alreadyConnected": "Уже подключен к голосовому каналу.", @@ -23,6 +22,7 @@ "togglecontroller": "Теперь у вас `{0}` контроллер музыки.", "toggleDuplicateTrack": "Теперь у вас `{0}` фильтр дубликатов в очереди.", "toggleControllerMsg": "Теперь у вас `{0}` сообщения от плеера.", + "toggleSilentMsg": "У вас {0} тихих сообщений.", "settingsMenu": "Настройки сервера | {0}", "settingsTitle": "❤️ Основная информация:", "settingsValue": "```Префикс: {0}\nЯзык: {1}\nВключить контроллер музыки: {2}\nDJ роль: @{3}\nОбход голосования: {4}\n24/7: {5}\nГромкость по умолчанию: {6}%\nВремя проигрывания: {7}```", @@ -38,7 +38,6 @@ "addEffect": "Применен `{0}` эффект.", "clearEffect": "Звуковые эффекты были очищены!", "FilterTagAlreadyInUse": "Этот звуковой эффект уже используется! Пожалуйста, используйте /cleareffect <Тег>, чтобы удалить его.", - "playlistViewTitle": "📜 Все плейлисты пользователя {0}", "playlistViewHeaders": "ID:,Время:,Название:,Треки:", "playlistFooter": "Введите /playlist play [плейлист], чтобы добавить плейлист в очередь.", @@ -77,7 +76,6 @@ "inboxFull": "Извините! Почтовый ящик {0} переполнен.", "inboxNoMsg": "В вашем почтовом ящике нет сообщений.", "invitationSent": "Приглашение отправлено {0}.", - "notInChannel": "{0}, для использования голосовых команд вы должны находиться в {1}. Пожалуйста, перезайдите, если вы уже находитесь в голосовом канале!", "noTrackPlaying": "Сейчас нет играющих треков.", "noTrackFound": "Треки по такому запросу не найдены! Пожалуйста, укажите действительную ссылку.", @@ -92,16 +90,13 @@ "lyricsNotFound": "Текст песни не найден. Введите /lyrics <Название песни> <Автор> для поиска текста.", "missingTrackInfo": "Некоторая информация о треке отсутствует.", "noVoiceChannel": "Голосовой канал не найден!", - "playlistAddError": "Вам не разрешено добавлять потоковые видео в плейлист!", "playlistAddError2": "Произошла ошибка при добавлении треков в плейлист!", "playlistlimited": "Вы достигли лимита! Вы можете добавить только {0} треков в свой плейлист.", "playlistrepeated": "Такой трек уже есть в плейлисте!", "playlistAdded": "❤️ Добавлен **{0}** в плейлист пользователя {1} [`{2}`]!", - "playerDropdown": "Выберите трек для перехода ...", "playerFilter": "Выберите фильтр для применения ...", - "buttonBack": "Назад", "buttonPause": "Пауза", "buttonResume": "Пуск", @@ -117,13 +112,10 @@ "buttonForward": "+10сек", "buttonRewind": "Назад", "buttonLyrics": "Тексты песен", - "nowplayingDesc": "**Сейчас играет:**\n```{0}```", "nowplayingField": "Следующее:", "nowplayingLink": "Слушать на {0}", - "connect": "Подключено к {0}", - "live": "В ЭФИРЕ", "playlistLoad": "🎶 Добавлен плейлист **{0}** с `{1}` треками в очередь.", "trackLoad": "Добавлен **[{0}](<{1}>)** от **{2}** (`{3}`) в очередь.\n", @@ -133,13 +125,11 @@ "searchWait": "Выберите трек, который хотите добавить в очередь.", "searchTimeout": "Поиск прерван: превышено время ожидания. Пожалуйста, попробуйте позже.", "searchSuccess": "Трек добавлен в очередь.", - "queueTitle": "Следующий в очереди:", "historyTitle": "История очереди:", "viewTitle": "Текущая очередь", "viewDesc": "**Сейчас играет: [*ссылка*]({0}) ⮯**\n{1}", "viewFooter": "Страница: {0}/{1} | Общая продолжительность: {2}", - "pauseError": "Плеер уже на паузе.", "pauseVote": "Пользователь `{0}` проголосовал за паузу плеера. [{1}/{2}]", "paused": "Пользователь `{0}` поставил плеер на паузу.", @@ -152,13 +142,10 @@ "skipError": "Нет треков для пропуска.", "skipVote": "Пользователь `{0}` проголосовал за пропуск трека. [{1}/{2}]", "skipped": "Пользователь `{0}` пропустил трек.", - "backVote": "Пользователь `{0}` проголосовал за возврат к предыдущему треку. [{1}/{2}]", "backed": "Пользователь `{0}` вернулся к предыдущему треку.", - "leaveVote": "Пользователь `{0}` проголосовал за остановку плеера. [{1}/{2}]", "left": "Пользователь `{0}` остановил плеер.", - "seek": "Перемотать на **{0}**", "repeat": "Режим повтора `{0}`", "cleared": "Очищены все треки в `{0}`", @@ -169,25 +156,20 @@ "swapped": "Треки `{0}` и `{1}` поменяны местами", "moved": "Трек `{0}` смещён на `{1}`", "autoplay": "Режим autoplay **{0}**", - "notdj": "Вы не DJ. Текущий DJ: {0}.", "djToMe": "Вы не можете передать роль DJ себе или боту.", "djnotinchannel": "Пользователь `{0}` не находится в голосовом канале.", "djswap": "Вы передали роль DJ пользователю `{0}`.", - "chaptersDropdown": "Выберите эпизод для перехода ...", "noChaptersFound": "Эпизод не найден!", "chatpersNotSupport": "Эта команда поддерживает только видео на YouTube!", - "voicelinkQueueFull": "Извините, вы достигли максимального количества `{0}` треков в очереди!", "voicelinkOutofList": "Пожалуйста, предоставьте действительный индекс трека!", "voicelinkDuplicateTrack": "Извините, этот трек уже есть в очереди.", - "deocdeError": "Что-то пошло не так при декодировании файла!", "invalidStartTime": "Недействительное значение! Диапозон времени должен быть между `00:00` и `{0}`.", "invalidEndTime": "Недействительное значение! Диапозон времени должен быть между `00:00` и `{0}`.", "invalidTimeOrder": "Время конца трека не может быть меньше или равно времени начала.", - "setStageAnnounceTemplate": "Готово! Новые настройки статуса канала скоро применятся в соответсвии с вашим шаблоном.", "createSongRequestChannel": "Канал для запроса песен ({0}) создан! Вы можете запросить любую песню по названию или URL в этом канале без использования префикса бота." -} +} \ No newline at end of file diff --git a/langs/UA.json b/langs/UA.json index 0f3c745..b77411b 100644 --- a/langs/UA.json +++ b/langs/UA.json @@ -2,7 +2,6 @@ "unknownException": "⚠️ Щось пішло не так під час виконання команди! Будь ласка, спробуйте пізніше або приєднайтеся до нашого сервера Discord для отримання додаткової підтримки.", "enabled": "вкл", "disabled": "викл", - "nodeReconnect": "Будь ласка, спробуйте знову! Після перепідключення вузла.", "noChannel": "Немає голосового каналу для підключення. Будь ласка, вкажіть або приєднайтеся до одного.", "alreadyConnected": "Уже підключений до голосового каналу.", @@ -23,6 +22,7 @@ "togglecontroller": "Тепер у вас `{0}` контролер музики.", "toggleDuplicateTrack": "Тепер у вас `{0}` запобігання дублюванню треку в черзі.", "toggleControllerMsg": "Тепер у вас `{0}` повідомлення від контролера музики.", + "toggleSilentMsg": "У вас {0} тихих повідомлень.", "settingsMenu": "Налаштування сервера | {0}", "settingsTitle": "❤️ Основна інформація:", "settingsValue": "```Префікс: {0}\nМова: {1}\nУвімкнути контролер музики: {2}\nDJ роль: {3}\nОбхід голосування: {4}\n24/7: {5}\nГромкість за замовчуванням: {6}%\nЧас програвання: {7}```", @@ -38,7 +38,6 @@ "addEffect": "Застосуйте ефект `{0}` фільтр.", "clearEffect": "Звукові ефекти були очищені!", "FilterTagAlreadyInUse": "Цей звуковий ефект уже використовується! Будь ласка, використовуйте /cleareffect <Тег>, щоб видалити його.", - "playlistViewTitle": "📜 Усі плейлисти користувача {0}", "playlistViewHeaders": "ID:,Час:,Назва:,Треки:", "playlistFooter": "Введите /playlist play [плейлист], чтобы добавить плейлист в очередь.", @@ -77,7 +76,6 @@ "inboxFull": "Вибачте! Поштова скринька {0} переповнена.", "inboxNoMsg": "У вашій поштовій скриньці немає повідомлень.", "invitationSent": "Запрошення надіслано {0}.", - "notInChannel": "{0}, для використання голосових команд ви маєте перебувати в {1}. Будь ласка, перезайдіть, якщо ви вже в голосі!", "noTrackPlaying": "Зараз немає пісень, які грають.", "noTrackFound": "Пісні з таким запитом не знайдено! Будь ласка, вкажіть дійсне посилання.", @@ -92,16 +90,13 @@ "lyricsNotFound": "Текст пісні не знайдено. Введіть /lyrics <Назва пісні> <Автор> для пошуку тексту.", "missingTrackInfo": "Деяка інформація про трек відсутня.", "noVoiceChannel": "Голосовий канал не знайдено!", - "playlistAddError": "Вам не дозволено додавати потокові відео в плейлист!", "playlistAddError2": "Сталася помилка під час додавання треків у плейлист!", "playlistlimited": "Ви досягли ліміту! Ви можете додати тільки {0} пісні до свого плейлиста.", "playlistrepeated": "Такий самий трек уже є у вашому плейлисті!", "playlistAdded": "❤️ Додано **{0}** до плейлиста користувача {1} [`{2}`]!", - "playerDropdown": "Виберіть пісню для переходу ...", "playerFilter": "Виберіть фільтр для застосування ...", - "buttonBack": "Назад", "buttonPause": "Призупинити", "buttonResume": "Продовжити", @@ -117,13 +112,10 @@ "buttonForward": "Вперед", "buttonRewind": "Назад", "buttonLyrics": "Тексти пісень", - "nowplayingDesc": "**Зараз грає:**\n```{0}```", "nowplayingField": "Наступне:", "nowplayingLink": "Слухати на {0}", - "connect": "Підключено до {0}", - "live": "Прямий ефір", "playlistLoad": "🎶 Додано плейлист **{0}** з `{1}` піснями в чергу.", "trackLoad": "Додано **[{0}](<{1}>)** від **{2}** (`{3}`) для початку програвання.\n", @@ -133,13 +125,11 @@ "searchWait": "Виберіть пісню, яку хочете додати в чергу.", "searchTimeout": "Пошук перервано: перевищено час очікування. Будь ласка, спробуйте пізніше.", "searchSuccess": "Пісню додано в чергу.", - "queueTitle": "Наступний у черзі:", "historyTitle": "Історія черги:", "viewTitle": "Поточна черга", "viewDesc": "**Заразом грає: [*посилання*]({0}) ⮯**\n{1}", "viewFooter": "Сторінка: {0}/{1} | Загальна тривалість: {2}", - "pauseError": "Плеєр уже на паузі.", "pauseVote": "{0} проголосував за паузу пісні. [{1}/{2}]", "paused": "`{0}` Поставив плеєр на паузу.", @@ -152,13 +142,10 @@ "skipError": "Немає пісень для пропуску.", "skipVote": "{0} проголосував за пропуск пісні. [{1}/{2}]", "skipped": "`{0}` пропустив пісню.", - "backVote": "{0} проголосував за повернення до попередньої пісні. [{1}/{2}]", "backed": "`{0}` повернувся до попередньої пісні.", - "leaveVote": "{0} проголосував за зупинку плеєра. [{1}/{2}]", "left": "`{0}` зупинив плеєр.", - "seek": "Встановити плеєр на **{0}**", "repeat": "Режим циклу встановлено на `{0}`", "cleared": "Очищено всі треки в `{0}`", @@ -169,25 +156,20 @@ "swapped": "Треки `{0}` і `{1}` обміняні місцями", "moved": "Трек `{0}` зміщений на `{1}`", "autoplay": "Режим autoplay встановлено на **{0}**", - "notdj": "Ви не DJ. Поточний DJ: {0}.", "djToMe": "Ви не можете передати роль DJ собі або боту.", "djnotinchannel": "`{0}` не знаходиться в голосовому каналі.", "djswap": "Ви передали роль DJ `{0}`.", - "chaptersDropdown": "Виберіть епізод для переходу ...", "noChaptersFound": "Епізод не знайдено!", "chatpersNotSupport": "Ця команда підтримує тільки відео з YouTube!", - "voicelinkQueueFull": "Вибачте, ви досягли максимальної кількості `{0}` треків у черзі!", "voicelinkOutofList": "Будь ласка, надайте дійсний індекс треку!", "voicelinkDuplicateTrack": "Вибачте, цей трек уже є в черзі.", - "deocdeError": "Щось пішло не так під час декодування файлу!", "invalidStartTime": "Недійснений час початку! Час має бути в межах `00:00` та `{0}`.", "invalidEndTime": "Недійснений час закінчення! Час має бути в межах `00:00` та `{0}`.", "invalidTimeOrder": "Час закінчення не може бути меншим або рівним часу початку.", - "setStageAnnounceTemplate": "Готово! Відтепер статус голосу, як той, в якому ви зараз перебуваєте, буде називатися відповідно до вашого шаблону. Ви повинні побачити оновлення через кілька секунд.", "createSongRequestChannel": "Канал для запитів пісень ({0}) створено! Ви можете почати запитувати будь-яку пісню за назвою або URL у цьому каналі без необхідності використовувати префікс бота." } \ No newline at end of file diff --git a/local_langs/zh-TW.json b/local_langs/zh-TW.json index d4ae83e..e56cd23 100644 --- a/local_langs/zh-TW.json +++ b/local_langs/zh-TW.json @@ -157,7 +157,9 @@ "customcontroller": "自定義控制器", "Customizes music controller embeds.": "自定義音樂控制器嵌入。", "controllermsg": "控制器訊息", + "silentmsg": "靜默訊息", "Toggles to send a message when clicking the button in the music controller.": "切換發送訊息當點擊音樂控制器中的按鈕。", + "Toggle silent messaging to send discreet messages without alerting recipients.": "切換靜默消息以發送不會提醒收件人的私密消息。", "debug": "除錯", "speed": "速度", "Sets the player's playback speed": "設置播放器的播放速度。", diff --git a/update.py b/update.py index cf537be..35cb466 100644 --- a/update.py +++ b/update.py @@ -31,7 +31,7 @@ import subprocess from io import BytesIO ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) -__version__ = "v2.7.2b1" +__version__ = "v2.7.2b2" # URLs for update and migration PYTHON_CMD_NAME = os.path.basename(sys.executable) From d9fb58ef927444f8c011012629207a8f81658344 Mon Sep 17 00:00:00 2001 From: Choco Date: Mon, 4 Aug 2025 10:57:54 +0800 Subject: [PATCH 21/22] Use snake_case for all database keys --- cogs/settings.py | 30 +++++++++++++++--------------- function.py | 2 +- ipc/methods.py | 8 +++++--- langs/CH.json | 2 +- langs/DE.json | 2 +- langs/EN.json | 2 +- langs/ES.json | 2 +- langs/FR.json | 2 +- langs/JA.json | 2 +- langs/KO.json | 2 +- langs/PL.json | 2 +- langs/RU.json | 2 +- langs/UA.json | 2 +- update.py | 2 +- voicelink/player.py | 17 ++++++++++------- voicelink/queue.py | 5 +++++ 16 files changed, 47 insertions(+), 37 deletions(-) diff --git a/cogs/settings.py b/cogs/settings.py index 3e662a6..ad6e22d 100644 --- a/cogs/settings.py +++ b/cogs/settings.py @@ -99,16 +99,16 @@ class Settings(commands.Cog, name="settings"): @settings.command(name="queue", aliases=get_aliases("queue")) @app_commands.choices(mode=[ - app_commands.Choice(name="FairQueue", value="FairQueue"), - app_commands.Choice(name="Queue", value="Queue") + app_commands.Choice(name=queue_type.capitalize(), value=queue_type) + for queue_type in voicelink.queue.QUEUE_TYPES.keys() ]) @commands.has_permissions(manage_guild=True) @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) async def queue(self, ctx: commands.Context, mode: str): "Change to another type of queue mode." - mode = "FairQueue" if mode.lower() == "fairqueue" else "Queue" - await update_settings(ctx.guild.id, {"$set": {"queueType": mode}}) - await send(ctx, "setqueue", mode) + mode = mode if mode.lower() in voicelink.queue.QUEUE_TYPES else next(iter(voicelink.queue.QUEUE_TYPES)) + await update_settings(ctx.guild.id, {"$set": {"queue_type": mode}}) + await send(ctx, "setQueue", mode.capitalize()) @settings.command(name="247", aliases=get_aliases("247")) @commands.has_permissions(manage_guild=True) @@ -126,8 +126,8 @@ class Settings(commands.Cog, name="settings"): async def bypassvote(self, ctx: commands.Context): "Toggles voting system." settings = await get_settings(ctx.guild.id) - toggle = settings.get('votedisable', True) - await update_settings(ctx.guild.id, {"$set": {'votedisable': not toggle}}) + toggle = settings.get('disabled_vote', True) + await update_settings(ctx.guild.id, {"$set": {'disabled_vote': not toggle}}) await send(ctx, 'bypassVote', await get_lang(ctx.guild.id, "enabled" if not toggle else "disabled")) @settings.command(name="view", aliases=get_aliases("view")) @@ -149,16 +149,16 @@ class Settings(commands.Cog, name="settings"): settings.get('lang', 'EN'), settings.get('controller', True), dj_role.name if dj_role else 'None', - settings.get('votedisable', False), + settings.get('disabled_vote', False), settings.get('24/7', False), settings.get('volume', 100), - ctime(settings.get('playTime', 0) * 60 * 1000), + ctime(settings.get('played_time', 0) * 60 * 1000), inline=True) ) embed.add_field(name=texts[3], value=texts[4].format( - settings.get("queueType", "Queue"), + settings.get("queue_type", "Queue"), func.settings.max_queue, - settings.get("duplicateTrack", True) + settings.get("duplicate_track", True) )) if stage_template := settings.get("stage_announce_template"): @@ -212,12 +212,12 @@ class Settings(commands.Cog, name="settings"): async def duplicatetrack(self, ctx: commands.Context): "Toggle Vocard to prevent duplicate songs from queuing." settings = await get_settings(ctx.guild.id) - toggle = not settings.get('duplicateTrack', False) + toggle = not settings.get('duplicate_track', False) player: voicelink.Player = ctx.guild.voice_client if player: player.queue._allow_duplicate = toggle - await update_settings(ctx.guild.id, {"$set": {'duplicateTrack': toggle}}) + await update_settings(ctx.guild.id, {"$set": {'duplicate_track': toggle}}) return await send(ctx, "toggleDuplicateTrack", await get_lang(ctx.guild.id, "disabled" if toggle else "enabled")) @settings.command(name="customcontroller", aliases=get_aliases("customcontroller")) @@ -248,9 +248,9 @@ class Settings(commands.Cog, name="settings"): async def silentmsg(self, ctx: commands.Context): "Toggle silent messaging to send discreet messages without alerting recipients." settings = await get_settings(ctx.guild.id) - toggle = not settings.get('silentMsg', False) + toggle = not settings.get('silent_msg', False) - await update_settings(ctx.guild.id, {"$set": {'silentMsg': toggle}}) + await update_settings(ctx.guild.id, {"$set": {'silent_msg': toggle}}) await send(ctx, 'toggleSilentMsg', await get_lang(ctx.guild.id, "enabled" if toggle else "disabled")) @settings.command(name="stageannounce", aliases=get_aliases("stageannounce")) diff --git a/function.py b/function.py index 7e1029a..207342c 100644 --- a/function.py +++ b/function.py @@ -223,7 +223,7 @@ async def send( "embed": embed, "ephemeral": ephemeral, "allowed_mentions": ALLOWED_MENTIONS, - "silent": settings.get("silentMsg", False), + "silent": settings.get("silent_msg", False), } if "delete_after" in send_func.__code__.co_varnames: diff --git a/ipc/methods.py b/ipc/methods.py index a3c3cca..384eece 100644 --- a/ipc/methods.py +++ b/ipc/methods.py @@ -12,12 +12,14 @@ RATELIMIT_COUNTER: Dict[int, Dict[str, float]] = {} SCOPES = { "prefix": str, "lang": str, - "queueType": str, + "queue_type": str, "dj": int, "controller": bool, + "controller_msg": bool, "24/7": bool, - "votedisable": bool, - "duplicateTrack": bool, + "disabled_vote": bool, + "duplicate_track": bool, + "silent_msg": bool, "default_controller": dict, "stage_announce_template": str } diff --git a/langs/CH.json b/langs/CH.json index e89990c..d61e527 100644 --- a/langs/CH.json +++ b/langs/CH.json @@ -15,7 +15,7 @@ "changedLanguage": "已成功切換到 `{0}` 語言包。", "setPrefix": "完成!我的前綴在您的伺服器中現在是 `{0}`。嘗試運行 `{1}ping` 來測試它。", "setDJ": "已將 DJ 設置為 {0}。", - "setqueue": "已將隊列模式設置為 `{0}`。", + "setQueue": "已將隊列模式設置為 `{0}`。", "247": "現在您有 `{0}` 24/7 模式。", "bypassVote": "現在您有 `{0}` 投票系統。", "setVolume": "已將音量設置為 `{0}`%", diff --git a/langs/DE.json b/langs/DE.json index 482d428..932564a 100644 --- a/langs/DE.json +++ b/langs/DE.json @@ -15,7 +15,7 @@ "changedLanguage": "Erfolgreich auf das Sprachpaket `{0}` geändert.", "setPrefix": "Erledigt! Mein Präfix ist jetzt `{0}` auf deinem Server. Versuche, `{1}ping` auszuführen, um es zu testen.", "setDJ": "Stelle den DJ auf {0}.", - "setqueue": "Stelle den Warteschlangenmodus auf `{0}` ein.", + "setQueue": "Stelle den Warteschlangenmodus auf `{0}` ein.", "247": "Der 24/7-Modus wurde erfolgreich `{0}`.", "bypassVote": "Das Abstimmungssystem wurde `{0}`", "setVolume": "Stelle die Lautstärke auf `{0}`%.", diff --git a/langs/EN.json b/langs/EN.json index 0f073f5..096d6a9 100644 --- a/langs/EN.json +++ b/langs/EN.json @@ -15,7 +15,7 @@ "changedLanguage": "Successfully changed to `{0}` language pack.", "setPrefix": "Done! My prefix in your server is now `{0}`. Try running `{1}ping` to test it out.", "setDJ": "Set the dj to {0}.", - "setqueue": "Set queue mode to `{0}`.", + "setQueue": "Set queue mode to `{0}`.", "247": "24/7 mode is now `{0}`.", "bypassVote": "You have `{0}` voting system.", "setVolume": "Set the volume to `{0}`%", diff --git a/langs/ES.json b/langs/ES.json index 6bd17cc..cb8691c 100644 --- a/langs/ES.json +++ b/langs/ES.json @@ -15,7 +15,7 @@ "changedLanguage": "Cambiado con éxito al paquete de idioma `{0}`.", "setPrefix": "¡Listo! Mi prefijo en tu servidor ahora es `{0}`. Intenta ejecutar `{1}ping` para probarlo.", "setDJ": "Establecer el DJ a {0}.", - "setqueue": "Establecer el modo de cola en `{0}`.", + "setQueue": "Establecer el modo de cola en `{0}`.", "247": "Ahora tienes `{0}` modo 24/7.", "bypassVote": "Ahora tienes `{0}` sistema de votación.", "setVolume": "Ajustar el volumen a `{0}`%", diff --git a/langs/FR.json b/langs/FR.json index de8f982..5dfbef2 100644 --- a/langs/FR.json +++ b/langs/FR.json @@ -15,7 +15,7 @@ "changedLanguage": "Changement réussi vers le pack de langue `{0}`.", "setPrefix": "C'est fait ! Mon préfixe dans votre serveur est désormais `{0}`. Essayez de lancer `{1}ping` pour tester.", "setDJ": "Le DJ a été défini sur {0}.", - "setqueue": "Le mode file d'attente est défini sur `{0}`.", + "setQueue": "Le mode file d'attente est défini sur `{0}`.", "247": "Le mode 24/7 est maintenant `{0}`.", "bypassVote": "Vous avez le système de vote `{0}`.", "setVolume": "Le volume est réglé à `{0}`%", diff --git a/langs/JA.json b/langs/JA.json index 6a47f8e..d79b725 100644 --- a/langs/JA.json +++ b/langs/JA.json @@ -15,7 +15,7 @@ "changedLanguage": "「{0}」言語パックに正常に変更しました。", "setPrefix": "完了!あなたのサーバーのプレフィックスは今や「{0}」です。 `{1}ping`を実行してテストしてみてください。", "setDJ": "{0}にDJを設定しました。", - "setqueue": "キューモードを「{0}」に設定しました。", + "setQueue": "キューモードを「{0}」に設定しました。", "247": "今、24/7モードは「{0}」です。", "bypassVote": "今、投票システムは「{0}」になりました。", "setVolume": "音量を「{0}%」に設定しました。", diff --git a/langs/KO.json b/langs/KO.json index c491e96..eedb8e0 100644 --- a/langs/KO.json +++ b/langs/KO.json @@ -15,7 +15,7 @@ "changedLanguage": "성공적으로 `{0}` 언어 팩으로 변경되었습니다.", "setPrefix": "완료되었습니다! 이 서버에서 내 접두사는 이제 `{0}`입니다. `{1}ping`을 실행하여 테스트해보세요.", "setDJ": "DJ를 {0}(으)로 설정했습니다.", - "setqueue": "큐 모드를 `{0}`(으)로 설정했습니다.", + "setQueue": "큐 모드를 `{0}`(으)로 설정했습니다.", "247": "이제 24/7 모드에서 `{0}`으로 변경되었습니다.", "bypassVote": "이제 `{0}` 투표 시스템을 사용할 수 있습니다.", "setVolume": "볼륨을 `{0}`%로 설정했습니다.", diff --git a/langs/PL.json b/langs/PL.json index 517fd28..91e7f20 100644 --- a/langs/PL.json +++ b/langs/PL.json @@ -15,7 +15,7 @@ "changedLanguage": "Pomyślnie zmieniono pakiet językowy na: `{0}` .", "setPrefix": "Gotowe! Mój prefix na tym serwerze to teraz `{0}`. Spróbuj uruchomić polecenie `{1}ping`, aby go przetestować.", "setDJ": "Ustawiono DJ jako {0}.", - "setqueue": "Zmienionio tryb kolejki na `{0}`.", + "setQueue": "Zmienionio tryb kolejki na `{0}`.", "247": "Tryb 24/7: `{0}`", "bypassVote": "System głosowania: `{0}`", "setVolume": "Ustawiono głośność na `{0}`%", diff --git a/langs/RU.json b/langs/RU.json index d7944a2..cae3948 100644 --- a/langs/RU.json +++ b/langs/RU.json @@ -15,7 +15,7 @@ "changedLanguage": "Язык успешно изменен на `{0}`.", "setPrefix": "Готово! Мой префикс на вашем сервере теперь `{0}`. Попробуйте запустить `{1}ping`, чтобы проверить его.", "setDJ": "Установить роль DJ {0}.", - "setqueue": "Установить режим очереди на `{0}`.", + "setQueue": "Установить режим очереди на `{0}`.", "247": "Теперь у вас `{0}` режим 24/7.", "bypassVote": "Теперь у вас `{0}` система голосования.", "setVolume": "Установить громкость на `{0}`%", diff --git a/langs/UA.json b/langs/UA.json index b77411b..6b8443d 100644 --- a/langs/UA.json +++ b/langs/UA.json @@ -15,7 +15,7 @@ "changedLanguage": "Успішно змінено на мовний пакет `{0}`.", "setPrefix": "Готово! Мій префікс на вашому сервері тепер `{0}`. Спробуйте запустити `{1}ping`, щоб перевірити його.", "setDJ": "Встановити роль DJ {0}.", - "setqueue": "Встановити режим черги на `{0}`.", + "setQueue": "Встановити режим черги на `{0}`.", "247": "Тепер у вас `{0}` режим 24/7.", "bypassVote": "Тепер у вас `{0}` система голосування.", "setVolume": "Встановити гучність на `{0}`%", diff --git a/update.py b/update.py index 35cb466..2ada435 100644 --- a/update.py +++ b/update.py @@ -31,7 +31,7 @@ import subprocess from io import BytesIO ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) -__version__ = "v2.7.2b2" +__version__ = "v2.7.2b3" # URLs for update and migration PYTHON_CMD_NAME = os.path.basename(sys.executable) diff --git a/voicelink/player.py b/voicelink/player.py index e53dc1d..fbf3d7f 100644 --- a/voicelink/player.py +++ b/voicelink/player.py @@ -51,8 +51,8 @@ from .exceptions import VoicelinkException, FilterInvalidArgument, TrackInvalidP from .filters import Filter, Filters from .objects import Track, Playlist from .pool import Node, NodePool -from .queue import Queue, FairQueue from .placeholders import Placeholders, build_embed +from .queue import Queue, QUEUE_TYPES from random import shuffle, choice async def connect_channel(ctx: Union[commands.Context, Interaction], channel: VoiceChannel = None): @@ -115,7 +115,10 @@ class Player(VoiceProtocol): self.settings: dict = settings self.joinTime: float = round(time.time()) self._volume: int = self.settings.get('volume', 100) - self.queue: Queue = eval(self.settings.get("queueType", "Queue"))(self.settings.get("maxQueue", func.settings.max_queue), self.settings.get("duplicateTrack", True), self.get_msg) + self.queue: Queue = QUEUE_TYPES.get(self.settings.get("queue_type", "queue").lower())( + self.settings.get("max_queue", func.settings.max_queue), + self.settings.get("duplicate_track", True), self.get_msg + ) self._node = NodePool.get_node() self._current: Optional[Track] = None @@ -263,7 +266,7 @@ class Player(VoiceProtocol): If `leave` is True and the channel has three members, the requirement adjusts to 2 votes. """ - if self.settings.get('votedisable'): + if self.settings.get('disabled_vote'): return 0 required = ceil((len(self.channel.members) - 1) / 2.5) @@ -493,8 +496,8 @@ class Player(VoiceProtocol): """Cleans up the player and associated resources.""" try: await func.update_settings(self.guild.id, {"$set": { - "lastActice": (timeNow := round(time.time())), - "playTime": round(self.settings.get("playTime", 0) + ((timeNow - self.joinTime) / 60), 2) + "last_active": (timeNow := round(time.time())), + "played_time": round(self.settings.get("played_time", 0) + ((timeNow - self.joinTime) / 60), 2) }}) if self.is_ipc_connected: @@ -711,7 +714,7 @@ class Player(VoiceProtocol): await self.send_ws({ "op": "shuffleTrack", "tracks": [{"trackId": track.track_id, "requesterId": str(track.requester.id)} for track in replacement], - "queueType": queue_type + "queue_type": queue_type }, requester) self._logger.debug(f"Player in {self.guild.name}({self.guild.id}) has been shuffled the queue.") @@ -785,7 +788,7 @@ class Player(VoiceProtocol): if self.is_ipc_connected: await self.send_ws({ "op": "clearQueue", - "queueType": queue_type + "queue_type": queue_type }, requester) async def remove_filter(self, filter_tag: str, requester: Member = None, fast_apply: bool = False) -> Filters: diff --git a/voicelink/queue.py b/voicelink/queue.py index cb5bd3b..58092e2 100644 --- a/voicelink/queue.py +++ b/voicelink/queue.py @@ -223,3 +223,8 @@ class FairQueue(Queue): self.put_at_index(lastIndex, item) return lastIndex + +QUEUE_TYPES: Dict[str, Queue] = { + "queue": Queue, + "fairqueue": FairQueue +} From e97a604aed9fbc7ed1a3ebccf4e0d76eb71fc4a2 Mon Sep 17 00:00:00 2001 From: Choco Date: Mon, 4 Aug 2025 11:07:59 +0800 Subject: [PATCH 22/22] Added silent mode to the controller --- voicelink/player.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/voicelink/player.py b/voicelink/player.py index fbf3d7f..3f80f15 100644 --- a/voicelink/player.py +++ b/voicelink/player.py @@ -463,11 +463,11 @@ class Player(VoiceProtocol): # Send a new controller message if none exists if not self.controller: - self.controller = await self.context.channel.send(embed=embed, view=view) + self.controller = await func.send(self.context, content=embed, view=view) elif not await self.is_position_fresh(): await self.controller.delete() - self.controller = await self.context.channel.send(embed=embed, view=view) + self.controller = await func.send(self.context, content=embed, view=view) else: await self.controller.edit(embed=embed, view=view)