diff --git a/.DS_Store b/.DS_Store index ef029df8..9c1109ae 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 53b4f729..77ad3eda 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 @@ -12,18 +16,42 @@ permissions: jobs: build: 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: 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: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Docker Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/chocomeow/vocard + tags: | + # For version tags (v1.2.3) + type=ref,event=tag + # For main branch + type=raw,value=latest,enable={{is_default_branch}} + # For beta branch + type=raw,value=beta,enable=${{ github.ref == 'refs/heads/beta' }} + flavor: | + latest=false + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + platforms: linux/amd64,linux/arm64 + cache-from: type=gha + cache-to: type=gha,mode=max \ No newline at end of file diff --git a/README.md b/README.md index afe10e37..55a58700 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. diff --git a/addons/lyrics.py b/addons/lyrics.py index 40b8e1da..e05168af 100644 --- a/addons/lyrics.py +++ b/addons/lyrics.py @@ -21,9 +21,17 @@ 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.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 @@ async def get_lyrics(self, title: str, artist: str) -> Optional[dict[str, str]]: 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 @@ async def get_lyrics(self, title: str, artist: str) -> Optional[dict[str, str]]: 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 @@ async def get(self, url, params: dict = None) -> list[dict]: 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 diff --git a/addons/settings.py b/addons/settings.py index 5e8e4a91..ff798076 100644 --- a/addons/settings.py +++ b/addons/settings.py @@ -21,6 +21,9 @@ SOFTWARE. """ +import os + +from dotenv import load_dotenv from typing import ( Dict, List, @@ -28,13 +31,15 @@ Union ) +load_dotenv() + class Settings: def __init__(self, settings: Dict) -> None: - self.token: str = settings.get("token") - self.client_id: int = int(settings.get("client_id", 0)) - self.genius_token: str = settings.get("genius_token") - self.mongodb_url: str = settings.get("mongodb_url") - self.mongodb_name: str = settings.get("mongodb_name") + self.token: str = settings.get("token") or os.getenv("TOKEN") + self.client_id: int = int(settings.get("client_id", 0)) or int(os.getenv("CLIENT_ID")) + self.genius_token: str = settings.get("genius_token") or os.getenv("GENIUS_TOKEN") + self.mongodb_url: str = settings.get("mongodb_url") or os.getenv("MONGODB_URL") + self.mongodb_name: str = settings.get("mongodb_name") or os.getenv("MONGODB_NAME") self.invite_link: str = "https://discord.gg/wRCgB7vBQv" self.nodes: Dict[str, Dict[str, Union[str, int, bool]]] = settings.get("nodes", {}) diff --git a/cogs/basic.py b/cogs/basic.py index 9eb6d364..0a10f96a 100644 --- a/cogs/basic.py +++ b/cogs/basic.py @@ -93,8 +93,15 @@ async def play_autocomplete(self, interaction: discord.Interaction, current: str 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) - 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 [] + + tracks: list[voicelink.Track] = await node.get_tracks(current, requester=interaction.user) + if not tracks: + return [] + + if isinstance(tracks, voicelink.Playlist): + tracks = tracks.tracks + + 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] history = {track["identifier"]: track for track_id in reversed(await get_user(interaction.user.id, "history")) if (track := voicelink.decode(track_id))["uri"]} return [app_commands.Choice(name=truncate_string(f"🕒 {track['author']} - {track['title']}", 100), value=track['uri']) for track in history.values() if len(track['uri']) <= 100][:25] @@ -308,7 +315,7 @@ async def forceplay(self, ctx: commands.Context, *, query: str, start: str = "0" player = await voicelink.connect_channel(ctx) if not player.is_privileged(ctx.author): - return await send(ctx, "missingPerms_function", ephemeral=True) + return await send(ctx, "missingFunctionPerm", ephemeral=True) if ctx.interaction: await ctx.interaction.response.defer() @@ -457,7 +464,7 @@ async def seek(self, ctx: commands.Context, position: str): return await send(ctx, "noPlayer", ephemeral=True) if not player.is_privileged(ctx.author): - return await send(ctx, "missingPerms_pos", ephemeral=True) + return await send(ctx, "missingPosPerm", ephemeral=True) if not player.current or player.position == 0: return await send(ctx, "noTrackPlaying", ephemeral=True) @@ -621,7 +628,7 @@ async def loop(self, ctx: commands.Context, mode: str): return await send(ctx, "noPlayer", ephemeral=True) if not player.is_privileged(ctx.author): - return await send(ctx, "missingPerms_mode", ephemeral=True) + return await send(ctx, "missingModePerm", ephemeral=True) await player.set_repeat(LoopType[mode] if mode in LoopType.__members__ else LoopType.OFF, ctx.author) await send(ctx, "repeat", mode.capitalize()) @@ -640,7 +647,7 @@ async def clear(self, ctx: commands.Context, queue: str = "queue"): return await send(ctx, "noPlayer", ephemeral=True) if not player.is_privileged(ctx.author): - return await send(ctx, "missingPerms_queue", ephemeral=True) + return await send(ctx, "missingQueuePerm", ephemeral=True) await player.clear_queue(queue, ctx.author) await send(ctx, "cleared", queue.capitalize()) @@ -659,7 +666,7 @@ async def remove(self, ctx: commands.Context, position1: int, position2: int = N return await send(ctx, "noPlayer", ephemeral=True) if not player.is_privileged(ctx.author): - return await send(ctx, "missingPerms_queue", ephemeral=True) + return await send(ctx, "missingQueuePerm", ephemeral=True) removed_tracks = await player.remove_track(position1, position2, remove_target=member, requester=ctx.author) await send(ctx, "removed", len(removed_tracks.keys())) @@ -674,7 +681,7 @@ async def forward(self, ctx: commands.Context, position: str = "10"): return await send(ctx, "noPlayer", ephemeral=True) if not player.is_privileged(ctx.author): - return await send(ctx, "missingPerms_pos", ephemeral=True) + return await send(ctx, "missingPosPerm", ephemeral=True) if not player.current: return await send(ctx, "noTrackPlaying", ephemeral=True) @@ -695,7 +702,7 @@ async def rewind(self, ctx: commands.Context, position: str = "10"): return await send(ctx, "noPlayer", ephemeral=True) if not player.is_privileged(ctx.author): - return await send(ctx, "missingPerms_pos", ephemeral=True) + return await send(ctx, "missingPosPerm", ephemeral=True) if not player.current: return await send(ctx, "noTrackPlaying", ephemeral=True) @@ -715,7 +722,7 @@ async def replay(self, ctx: commands.Context): return await send(ctx, "noPlayer", ephemeral=True) if not player.is_privileged(ctx.author): - return await send(ctx, "missingPerms_pos", ephemeral=True) + return await send(ctx, "missingPosPerm", ephemeral=True) if not player.current: return await send(ctx, "noTrackPlaying", ephemeral=True) @@ -755,7 +762,7 @@ async def swap(self, ctx: commands.Context, position1: int, position2: int): return await send(ctx, "noPlayer", ephemeral=True) if not player.is_privileged(ctx.author): - return await send(ctx, "missingPerms_pos", ephemeral=True) + return await send(ctx, "missingPosPerm", ephemeral=True) track1, track2 = await player.swap_track(position1, position2, ctx.author) await send(ctx, "swapped", track1.title, track2.title) @@ -773,7 +780,7 @@ async def move(self, ctx: commands.Context, target: int, to: int): return await send(ctx, "noPlayer", ephemeral=True) if not player.is_privileged(ctx.author): - return await send(ctx, "missingPerms_pos", ephemeral=True) + return await send(ctx, "missingPosPerm", ephemeral=True) moved_track = await player.move_track(target, to, ctx.author) await send(ctx, "moved", moved_track, to) @@ -820,7 +827,7 @@ async def swapdj(self, ctx: commands.Context, member: discord.Member): return await send(ctx, "djToMe", ephemeral=True) if member not in player.channel.members: - return await send(ctx, "djnotinchannel", member, ephemeral=True) + return await send(ctx, "djNotInChannel", member, ephemeral=True) player.dj = member await send(ctx, "djswap", member) @@ -834,7 +841,7 @@ async def autoplay(self, ctx: commands.Context): return await send(ctx, "noPlayer", ephemeral=True) if not player.is_privileged(ctx.author): - return await send(ctx, "missingPerms_autoplay", ephemeral=True) + return await send(ctx, "missingAutoPlayPerm", ephemeral=True) check = not player.settings.get("autoplay", False) player.settings['autoplay'] = check @@ -863,7 +870,7 @@ async def ping(self, ctx: commands.Context): "Test if the bot is alive, and see the delay between your commands and my response." player: voicelink.Player = ctx.guild.voice_client - value = await get_lang(ctx.guild.id, "pingTitle1", "pingfield1", "pingTitle2", "pingfield2") + value = await get_lang(ctx.guild.id, "pingTitle1", "pingField1", "pingTitle2", "pingField2") embed = discord.Embed(color=settings.embed_color) embed.add_field( diff --git a/cogs/settings.py b/cogs/settings.py index ed47037c..b3834fde 100644 --- a/cogs/settings.py +++ b/cogs/settings.py @@ -99,16 +99,16 @@ async def dj(self, ctx: commands.Context, role: discord.Role = None): @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 @@ async def playforever(self, ctx: commands.Context): 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 @@ async def view(self, ctx: commands.Context): 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"): @@ -204,7 +204,7 @@ async def togglecontroller(self, ctx: commands.Context): discord.ui.View.from_message(player.controller).stop() await update_settings(ctx.guild.id, {"$set": {'controller': toggle}}) - await send(ctx, 'togglecontroller', await get_lang(ctx.guild.id, "enabled" if toggle else "disabled")) + await send(ctx, 'toggleController', await get_lang(ctx.guild.id, "enabled" if toggle else "disabled")) @settings.command(name="duplicatetrack", aliases=get_aliases("duplicatetrack")) @commands.has_permissions(manage_guild=True) @@ -212,12 +212,12 @@ async def togglecontroller(self, ctx: commands.Context): 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")) @@ -241,6 +241,17 @@ async def controllermsg(self, ctx: commands.Context): 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('silent_msg', False) + + 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")) @commands.has_permissions(manage_guild=True) diff --git a/docker-compose.yml b/docker-compose.yml index 6a74b4df..0fc8e395 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,40 +1,7 @@ # ------------------------------------------------------------------------------------------------------------ # -# READ THIS BEFORE INSTALL! - -# This is a docker-compose file for running Vocard with Lavalink and MongoDB. -# In order to run this, you need to have Docker and Docker Compose installed. -# You can install Docker from https://docs.docker.com/get-docker/ -# and Docker Compose from https://docs.docker.com/compose/install/ - -# Step 1: Start the installation by creating the future config directory for Vocard. -# example - `root@docker:~# mkdir -p /opt/vocard/config` - -# Use `cd` to navigate to the config directory. -# example - `root@docker:~# cd /opt/vocard/config` - -# Step 3: Choose installation method: Build the image from the Dockerfile or pull it from GitHub(recommended). -# If you chose to pull from Docker Hub, comment the "build" lines and uncomment the "image" line. -# If you chose to build the image from the Dockerfile, do the following: -# uncomment this -# build: -# dockerfile: ./Dockerfile -# and comment this -# image: ghcr.io/chocomeow/vocard:latest -# example - `root@docker:/opt/vocard/config# wget https://github.com/ChocoMeow/Vocard/archive/refs/heads/main.zip` - -# Step 4: Configure application.yml and settings.json in the config directory. -# In order to avoid silly syntax errors it is recommended to use external code editor such as VS Code or Notepad++. -# Then you can upload files to host using tools such as WinSCP or -# using `nano` to create and edit the files directly using hosts terminal. -# NOTE that some terminals DO NOT let you paste, so you can either use WinSCP or SSH app like Putty. - -# example - `root@docker:/opt/vocard/config# nano application.yml` -# example - `root@docker:/opt/vocard/config# nano settings.json` -# To exit nano, press `Ctrl + S`, then `Ctrl + X` to save changes. - -# Step 5: If the values are set correctly, you can start the installation by running the following command -# example - `root@docker:/opt/vocard/config# docker-compose up -d` (could be `docker compose` on some systems) +# For installation instructions please visit - https://docs.vocard.xyz/latest/bot/setup/docker-linux/ +# or for Windows Docker - https://docs.vocard.xyz/latest/bot/setup/docker-windows/ # ------------------------------------------ THANK YOU FOR READING! ------------------------------------------ # name: vocard @@ -64,6 +31,22 @@ services: timeout: 5s retries: 5 + spotify-tokener: + image: ghcr.io/topi314/spotify-tokener:master + container_name: spotify-tokener + restart: unless-stopped + environment: + - SPOTIFY_TOKENER_ADDR=0.0.0.0:49152 + networks: + - vocard + ports: + - 49152:49152 + healthcheck: + test: nc -z -v localhost 49152 + interval: 10s + timeout: 5s + retries: 5 + vocard-db: container_name: vocard-db image: mongo:8 @@ -85,7 +68,6 @@ services: timeout: 5s retries: 5 start_period: 10s - # vocard-dashboard: # container_name: vocard-dashboard @@ -101,7 +83,6 @@ services: # - 8000:8000 # networks: # - vocard - # - web vocard: container_name: vocard @@ -117,8 +98,6 @@ services: depends_on: lavalink: condition: service_healthy - # vocard-dashboard: - # condition: service_started vocard-db: condition: service_healthy diff --git a/function.py b/function.py index e8fcae58..b8a1b5ab 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: @@ -192,7 +192,8 @@ async def send( *params, view: discord.ui.View = None, delete_after: float = None, - ephemeral: bool = False + ephemeral: bool = False, + requires_fetch: bool = False ) -> Optional[discord.Message]: if content is None: content = "No content provided." @@ -211,20 +212,42 @@ 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.send if isinstance(ctx, commands.Context) else + ctx.channel.send if isinstance(ctx, TempCtx) else + ctx.followup.send if ctx.response.is_done() else + ctx.response.send_message ) # 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, + "allowed_mentions": ALLOWED_MENTIONS, + "silent": settings.get("silent_msg", 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 "ephemeral" in send_func.__code__.co_varnames: + send_kwargs["ephemeral"] = ephemeral - # Send the message or embed 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 + message = await send_func(**send_kwargs) + + if isinstance(message, discord.InteractionCallbackResponse): + message = message.resource + + if requires_fetch and isinstance(message, (discord.WebhookMessage, discord.InteractionMessage)): + message = await message.fetch() + + return message async def update_db(db: AsyncIOMotorCollection, tempStore: dict, filter: dict, data: dict) -> bool: for mode, action in data.items(): diff --git a/ipc/methods.py b/ipc/methods.py index 8365b2bc..9a93a173 100644 --- a/ipc/methods.py +++ b/ipc/methods.py @@ -12,12 +12,14 @@ 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 } @@ -303,7 +305,7 @@ async def updatePosition(player: Player, member: Member, data: Dict) -> None: async def toggleAutoplay(player: Player, member: Member, data: Dict) -> Dict: if not player.is_privileged(member): - return error_msg(player.get_msg('missingPerms_autoplay')) + return error_msg(player.get_msg('missingAutoPlayPerm')) check = data.get("status", False) player.settings['autoplay'] = check @@ -314,7 +316,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) } diff --git a/langs/CH.json b/langs/CH.json index 0d21c97c..29b890ab 100644 --- a/langs/CH.json +++ b/langs/CH.json @@ -2,7 +2,6 @@ "unknownException": "⚠️ 執行命令時出現問題!請稍後再試,或加入我們的 Discord 伺服器獲得進一步支援。", "enabled": "已啟用", "disabled": "已停用", - "nodeReconnect": "請稍後再試!在節點重新連接後再嘗試。", "noChannel": "沒有語音頻道可供連接。請提供一個語音頻道或加入一個語音頻道。", "alreadyConnected": "已經連接到語音頻道。", @@ -16,13 +15,14 @@ "changedLanguage": "已成功切換到 `{0}` 語言包。", "setPrefix": "完成!我的前綴在您的伺服器中現在是 `{0}`。嘗試運行 `{1}ping` 來測試它。", "setDJ": "已將 DJ 設置為 {0}。", - "setqueue": "已將隊列模式設置為 `{0}`。", + "setQueue": "已將隊列模式設置為 `{0}`。", "247": "現在您有 `{0}` 24/7 模式。", "bypassVote": "現在您有 `{0}` 投票系統。", "setVolume": "已將音量設置為 `{0}`%", - "togglecontroller": "現在您已 `{0}` 音樂控制器。", + "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}```", @@ -33,12 +33,11 @@ "settingsPermValue": "```{0} 管理員\n{1} 管理伺服器\n{2} 管理頻道\n{3} 管理訊息```", "pingTitle1": "機器人資訊:", "pingTitle2": "播放器信息:", - "pingfield1": "```分片 ID: {0}/{1}\n分片延遲: {2:.3f}s {3}\n區域: {4}```", - "pingfield2": "```節點: {0} - {1:.3f}s\n播放器數量: {2}\n語音區域: {3}```", + "pingField1": "```分片 ID: {0}/{1}\n分片延遲: {2:.3f}s {3}\n區域: {4}```", + "pingField2": "```節點: {0} - {1:.3f}s\n播放器數量: {2}\n語音區域: {3}```", "addEffect": "套用音效`{0}`濾鏡。", "clearEffect": "聲音效果已清除!", - "FilterTagAlreadyInUse": "此聲音效果已在使用中!請使用 /cleareffect 移除它。", - + "filterTagAlreadyInUse": "此聲音效果已在使用中!請使用 /cleareffect 移除它。", "playlistViewTitle": "📜 所有 {0} 的播放清單", "playlistViewHeaders": "ID:,時間:,名稱:,曲目數:", "playlistFooter": "輸入 /playlist play [播放清單] 加入此播放清單至隊列中。", @@ -77,31 +76,27 @@ "inboxFull": "抱歉!{0} 的收件匣已滿。", "inboxNoMsg": "您的收件匣中沒有任何訊息。", "invitationSent": "已發送邀請給 {0}。", - "notInChannel": "{0},您必須在 {1} 中使用語音指令。如果您已在語音頻道中,請重新加入!", "noTrackPlaying": "現在沒有歌曲正在播放", "noTrackFound": "找不到符合該查詢的歌曲!請提供有效的網址。", "noLinkSupport": "搜索命令不支援網址!", "voted": "您已投票!", - "missingPerms_pos": "只有 DJ 或管理員才能更改位置。", - "missingPerms_mode": "只有 DJ 或管理員才能切換循環模式。", - "missingPerms_queue": "只有 DJ 或管理員才能從隊列中移除音軌。", - "missingPerms_autoplay": "只有 DJ 或管理員才能啟用或停用自動播放模式!", - "missingPerms_function": "只有 DJ 或管理員才能使用此功能。", + "missingPosPerm": "只有 DJ 或管理員才能更改位置。", + "missingModePerm": "只有 DJ 或管理員才能切換循環模式。", + "missingQueuePerm": "只有 DJ 或管理員才能從隊列中移除音軌。", + "missingAutoPlayPerm": "只有 DJ 或管理員才能啟用或停用自動播放模式!", + "missingFunctionPerm": "只有 DJ 或管理員才能使用此功能。", "timeFormatError": "時間格式不正確。例如:2:42 或 12:39:31", "lyricsNotFound": "找不到歌詞。輸入 /lyrics <歌曲名稱> <作者> 查找歌詞。", "missingTrackInfo": "有些音軌資訊缺失。", "noVoiceChannel": "找不到語音頻道!", - "playlistAddError": "您無權將串流視訊添加到播放清單中!", "playlistAddError2": "添加音軌到播放清單時發生問題!", - "playlistlimited": "您已達到上限!您只能將 {0} 首歌曲添加到播放清單中。", - "playlistrepeated": "您的播放清單中已經存在相同的音軌!", + "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,17 @@ "swapped": "已交換 `{0}` 和 `{1}`。", "moved": "已將 `{0}` 移動到 `{1}`。", "autoplay": "自動播放模式現在為 **{0}**。", - "notdj": "您不是DJ,當前DJ為 {0}。", "djToMe": "您無法將DJ權限轉移給自己或機器人。", - "djnotinchannel": "`{0}` 不在語音頻道中。", + "djNotInChannel": "`{0}` 不在語音頻道中。", "djswap": "您已將DJ權限轉移給 `{0}`。", - - "chaptersDropdown": "選擇要跳轉到的章節...", - "noChaptersFound": "找不到任何章節!", - "chatpersNotSupport": "此命令僅支持 YouTube 影片!", - "voicelinkQueueFull": "抱歉,您已達到隊列中 `{0}` 首歌曲的最大數量!", "voicelinkOutofList": "請提供有效的歌曲索引!", "voicelinkDuplicateTrack": "抱歉,此歌曲已在隊列中。", - - "deocdeError": "解碼文件時出現問題!", + "decodeError": "解碼文件時出現問題!", "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 155b2815..f33ab0c8 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.", @@ -16,13 +15,14 @@ "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}`%.", - "togglecontroller": "Der Musikcontroller wurde erfolgreich `{0}`", + "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}```", @@ -33,12 +33,11 @@ "settingsPermValue": "```{0} Administrator\n{1} Guild verwalten\n{2} Kanal verwalten\n{3} Manage_Messages```", "pingTitle1": "Bot-Info:", "pingTitle2": "Player Info:", - "pingfield1": "```Shard-ID: {0}/{1}\nShard-Latenz: {2:.3f}s {3}\nRegion: {4}```", - "pingfield2": "```Node: {0} - {1:.3f}s\nPlayer: {2}\nSprachregion: {3}```", + "pingField1": "```Shard-ID: {0}/{1}\nShard-Latenz: {2:.3f}s {3}\nRegion: {4}```", + "pingField2": "```Node: {0} - {1:.3f}s\nPlayer: {2}\nSprachregion: {3}```", "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.", - + "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,31 +76,27 @@ "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.", "noLinkSupport": "Der Suchbefehl unterstützt keine Links!", "voted": "Du hast abgestimmt!", - "missingPerms_pos": "Nur der DJ oder Admins können die Position ändern.", - "missingPerms_mode": "Nur der DJ oder Admins können den Wiederholungsmodus wechseln.", - "missingPerms_queue": "Nur der DJ oder Admins können Tracks aus der Warteschlange entfernen.", - "missingPerms_autoplay": "Nur der DJ oder Admins können den Autoplay-Modus aktivieren oder deaktivieren!", - "missingPerms_function": "Nur DJ oder Admins können diese Funktion verwenden.", + "missingPosPerm": "Nur der DJ oder Admins können die Position ändern.", + "missingModePerm": "Nur der DJ oder Admins können den Wiederholungsmodus wechseln.", + "missingQueuePerm": "Nur der DJ oder Admins können Tracks aus der Warteschlange entfernen.", + "missingAutoPlayPerm": "Nur der DJ oder Admins können den Autoplay-Modus aktivieren oder deaktivieren!", + "missingFunctionPerm": "Nur DJ oder Admins können diese Funktion verwenden.", "timeFormatError": "Falsches Zeitformat. Beispiel: 2:42 oder 12:39:31", "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!", + "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,17 @@ "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.", + "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!", + "decodeError": "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 d3050058..378a207f 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.", @@ -16,13 +15,14 @@ "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}`%", - "togglecontroller": "You now have `{0}` the music controller.", + "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}```", @@ -33,12 +33,11 @@ "settingsPermValue": "```{0} Administrator\n{1} Manage_Guild\n{2} Manage_Channel\n{3} Manage_Messages```", "pingTitle1": "Bot Info:", "pingTitle2": "Player Info:", - "pingfield1": "```Shard ID: {0}/{1}\nShard Latency: {2:.3f}s {3}\nRegion: {4}```", - "pingfield2": "```Node: {0} - {1:.3f}s\nPlayers: {2}\nVoice Region: {3}```", + "pingField1": "```Shard ID: {0}/{1}\nShard Latency: {2:.3f}s {3}\nRegion: {4}```", + "pingField2": "```Node: {0} - {1:.3f}s\nPlayers: {2}\nVoice Region: {3}```", "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.", - + "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,31 +76,27 @@ "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.", "noLinkSupport": "Search command does not support links!", "voted": "You have voted!", - "missingPerms_pos": "Only the DJ or admins may change the position.", - "missingPerms_mode": "Only the DJ or admins may switch loop mode.", - "missingPerms_queue": "Only the DJ or admins may remove track from the queue.", - "missingPerms_autoplay": "Only the DJ or admins can enable or disable autoplay mode!", - "missingPerms_function": "Only DJ or Admin can use this function.", + "missingPosPerm": "Only the DJ or admins may change the position.", + "missingModePerm": "Only the DJ or admins may switch loop mode.", + "missingQueuePerm": "Only the DJ or admins may remove track from the queue.", + "missingAutoPlayPerm": "Only the DJ or admins can enable or disable autoplay mode!", + "missingFunctionPerm": "Only DJ or Admin can use this function.", "timeFormatError": "Incorrect time format. Example: 2:42 or 12:39:31", "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!", + "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,17 @@ "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.", + "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!", + "decodeError": "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 3ee78776..b2ec7bb8 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.", @@ -16,13 +15,14 @@ "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}`%", - "togglecontroller": "Ahora tienes `{0}` el controlador de música.", + "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}```", @@ -33,12 +33,11 @@ "settingsPermValue": "```{0} Administrador\n{1} Administrar servidor\n{2} Administrar canal\n{3} Administrar mensajes```", "pingTitle1": "Información del bot:", "pingTitle2": "Información del reproductor:", - "pingfield1": "```ID de fragmento: {0}/{1}\nLatencia de fragmento: {2:.3f}s {3}\nRegión: {4}```", - "pingfield2": "```Nodo: {0} - {1:.3f}s\nReproductores: {2}\nRegión de voz: {3}```", + "pingField1": "```ID de fragmento: {0}/{1}\nLatencia de fragmento: {2:.3f}s {3}\nRegión: {4}```", + "pingField2": "```Nodo: {0} - {1:.3f}s\nReproductores: {2}\nRegión de voz: {3}```", "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.", - + "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,31 +76,27 @@ "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.", "noLinkSupport": "¡El comando de búsqueda no admite enlaces!", "voted": "¡Has votado!", - "missingPerms_pos": "Solo el DJ o los administradores pueden cambiar la posición.", - "missingPerms_mode": "Solo el DJ o los administradores pueden cambiar el modo de bucle.", - "missingPerms_queue": "Solo el DJ o los administradores pueden eliminar una canción de la cola.", - "missingPerms_autoplay": "¡Solo el DJ o los administradores pueden habilitar o deshabilitar el modo de reproducción automática!", - "missingPerms_function": "Solo el DJ o los administradores pueden usar esta función.", + "missingPosPerm": "Solo el DJ o los administradores pueden cambiar la posición.", + "missingModePerm": "Solo el DJ o los administradores pueden cambiar el modo de bucle.", + "missingQueuePerm": "Solo el DJ o los administradores pueden eliminar una canción de la cola.", + "missingAutoPlayPerm": "¡Solo el DJ o los administradores pueden habilitar o deshabilitar el modo de reproducción automática!", + "missingFunctionPerm": "Solo el DJ o los administradores pueden usar esta función.", "timeFormatError": "Formato de tiempo incorrecto. Ejemplo: 2:42 o 12:39:31", "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!", + "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,17 @@ "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.", + "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!", + "decodeError": "¡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 new file mode 100644 index 00000000..fb1df9e1 --- /dev/null +++ b/langs/FR.json @@ -0,0 +1,172 @@ +{ + "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.", + "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}```", + "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é !", + "missingPosPerm": "Seul le DJ ou les admins peuvent changer la position.", + "missingModePerm": "Seul le DJ ou les admins peuvent changer le mode de boucle.", + "missingQueuePerm": "Seul le DJ ou les admins peuvent retirer un morceau de la file.", + "missingAutoPlayPerm": "Seul le DJ ou les admins peuvent activer/désactiver l'autoplay !", + "missingFunctionPerm": "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}`.", + "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.", + "decodeError": "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 b8d9ebdb..88660fe9 100644 --- a/langs/JA.json +++ b/langs/JA.json @@ -2,7 +2,6 @@ "unknownException": "⚠️ コマンドの実行中に何かが間違っています!後でもう一度試してください。または、より詳しいサポートを求めるために当社のDiscordサーバーに参加してください。", "enabled": "有効化", "disabled": "無効化", - "nodeReconnect": "再試行してください!ノードが再接続した後。", "noChannel": "接続する音声チャンネルがありません。提供するか、参加してください。", "alreadyConnected": "すでに音声チャンネルに接続しています。", @@ -16,13 +15,14 @@ "changedLanguage": "「{0}」言語パックに正常に変更しました。", "setPrefix": "完了!あなたのサーバーのプレフィックスは今や「{0}」です。 `{1}ping`を実行してテストしてみてください。", "setDJ": "{0}にDJを設定しました。", - "setqueue": "キューモードを「{0}」に設定しました。", + "setQueue": "キューモードを「{0}」に設定しました。", "247": "今、24/7モードは「{0}」です。", "bypassVote": "今、投票システムは「{0}」になりました。", "setVolume": "音量を「{0}%」に設定しました。", - "togglecontroller": "今、音楽コントローラーは「{0}」です。", + "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}```", @@ -33,12 +33,11 @@ "settingsPermValue": "```{0} 管理者\n{1} ギルド管理\n{2} チャンネル管理\n{3} メッセージ管理```", "pingTitle1": "ボット情報:", "pingTitle2": "プレーヤー情報:", - "pingfield1": "```シャードID:{0}/{1}\nシャードレイテンシ:{2:.3f}s {3}\nリージョン:{4}```", - "pingfield2": "```ノード:{0} - {1:.3f}s\nプレイヤー数:{2}\n音声リージョン:{3}```", + "pingField1": "```シャードID:{0}/{1}\nシャードレイテンシ:{2:.3f}s {3}\nリージョン:{4}```", + "pingField2": "```ノード:{0} - {1:.3f}s\nプレイヤー数:{2}\n音声リージョン:{3}```", "addEffect": "`{0}` フィルターを適用します。", "clearEffect": "効果音がクリアされました!", - "FilterTagAlreadyInUse": "このサウンドエフェクトはすでに使用されています!削除するには/cleareffect を使用してください。", - + "filterTagAlreadyInUse": "このサウンドエフェクトはすでに使用されています!削除するには/cleareffect を使用してください。", "playlistViewTitle": "📜 {0}のすべてのプレイリスト", "playlistViewHeaders": "ID:,時間:,名前:,トラック:", "playlistFooter": "プレイリストをキューに追加するには、/playlist play [playlist]を入力してください。", @@ -77,31 +76,27 @@ "inboxFull": "申し訳ありません!{0}さんの受信トレイはいっぱいです。", "inboxNoMsg": "受信トレイにメッセージはありません。", "invitationSent": "{0}さんに招待状を送信しました。", - "notInChannel": "{0}さん、音声コマンドを使用するには{1}にいる必要があります。音声に接続している場合は再接続してください!", "noTrackPlaying": "現在再生中の曲はありません", "noTrackFound": "そのクエリで曲が見つかりませんでした!有効なURLを入力してください。", "noLinkSupport": "検索コマンドはリンクをサポートしていません!", "voted": "投票しました!", - "missingPerms_pos": "DJまたは管理者のみが位置を変更できます。", - "missingPerms_mode": "DJまたは管理者のみがループモードを切り替えることができます。", - "missingPerms_queue": "DJまたは管理者のみがキューからトラックを削除できます。", - "missingPerms_autoplay": "DJまたは管理者のみがオートプレイモードを有効化または無効化できます!", - "missingPerms_function": "DJまたは管理者のみがこの機能を使用できます。", + "missingPosPerm": "DJまたは管理者のみが位置を変更できます。", + "missingModePerm": "DJまたは管理者のみがループモードを切り替えることができます。", + "missingQueuePerm": "DJまたは管理者のみがキューからトラックを削除できます。", + "missingAutoPlayPerm": "DJまたは管理者のみがオートプレイモードを有効化または無効化できます!", + "missingFunctionPerm": "DJまたは管理者のみがこの機能を使用できます。", "timeFormatError": "時間の形式が間違っています。例:2:42または12:39:31", "lyricsNotFound": "歌詞が見つかりませんでした。/lyrics <曲名> <アーティスト>と入力して歌詞を検索してください。", "missingTrackInfo": "一部のトラック情報が欠落しています。", "noVoiceChannel": "音声チャンネルが見つかりません!", - "playlistAddError": "ストリーミングビデオをプレイリストに追加することはできません!", "playlistAddError2": "トラックをプレイリストに追加する際に問題が発生しました!", - "playlistlimited": "制限に達しました!プレイリストには{0}曲しか追加できません。", - "playlistrepeated": "すでにプレイリストに同じトラックがあります!", + "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,17 @@ "swapped": "`{0}`と`{1}`が交換されました。", "moved": "`{0}`を`{1}`に移動しました。", "autoplay": "オートプレイモードが **{0}** に設定されました。", - "notdj": "DJではありません。現在のDJは{0}です。", "djToMe": "自分自身またはボットにDJ権限を移行することはできません。", - "djnotinchannel": "`{0}`はボイスチャンネルにいません。", + "djNotInChannel": "`{0}`はボイスチャンネルにいません。", "djswap": "DJの役割を`{0}`に移行しました。", - - "chaptersDropdown": "スキップする章を選択してください...", - "noChaptersFound": "章が見つかりませんでした!", - "chatpersNotSupport": "このコマンドはYouTubeの動画のみサポートしています!", - "voicelinkQueueFull": "申し訳ありませんが、キュー内の曲数が最大値の`{0}`に達しました!", "voicelinkOutofList": "有効なトラックインデックスを指定してください!", "voicelinkDuplicateTrack": "申し訳ありませんが、このトラックは既にキューに存在します。", - - "deocdeError": "ファイルのデコード中に問題が発生しました!", + "decodeError": "ファイルのデコード中に問題が発生しました!", "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 5582649b..fe49dfca 100644 --- a/langs/KO.json +++ b/langs/KO.json @@ -2,7 +2,6 @@ "unknownException": "⚠️ 명령어 실행 중 오류가 발생했습니다! 나중에 다시 시도하거나 추가 지원을 위해 디스코드 서버에 참여하십시오.", "enabled": "활성화됨", "disabled": "비활성화됨", - "nodeReconnect": "다시 시도하세요! 노드가 다시 연결될 때까지 기다려주세요.", "noChannel": "연결할 음성 채널이 없습니다. 하나를 제공하거나 참여하십시오.", "alreadyConnected": "이미 음성 채널에 연결되어 있습니다.", @@ -16,13 +15,14 @@ "changedLanguage": "성공적으로 `{0}` 언어 팩으로 변경되었습니다.", "setPrefix": "완료되었습니다! 이 서버에서 내 접두사는 이제 `{0}`입니다. `{1}ping`을 실행하여 테스트해보세요.", "setDJ": "DJ를 {0}(으)로 설정했습니다.", - "setqueue": "큐 모드를 `{0}`(으)로 설정했습니다.", + "setQueue": "큐 모드를 `{0}`(으)로 설정했습니다.", "247": "이제 24/7 모드에서 `{0}`으로 변경되었습니다.", "bypassVote": "이제 `{0}` 투표 시스템을 사용할 수 있습니다.", "setVolume": "볼륨을 `{0}`%로 설정했습니다.", - "togglecontroller": "이제 음악 컨트롤러가 `{0}`(으)로 설정되었습니다.", + "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}```", @@ -33,12 +33,11 @@ "settingsPermValue": "```{0} 관리자\n{1} 서버 관리\n{2} 채널 관리\n{3} 메시지 관리```", "pingTitle1": "봇 정보:", "pingTitle2": "플레이어 정보:", - "pingfield1": "```쉬드 ID: {0}/{1}\n쉬드 대기 시간: {2:.3f}s {3}\n지역: {4}```", - "pingfield2": "```노드: {0} - {1:.3f}s\n플레이어: {2}\n음성 지역: {3}```", + "pingField1": "```쉬드 ID: {0}/{1}\n쉬드 대기 시간: {2:.3f}s {3}\n지역: {4}```", + "pingField2": "```노드: {0} - {1:.3f}s\n플레이어: {2}\n음성 지역: {3}```", "addEffect": "`{0}` 필터를 적용하세요.", "clearEffect": "효과가 삭제되었습니다!", - "FilterTagAlreadyInUse": "이 필터는 이미 사용 중입니다! 삭제하려면 /cleareffect 를 사용하십시오.", - + "filterTagAlreadyInUse": "이 필터는 이미 사용 중입니다! 삭제하려면 /cleareffect 를 사용하십시오.", "playlistViewTitle": "📜 {0}의 모든 재생 목록", "playlistViewHeaders": "ID:,시간:,이름:,트랙:", "playlistFooter": "/playlist play [재생 목록]을 입력하여 재생 목록을 대기열에 추가하세요.", @@ -77,31 +76,27 @@ "inboxFull": "죄송합니다! {0}님의 받은 편지함이 가득 찼습니다.", "inboxNoMsg": "받은 편지함에 메시지가 없습니다.", "invitationSent": "{0}님에게 초대장을 보냈습니다.", - "notInChannel": "{0}, 음성 명령을 사용하려면 {1}에 있어야합니다. 음성 채널에 있지 않으면 다시 참여하세요!", "noTrackPlaying": "현재 재생중인 곡이 없습니다.", "noTrackFound": "해당 쿼리로 곡을 찾을 수 없습니다. 유효한 URL을 제공해주세요.", "noLinkSupport": "검색 명령은 링크를 지원하지 않습니다!", "voted": "투표하셨습니다!", - "missingPerms_pos": "DJ 또는 관리자만 위치를 변경할 수 있습니다.", - "missingPerms_mode": "DJ 또는 관리자만 루프 모드를 전환할 수 있습니다.", - "missingPerms_queue": "DJ 또는 관리자만 대기열에서 곡을 제거할 수 있습니다.", - "missingPerms_autoplay": "DJ 또는 관리자만 자동재생 모드를 활성화하거나 비활성화할 수 있습니다!", - "missingPerms_function": "DJ 또는 관리자만이 이 기능을 사용할 수 있습니다.", + "missingPosPerm": "DJ 또는 관리자만 위치를 변경할 수 있습니다.", + "missingModePerm": "DJ 또는 관리자만 루프 모드를 전환할 수 있습니다.", + "missingQueuePerm": "DJ 또는 관리자만 대기열에서 곡을 제거할 수 있습니다.", + "missingAutoPlayPerm": "DJ 또는 관리자만 자동재생 모드를 활성화하거나 비활성화할 수 있습니다!", + "missingFunctionPerm": "DJ 또는 관리자만이 이 기능을 사용할 수 있습니다.", "timeFormatError": "잘못된 시간 형식입니다. 예: 2:42 또는 12:39:31", "lyricsNotFound": "가사를 찾을 수 없습니다. 가사를 찾으려면 /lyrics <노래 제목> <작곡가>를 입력하세요.", "missingTrackInfo": "일부 트랙 정보가 누락되었습니다.", "noVoiceChannel": "음성 채널을 찾을 수 없습니다!", - "playlistAddError": "스트리밍 비디오를 재생목록에 추가할 수 없습니다!", "playlistAddError2": "재생목록에 곡을 추가하는 중 문제가 발생했습니다!", - "playlistlimited": "한 재생목록에 최대 {0}곡까지 추가 가능합니다. 이제 한도에 도달했습니다!", - "playlistrepeated": "재생목록에 이미 같은 곡이 있습니다!", + "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,17 @@ "swapped": "`{0}`와 `{1}`이(가) 스왑되었습니다.", "moved": "`{0}`을(를) `{1}`로 이동했습니다.", "autoplay": "자동 재생 모드가 **{0}**(으)로 변경되었습니다.", - "notdj": "당신은 DJ가 아닙니다. 현재 DJ는 {0}입니다.", "djToMe": "자신이나 봇에게 DJ를 이전할 수 없습니다.", - "djnotinchannel": "`{0}`님이 음성 채널에 없습니다.", + "djNotInChannel": "`{0}`님이 음성 채널에 없습니다.", "djswap": "당신은 DJ 권한을 `{0}`님에게 이전했습니다.", - - "chaptersDropdown": "건너뛸 챕터를 선택해주세요.", - "noChaptersFound": "챕터를 찾을 수 없습니다!", - "chatpersNotSupport": "이 명령어는 유튜브 비디오만 지원합니다!", - "voicelinkQueueFull": "죄송합니다. 큐에 `{0}`개의 트랙을 모두 추가하셨습니다!", "voicelinkOutofList": "유효한 트랙 인덱스를 제공해주세요!", "voicelinkDuplicateTrack": "죄송합니다. 이 트랙은 이미 큐에 있습니다.", - - "deocdeError": "파일 디코딩 중 문제가 발생했습니다!", + "decodeError": "파일 디코딩 중 문제가 발생했습니다!", "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 e2d8d40e..1e0ff063 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.", @@ -16,13 +15,14 @@ "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}`%", - "togglecontroller": "Kontroler muzyki: `{0}`", + "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}```", @@ -33,12 +33,11 @@ "settingsPermValue": "```{0} Administrator\n{1} Zarządzanie_serwerem\n{2} Zarządzanie_kanałem\n{3} Zarządzanie_wiadomościami```", "pingTitle1": "Informacje o bocie:", "pingTitle2": "Infomracje o odtwarzaczu:", - "pingfield1": "```ID Shard'a: {0}/{1}\nOpóźnienie shard'a: {2:.3f}s {3}\nRegion: {4}```", - "pingfield2": "```Węzeł: {0} - {1:.3f}s\nOdtwarzacze: {2}\nRegion: {3}```", + "pingField1": "```ID Shard'a: {0}/{1}\nOpóźnienie shard'a: {2:.3f}s {3}\nRegion: {4}```", + "pingField2": "```Węzeł: {0} - {1:.3f}s\nOdtwarzacze: {2}\nRegion: {3}```", "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ć.", - + "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,31 +76,27 @@ "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.", "noLinkSupport": "Komenda /search nie obsługuje linków!", "voted": "Zagłosowałeś!", - "missingPerms_pos": "Tylko DJ lub administracja może zmieniać pozycje.", - "missingPerms_mode": "Tylko DJ lub administracja może zmieniać tryb pętli.", - "missingPerms_queue": "Tylko DJ lub administracja może usuwać pozycje z kolejki.", - "missingPerms_autoplay": "Tylko DJ lub administracja może włączać lub wyłączać tryb autoplay!", - "missingPerms_function": "Tylko DJ lub administracja może używać tej funkcji.", + "missingPosPerm": "Tylko DJ lub administracja może zmieniać pozycje.", + "missingModePerm": "Tylko DJ lub administracja może zmieniać tryb pętli.", + "missingQueuePerm": "Tylko DJ lub administracja może usuwać pozycje z kolejki.", + "missingAutoPlayPerm": "Tylko DJ lub administracja może włączać lub wyłączać tryb autoplay!", + "missingFunctionPerm": "Tylko DJ lub administracja może używać tej funkcji.", "timeFormatError": "Niepoprawny format czasu. Przykład: 2:42 lub 12:39:31", "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!", + "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,17 @@ "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.", + "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!", + "decodeError": "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 d0293d00..2eb340ba 100644 --- a/langs/RU.json +++ b/langs/RU.json @@ -2,7 +2,6 @@ "unknownException": "⚠️ Что-то пошло не так при выполнении команды! Пожалуйста, попробуйте позже или присоединитесь к нашему серверу Discord для получения дополнительной поддержки.", "enabled": "вкл", "disabled": "выкл", - "nodeReconnect": "Пожалуйста, попробуйте чуть позже!", "noChannel": "Нет голосового канала для подключения. Пожалуйста, укажите или присоединитесь к одному.", "alreadyConnected": "Уже подключен к голосовому каналу.", @@ -16,13 +15,14 @@ "changedLanguage": "Язык успешно изменен на `{0}`.", "setPrefix": "Готово! Мой префикс на вашем сервере теперь `{0}`. Попробуйте запустить `{1}ping`, чтобы проверить его.", "setDJ": "Установить роль DJ {0}.", - "setqueue": "Установить режим очереди на `{0}`.", + "setQueue": "Установить режим очереди на `{0}`.", "247": "Теперь у вас `{0}` режим 24/7.", "bypassVote": "Теперь у вас `{0}` система голосования.", "setVolume": "Установить громкость на `{0}`%", - "togglecontroller": "Теперь у вас `{0}` контроллер музыки.", + "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}```", @@ -33,12 +33,11 @@ "settingsPermValue": "```{0} Администратор\n{1} Управление_Сервером\n{2} Управление_Каналом\n{3} Управление_Сообщениями```", "pingTitle1": "Информация о боте:", "pingTitle2": "Информация о плеере:", - "pingfield1": "```ID Шарда: {0}/{1}\nЗадержка Шарда: {2:.3f}s {3}\nРегион: {4}```", - "pingfield2": "```Сервис: {0} - {1:.3f}s\nИгроки: {2}\nРегион Голоса: {3}```", + "pingField1": "```ID Шарда: {0}/{1}\nЗадержка Шарда: {2:.3f}s {3}\nРегион: {4}```", + "pingField2": "```Сервис: {0} - {1:.3f}s\nИгроки: {2}\nРегион Голоса: {3}```", "addEffect": "Применен `{0}` эффект.", "clearEffect": "Звуковые эффекты были очищены!", - "FilterTagAlreadyInUse": "Этот звуковой эффект уже используется! Пожалуйста, используйте /cleareffect <Тег>, чтобы удалить его.", - + "filterTagAlreadyInUse": "Этот звуковой эффект уже используется! Пожалуйста, используйте /cleareffect <Тег>, чтобы удалить его.", "playlistViewTitle": "📜 Все плейлисты пользователя {0}", "playlistViewHeaders": "ID:,Время:,Название:,Треки:", "playlistFooter": "Введите /playlist play [плейлист], чтобы добавить плейлист в очередь.", @@ -77,31 +76,27 @@ "inboxFull": "Извините! Почтовый ящик {0} переполнен.", "inboxNoMsg": "В вашем почтовом ящике нет сообщений.", "invitationSent": "Приглашение отправлено {0}.", - "notInChannel": "{0}, для использования голосовых команд вы должны находиться в {1}. Пожалуйста, перезайдите, если вы уже находитесь в голосовом канале!", "noTrackPlaying": "Сейчас нет играющих треков.", "noTrackFound": "Треки по такому запросу не найдены! Пожалуйста, укажите действительную ссылку.", "noLinkSupport": "Команда поиска не поддерживает ссылки!", "voted": "Вы проголосовали!", - "missingPerms_pos": "Только DJ или администраторы могут изменять позицию.", - "missingPerms_mode": "Только DJ или администраторы могут переключить режим цикла.", - "missingPerms_queue": "Только DJ или администраторы могут удалять треки из очереди.", - "missingPerms_autoplay": "Только DJ или администраторы могут включать или выключать режим autoplay!", - "missingPerms_function": "Только DJ или администраторы могут использовать эту функцию.", + "missingPosPerm": "Только DJ или администраторы могут изменять позицию.", + "missingModePerm": "Только DJ или администраторы могут переключить режим цикла.", + "missingQueuePerm": "Только DJ или администраторы могут удалять треки из очереди.", + "missingAutoPlayPerm": "Только DJ или администраторы могут включать или выключать режим autoplay!", + "missingFunctionPerm": "Только DJ или администраторы могут использовать эту функцию.", "timeFormatError": "Неверный формат времени. Пример: 2:42PM или 12:39:31", "lyricsNotFound": "Текст песни не найден. Введите /lyrics <Название песни> <Автор> для поиска текста.", "missingTrackInfo": "Некоторая информация о треке отсутствует.", "noVoiceChannel": "Голосовой канал не найден!", - "playlistAddError": "Вам не разрешено добавлять потоковые видео в плейлист!", "playlistAddError2": "Произошла ошибка при добавлении треков в плейлист!", - "playlistlimited": "Вы достигли лимита! Вы можете добавить только {0} треков в свой плейлист.", - "playlistrepeated": "Такой трек уже есть в плейлисте!", + "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,17 @@ "swapped": "Треки `{0}` и `{1}` поменяны местами", "moved": "Трек `{0}` смещён на `{1}`", "autoplay": "Режим autoplay **{0}**", - "notdj": "Вы не DJ. Текущий DJ: {0}.", "djToMe": "Вы не можете передать роль DJ себе или боту.", - "djnotinchannel": "Пользователь `{0}` не находится в голосовом канале.", + "djNotInChannel": "Пользователь `{0}` не находится в голосовом канале.", "djswap": "Вы передали роль DJ пользователю `{0}`.", - - "chaptersDropdown": "Выберите эпизод для перехода ...", - "noChaptersFound": "Эпизод не найден!", - "chatpersNotSupport": "Эта команда поддерживает только видео на YouTube!", - "voicelinkQueueFull": "Извините, вы достигли максимального количества `{0}` треков в очереди!", "voicelinkOutofList": "Пожалуйста, предоставьте действительный индекс трека!", "voicelinkDuplicateTrack": "Извините, этот трек уже есть в очереди.", - - "deocdeError": "Что-то пошло не так при декодировании файла!", + "decodeError": "Что-то пошло не так при декодировании файла!", "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 0f3c7454..cecbf4b1 100644 --- a/langs/UA.json +++ b/langs/UA.json @@ -2,7 +2,6 @@ "unknownException": "⚠️ Щось пішло не так під час виконання команди! Будь ласка, спробуйте пізніше або приєднайтеся до нашого сервера Discord для отримання додаткової підтримки.", "enabled": "вкл", "disabled": "викл", - "nodeReconnect": "Будь ласка, спробуйте знову! Після перепідключення вузла.", "noChannel": "Немає голосового каналу для підключення. Будь ласка, вкажіть або приєднайтеся до одного.", "alreadyConnected": "Уже підключений до голосового каналу.", @@ -16,13 +15,14 @@ "changedLanguage": "Успішно змінено на мовний пакет `{0}`.", "setPrefix": "Готово! Мій префікс на вашому сервері тепер `{0}`. Спробуйте запустити `{1}ping`, щоб перевірити його.", "setDJ": "Встановити роль DJ {0}.", - "setqueue": "Встановити режим черги на `{0}`.", + "setQueue": "Встановити режим черги на `{0}`.", "247": "Тепер у вас `{0}` режим 24/7.", "bypassVote": "Тепер у вас `{0}` система голосування.", "setVolume": "Встановити гучність на `{0}`%", - "togglecontroller": "Тепер у вас `{0}` контролер музики.", + "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}```", @@ -33,12 +33,11 @@ "settingsPermValue": "```{0} Адміністратор\n{1} Керування_Сервером\n{2} Керування_Каналом\n{3} Керування_Сообщениями```", "pingTitle1": "Інформація про бота:", "pingTitle2": "Інформація про плеєр:", - "pingfield1": "````ID Шарда: {0}/{1}\nЗатримка Шарда: {2:.3f}s {3}\nРегіон: {4}````", - "pingfield2": "```Вузол: {0} - {1:.3f}s\nГравці: {2}\nРегіон Голосу: {3}```", + "pingField1": "````ID Шарда: {0}/{1}\nЗатримка Шарда: {2:.3f}s {3}\nРегіон: {4}````", + "pingField2": "```Вузол: {0} - {1:.3f}s\nГравці: {2}\nРегіон Голосу: {3}```", "addEffect": "Застосуйте ефект `{0}` фільтр.", "clearEffect": "Звукові ефекти були очищені!", - "FilterTagAlreadyInUse": "Цей звуковий ефект уже використовується! Будь ласка, використовуйте /cleareffect <Тег>, щоб видалити його.", - + "filterTagAlreadyInUse": "Цей звуковий ефект уже використовується! Будь ласка, використовуйте /cleareffect <Тег>, щоб видалити його.", "playlistViewTitle": "📜 Усі плейлисти користувача {0}", "playlistViewHeaders": "ID:,Час:,Назва:,Треки:", "playlistFooter": "Введите /playlist play [плейлист], чтобы добавить плейлист в очередь.", @@ -77,31 +76,27 @@ "inboxFull": "Вибачте! Поштова скринька {0} переповнена.", "inboxNoMsg": "У вашій поштовій скриньці немає повідомлень.", "invitationSent": "Запрошення надіслано {0}.", - "notInChannel": "{0}, для використання голосових команд ви маєте перебувати в {1}. Будь ласка, перезайдіть, якщо ви вже в голосі!", "noTrackPlaying": "Зараз немає пісень, які грають.", "noTrackFound": "Пісні з таким запитом не знайдено! Будь ласка, вкажіть дійсне посилання.", "noLinkSupport": "Команда пошуку не підтримує посилання!", "voted": "Ви проголосували!", - "missingPerms_pos": "Тільки DJ або адміністратори можуть змінювати позицію.", - "missingPerms_mode": "Тільки DJ або адміністратори можуть перемкнути режим циклу.", - "missingPerms_queue": "Тільки DJ або адміністратори можуть видаляти треки з черги.", - "missingPerms_autoplay": "Тільки DJ або адміністратори можуть вмикати або вимикати режим autoplay!", - "missingPerms_function": "Тільки DJ або адміністратори можуть використовувати цю функцію.", + "missingPosPerm": "Тільки DJ або адміністратори можуть змінювати позицію.", + "missingModePerm": "Тільки DJ або адміністратори можуть перемкнути режим циклу.", + "missingQueuePerm": "Тільки DJ або адміністратори можуть видаляти треки з черги.", + "missingAutoPlayPerm": "Тільки DJ або адміністратори можуть вмикати або вимикати режим autoplay!", + "missingFunctionPerm": "Тільки DJ або адміністратори можуть використовувати цю функцію.", "timeFormatError": "Неправильний формат часу. Приклад: 2:42", "lyricsNotFound": "Текст пісні не знайдено. Введіть /lyrics <Назва пісні> <Автор> для пошуку тексту.", "missingTrackInfo": "Деяка інформація про трек відсутня.", "noVoiceChannel": "Голосовий канал не знайдено!", - "playlistAddError": "Вам не дозволено додавати потокові відео в плейлист!", "playlistAddError2": "Сталася помилка під час додавання треків у плейлист!", - "playlistlimited": "Ви досягли ліміту! Ви можете додати тільки {0} пісні до свого плейлиста.", - "playlistrepeated": "Такий самий трек уже є у вашому плейлисті!", + "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,17 @@ "swapped": "Треки `{0}` і `{1}` обміняні місцями", "moved": "Трек `{0}` зміщений на `{1}`", "autoplay": "Режим autoplay встановлено на **{0}**", - "notdj": "Ви не DJ. Поточний DJ: {0}.", "djToMe": "Ви не можете передати роль DJ собі або боту.", - "djnotinchannel": "`{0}` не знаходиться в голосовому каналі.", + "djNotInChannel": "`{0}` не знаходиться в голосовому каналі.", "djswap": "Ви передали роль DJ `{0}`.", - - "chaptersDropdown": "Виберіть епізод для переходу ...", - "noChaptersFound": "Епізод не знайдено!", - "chatpersNotSupport": "Ця команда підтримує тільки відео з YouTube!", - "voicelinkQueueFull": "Вибачте, ви досягли максимальної кількості `{0}` треків у черзі!", "voicelinkOutofList": "Будь ласка, надайте дійсний індекс треку!", "voicelinkDuplicateTrack": "Вибачте, цей трек уже є в черзі.", - - "deocdeError": "Щось пішло не так під час декодування файлу!", + "decodeError": "Щось пішло не так під час декодування файлу!", "invalidStartTime": "Недійснений час початку! Час має бути в межах `00:00` та `{0}`.", "invalidEndTime": "Недійснений час закінчення! Час має бути в межах `00:00` та `{0}`.", "invalidTimeOrder": "Час закінчення не може бути меншим або рівним часу початку.", - "setStageAnnounceTemplate": "Готово! Відтепер статус голосу, як той, в якому ви зараз перебуваєте, буде називатися відповідно до вашого шаблону. Ви повинні побачити оновлення через кілька секунд.", "createSongRequestChannel": "Канал для запитів пісень ({0}) створено! Ви можете почати запитувати будь-яку пісню за назвою або URL у цьому каналі без необхідності використовувати префікс бота." } \ No newline at end of file diff --git a/langs/VN.json b/langs/VN.json new file mode 100644 index 00000000..6fef0209 --- /dev/null +++ b/langs/VN.json @@ -0,0 +1,172 @@ +{ + "unknownException": "⚠️ Đã xảy ra lỗi khi chạy lệnh! Vui lòng thử lại sau hoặc vào server Discord của chúng tôi để được hỗ trợ.", + "enabled": "đã bật", + "disabled": "đã tắt", + "nodeReconnect": "Vui lòng thử lại! Sau khi node kết nối lại.", + "noChannel": "Không có kênh voice để kết nối. Vui lòng cung cấp một kênh hoặc tham gia một kênh.", + "alreadyConnected": "Đã kết nối với kênh voice.", + "noPermission": "Xin lỗi! Tôi không có quyền tham gia hoặc nói trong kênh voice của bạn.", + "noCreatePermission": "Xin lỗi! Tôi không có quyền tạo kênh yêu cầu bài hát.", + "noPlaySource": "Không thể tìm thấy nguồn phát nào!", + "noPlayer": "Không tìm thấy trình phát nào trên máy chủ này.", + "notVote": "Lệnh này yêu cầu bạn bình chọn! Gõ `/vote` để biết thêm thông tin.", + "missingIntents": "Xin lỗi, lệnh này không thể thực hiện vì bot thiếu quyền yêu cầu cần thiết: `({0})`.", + "languageNotFound": "Không tìm thấy gói ngôn ngữ! Vui lòng chọn một gói ngôn ngữ hiện có.", + "changedLanguage": "Đã thay đổi thành công sang gói ngôn ngữ `{0}`.", + "setPrefix": "Hoàn thành! Tiền tố của tôi trong máy chủ của bạn bây giờ là `{0}`. Thử chạy `{1}ping` để kiểm tra.", + "setDJ": "Đặt DJ thành {0}.", + "setQueue": "Đặt chế độ hàng đợi thành `{0}`.", + "247": "Chế độ 24/7 bây giờ là `{0}`.", + "bypassVote": "Bạn có hệ thống bình chọn `{0}`.", + "setVolume": "Đặt âm lượng thành `{0}`%", + "toggleController": "Bây giờ bạn đã `{0}` bộ điều khiển nhạc.", + "toggleDuplicateTrack": "Bây giờ bạn đã `{0}` tính năng ngăn chặn bài hát trùng lặp.", + "toggleControllerMsg": "Bây giờ bạn đã `{0}` tin nhắn từ bộ điều khiển nhạc.", + "toggleSilentMsg": "Bạn có {0} tin nhắn im lặng.", + "settingsMenu": "Cài Đặt Máy Chủ | {0}", + "settingsTitle": "❤️ Thông Tin Cơ Bản:", + "settingsValue": "```Tiền tố: {0}\nNgôn ngữ: {1}\nBộ Điều Khiển Nhạc: {2}\nVai Trò DJ: @{3}\nBỏ Qua Bình Chọn: {4}\n24/7: {5}\nÂm Lượng Mặc Định: {6}%\nThời Gian Phát: {7}```", + "settingsTitle2": "🔗 Thông tin hàng đợi:", + "settingsValue2": "```Chế Độ Hàng Đợi: {0}\nSố Bài Tối Đa: {1}\nBài Hát Trùng Lặp: {2}```", + "settingsTitle3": "🎤 Thông Tin Trạng Thái Voice:", + "settingsPermTitle": "✨ Quyền hạn:", + "settingsPermValue": "```{0} Quản Trị Viên\n{1} Quản_Lý_Guild\n{2} Quản_Lý_Kênh\n{3} Quản_Lý_Tin_Nhắn```", + "pingTitle1": "Thông Tin Bot:", + "pingTitle2": "Thông Tin Trình Phát:", + "pingField1": "```ID Shard: {0}/{1}\nĐộ Trễ Shard: {2:.3f}s {3}\nKhu Vực: {4}```", + "pingField2": "```Node: {0} - {1:.3f}s\nTrình Phát: {2}\nKhu Vực Voice: {3}```", + "addEffect": "Đã áp dụng hiệu ứng `{0}`.", + "clearEffect": "Các hiệu ứng âm thanh đã được xóa!", + "filterTagAlreadyInUse": "Hiệu ứng âm thanh này đã được sử dụng! Vui lòng sử dụng /cleareffect để xóa nó.", + "playlistViewTitle": "📜 Tất Cả Playlist Của {0}", + "playlistViewHeaders": "ID:,Thời Gian:,Tên:,Bài Hát:", + "playlistFooter": "Gõ /playlist play [playlist] để thêm playlist vào hàng đợi.", + "playlistNotFound": "Không tìm thấy playlist [`{0}`]. Gõ /playlist view để xem tất cả playlist của bạn.", + "playlistNotAccess": "Xin lỗi! Bạn không được phép truy cập playlist này!", + "playlistNoTrack": "Xin lỗi! Không có bài hát nào trong playlist [`{0}`].", + "playlistNotAllow": "Lệnh này không được phép trên playlist được liên kết và chia sẻ.", + "playlistPlay": "Đã thêm playlist [`{0}`] với `{1}` bài hát vào hàng đợi.", + "playlistOverText": "Xin lỗi! Tên playlist không thể vượt quá 10 ký tự.", + "playlistSameName": "Xin lỗi! Vui lòng chọn tên mới cho playlist.", + "playlistDeleteError": "Bạn không được phép xóa playlist mặc định.", + "playlistRemove": "Bạn đã xóa playlist [`{0}`].", + "playlistSendErrorPlayer": "Xin lỗi! Bạn không thể gửi lời mời cho chính mình.", + "playlistSendErrorBot": "Xin lỗi! Bạn không thể gửi lời mời cho một bot.", + "playlistBelongs": "Xin lỗi! Playlist này thuộc về <@{0}>.", + "playlistShare": "Xin lỗi! Playlist này đã được chia sẻ với {0}.", + "playlistSent": "Xin lỗi! Bạn đã gửi lời mời cho người dùng đó trước đó rồi.", + "noPlaylistAcc": "{0} không tạo tài khoản playlist.", + "overPlaylistCreation": "Bạn không thể tạo nhiều hơn `{0}` playlist!", + "playlistExists": "Playlist [`{0}`] đã tồn tại.", + "playlistNotInvalidUrl": "Vui lòng nhập liên kết playlist Spotify hoặc Youtube công khai hợp lệ.", + "playlistCreated": "Bạn đã tạo playlist `{0}`. Gõ /playlist view để biết thêm thông tin.", + "playlistRenamed": "Bạn đã đổi tên `{0}` thành `{1}`.", + "playlistLimitTrack": "Bạn đã đạt giới hạn! Bạn chỉ có thể thêm `{0}` bài hát vào playlist của mình.", + "playlistPlaylistLink": "Bạn không được phép sử dụng liên kết playlist.", + "playlistStream": "Bạn không được phép thêm video trực tiếp vào playlist của mình.", + "playlistPositionNotFound": "Không thể tìm thấy vị trí `{0}` từ playlist [`{1}`] của bạn!", + "playlistRemoved": "👋 Đã xóa **{0}** khỏi playlist [`{2}`] của {1}.", + "playlistClear": "Bạn đã xóa thành công playlist [`{0}`] của mình.", + "playlistView": "Trình Xem Playlist", + "playlistViewDesc": "```Tên | ID: {0} | {1}\nTổng Số Bài Hát: {2}\nChủ Sở Hữu: {3}\nLoại: {4}\n```", + "playlistViewPermsValue": "📖 Đọc: ✓ ✍🏽 Viết: {0} 🗑️ Xóa: {1}", + "playlistViewPermsValue2": "📖 Đọc: {0}", + "playlistViewTrack": "Bài Hát", + "playlistViewPage": "Trang: {0}/{1} | Tổng Thời Lượng: {2}", + "inboxFull": "Xin lỗi! Hộp thư của {0} đã đầy.", + "inboxNoMsg": "Không có tin nhắn nào trong hộp thư của bạn.", + "invitationSent": "Đã gửi lời mời đến {0}.", + "notInChannel": "{0}, bạn phải ở trong {1} để sử dụng lệnh voice. Vui lòng tham gia lại nếu bạn đang ở trong voice!", + "noTrackPlaying": "Hiện tại không có bài hát nào đang phát", + "noTrackFound": "Không tìm thấy bài hát nào với truy vấn đó! Vui lòng cung cấp url hợp lệ.", + "noLinkSupport": "Lệnh tìm kiếm không hỗ trợ liên kết!", + "voted": "Bạn đã bình chọn!", + "missingPosPerm": "Chỉ DJ hoặc quản trị viên mới có thể thay đổi vị trí.", + "missingModePerm": "Chỉ DJ hoặc quản trị viên mới có thể chuyển chế độ lặp.", + "missingQueuePerm": "Chỉ DJ hoặc quản trị viên mới có thể xóa bài hát khỏi hàng đợi.", + "missingAutoPlayPerm": "Chỉ DJ hoặc quản trị viên mới có thể bật hoặc tắt chế độ tự động phát!", + "missingFunctionPerm": "Chỉ DJ hoặc Admin mới có thể sử dụng chức năng này.", + "timeFormatError": "Định dạng thời gian không chính xác. Ví dụ: 2:42 hoặc 12:39:31", + "lyricsNotFound": "Không tìm thấy lời bài hát. Gõ /lyrics để tìm lời bài hát.", + "missingTrackInfo": "Một số chi tiết bài hát bị thiếu.", + "noVoiceChannel": "Không Tìm Thấy Kênh Voice!", + "playlistAddError": "Bạn không được phép thêm video trực tiếp vào playlist của mình!", + "playlistAddError2": "Đã xảy ra sự cố khi thêm bài hát vào playlist!", + "playlistLimited": "Bạn đã đạt giới hạn! Bạn chỉ có thể thêm {0} bài hát vào playlist của mình.", + "playlistRepeated": "Bài hát này đã có trong playlist của bạn!", + "playlistAdded": "❤️ Đã thêm **{0}** vào playlist [`{2}`] của {1}!", + "playerDropdown": "Chọn một bài hát để bỏ qua đến ...", + "playerFilter": "Chọn một bộ lọc để áp dụng ...", + "buttonBack": "Quay Lại", + "buttonPause": "Tạm Dừng", + "buttonResume": "Tiếp Tục", + "buttonSkip": "Bỏ Qua", + "buttonLeave": "Rời Khỏi", + "buttonLoop": "Lặp Lại", + "buttonVolumeUp": "Tăng Âm Lượng", + "buttonVolumeDown": "Giảm Âm Lượng", + "buttonVolumeMute": "Tắt Tiếng", + "buttonVolumeUnmute": "Bật Tiếng", + "buttonAutoPlay": "Tự Động Phát", + "buttonShuffle": "Trộn Bài", + "buttonForward": "Tua Tới", + "buttonRewind": "Tua Lùi", + "buttonLyrics": "Lời Bài Hát", + "nowplayingDesc": "**Đang Phát:**\n```{0}```", + "nowplayingField": "Tiếp Theo:", + "nowplayingLink": "Nghe trên {0}", + "connect": "Đã kết nối với {0}", + "live": "TRỰC TIẾP", + "playlistLoad": " 🎶 Đã thêm playlist **{0}** với `{1}` bài hát vào hàng đợi.", + "trackLoad": "Đã thêm **[{0}](<{1}>)** bởi **{2}** (`{3}`) để bắt đầu phát.\n", + "trackLoad_pos": "Đã thêm **[{0}](<{1}>)** bởi **{2}** (`{3}`) vào hàng đợi ở vị trí **{4}**\n", + "searchTitle": "Truy Vấn Tìm Kiếm: {0}", + "searchDesc": "➥ Nền tảng: {0} **{1}**\n➥ Kết quả: **{2}**\n\n{3}", + "searchWait": "Chọn bài hát bạn muốn thêm vào hàng đợi.", + "searchTimeout": "Tìm kiếm hết thời gian. Vui lòng thử lại sau.", + "searchSuccess": "Đã thêm bài hát vào hàng đợi.", + "queueTitle": "Hàng Đợi Sắp Tới:", + "historyTitle": "Lịch Sử Hàng Đợi:", + "viewTitle": "Hàng Đợi Nhạc", + "viewDesc": "**Đang Phát: [Nhấp Vào Đây]({0}) ⮯**\n{1}", + "viewFooter": "Trang: {0}/{1} | Tổng Thời Lượng: {2}", + "pauseError": "Trình phát đã được tạm dừng.", + "pauseVote": "{0} đã bình chọn tạm dừng bài hát. [{1}/{2}]", + "paused": "`{0}` đã tạm dừng trình phát.", + "resumeError": "Trình phát không bị tạm dừng.", + "resumeVote": "{0} đã bình chọn tiếp tục bài hát. [{1}/{2}]", + "resumed": "`{0}` đã tiếp tục trình phát.", + "shuffleError": "Thêm nhiều bài hát vào hàng đợi trước khi trộn bài.", + "shuffleVote": "{0} đã bình chọn trộn hàng đợi. [{1}/{2}]", + "shuffled": "Hàng đợi đã được trộn.", + "skipError": "Không có bài hát nào để bỏ qua.", + "skipVote": "{0} đã bình chọn bỏ qua bài hát. [{1}/{2}]", + "skipped": "`{0}` đã bỏ qua bài hát.", + "backVote": "{0} đã bình chọn quay lại bài hát trước. [{1}/{2}]", + "backed": "`{0}` đang quay lại bài hát trước.", + "leaveVote": "{0} đã bình chọn dừng trình phát. [{1}/{2}]", + "left": "`{0}` đã dừng trình phát.", + "seek": "Đặt trình phát thành **{0}**", + "repeat": "Chế độ lặp lại đã được đặt thành `{0}`", + "cleared": "Đã xóa tất cả bài hát trong `{0}`", + "removed": "`{0}` bài hát đã được xóa khỏi hàng đợi.", + "forward": "Tua tới trình phát đến **{0}**", + "rewind": "Tua lùi trình phát đến **{0}**", + "replay": "Phát lại bài hát hiện tại.", + "swapped": "`{0}` và `{1}` đã được hoán đổi", + "moved": "Đã di chuyển `{0}` đến `{1}`", + "autoplay": "Chế độ tự động phát bây giờ là **{0}**", + "notdj": "Bạn không phải là DJ. DJ hiện tại là {0}.", + "djToMe": "Bạn không thể chuyển vai trò DJ cho chính mình hoặc một bot.", + "djNotInChannel": "`{0}` không ở trong kênh voice.", + "djswap": "Bạn đã chuyển vai trò DJ cho `{0}`.", + "voicelinkQueueFull": "Xin lỗi, bạn đã đạt tối đa `{0}` bài hát trong hàng đợi!", + "voicelinkOutofList": "Vui lòng cung cấp chỉ số bài hát hợp lệ!", + "voicelinkDuplicateTrack": "Xin lỗi, bài hát này đã có trong hàng đợi.", + "decodeError": "Đã xảy ra lỗi khi giải mã tệp!", + "invalidStartTime": "Thời gian bắt đầu không hợp lệ, phải nằm giữa `00:00` và `{0}`", + "invalidEndTime": "Thời gian kết thúc không hợp lệ, phải nằm giữa `00:00` và `{0}`", + "invalidTimeOrder": "Thời gian kết thúc không thể nhỏ hơn hoặc bằng thời gian bắt đầu", + "setStageAnnounceTemplate": "Hoàn thành! Từ bây giờ, trạng thái voice như cái bạn đang ở sẽ được đặt tên theo mẫu của bạn. Bạn sẽ thấy nó cập nhật trong vài giây.", + "createSongRequestChannel": "Một kênh yêu cầu bài hát ({0}) đã được tạo! Bạn có thể bắt đầu yêu cầu bất kỳ bài hát nào bằng tên hoặc URL trong kênh đó, mà không cần sử dụng tiền tố bot." +} \ No newline at end of file diff --git a/lavalink/Dockerfile-lavalink b/lavalink/Dockerfile-lavalink index 48804af7..eaa9ca8e 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/latest/download/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 9c17b121..059f6dd0 100644 --- a/lavalink/application.yml +++ b/lavalink/application.yml @@ -64,6 +64,8 @@ plugins: albumLoadLimit: 6 # The number of pages at 50 tracks each resolveArtistsInSearch: true # Whether to resolve artists in track search results (can be slow) localFiles: false # Enable local files support with Spotify playlists. Please note `uri` & `isrc` will be `null` & `identifier` will be `"local"` + preferAnonymousToken: true # Whether to use the anonymous token for resolving tracks, artists and albums. Spotify generated playlists are always resolved with the anonymous tokens since they do not work otherwise. This requires the customTokenEndpoint to be set. + customTokenEndpoint: "http://spotify-tokener:49152/api/token" # Optional custom endpoint for getting the anonymous token. If not set, spotify's default endpoint will be used which might not work. The response must match spotify's anonymous token response format. applemusic: countryCode: "US" # the country code you want to use for filtering the artists top tracks and language. See https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 mediaAPIToken: "your apple music api token" # apple music api token @@ -100,9 +102,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.5" # 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.7.3" # 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 +194,4 @@ logging: logback: rollingpolicy: max-file-size: 1GB - max-history: 30 \ No newline at end of file + max-history: 30 diff --git a/local_langs/es-419.json b/local_langs/es-419.json new file mode 100644 index 00000000..a0471015 --- /dev/null +++ b/local_langs/es-419.json @@ -0,0 +1,235 @@ +{ + "connect": "conectar", + "Connect to a voice channel.": "Conectar a un canal de voz.", + "channel": "canal", + "Provide a channel to connect.": "Proporciona un canal para conectar.", + "play": "reproducir", + "Loads your input and added it to the queue.": "Carga tu entrada y la agrega a la cola.", + "query": "consulta", + "Input a query or a searchable link.": "Ingresa una consulta o un enlace de búsqueda.", + "search": "buscar", + "Input the name of the song.": "Ingresa el nombre de la canción.", + "platform": "plataforma", + "Select the platform you want to search.": "Selecciona la plataforma en la que quieres buscar.", + "Youtube": "YouTube", + "Youtube Music": "YouTube Music", + "Spotify": "Spotify", + "SoundCloud": "SoundCloud", + "Apple Music": "Apple Music", + "playtop": "reproducir_primero", + "Adds a song with the given url or query on the top of the queue.": "Agrega una canción con la URL o consulta al principio de la cola.", + "forceplay": "forzar_reproducir", + "Enforce playback using the given URL or query.": "Fuerza la reproducción usando la URL o consulta especificada.", + "pause": "pausar", + "Pause the music.": "Pausar la música.", + "resume": "reanudar", + "Resume the music.": "Reanudar la música.", + "skip": "saltar", + "Skips to the next song or skips to the specified song.": "Salta a la siguiente canción o a la canción especificada.", + "index": "indice", + "Enter a index that you want to skip to.": "Ingresa un índice al que quieras saltar.", + "back": "anterior", + "Skips back to the previous song or skips to the specified previous song.": "Regresa a la canción anterior o a la canción anterior especificada.", + "Enter a index that you want to skip back to.": "Ingresa un índice al que quieras regresar.", + "seek": "buscar_tiempo", + "Change the player position.": "Cambiar la posición del reproductor.", + "position": "posicion", + "Input position. Exmaple: 1:20.": "Ingresa posición. Ejemplo: 1:20.", + "queue": "cola", + "Display the players queue songs in your queue.": "Muestra las canciones en tu cola de reproducción.", + "export": "exportar", + "Exports the entire queue to a text file": "Exporta toda la cola a un archivo de texto.", + "import": "importar", + "Imports the text file and adds the track to the current queue.": "Importa el archivo de texto y agrega las pistas a la cola actual.", + "attachment": "adjunto", + "history": "historial", + "Display the players queue songs in your history queue.": "Muestra las canciones del historial de tu cola.", + "leave": "desconectar", + "Disconnects the bot from your voice channel and chears the queue.": "Desconecta el bot de tu canal de voz y limpia la cola.", + "nowplaying": "reproduciendo", + "Shows details of the current track.": "Muestra detalles de la pista actual.", + "loop": "bucle", + "Changes Loop mode.": "Cambia el modo de bucle.", + "mode": "modo", + "Choose a looping mode.": "Elige un modo de bucle.", + "Off": "Desactivado", + "Track": "Pista", + "Queue": "Cola", + "clear": "limpiar", + "Remove all the tracks in your queue or history queue.": "Elimina todas las pistas de tu cola o historial.", + "Choose a queue that you want to clear.": "Elige una cola que quieras limpiar.", + "History": "Historial", + "remove": "eliminar", + "Removes specified track or a range of tracks from the queue.": "Elimina la pista especificada o un rango de pistas de la cola.", + "position1": "posicion1", + "Input a position from the queue to be removed.": "Ingresa una posición de la cola para eliminar.", + "position2": "posicion2", + "Set the range of the queue to be removed.": "Establece el rango de la cola a eliminar.", + "member": "miembro", + "Remove tracks requested by a specific member.": "Elimina pistas solicitadas por un miembro específico.", + "forward": "adelantar", + "Forwards by a certain amount of time in the current track. The default is 10 seconds.": "Adelanta una cantidad de tiempo en la pista actual. Por defecto son 10 segundos.", + "Input an amount that you to forward to. Exmaple: 1:20": "Ingresa una cantidad a adelantar. Ejemplo: 1:20", + "rewind": "retroceder", + "Rewind by a certain amount of time in the current track. The default is 10 seconds.": "Retrocede una cantidad de tiempo en la pista actual. Por defecto son 10 segundos.", + "Input an amount that you to rewind to. Exmaple: 1:20": "Ingresa una cantidad a retroceder. Ejemplo: 1:20", + "replay": "repetir", + "Reset the progress of the current song.": "Reinicia el progreso de la canción actual.", + "shuffle": "aleatorio", + "Randomizes the tracks in the queue.": "Aleatoriza las pistas en la cola.", + "swap": "intercambiar", + "Swaps the specified song to the specified song.": "Intercambia la canción especificada con otra especificada.", + "The track to swap. Example: 2": "La pista a intercambiar. Ejemplo: 2", + "The track to swap with position1. Exmaple: 1": "La pista a intercambiar con posición1. Ejemplo: 1", + "move": "mover", + "Moves the specified song to the specified position.": "Mueve la canción especificada a la posición especificada.", + "target": "objetivo", + "The track to move. Example: 2": "La pista a mover. Ejemplo: 2", + "to": "a", + "The new position to move the track to. Exmaple: 1": "La nueva posición donde mover la pista. Ejemplo: 1", + "lyrics": "letra", + "Displays lyrics for the playing track.": "Muestra la letra de la pista en reproducción.", + "title": "titulo", + "Searches for your query and displays the reutned lyrics.": "Busca tu consulta y muestra la letra encontrada.", + "artist": "artista", + "swapdj": "cambiar_dj", + "Transfer dj to another.": "Transfiere el DJ a otro.", + "Choose a member to transfer the dj role.": "Elige un miembro para transferir el rol de DJ.", + "autoplay": "reproduccion_auto", + "Toggles autoplay mode, it will automatically queue the best songs to play.": "Activa la reproducción automática, agregará automáticamente las mejores canciones.", + "help": "ayuda", + "Lists all the commands in Vocard.": "Lista todos los comandos en Vocard.", + "category": "categoria", + "Test if the bot is alive, and see the delay between your commands and my response.": "Prueba si el bot está activo y ve el retraso entre tus comandos y mi respuesta.", + "playlist": "lista_reproduccion", + "Play all songs from your favorite playlist.": "Reproduce todas las canciones de tu lista de reproducción favorita.", + "name": "nombre", + "Input the name of your custom playlist": "Ingresa el nombre de tu lista de reproducción personalizada", + "value": "valor", + "Play the specific track from your custom playlist.": "Reproduce la pista específica de tu lista de reproducción personalizada.", + "view": "ver", + "List all your playlist and all songs in your favourite playlist.": "Lista todas tus listas de reproducción y canciones en tu lista favorita.", + "create": "crear", + "Create your custom playlist.": "Crea tu lista de reproducción personalizada.", + "Give a name to your playlist.": "Dale un nombre a tu lista de reproducción.", + "link": "enlace", + "Provide a playlist link if you are creating link playlist.": "Proporciona un enlace de lista si estás creando una lista vinculada.", + "delete": "borrar", + "Delete your custom playlist.": "Borra tu lista de reproducción personalizada.", + "The name of the playlist.": "El nombre de la lista de reproducción.", + "share": "compartir", + "Share your custom playlist with your friends.": "Comparte tu lista de reproducción personalizada con tus amigos.", + "The user id of your friend.": "El ID de usuario de tu amigo.", + "The name of the playlist that you want to share.": "El nombre de la lista de reproducción que quieres compartir.", + "rename": "renombrar", + "Rename your custom playlist.": "Renombra tu lista de reproducción personalizada.", + "The name of your playlist.": "El nombre de tu lista de reproducción.", + "newname": "nuevo_nombre", + "The new name of your playlist.": "El nuevo nombre de tu lista de reproducción.", + "inbox": "bandeja_entrada", + "Show your playlist invitation.": "Muestra tu invitación de lista de reproducción.", + "add": "agregar", + "Add tracks in to your custom playlist.": "Agrega pistas a tu lista de reproducción personalizada.", + "Remove song from your favorite playlist.": "Elimina canción de tu lista de reproducción favorita.", + "Input a position from the playlist to be removed.": "Ingresa una posición de la lista para eliminar.", + "Remove all songs from your favorite playlist.": "Elimina todas las canciones de tu lista de reproducción favorita.", + "Exports the entire playlist to a text file": "Exporta toda la lista de reproducción a un archivo de texto.", + "settings": "configuracion", + "prefix": "prefijo", + "Change the default prefix for message commands.": "Cambia el prefijo por defecto para comandos de mensaje.", + "language": "idioma", + "You can choose your preferred language, the bot message will change to the language you set.": "Puedes elegir tu idioma preferido, los mensajes del bot cambiarán al idioma que configures.", + "Set a DJ role or remove DJ role.": "Establece un rol de DJ o elimina el rol de DJ.", + "role": "rol", + "Change to another type of queue mode.": "Cambia a otro tipo de modo de cola.", + "FairQueue": "Cola_Justa", + "Toggles 24/7 mode, which disables automatic inactivity-based disconnects.": "Activa el modo 24/7, que desactiva las desconexiones automáticas por inactividad.", + "bypassvote": "saltar_votacion", + "Toggles voting system.": "Activa el sistema de votación.", + "Show all the bot settings in your server.": "Muestra toda la configuración del bot en tu servidor.", + "volume": "volumen", + "Set the player's volume.": "Establece el volumen del reproductor.", + "Input a integer.": "Ingresa un número entero.", + "toggleController": "alternar_controlador", + "Toggles the music controller.": "Alterna el controlador de música.", + "duplicatetrack": "pista_duplicada", + "Toggle Vocard to prevent duplicate songs from queuing.": "Activa Vocard para prevenir que se agreguen canciones duplicadas a la cola.", + "customcontroller": "controlador_personalizado", + "Customizes music controller embeds.": "Personaliza los embeds del controlador de música.", + "controllermsg": "mensaje_controlador", + "silentmsg": "mensaje_silencioso", + "Toggles to send a message when clicking the button in the music controller.": "Activa enviar un mensaje al hacer clic en el botón del controlador de música.", + "Toggle silent messaging to send discreet messages without alerting recipients.": "Activa mensajería silenciosa para enviar mensajes discretos sin alertar a los destinatarios.", + "debug": "depurar", + "speed": "velocidad", + "Sets the player's playback speed": "Establece la velocidad de reproducción del reproductor.", + "The value to set the speed to. Default is `1.0`": "El valor para establecer la velocidad. Por defecto es `1.0`.", + "karaoke": "karaoke", + "Uses equalization to eliminate part of a band, usually targeting vocals.": "Usa ecualización para eliminar parte de una banda, normalmente las voces.", + "level": "nivel", + "The level of the karaoke. Default is `1.0`": "El nivel del karaoke. Por defecto es `1.0`.", + "monolevel": "nivel_mono", + "The monolevel of the karaoke. Default is `1.0`": "El nivel mono del karaoke. Por defecto es `1.0`.", + "filterband": "banda_filtro", + "The filter band of the karaoke. Default is `220.0`": "La banda de filtro del karaoke. Por defecto es `220.0`.", + "filterwidth": "ancho_filtro", + "The filter band of the karaoke. Default is `100.0`": "El ancho de filtro del karaoke. Por defecto es `100.0`.", + "tremolo": "tremolo", + "Uses amplification to create a shuddering effect, where the volume quickly oscillates.": "Usa amplificación para crear un efecto de vibración donde el volumen oscila rápidamente.", + "frequency": "frecuencia", + "The frequency of the tremolo. Default is `2.0`": "La frecuencia del tremolo. Por defecto es `2.0`.", + "depth": "profundidad", + "The depth of the tremolo. Default is `0.5`": "La profundidad del tremolo. Por defecto es `0.5`.", + "vibrato": "vibrato", + "Similar to tremolo. While tremolo oscillates the volume, vibrato oscillates the pitch.": "Similar al tremolo. Mientras el tremolo oscila el volumen, el vibrato oscila el tono.", + "The frequency of the vibrato. Default is `2.0`": "La frecuencia del vibrato. Por defecto es `2.0`.", + "The Depth of the vibrato. Default is `0.5`": "La profundidad del vibrato. Por defecto es `0.5`.", + "rotation": "rotacion", + "Rotates the sound around the stereo channels/user headphones aka Audio Panning.": "Rota el sonido alrededor de los canales estéreo/auriculares del usuario (panorámica de audio).", + "hertz": "hertz", + "The hertz of the rotation. Default is `0.2`": "Los hertz de la rotación. Por defecto es `0.2`.", + "distortion": "distorsion", + "Distortion effect. It can generate some pretty unique audio effects.": "Efecto de distorsión. Puede generar efectos de audio bastante únicos.", + "lowpass": "paso_bajo", + "Filter which supresses higher frequencies and allows lower frequencies to pass.": "Filtro que suprime frecuencias altas y permite que pasen las frecuencias bajas.", + "smoothing": "suavizado", + "The level of the lowPass. Default is `20.0`": "El nivel del paso bajo. Por defecto es `20.0`.", + "channelmix": "mezcla_canales", + "Filter which manually adjusts the panning of the audio.": "Filtro que ajusta manualmente la panorámica del audio.", + "left_to_left": "izq_a_izq", + "Sounds from left to left. Default is `1.0`": "Sonido de izquierda a izquierda. Por defecto es `1.0`.", + "right_to_right": "der_a_der", + "Sounds from right to right. Default is `1.0`": "Sonido de derecha a derecha. Por defecto es `1.0`.", + "left_to_right": "izq_a_der", + "Sounds from left to right. Default is `0.0`": "Sonido de izquierda a derecha. Por defecto es `0.0`.", + "right_to_left": "der_a_izq", + "Sounds from right to left. Default is `0.0`": "Sonido de derecha a izquierda. Por defecto es `0.0`.", + "nightcore": "nightcore", + "Add nightcore filter into your player.": "Agrega el filtro nightcore a tu reproductor.", + "Add 8D filter into your player.": "Agrega el filtro 8D a tu reproductor.", + "vaporwave": "vaporwave", + "Add vaporwave filter into your player.": "Agrega el filtro vaporwave a tu reproductor.", + "cleareffect": "limpiar_efectos", + "Clear all or specific sound effects.": "Limpia todos o efectos de sonido específicos.", + "effect": "efecto", + "Remove a specific sound effects.": "Elimina efectos de sonido específicos.", + "start": "inicio", + "end": "fin", + "Specify a time you would like to start, e.g. 1:00": "Especifica la hora a la que quieres empezar, ej. 1:00.", + "Specify a time you would like to end, e.g. 4:00": "Especifica la hora a la que quieres terminar, ej. 4:00.", + "list": "lista", + "Customize the channel topic template": "Personaliza la plantilla del tema del canal.", + "template": "plantilla", + "setupchannel": "configurar_canal", + "Sets up a dedicated channel for song requests in your server.": "Configura un canal dedicado para peticiones de canciones en tu servidor.", + "Provide a request channel. If not, a text channel will be generated.": "Proporciona un canal de peticiones. Si no, se generará un canal de texto.", + "ping": "ping", + "…": "...", + "8d": "8d", + "dj": "dj", + "247": "247", + "stageannounce": "anuncio_escenario", + "Soundcloud": "Soundcloud", + "Fairqueue":"ColaJusta", + "togglecontroller":"cambiarcontrolador" +} diff --git a/local_langs/es-ES.json b/local_langs/es-ES.json new file mode 100644 index 00000000..e8cfd59b --- /dev/null +++ b/local_langs/es-ES.json @@ -0,0 +1,235 @@ +{ + "connect": "conectar", + "Connect to a voice channel.": "Conectar a un canal de voz.", + "channel": "canal", + "Provide a channel to connect.": "Proporciona un canal para conectar.", + "play": "reproducir", + "Loads your input and added it to the queue.": "Carga tu entrada y la añade a la cola.", + "query": "consulta", + "Input a query or a searchable link.": "Introduce una consulta o un enlace de búsqueda.", + "search": "buscar", + "Input the name of the song.": "Introduce el nombre de la canción.", + "platform": "plataforma", + "Select the platform you want to search.": "Selecciona la plataforma en la que quieres buscar.", + "Youtube": "YouTube", + "Youtube Music": "YouTube Music", + "Spotify": "Spotify", + "SoundCloud": "SoundCloud", + "Apple Music": "Apple Music", + "playtop": "reproducir_primero", + "Adds a song with the given url or query on the top of the queue.": "Añade una canción con la URL o consulta al principio de la cola.", + "forceplay": "forzar_reproducir", + "Enforce playback using the given URL or query.": "Fuerza la reproducción usando la URL o consulta especificada.", + "pause": "pausar", + "Pause the music.": "Pausar la música.", + "resume": "reanudar", + "Resume the music.": "Reanudar la música.", + "skip": "saltar", + "Skips to the next song or skips to the specified song.": "Salta a la siguiente canción o a la canción especificada.", + "index": "indice", + "Enter a index that you want to skip to.": "Introduce un índice al que quieras saltar.", + "back": "anterior", + "Skips back to the previous song or skips to the specified previous song.": "Vuelve a la canción anterior o a la canción anterior especificada.", + "Enter a index that you want to skip back to.": "Introduce un índice al que quieras volver.", + "seek": "buscar_tiempo", + "Change the player position.": "Cambiar la posición del reproductor.", + "position": "posicion", + "Input position. Exmaple: 1:20.": "Introduce posición. Ejemplo: 1:20.", + "queue": "cola", + "Display the players queue songs in your queue.": "Muestra las canciones en tu cola de reproducción.", + "export": "exportar", + "Exports the entire queue to a text file": "Exporta toda la cola a un archivo de texto.", + "import": "importar", + "Imports the text file and adds the track to the current queue.": "Importa el archivo de texto y añade las pistas a la cola actual.", + "attachment": "adjunto", + "history": "historial", + "Display the players queue songs in your history queue.": "Muestra las canciones del historial de tu cola.", + "leave": "desconectar", + "Disconnects the bot from your voice channel and chears the queue.": "Desconecta el bot de tu canal de voz y limpia la cola.", + "nowplaying": "reproduciendo", + "Shows details of the current track.": "Muestra detalles de la pista actual.", + "loop": "bucle", + "Changes Loop mode.": "Cambia el modo de bucle.", + "mode": "modo", + "Choose a looping mode.": "Elige un modo de bucle.", + "Off": "Desactivado", + "Track": "Pista", + "Queue": "Cola", + "clear": "limpiar", + "Remove all the tracks in your queue or history queue.": "Elimina todas las pistas de tu cola o historial.", + "Choose a queue that you want to clear.": "Elige una cola que quieras limpiar.", + "History": "Historial", + "remove": "eliminar", + "Removes specified track or a range of tracks from the queue.": "Elimina la pista especificada o un rango de pistas de la cola.", + "position1": "posicion1", + "Input a position from the queue to be removed.": "Introduce una posición de la cola para eliminar.", + "position2": "posicion2", + "Set the range of the queue to be removed.": "Establece el rango de la cola a eliminar.", + "member": "miembro", + "Remove tracks requested by a specific member.": "Elimina pistas solicitadas por un miembro específico.", + "forward": "adelantar", + "Forwards by a certain amount of time in the current track. The default is 10 seconds.": "Adelanta una cantidad de tiempo en la pista actual. Por defecto son 10 segundos.", + "Input an amount that you to forward to. Exmaple: 1:20": "Introduce una cantidad a adelantar. Ejemplo: 1:20", + "rewind": "retroceder", + "Rewind by a certain amount of time in the current track. The default is 10 seconds.": "Retrocede una cantidad de tiempo en la pista actual. Por defecto son 10 segundos.", + "Input an amount that you to rewind to. Exmaple: 1:20": "Introduce una cantidad a retroceder. Ejemplo: 1:20", + "replay": "repetir", + "Reset the progress of the current song.": "Reinicia el progreso de la canción actual.", + "shuffle": "aleatorio", + "Randomizes the tracks in the queue.": "Aleatoriza las pistas en la cola.", + "swap": "intercambiar", + "Swaps the specified song to the specified song.": "Intercambia la canción especificada con otra especificada.", + "The track to swap. Example: 2": "La pista a intercambiar. Ejemplo: 2", + "The track to swap with position1. Exmaple: 1": "La pista a intercambiar con posición1. Ejemplo: 1", + "move": "mover", + "Moves the specified song to the specified position.": "Mueve la canción especificada a la posición especificada.", + "target": "objetivo", + "The track to move. Example: 2": "La pista a mover. Ejemplo: 2", + "to": "a", + "The new position to move the track to. Exmaple: 1": "La nueva posición donde mover la pista. Ejemplo: 1", + "lyrics": "letra", + "Displays lyrics for the playing track.": "Muestra la letra de la pista en reproducción.", + "title": "titulo", + "Searches for your query and displays the reutned lyrics.": "Busca tu consulta y muestra la letra encontrada.", + "artist": "artista", + "swapdj": "cambiar_dj", + "Transfer dj to another.": "Transfiere el DJ a otro.", + "Choose a member to transfer the dj role.": "Elige un miembro para transferir el rol de DJ.", + "autoplay": "reproduccion_auto", + "Toggles autoplay mode, it will automatically queue the best songs to play.": "Activa la reproducción automática, añadirá automáticamente las mejores canciones.", + "help": "ayuda", + "Lists all the commands in Vocard.": "Lista todos los comandos en Vocard.", + "category": "categoria", + "Test if the bot is alive, and see the delay between your commands and my response.": "Prueba si el bot está activo y ve el retraso entre tus comandos y mi respuesta.", + "playlist": "lista_reproduccion", + "Play all songs from your favorite playlist.": "Reproduce todas las canciones de tu lista de reproducción favorita.", + "name": "nombre", + "Input the name of your custom playlist": "Introduce el nombre de tu lista de reproducción personalizada", + "value": "valor", + "Play the specific track from your custom playlist.": "Reproduce la pista específica de tu lista de reproducción personalizada.", + "view": "ver", + "List all your playlist and all songs in your favourite playlist.": "Lista todas tus listas de reproducción y canciones en tu lista favorita.", + "create": "crear", + "Create your custom playlist.": "Crea tu lista de reproducción personalizada.", + "Give a name to your playlist.": "Dale un nombre a tu lista de reproducción.", + "link": "enlace", + "Provide a playlist link if you are creating link playlist.": "Proporciona un enlace de lista si estás creando una lista vinculada.", + "delete": "borrar", + "Delete your custom playlist.": "Borra tu lista de reproducción personalizada.", + "The name of the playlist.": "El nombre de la lista de reproducción.", + "share": "compartir", + "Share your custom playlist with your friends.": "Comparte tu lista de reproducción personalizada con tus amigos.", + "The user id of your friend.": "El ID de usuario de tu amigo.", + "The name of the playlist that you want to share.": "El nombre de la lista de reproducción que quieres compartir.", + "rename": "renombrar", + "Rename your custom playlist.": "Renombra tu lista de reproducción personalizada.", + "The name of your playlist.": "El nombre de tu lista de reproducción.", + "newname": "nuevo_nombre", + "The new name of your playlist.": "El nuevo nombre de tu lista de reproducción.", + "inbox": "bandeja_entrada", + "Show your playlist invitation.": "Muestra tu invitación de lista de reproducción.", + "add": "anadir", + "Add tracks in to your custom playlist.": "Añade pistas a tu lista de reproducción personalizada.", + "Remove song from your favorite playlist.": "Elimina canción de tu lista de reproducción favorita.", + "Input a position from the playlist to be removed.": "Introduce una posición de la lista para eliminar.", + "Remove all songs from your favorite playlist.": "Elimina todas las canciones de tu lista de reproducción favorita.", + "Exports the entire playlist to a text file": "Exporta toda la lista de reproducción a un archivo de texto.", + "settings": "configuracion", + "prefix": "prefijo", + "Change the default prefix for message commands.": "Cambia el prefijo por defecto para comandos de mensaje.", + "language": "idioma", + "You can choose your preferred language, the bot message will change to the language you set.": "Puedes elegir tu idioma preferido, los mensajes del bot cambiarán al idioma que configures.", + "Set a DJ role or remove DJ role.": "Establece un rol de DJ o elimina el rol de DJ.", + "role": "rol", + "Change to another type of queue mode.": "Cambia a otro tipo de modo de cola.", + "FairQueue": "Cola_Justa", + "Toggles 24/7 mode, which disables automatic inactivity-based disconnects.": "Activa el modo 24/7, que desactiva las desconexiones automáticas por inactividad.", + "bypassvote": "saltar_votacion", + "Toggles voting system.": "Activa el sistema de votación.", + "Show all the bot settings in your server.": "Muestra toda la configuración del bot en tu servidor.", + "volume": "volumen", + "Set the player's volume.": "Establece el volumen del reproductor.", + "Input a integer.": "Introduce un número entero.", + "toggleController": "alternar_controlador", + "Toggles the music controller.": "Alterna el controlador de música.", + "duplicatetrack": "pista_duplicada", + "Toggle Vocard to prevent duplicate songs from queuing.": "Activa Vocard para prevenir que se añadan canciones duplicadas a la cola.", + "customcontroller": "controlador_personalizado", + "Customizes music controller embeds.": "Personaliza los embeds del controlador de música.", + "controllermsg": "mensaje_controlador", + "silentmsg": "mensaje_silencioso", + "Toggles to send a message when clicking the button in the music controller.": "Activa enviar un mensaje al hacer clic en el botón del controlador de música.", + "Toggle silent messaging to send discreet messages without alerting recipients.": "Activa mensajería silenciosa para enviar mensajes discretos sin alertar a los destinatarios.", + "debug": "depurar", + "speed": "velocidad", + "Sets the player's playback speed": "Establece la velocidad de reproducción del reproductor.", + "The value to set the speed to. Default is `1.0`": "El valor para establecer la velocidad. Por defecto es `1.0`.", + "karaoke": "karaoke", + "Uses equalization to eliminate part of a band, usually targeting vocals.": "Usa ecualización para eliminar parte de una banda, normalmente las voces.", + "level": "nivel", + "The level of the karaoke. Default is `1.0`": "El nivel del karaoke. Por defecto es `1.0`.", + "monolevel": "nivel_mono", + "The monolevel of the karaoke. Default is `1.0`": "El nivel mono del karaoke. Por defecto es `1.0`.", + "filterband": "banda_filtro", + "The filter band of the karaoke. Default is `220.0`": "La banda de filtro del karaoke. Por defecto es `220.0`.", + "filterwidth": "ancho_filtro", + "The filter band of the karaoke. Default is `100.0`": "El ancho de filtro del karaoke. Por defecto es `100.0`.", + "tremolo": "tremolo", + "Uses amplification to create a shuddering effect, where the volume quickly oscillates.": "Usa amplificación para crear un efecto de vibración donde el volumen oscila rápidamente.", + "frequency": "frecuencia", + "The frequency of the tremolo. Default is `2.0`": "La frecuencia del tremolo. Por defecto es `2.0`.", + "depth": "profundidad", + "The depth of the tremolo. Default is `0.5`": "La profundidad del tremolo. Por defecto es `0.5`.", + "vibrato": "vibrato", + "Similar to tremolo. While tremolo oscillates the volume, vibrato oscillates the pitch.": "Similar al tremolo. Mientras el tremolo oscila el volumen, el vibrato oscila el tono.", + "The frequency of the vibrato. Default is `2.0`": "La frecuencia del vibrato. Por defecto es `2.0`.", + "The Depth of the vibrato. Default is `0.5`": "La profundidad del vibrato. Por defecto es `0.5`.", + "rotation": "rotacion", + "Rotates the sound around the stereo channels/user headphones aka Audio Panning.": "Rota el sonido alrededor de los canales estéreo/auriculares del usuario (panorámica de audio).", + "hertz": "hertz", + "The hertz of the rotation. Default is `0.2`": "Los hertz de la rotación. Por defecto es `0.2`.", + "distortion": "distorsion", + "Distortion effect. It can generate some pretty unique audio effects.": "Efecto de distorsión. Puede generar efectos de audio bastante únicos.", + "lowpass": "paso_bajo", + "Filter which supresses higher frequencies and allows lower frequencies to pass.": "Filtro que suprime frecuencias altas y permite que pasen las frecuencias bajas.", + "smoothing": "suavizado", + "The level of the lowPass. Default is `20.0`": "El nivel del paso bajo. Por defecto es `20.0`.", + "channelmix": "mezcla_canales", + "Filter which manually adjusts the panning of the audio.": "Filtro que ajusta manualmente la panorámica del audio.", + "left_to_left": "izq_a_izq", + "Sounds from left to left. Default is `1.0`": "Sonido de izquierda a izquierda. Por defecto es `1.0`.", + "right_to_right": "der_a_der", + "Sounds from right to right. Default is `1.0`": "Sonido de derecha a derecha. Por defecto es `1.0`.", + "left_to_right": "izq_a_der", + "Sounds from left to right. Default is `0.0`": "Sonido de izquierda a derecha. Por defecto es `0.0`.", + "right_to_left": "der_a_izq", + "Sounds from right to left. Default is `0.0`": "Sonido de derecha a izquierda. Por defecto es `0.0`.", + "nightcore": "nightcore", + "Add nightcore filter into your player.": "Añade el filtro nightcore a tu reproductor.", + "Add 8D filter into your player.": "Añade el filtro 8D a tu reproductor.", + "vaporwave": "vaporwave", + "Add vaporwave filter into your player.": "Añade el filtro vaporwave a tu reproductor.", + "cleareffect": "limpiar_efectos", + "Clear all or specific sound effects.": "Limpia todos o efectos de sonido específicos.", + "effect": "efecto", + "Remove a specific sound effects.": "Elimina efectos de sonido específicos.", + "start": "inicio", + "end": "fin", + "Specify a time you would like to start, e.g. 1:00": "Especifica la hora a la que quieres empezar, ej. 1:00.", + "Specify a time you would like to end, e.g. 4:00": "Especifica la hora a la que quieres terminar, ej. 4:00.", + "list": "lista", + "Customize the channel topic template": "Personaliza la plantilla del tema del canal.", + "template": "plantilla", + "setupchannel": "configurar_canal", + "Sets up a dedicated channel for song requests in your server.": "Configura un canal dedicado para peticiones de canciones en tu servidor.", + "Provide a request channel. If not, a text channel will be generated.": "Proporciona un canal de peticiones. Si no, se generará un canal de texto.", + "ping": "ping", + "…": "...", + "8d": "8d", + "dj": "dj", + "247": "247", + "stageannounce": "anuncio_escenario", + "Soundcloud": "Soundcloud", + "Fairqueue":"ColaJusta", + "togglecontroller":"cambiarcontrolador" +} diff --git a/local_langs/zh-TW.json b/local_langs/zh-TW.json index d4ae83eb..eecde646 100644 --- a/local_langs/zh-TW.json +++ b/local_langs/zh-TW.json @@ -150,14 +150,16 @@ "volume": "音量", "Set the player's volume.": "設置播放器的音量。", "Input a integer.": "輸入一個整數。", - "togglecontroller": "切換控制器", + "toggleController": "切換控制器", "Toggles the music controller.": "切換音樂控制器。", "duplicatetrack": "重複歌曲", "Toggle Vocard to prevent duplicate songs from queuing.": "切換 Vocard 以防止重複歌曲加入隊列。", "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": "設置播放器的播放速度。", @@ -227,5 +229,7 @@ "dj": "dj", "247": "247", "stageannounce": "舞台公告", - "Soundcloud": "Soundcloud" + "Soundcloud": "Soundcloud", + "Fairqueue": "公平隊列", + "togglecontroller": "切換控制器" } \ No newline at end of file diff --git a/main.py b/main.py index 202db236..d8111cf5 100644 --- a/main.py +++ b/main.py @@ -45,7 +45,7 @@ async def unload(self): async def translate(self, string: discord.app_commands.locale_str, locale: discord.Locale, context: discord.app_commands.TranslationContext): locale_key = str(locale) - + if locale_key in func.LOCAL_LANGS: translated_text = func.LOCAL_LANGS[locale_key].get(string.message) @@ -53,9 +53,9 @@ async def translate(self, string: discord.app_commands.locale_str, locale: disco missing_translations = func.MISSING_TRANSLATOR.setdefault(locale_key, []) if string.message not in missing_translations: missing_translations.append(string.message) - + return translated_text - + return None class Vocard(commands.Bot): @@ -70,11 +70,11 @@ async def on_message(self, message: discord.Message, /) -> None: return False # Check if the bot is directly mentioned - if self.user.id in message.raw_mentions and not message.mention_everyone: + if message.content.strip() == self.user.mention and not message.mention_everyone: prefix = await self.command_prefix(self, message) if not prefix: return await message.channel.send("I don't have a bot prefix set.") - await message.channel.send(f"My prefix is `{prefix}`") + return await message.channel.send(f"My prefix is `{prefix}`") # Fetch guild settings and check if the mesage is in the music request channel settings = await func.get_settings(message.guild.id) @@ -89,13 +89,13 @@ async def on_message(self, message: discord.Message, /) -> None: elif message.attachments: for attachment in message.attachments: await cmd(ctx, query=attachment.url) - + except Exception as e: await func.send(ctx, str(e), ephemeral=True) - + finally: return await message.delete() - + await self.process_commands(message) async def connect_db(self) -> None: @@ -110,19 +110,19 @@ async def connect_db(self) -> None: except Exception as e: func.logger.error("Not able to connect MongoDB! Reason:", exc_info=e) exit() - + func.SETTINGS_DB = func.MONGO_DB[db_name]["Settings"] func.USERS_DB = func.MONGO_DB[db_name]["Users"] async def setup_hook(self) -> None: func.langs_setup() - + # Connecting to MongoDB await self.connect_db() # Set translator await self.tree.set_translator(Translator()) - + # Loading all the module in `cogs` folder for module in os.listdir(func.ROOT_DIR + '/cogs'): if module.endswith('.py'): @@ -139,6 +139,7 @@ async def setup_hook(self) -> None: except Exception as e: func.logger.error(f"Cannot connected to dashboard! - Reason: {e}") + # Update version tracking if not func.settings.version or func.settings.version != update.__version__: await self.tree.sync() func.update_json("settings.json", new_data={"version": update.__version__}) @@ -162,7 +163,7 @@ async def on_command_error(self, ctx: commands.Context, exception, /) -> None: error = getattr(exception, 'original', exception) if ctx.interaction: error = getattr(error, 'original', error) - + if isinstance(error, (commands.CommandNotFound, aiohttp.client_exceptions.ClientOSError, discord.errors.NotFound)): return @@ -184,7 +185,7 @@ async def on_command_error(self, ctx: commands.Context, exception, /) -> None: elif not issubclass(error.__class__, voicelink.VoicelinkException): error = await func.get_lang(ctx.guild.id, "unknownException") + func.settings.invite_link func.logger.error(f"An unexpected error occurred in the {ctx.command.name} command on the {ctx.guild.name}({ctx.guild.id}).", exc_info=exception) - + try: return await ctx.reply(error, ephemeral=True) except: @@ -192,10 +193,16 @@ async def on_command_error(self, ctx: commands.Context, exception, /) -> None: class CommandCheck(discord.app_commands.CommandTree): async def interaction_check(self, interaction: discord.Interaction, /) -> bool: - if not interaction.guild: - await interaction.response.send_message("This command can only be used in guilds!") - return False - + if interaction.type == discord.InteractionType.application_command: + if not interaction.guild: + await interaction.response.send_message("This command can only be used in guilds!") + return False + + channel_perm = interaction.channel.permissions_for(interaction.guild.me) + if not channel_perm.read_messages or not channel_perm.send_messages: + await interaction.response.send_message("I don't have permission to read or send messages in this channel.") + return False + return True async def get_prefix(bot: commands.Bot, message: discord.Message) -> str: @@ -225,7 +232,7 @@ async def get_prefix(bot: commands.Bot, message: discord.Message) -> str: for log_name, log_level in LOG_SETTINGS.get("level", {}).items(): _logger = logging.getLogger(log_name) _logger.setLevel(log_level) - + # Setup the bot object intents = discord.Intents.default() intents.message_content = False if func.settings.bot_prefix is None else True @@ -244,4 +251,4 @@ async def get_prefix(bot: commands.Bot, message: discord.Message) -> str: if __name__ == "__main__": update.check_version(with_msg=True) - bot.run(func.settings.token, root_logger=True) \ No newline at end of file + bot.run(func.settings.token, root_logger=True) diff --git a/requirements.txt b/requirements.txt index 352250a0..30dac478 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,4 +6,5 @@ validators==0.18.2 humanize==4.0.0 beautifulsoup4==4.11.1 psutil==5.9.8 -aiohttp==3.11.12 \ No newline at end of file +aiohttp==3.11.12 +python-dotenv==1.1.1 \ No newline at end of file diff --git a/settings Example.json b/settings Example.json index ed299287..3a359b6f 100644 --- a/settings Example.json +++ b/settings Example.json @@ -117,11 +117,45 @@ "color": "@@default_embed_color@@" } }, - "default_buttons": [ - ["back", "resume", "skip", {"stop": "red"}, "add"], - ["tracks"] - ], - "disableButtonText": false + "buttons": [ + { + "back": { + "emoji": "⏮️", + "label": "@@t_buttonBack@@" + }, + "play-pause": { + "states": { + "pause": { + "emoji": "⏸️", + "label": "@@t_buttonPause@@" + }, + "resume": { + "emoji": "▶️", + "label": "@@t_buttonResume@@" + } + } + }, + "skip": { + "emoji": "⏭️", + "label": "@@t_buttonSkip@@" + }, + "stop": { + "emoji": "⏹️", + "label": "@@t_buttonLeave@@", + "style": "red" + }, + "add-fav": { + "emoji": "❤️", + "label": "" + } + }, + { + "tracks": { + "label": "@@t_playerDropdown@@", + "max_options": 10 + } + } + ] }, "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 1560d4b4..2ada4351 100644 --- a/update.py +++ b/update.py @@ -31,11 +31,10 @@ from io import BytesIO ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) -__version__ = "v2.7.1" +__version__ = "v2.7.2b3" # 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 d403e83e..9bfc8b86 100644 --- a/views/controller.py +++ b/views/controller.py @@ -29,23 +29,49 @@ 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 - + class ControlButton(discord.ui.Button): def __init__( self, - player, - label: str = None, + player: "voicelink.Player", + 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) + super().__init__(**kwargs) + self.player: voicelink.Player = player + self.btn_data: Dict[str, Any] = btn_data + self.change_states(default_states) + + 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.""" + states = states.lower() if states else None + state_config = self._get_button_config(states) + if state_config: + self.emoji = state_config.get("emoji") or None + self.style = self._get_button_style(state_config.get("style")) + 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) return await func.send( @@ -58,8 +84,6 @@ async def send(self, interaction: discord.Interaction, key: str, *params, view: 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 +111,20 @@ async def callback(self, interaction: discord.Interaction): 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 +134,13 @@ async def callback(self, interaction: discord.Interaction): 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 +165,7 @@ async def callback(self, interaction: discord.Interaction): 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 +181,9 @@ async def callback(self, interaction: discord.Interaction): 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 @@ -185,10 +194,10 @@ async def callback(self, interaction: discord.Interaction): user = await func.get_user(interaction.user.id, 'playlist') rank, max_p, max_t = func.check_roles() if len(user['200']['tracks']) >= max_t: - return await self.send(interaction, "playlistlimited", max_t, ephemeral=True) + return await self.send(interaction, "playlistLimited", max_t, ephemeral=True) if track.track_id in user['200']['tracks']: - return await self.send(interaction, "playlistrepeated", ephemeral=True) + return await self.send(interaction, "playlistRepeated", ephemeral=True) respond = await func.update_user(interaction.user.id, {"$push": {'playlist.200.tracks': track.track_id}}) if respond: await self.send(interaction, "playlistAdded", track.title, interaction.user.mention, user['200']['name'], ephemeral=True) @@ -197,46 +206,27 @@ async def callback(self, interaction: discord.Interaction): class Loop(ControlButton): def __init__(self, **kwargs): - self.btn_emojis: dict[str, str] = { - "off": "🔁", - "track": "🔂", - "queue": "🔁" - } - super().__init__( - emoji=self.get_next_loop_emoji(kwargs["player"]), - label="buttonLoop", + default_states=kwargs["player"].queue._repeat.peek_next().name, **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) + return await self.send(interaction, 'missingModePerm', ephemeral=True) + + await self.player.set_repeat(requester=interaction.user) + self.change_states(self.player.queue._repeat.peek_next().name) - mode = await self.player.set_repeat(requester=interaction.user) - self.emoji = self.get_next_loop_emoji(self.player) - 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): - return await self.send(interaction, "missingPerms_function") + return await self.send(interaction, "missingFunctionPerm") value = value if (value := self.player.volume + 20) <= 150 else 150 await self.player.set_volume(value, interaction.user) @@ -245,15 +235,11 @@ async def callback(self, interaction: discord.Interaction): 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): - return await self.send(interaction, "missingPerms_function") + return await self.send(interaction, "missingFunctionPerm") value = value if (value := self.player.volume - 20) >= 0 else 0 await self.player.set_volume(value, interaction.user) @@ -263,35 +249,27 @@ async def callback(self, interaction: discord.Interaction): 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 ) async def callback(self, interaction: discord.Interaction): if not self.player.is_privileged(interaction.user): - return await self.send(interaction, "missingPerms_function") + return await self.send(interaction, "missingFunctionPerm") 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): - return await self.send(interaction, "missingPerms_autoplay", ephemeral=True) + return await self.send(interaction, "missingAutoPlayPerm", ephemeral=True) check = not self.player.settings.get("autoplay", False) self.player.settings['autoplay'] = check @@ -302,11 +280,7 @@ async def callback(self, interaction: discord.Interaction): 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,15 +299,13 @@ async def callback(self, interaction: discord.Interaction): class Forward(ControlButton): def __init__(self, **kwargs): super().__init__( - emoji="⏩", - label="buttonForward", disabled=kwargs["player"].current is None, **kwargs ) async def callback(self, interaction: discord.Interaction): if not self.player.is_privileged(interaction.user): - return await self.send(interaction, 'missingPerms_pos', ephemeral=True) + return await self.send(interaction, 'missingPosPerm', ephemeral=True) if not self.player.current: return await self.send(interaction, 'noTrackPlaying', ephemeral=True) @@ -346,15 +318,13 @@ async def callback(self, interaction: discord.Interaction): class Rewind(ControlButton): def __init__(self, **kwargs): super().__init__( - emoji="⏪", - label="buttonRewind", disabled=kwargs["player"].current is None, **kwargs ) async def callback(self, interaction: discord.Interaction): if not self.player.is_privileged(interaction.user): - return await self.send(interaction, 'missingPerms_pos', ephemeral=True) + return await self.send(interaction, 'missingPosPerm', ephemeral=True) if not self.player.current: return await self.send(interaction, 'noTrackPlaying', ephemeral=True) @@ -367,8 +337,6 @@ async def callback(self, interaction: discord.Interaction): class Lyrics(ControlButton): def __init__(self, **kwargs): super().__init__( - emoji="📜", - label="buttonLyrics", disabled=kwargs["player"].current is None, **kwargs ) @@ -390,25 +358,28 @@ async def callback(self, interaction: discord.Interaction): 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: "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=self.player.get_msg("playerDropdown"), + placeholder=self.player._ph.replace(btn_data.get("label"), {}), options=options, - row=row + 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) + return await func.send(interaction, "missingFunctionPerm", ephemeral=True) self.player.queue.skipto(int(self.values[0].split(". ")[0])) await self.player.stop() @@ -417,7 +388,7 @@ async def callback(self, interaction: discord.Interaction): await func.send(interaction, "skipped", interaction.user) class Effects(discord.ui.Select): - def __init__(self, player, style, row): + def __init__(self, player: "voicelink.Player", btn_data, row): self.player: voicelink.Player = player @@ -426,14 +397,14 @@ def __init__(self, player, style, row): options.append(discord.SelectOption(label=name.capitalize(), value=name)) super().__init__( - placeholder=self.player.get_msg("playerFilter"), + 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) + return await func.send(interaction, "missingFunctionPerm", ephemeral=True) avalibable_filters = voicelink.Filters.get_available_filters() if self.values[0] == "None": @@ -448,12 +419,12 @@ async def callback(self, interaction: discord.Interaction): 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,30 +438,22 @@ async def callback(self, interaction: discord.Interaction): "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: continue - self.add_item(btnClass(player=player, style=style, row=row)) - + + 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): @@ -510,7 +473,7 @@ async def interaction_check(self, interaction: discord.Interaction): 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) diff --git a/voicelink/placeholders.py b/voicelink/placeholders.py index b1f361e6..407225cc 100644 --- a/voicelink/placeholders.py +++ b/voicelink/placeholders.py @@ -70,6 +70,10 @@ def __init__(self, bot: Client, player: Player = None) -> None: "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 @@ def default_embed_color(self) -> int: 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,42 +192,44 @@ def replace(self, text: str, variables: dict[str, str]) -> str: 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 -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 81148e6f..752162b0 100644 --- a/voicelink/player.py +++ b/voicelink/player.py @@ -51,8 +51,8 @@ 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 @@ def __init__( 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 @@ def required(self, leave=False): 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) @@ -302,9 +305,9 @@ def is_privileged(self, user: Member, check_user_join: bool = True) -> bool: 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.""" @@ -452,19 +455,19 @@ async def invoke_controller(self): if request_channel_data := self.settings.get("music_request_channel"): channel = self.bot.get_channel(request_channel_data.get("text_channel_id")) if channel: - self.controller = channel.get_partial_message(request_channel_data.get("controller_msg_id")) try: + self.controller = await channel.fetch_message(request_channel_data.get("controller_msg_id")) await self.controller.edit(embed=embed, view=view) except errors.NotFound: self.controller = None # 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, requires_fetch=True) 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, requires_fetch=True) else: await self.controller.edit(embed=embed, view=view) @@ -493,8 +496,8 @@ async def teardown(self): """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 @@ async def shuffle(self, queue_type: str, requester: Member = None) -> None: 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.") @@ -757,7 +760,7 @@ async def add_filter(self, filter: Filter, requester: Member = None, fast_apply: try: self._filters.add_filter(filter=filter) except FilterTagAlreadyInUse: - raise FilterTagAlreadyInUse(self.get_msg("FilterTagAlreadyInUse")) + raise FilterTagAlreadyInUse(self.get_msg("filterTagAlreadyInUse")) payload = self._filters.get_all_payloads() await self.send(method=RequestMethod.PATCH, data={"filters": payload}) diff --git a/voicelink/pool.py b/voicelink/pool.py index ec625d0d..427fcfd3 100644 --- a/voicelink/pool.py +++ b/voicelink/pool.py @@ -113,7 +113,7 @@ def __init__( 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") @@ -399,6 +399,9 @@ async def get_recommendations(self, track: Track, limit: int = 20) -> List[Optio return [] tracks = await self.get_tracks(query=query, requester=self.bot.user) + if not track: + return [] + if isinstance(tracks, Playlist): tracks = tracks.tracks diff --git a/voicelink/queue.py b/voicelink/queue.py index 10beee01..58092e26 100644 --- a/voicelink/queue.py +++ b/voicelink/queue.py @@ -38,6 +38,13 @@ def next(self) -> LoopType: 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 @@ -216,3 +223,8 @@ def put(self, item: Track) -> int: self.put_at_index(lastIndex, item) return lastIndex + +QUEUE_TYPES: Dict[str, Queue] = { + "queue": Queue, + "fairqueue": FairQueue +}