Merge pull request #12 from ChocoMeow/beta

Vocard v2.6.6 Update: new features, bug fixes, and code clean-up
This commit is contained in:
Choco
2023-08-03 21:16:49 +08:00
committed by GitHub
23 changed files with 702 additions and 383 deletions

View File

@@ -8,8 +8,6 @@ BUG_REPORT_CHANNEL_ID = YOUR_DISCORD_CHANNEL_ID
SPOTIFY_CLIENT_ID = YOUR_SPOTIFY_CLIENT_ID
SPOTIFY_CLIENT_SECRET = YOUR_SPOTIFY_CLIENT_SECRET
YOUTUBE_API_KEY = YOUR_YOUTUBE_API_KEY
GENIUS_TOKEN = YOUR_GENIUS_TOKEN
MONGODB_URL = YOUR_MONGODB_URL

View File

@@ -171,6 +171,17 @@ MONGODB_NAME = Vocard
* For `default_controller` you can set custom embeds and buttons in controller, [Example Here](https://github.com/ChocoMeow/Vocard/blob/main/PLACEHOLDERS.md#controller-embeds)
## How to update? (For Windows and Linux)
1. Run `python update.py --check` to check if your bot is up to date
2. Run `python update.py --start` to start update your bot <br/>
***Note: Make sure there are no personal files in the directory! Otherwise it will be deleted.***
```sh
# Check the current version
python update.py -c
# Install the latest version
python update.py -l
# Install the specified version
python update.py -v VERSION
# Install the beta version
python update.py -b
```

View File

@@ -62,7 +62,6 @@ class TOKENS:
self.bug_report_channel_id = int(os.getenv("BUG_REPORT_CHANNEL_ID"))
self.spotify_client_id = os.getenv("SPOTIFY_CLIENT_ID")
self.spotify_client_secret = os.getenv("SPOTIFY_CLIENT_SECRET")
self.youtube_api_key = os.getenv("YOUTUBE_API_KEY")
self.genius_token = os.getenv("GENIUS_TOKEN")
self.mongodb_url = os.getenv("MONGODB_URL")
self.mongodb_name = os.getenv("MONGODB_NAME")

View File

@@ -223,7 +223,7 @@ class Admin(commands.Cog, name="settings"):
@settings.command(name="customcontroller", aliases=get_aliases("customcontroller"))
@commands.has_permissions(manage_guild=True)
@commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild)
async def customController(self, ctx: commands.Context):
async def customcontroller(self, ctx: commands.Context):
"Customizes music controller embeds."
player, settings = self.get_settings(ctx)
controller_settings = settings.get("default_controller", func.settings.controller)
@@ -232,6 +232,19 @@ class Admin(commands.Cog, name="settings"):
message = await ctx.send(embed=view.build_embed(), view=view)
view.response = message
@settings.command(name="controllermsg", aliases=get_aliases("controllermsg"))
@commands.has_permissions(manage_guild=True)
@commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild)
async def controllermsg(self, ctx: commands.Context):
"Toggles to send a message when clicking the button in the music controller."
player, settings = self.get_settings(ctx)
toggle = settings.get('controller_msg', True)
settings['controller_msg'] = not toggle
update_settings(ctx.guild.id, {'controller_msg': not toggle})
toggle = get_lang(ctx.guild.id, "enabled" if not toggle else "disabled")
await ctx.send(get_lang(ctx.guild.id, 'toggleControllerMsg').format(toggle))
@app_commands.command(name="debug")
async def debug(self, interaction: discord.Interaction):
if interaction.user.id not in func.settings.bot_access_user:

View File

@@ -103,7 +103,7 @@ class Basic(commands.Cog):
await ctx.send(player.get_msg('playlistLoad').format(tracks.name, index))
else:
position = await player.add_track(tracks[0])
await ctx.send((f"`{player.get_msg('live')}`" if tracks[0].is_stream else "") + (player.get_msg('trackLoad_pos').format(tracks[0].title, tracks[0].author, tracks[0].formatLength, position) if position >= 1 and player.is_playing else player.get_msg('trackLoad').format(tracks[0].title, tracks[0].author, tracks[0].formatLength)), allowed_mentions=False)
await ctx.send((f"`{player.get_msg('live')}`" if tracks[0].is_stream else "") + (player.get_msg('trackLoad_pos').format(tracks[0].title, tracks[0].uri, tracks[0].author, tracks[0].formatLength, position) if position >= 1 and player.is_playing else player.get_msg('trackLoad').format(tracks[0].title, tracks[0].uri, tracks[0].author, tracks[0].formatLength)), allowed_mentions=False)
except voicelink.QueueFull as e:
await ctx.send(e)
finally:
@@ -143,7 +143,7 @@ class Basic(commands.Cog):
await interaction.response.send_message(player.get_msg('playlistLoad').format(tracks.name, index))
else:
position = await player.add_track(tracks[0])
await interaction.response.send_message((f"`{player.get_msg('live')}`" if tracks[0].is_stream else "") + (player.get_msg('trackLoad_pos').format(tracks[0].title, tracks[0].author, tracks[0].formatLength, position) if position >= 1 and player.is_playing else player.get_msg('trackLoad').format(tracks[0].title, tracks[0].author, tracks[0].formatLength)), allowed_mentions=False)
await interaction.response.send_message((f"`{player.get_msg('live')}`" if tracks[0].is_stream else "") + (player.get_msg('trackLoad_pos').format(tracks[0].title, tracks[0].uri, tracks[0].author, tracks[0].formatLength, position) if position >= 1 and player.is_playing else player.get_msg('trackLoad').format(tracks[0].title, tracks[0].uri, tracks[0].author, tracks[0].formatLength)), allowed_mentions=False)
except voicelink.QueueFull as e:
await interaction.response.send_message(e)
@@ -202,8 +202,8 @@ class Basic(commands.Cog):
for value in view.values:
track = tracks[int(value.split(". ")[0]) - 1]
position = await player.add_track(track)
msg += ((f"`{player.get_msg('live')}`" if track.is_stream else "") + (player.get_msg('trackLoad_pos').format(track.title, track.author, track.formatLength,
position) if position >= 1 else player.get_msg('trackLoad').format(track.title, track.author, track.formatLength)))
msg += ((f"`{player.get_msg('live')}`" if track.is_stream else "") + (player.get_msg('trackLoad_pos').format(track.title, track.uri, track.author, track.formatLength,
position) if position >= 1 else player.get_msg('trackLoad').format(track.title, track.uri, track.author, track.formatLength)))
await ctx.send(msg, allowed_mentions=False)
if not player.is_playing:
@@ -231,7 +231,7 @@ class Basic(commands.Cog):
await ctx.send(player.get_msg('playlistLoad').format(tracks.name, index))
else:
position = await player.add_track(tracks[0], at_font=True)
await ctx.send((f"`{player.get_msg('live')}`" if tracks[0].is_stream else "") + (player.get_msg('trackLoad_pos').format(tracks[0].title, tracks[0].author, tracks[0].formatLength, position) if position >= 1 and player.is_playing else player.get_msg('trackLoad').format(tracks[0].title, tracks[0].author, tracks[0].formatLength)), allowed_mentions=False)
await ctx.send((f"`{player.get_msg('live')}`" if tracks[0].is_stream else "") + (player.get_msg('trackLoad_pos').format(tracks[0].title, tracks[0].uri, tracks[0].author, tracks[0].formatLength, position) if position >= 1 and player.is_playing else player.get_msg('trackLoad').format(tracks[0].title, tracks[0].uri, tracks[0].author, tracks[0].formatLength)), allowed_mentions=False)
except voicelink.QueueFull as e:
await ctx.send(e)
@@ -240,6 +240,39 @@ class Basic(commands.Cog):
if not player.is_playing:
await player.do_next()
@commands.hybrid_command(name="forceplay", aliases=get_aliases("forceplay"))
@app_commands.describe(query="Input a query or a searchable link.")
@commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild)
async def forceplay(self, ctx: commands.Context, query: str):
"Enforce playback using the given URL or query."
player: voicelink.Player = ctx.guild.voice_client
if not player:
player = await voicelink.connect_channel(ctx)
if not player.is_privileged(ctx.author):
return await ctx.send(player.get_msg('missingPerms_function'), ephemeral=True)
tracks = await player.get_tracks(query, requester=ctx.author)
if not tracks:
return await ctx.send(player.get_msg('noTrackFound'))
try:
if isinstance(tracks, voicelink.Playlist):
index = await player.add_track(tracks.tracks, at_font=True)
await ctx.send(player.get_msg('playlistLoad').format(tracks.name, index))
else:
await player.add_track(tracks[0], at_font=True)
await ctx.send((f"`{player.get_msg('live')}`" if tracks[0].is_stream else "") + player.get_msg('trackLoad').format(tracks[0].title, tracks[0].uri, tracks[0].author, tracks[0].formatLength), allowed_mentions=False)
except voicelink.QueueFull as e:
await ctx.send(e)
finally:
if player.queue._repeat.mode == voicelink.LoopType.track:
await player.set_repeat(voicelink.LoopType.off.name)
await player.stop() if player.is_playing else await player.do_next()
@commands.hybrid_command(name="pause", aliases=get_aliases("pause"))
@commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild)
async def pause(self, ctx: commands.Context):

View File

@@ -32,17 +32,16 @@ class Nodes(commands.Cog):
@commands.Cog.listener()
async def on_voicelink_track_stuck(self, player: voicelink.Player, track, _):
await asyncio.sleep(5)
await asyncio.sleep(10)
await player.do_next()
@commands.Cog.listener()
async def on_voicelink_track_exception(self, player: voicelink.Player, track, _):
try:
await player.context.send(f"{_} Please wait for 5 seconds.", delete_after=5)
player._track_is_stuck = True
await player.context.send(f"{_} Please wait for 5 seconds.", delete_after=10)
except:
pass
await asyncio.sleep(5)
await player.do_next()
@commands.Cog.listener()
async def on_voice_state_update(self, member: discord.Member, before: discord.VoiceState, after: discord.VoiceState):

View File

@@ -4,7 +4,6 @@ import aiohttp
import os
from discord.ext import commands
from random import choice
from datetime import datetime
from time import strptime
from io import BytesIO
@@ -71,7 +70,7 @@ def update_settings(guild_id:int, data: dict, mode="Set") -> None:
def open_json(path: str) -> dict:
try:
with open(path, encoding="utf8") as json_file:
with open(os.path.join(root_dir, path), encoding="utf8") as json_file:
return json.load(json_file)
except:
return {}
@@ -83,13 +82,86 @@ def update_json(path: str, new_data: dict) -> None:
data.update(new_data)
with open(path, "w") as json_file:
with open(os.path.join(root_dir, path), "w") as json_file:
json.dump(data, json_file, indent=4)
def get_lang(guild_id:int, key:str) -> str:
lang = get_settings(guild_id).get("lang", "EN")
return langs.get(lang, langs["EN"])[key]
if lang in langs and not langs[lang]:
langs[lang] = open_json(os.path.join("langs", f"{lang}.json"))
return langs.get(lang, {}).get(key, "Language pack not found!")
def init() -> None:
global settings
json = open_json("settings.json")
if json is not None:
settings = Settings(json)
def langs_setup() -> None:
for language in os.listdir(os.path.join(root_dir, "langs")):
if language.endswith('.json'):
langs[language[:-5]] = {}
for language in os.listdir(os.path.join(root_dir, "local_langs")):
if language.endswith('.json'):
local_langs[language[:-5]] = open_json(os.path.join("local_langs", language))
return
def time(millis:int) -> str:
seconds=(millis/1000)%60
minutes=(millis/(1000*60))%60
hours=(millis/(1000*60*60))%24
if hours > 1:
return "%02d:%02d:%02d" % (hours, minutes, seconds)
else:
return "%02d:%02d" % (minutes, seconds)
def formatTime(number:str) -> Optional[int]:
try:
try:
num = strptime(number, '%M:%S')
except ValueError:
try:
num = strptime(number, '%S')
except ValueError:
num = strptime(number, '%H:%M:%S')
except:
return None
return (int(num.tm_hour) * 3600 + int(num.tm_min) * 60 + int(num.tm_sec)) * 1000
def emoji_source(emoji:str):
return settings.emoji_source_raw.get(emoji.lower(), "🔗")
def gen_report() -> Optional[discord.File]:
if error_log:
errorText = ""
for guild_id, error in error_log.items():
errorText += f"Guild ID: {guild_id}\n" + "-" * 30 + "\n"
for index, (key, value) in enumerate(error.items() , start=1):
errorText += f"Error No: {index}, Time: {datetime.fromtimestamp(key)}\n" + value + "-" * 30 + "\n\n"
buffer = BytesIO(errorText.encode('utf-8'))
file = discord.File(buffer, filename='report.txt')
buffer.close()
return file
return None
def cooldown_check(ctx: commands.Context) -> Optional[commands.Cooldown]:
if ctx.author.id in settings.bot_access_user:
return None
cooldown = settings.cooldowns_settings.get(f"{ctx.command.parent.qualified_name} {ctx.command.name}" if ctx.command.parent else ctx.command.name)
if not cooldown:
return None
return commands.Cooldown(cooldown[0], cooldown[1])
def get_aliases(name: str) -> list:
return settings.aliases_settings.get(name, [])
async def requests_api(url: str) -> dict:
async with aiohttp.ClientSession() as session:
resp = await session.get(url)
@@ -98,24 +170,6 @@ async def requests_api(url: str) -> dict:
return await resp.json(encoding="utf-8")
def init() -> None:
global settings
json = open_json(os.path.join(root_dir, "settings.json"))
if json is not None:
settings = Settings(json)
def langs_setup() -> None:
for language in os.listdir(os.path.join(root_dir, "langs")):
if language.endswith('.json'):
langs[language[:-5]] = open_json(os.path.join(root_dir, "langs", language))
for language in os.listdir(os.path.join(root_dir, "local_langs")):
if language.endswith('.json'):
local_langs[language[:-5]] = open_json(os.path.join(root_dir, "local_langs", language))
return
async def create_account(ctx: Union[commands.Context, discord.Interaction]) -> None:
author = ctx.author if isinstance(ctx, commands.Context) else ctx.user
if not author:
@@ -168,98 +222,4 @@ async def update_inbox(userid:int, data:dict) -> None:
async def checkroles(userid:int):
rank, max_p, max_t = 'Normal', 5, 500
return rank, max_p, max_t
async def similar_track(player) -> bool:
trackids = [ track.identifier for track in player.queue.history(incTrack=True) if track.source == 'youtube' ]
randomTrack = choice(player.queue.history(incTrack=True)[-10:])
tracks = []
if randomTrack.spotify:
tracks = await player.spotifyRelatedTrack(randomTrack.identifier)
else:
if randomTrack.source != 'youtube':
return False
if not tokens.youtube_api_key:
return False
request_url = "https://youtube.googleapis.com/youtube/v3/search?part={part}&relatedToVideoId={videoId}&type={type}&videoCategoryId={videoCategoryId}&key={key}".format(
part="snippet",
videoId=randomTrack.identifier,
type="video",
videoCategoryId="10",
key=tokens.youtube_api_key
)
try:
data = await requests_api(request_url)
if not data:
return False
for item in data['items']:
if 'snippet' not in item:
continue
if item['id']['videoId'] not in trackids:
tracks = await player.get_tracks(f"https://www.youtube.com/watch?v={item['id']['videoId']}", requester=player._bot.user)
break
except:
return False
if tracks:
await player.add_track(tracks)
return True
return False
def time(millis:int) -> str:
seconds=(millis/1000)%60
minutes=(millis/(1000*60))%60
hours=(millis/(1000*60*60))%24
if hours > 1:
return "%02d:%02d:%02d" % (hours, minutes, seconds)
else:
return "%02d:%02d" % (minutes, seconds)
def formatTime(number:str) -> Optional[int]:
try:
try:
num = strptime(number, '%M:%S')
except ValueError:
try:
num = strptime(number, '%S')
except ValueError:
num = strptime(number, '%H:%M:%S')
except:
return None
return (int(num.tm_hour) * 3600 + int(num.tm_min) * 60 + int(num.tm_sec)) * 1000
def emoji_source(emoji:str):
return settings.emoji_source_raw.get(emoji.lower(), "🔗")
def gen_report() -> Optional[discord.File]:
if error_log:
errorText = ""
for guild_id, error in error_log.items():
errorText += f"Guild ID: {guild_id}\n" + "-" * 30 + "\n"
for index, (key, value) in enumerate(error.items() , start=1):
errorText += f"Error No: {index}, Time: {datetime.fromtimestamp(key)}\n" + value + "-" * 30 + "\n\n"
buffer = BytesIO(errorText.encode('utf-8'))
file = discord.File(buffer, filename='report.txt')
buffer.close()
return file
return None
def cooldown_check(ctx: commands.Context) -> Optional[commands.Cooldown]:
if ctx.author.id in settings.bot_access_user:
return None
cooldown = settings.cooldowns_settings.get(f"{ctx.command.parent.qualified_name} {ctx.command.name}" if ctx.command.parent else ctx.command.name)
if not cooldown:
return None
return commands.Cooldown(cooldown[0], cooldown[1])
def get_aliases(name: str) -> list:
return settings.aliases_settings.get(name, [])
return rank, max_p, max_t

View File

@@ -20,6 +20,7 @@
"setVolume": "已將音量設置為 `{0}`%",
"togglecontroller": "現在您已 `{0}` 音樂控制器。",
"toggleDuplicateTrack": "現在您已 `{0}` 防止隊列中存在重複曲目。",
"toggleControllerMsg": "現在您已從音樂控制器 `{0}` 消息。",
"settingsMenu": "伺服器設置 | {0}",
"settingsTitle": "❤️ 基本資訊:",
"settingsValue": "前綴:`{0}`\n語言`{1}`\n啟用音樂控制器`{2}`\nDJ 角色:{3}\n投票繞過`{4}`\n24/7`{5}`\n默認音量`{6}%`\n播放時間`{7}`",
@@ -130,8 +131,8 @@
"live": "直播",
"playlistLoad": " 🎶 已添加播放清單 **{0}**,共 `{1}` 首歌曲至隊列。",
"trackLoad": "已添加 **{0}**,由 **{1}** (`{2}`) 開始播放。\n",
"trackLoad_pos": "已將 **{0}**,由 **{1}** (`{2}`) 添加到隊列中位置 **{3}**\n",
"trackLoad": "已添加 **[{0}](<{1}>)**,由 **{2}** (`{3}`) 開始播放。\n",
"trackLoad_pos": "已將 **[{0}](<{1}>)**,由 **{2}** (`{3}`) 添加到隊列中位置 **{4}**\n",
"searchTitle": "搜索查詢: {0}",
"searchDesc": "➥ 平台: {0} **{1}**\n➥ 結果: **{2}**\n\n{3}",

View File

@@ -20,6 +20,7 @@
"setVolume": "Stellen Sie die Lautstärke auf `{0}` % ein.",
"togglecontroller": "Jetzt haben Sie `{0}` den Musikcontroller.",
"toggleDuplicateTrack": "Jetzt haben Sie `{0}`, um doppelte Tracks in der Warteschlange zu verhindern.",
"toggleControllerMsg": "Sie haben jetzt `{0}` Nachrichten vom Musik-Controller.",
"settingsMenu": "Servereinstellungen | {0}",
"settingsTitle": "❤️ Grundlegende Informationen:",
"settingsValue": "Präfix: `{0}`\nSprache: `{1}`\nMusik-Controller aktivieren: `{2}`\nDJ-Rolle: {3}\nAbstimmungsumgehung: `{4}`\nRund um die Uhr: `{5}`\nStandardvolumen: `{6} %`\nSpielzeit: `{7}`",
@@ -130,8 +131,8 @@
"live": "LIVE",
"playlistLoad": " 🎶 Die Wiedergabeliste **{0}** mit `{1}` Songs wurde zur Warteschlange hinzugefügt.",
"trackLoad": "**{0}** von **{1}** (`{2}`) wurde zum Abspielen hinzugefügt.\n",
"trackLoad_pos": "**{0}** von **{1}** (`{2}`) wurde in der Warteschlange an Position **{3}** hinzugefügt.\n",
"trackLoad": "**[{0}](<{1}>)** von **{2}** (`{3}`) wurde zum Abspielen hinzugefügt.\n",
"trackLoad_pos": "**[{0}](<{1}>)** von **{2}** (`{3}`) wurde in der Warteschlange an Position **{4}** hinzugefügt.\n",
"searchTitle": "Suchabfrage: {0}",
"searchDesc": "➥ Plattform: {0} **{1}**\n➥ Ergebnisse: **{2}**\n\n{3}",

View File

@@ -20,6 +20,7 @@
"setVolume": "Set the volume to `{0}`%",
"togglecontroller": "Now you have `{0}` the music controller.",
"toggleDuplicateTrack": "Now you have `{0}` to prevent duplicate track in the queue.",
"toggleControllerMsg": "Now you have `{0}` messages from the music controller.",
"settingsMenu": "Server Settings | {0}",
"settingsTitle": "❤️ Basic Info:",
"settingsValue": "Prefix: `{0}`\nLanguage: `{1}`\nEnable Music Controller: `{2}`\nDJ Role: {3}\nVote Bypass: `{4}`\n24/7: `{5}`\nDefault Volume: `{6}%`\nPlay Time: `{7}`",
@@ -130,8 +131,8 @@
"live": "LIVE",
"playlistLoad": " 🎶 Added the playlist **{0}** with `{1}` songs to the queue.",
"trackLoad": "Added **{0}** by **{1}** (`{2}`) to begin playing.\n",
"trackLoad_pos": "Added **{0}** by **{1}** (`{2}`) to the queue at position **{3}**\n",
"trackLoad": "Added **[{0}](<{1}>)** by **{2}** (`{3}`) to begin playing.\n",
"trackLoad_pos": "Added **[{0}](<{1}>)** by **{3}** (`{3}`) to the queue at position **{4}**\n",
"searchTitle": "Search Query: {0}",
"searchDesc": "➥ Platform: {0} **{1}**\n➥ Results: **{2}**\n\n{3}",

View File

@@ -20,6 +20,7 @@
"setVolume": "Ajustar el volumen a `{0}`%",
"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.",
"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}`",
@@ -130,8 +131,8 @@
"live": "EN VIVO",
"playlistLoad": " 🎶 Se agregó la lista de reproducción {0} con {1} canciones a la cola.",
"trackLoad": "Se agregó {0} de {1} ({2}) para comenzar a reproducir.\n",
"trackLoad_pos": "Se agregó {0} de {1} ({2}) a la cola en la posición {3}\n",
"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}",

View File

@@ -20,6 +20,7 @@
"setVolume": "音量を「{0}%」に設定しました。",
"togglecontroller": "今、音楽コントローラーは「{0}」です。",
"toggleDuplicateTrack": "今、キュー内の重複トラックを防止するための設定は「{0}」です。",
"toggleControllerMsg": "現在、音楽コントローラーから `{0}` 件のメッセージがあります。",
"settingsMenu": "サーバー設定| {0}",
"settingsTitle": "❤️ 基本情報:",
"settingsValue": "プレフィックス:「{0}」\n言語「{1}」\n音楽コントローラーを有効にする「{2}」\nDJロール{3}\n投票バイパス「{4}」\n24/7「{5}」\nデフォルトボリューム「{6}%」\n再生時間「{7}」",
@@ -130,8 +131,8 @@
"live": "ライブ",
"playlistLoad": " 🎶 プレイリスト**{0}**をキューに`{1}`曲追加しました。",
"trackLoad": "**{1}**の**{0}** (`{2}`)を再生を開始するために追加しました。\n",
"trackLoad_pos": "**{1}**の**{0}** (`{2}`)をキューの位置**{3}**に追加しました。\n",
"trackLoad": "**{2}**の**[{0}](<{1}>)** (`{3}`)を再生を開始するために追加しました。\n",
"trackLoad_pos": "**{2}**の**[{0}](<{1}>)** (`{3}`)をキューの位置**{4}**に追加しました。\n",
"searchTitle": "検索クエリ:{0}",
"searchDesc": "➥ プラットフォーム:{0} **{1}**\n➥ 結果:**{2}**\n\n{3}",

View File

@@ -20,6 +20,7 @@
"setVolume": "볼륨을 `{0}`%로 설정했습니다.",
"togglecontroller": "이제 음악 컨트롤러가 `{0}`(으)로 설정되었습니다.",
"toggleDuplicateTrack": "이제 대기열에서 중복된 트랙을 방지하기 위해 `{0}`(으)로 설정되었습니다.",
"toggleControllerMsg": "이제 음악 컨트롤러로부터 `{0}` 메시지가 있습니다.",
"settingsMenu": "서버 설정 | {0}",
"settingsTitle": "❤️ 기본 정보:",
"settingsValue": "접두사: `{0}`\n언어: `{1}`\n음악 컨트롤러 사용: `{2}`\nDJ 역할: {3}\n투표 우회: `{4}`\n24/7 모드: `{5}`\n기본 볼륨: `{6}%`\n재생 시간: `{7}`",
@@ -130,8 +131,8 @@
"live": "라이브",
"playlistLoad": "재생목록 **{0}**을(를) 대기열에 `{1}`개의 곡과 함께 추가했습니다.",
"trackLoad": "**{1}**의 **{0}** (`{2}`)를 재생목록에 추가하고 재생을 시작합니다.\n",
"trackLoad_pos": "**{1}**의 **{0}** (`{2}`)를 대기열의 **{3}**번째로 추가합니다.\n",
"trackLoad": "**{2}**의 **[{0}](<{1}>)** (`{3}`)를 재생목록에 추가하고 재생을 시작합니다.\n",
"trackLoad_pos": "**{2}**의 **[{0}](<{1}>)** (`{3}`)를 대기열의 **{4}**번째로 추가합니다.\n",
"searchTitle": "검색 쿼리: {0}",
"searchDesc": "➥ 플랫폼: {0} **{1}**\n➥ 결과: **{2}**\n\n{3}",

192
langs/RU.json Normal file
View File

@@ -0,0 +1,192 @@
{
"unknownException": "⚠️ Что-то пошло не так при выполнении команды! Пожалуйста, попробуйте позже или присоединитесь к нашему серверу Discord для получения дополнительной поддержки.",
"enabled": "вкл",
"disabled": "выкл",
"nodeReconnect": "Пожалуйста, попробуйте снова! После переподключения узла.",
"noChannel": "Нет голосового канала для подключения. Пожалуйста, укажите или присоединитесь к одному.",
"alreadyConnected": "Уже подключен к голосовому каналу.",
"noPermission": "Извините! У меня нет разрешения на подключение или разговор в вашем голосовом канале.",
"noPlaySource": "Невозможно найти рабочие источники!",
"noPlayer": "На этом сервере не найдено ни одного активного плеера.",
"notVote": "Эта команда требует вашего голоса! Введите `/vote` для получения дополнительной информации.",
"languageNotFound": "Языковой пакет не найден! Пожалуйста, выберите существующий языковой пакет.",
"changedLanguage": "Успешно изменен на языковой пакет `{0}`.",
"setPrefix": "Готово! Мой префикс на вашем сервере теперь `{0}`. Попробуйте запустить `{1}ping`, чтобы проверить его.",
"setDJ": "Установить роль DJ {0}.",
"setqueue": "Установить режим очереди на `{0}`.",
"247": "Теперь у вас `{0}` режим 24/7.",
"bypassVote": "Теперь у вас `{0}` система голосования.",
"setVolume": "Установить громкость на `{0}`%",
"togglecontroller": "Теперь у вас `{0}` контроллер музыки.",
"toggleDuplicateTrack": "Теперь у вас `{0}` предотвращение дублирования трека в очереди.",
"toggleControllerMsg": "Теперь у вас `{0}` сообщения от контроллера музыки.",
"settingsMenu": "Настройки сервера | {0}",
"settingsTitle": "❤️ Основная информация:",
"settingsValue": "Префикс: `{0}`\nЯзык: `{1}`\nВключить контроллер музыки: `{2}`\nDJ роль: {3}\nОбход голосования: `{4}`\n24/7: `{5}`\nГромкость по умолчанию: `{6}%`\nВремя проигрывания: `{7}`",
"settingsTitle2": "🔗 Информация об очереди:",
"settingsValue2": "Режим очереди: `{0}`\nМаксимальное количество песен: `{1}`\nРазрешить дублирование треков: `{2}`",
"settingsPermTitle": "✨ Права:",
"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}```",
"karaoke": "Вы обновили уровень на **{0}**, моноуровень на **{1}**, фильтрующая полоса на **{2}** и ширина фильтра на **{3}**",
"tremolo&vibrato": "Вы обновили значение эффекта tremolo на **{0}** и vibrato на **{1}**",
"rotation": "Вы обновили значение эффекта rotation на **{0}**",
"distortion": "Вы включили эффект rotation.",
"lowpass": "Вы обновили значение эффекта lowpass на **{0}**",
"channelmix": "Вы обновили ChannelMix. Левый-на-левый: **{0}**, Правый-на-правый: **{1}**, Левый-на-правый: **{2}**, Правый-на-левый: **{3}**",
"nightcore": "Вы включили эффект Nightcore.",
"8d": "Вы включили эффект 8D.",
"vaporwave": "Вы включили эффект Vaporwave.",
"cleareffect": "Звуковые эффекты были очищены!",
"FilterTagAlreadyInUse": "Этот звуковой эффект уже используется! Пожалуйста, используйте /cleareffect <Тег>, чтобы удалить его.",
"playlistViewTitle": "📜 Все плейлисты пользователя {0}",
"playlistViewHeaders": [" ", "ID:", "Время:", "Название:", "Треки:"],
"playlistMaxP": "Максимальное количество плейлистов:",
"playlistMaxT": "Максимальное количество треков:",
"playlistFooter": "Введите /playlist play [плейлист], чтобы добавить плейлист в очередь.",
"playlistNotFound": "Плейлист [`{0}`] не найден. Введите /playlist view, чтобы посмотреть все ваши плейлисты.",
"playlistNotAccess": "Извините! У вас нет доступа к этому плейлисту!",
"playlistNoTrack": "Извините! В плейлисте [`{0}`] нет треков.",
"playlistNotAllow": "Эта команда не разрешена для связанных и общих плейлистов.",
"playlistPlay": "Добавлен плейлист [`{0}`] с `{1}` песнями в очередь.",
"playlistOverText": "Извините! Имя плейлиста не может превышать 10 символов.",
"playlistSameName": "Извините! Это имя не может совпадать с вашим новым именем.",
"playlistDeleteError": "Вы не можете удалить плейлист по умолчанию.",
"playlistRemove": "Вы удалили плейлист [`{0}`].",
"playlistSendErrorPlayer": "Извините! Вы не можете отправить приглашение самому себе.",
"playlistSendErrorBot": "Извините! Вы не можете отправить приглашение боту.",
"playlistBelongs": "Извините! Этот плейлист принадлежит <@{0}>.",
"playlistShare": "Извините! Этим плейлистом уже поделились с {0}.",
"playlistSent": "Извините! Вы уже отправили приглашение ранее.",
"noPlaylistAcc": "{0} не создал учетную запись плейлиста.",
"overPlaylistCreation": "Вы не можете создавать больше `{0}` плейлистов!",
"playlistExists": "Плейлист [`{0}`] уже существует.",
"playlistNotInvaildUrl": "Пожалуйста, введите действительную ссылку или публичную плейлист-ссылку на Spotify или YouTube.",
"playlistCreated": "Вы создали плейлист `{0}`. Введите /playlist view для получения дополнительной информации.",
"playlistRenamed": "Вы переименовали `{0}` в `{1}`.",
"playlistLimitTrack": "Вы достигли лимита! Вы можете добавить только `{0}` песен в свой плейлист.",
"playlistPlaylistLink": "Вам не разрешено использовать ссылку на плейлист.",
"playlistStream": "Вам не разрешено добавлять потоковые видео в свой плейлист.",
"playlistPositionNotFound": "Не удается найти позицию `{0}` в вашем плейлисте [`{1}`]!",
"playlistRemoved": "👋 Удалено **{0}** из плейлиста {1} [`{2}`].",
"playlistClear": "Вы успешно очистили свой плейлист [`{0}`].",
"playlistView": "Просмотр плейлистов",
"playlistViewDesc": "```Имя | ID: {0} | {1}\nВсего треков: {2}\nВладелец: {3}\nТип: {4}\n```",
"playlistViewPermsValue": "📖 Чтение: ✓ ✍🏽 Запись: {0} 🗑️ Удаление: {1}",
"playlistViewPermsValue2": "📖 Чтение: {0}",
"playlistViewTrack": "Треки",
"playlistViewPage": "Страница: {0}/{1} | Общая продолжительность: {2}",
"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 или администраторы могут использовать эту функцию.",
"timeFormatError": "Неверный формат времени. Пример: 2:42PM или 12:39:31",
"lyricsNotFound": "Текст песни не найден. Введите /lyrics <Название песни> <Автор> для поиска текста.",
"missingTrackInfo": "Некоторая информация о треке отсутствует.",
"noVoiceChannel": "Голосовой канал не найден!",
"playlistAddError": "Вам не разрешено добавлять потоковые видео в плейлист!",
"playlistAddError2": "Произошла ошибка при добавлении треков в плейлист!",
"playlistlimited": "Вы достигли лимита! Вы можете добавить только {0} песни в свой плейлист.",
"playlistrepeated": "Такой же трек уже есть в вашем плейлисте!",
"playlistAdded": "❤️ Добавлен **{0}** в плейлист пользователя {1} [`{2}`]!",
"playerDropdown": "Выберите песню для перехода ...",
"buttonBack": "Назад",
"buttonPause": "Приостановить",
"buttonResume": "Продолжить",
"buttonSkip": "Вперед",
"buttonLeave": "СТОП",
"buttonLoop": "Зациклить",
"buttonVolumeUp": "Громкость +",
"buttonVolumeDown": "Громкость -",
"buttonVolumeMute": "Выключить звук",
"buttonVolumeUnmute": "Включить звук",
"buttonAutoPlay": "Autoplay",
"buttonShuffle": "Перемешать",
"buttonForward": "+10сек",
"buttonRewind": "Назад",
"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}` Поставил плеер на паузу.",
"resumeError": "Плеер не на паузе.",
"resumeVote": "{0} проголосовал за продолжение песни. [{1}/{2}]",
"resumed": "`{0}` Убрал плеер с паузы.",
"shuffleError": "Добавить больше песен в очередь перед перемешиванием.",
"shuffleVote": "{0} проголосовал за перемешивание очереди. [{1}/{2}]",
"shuffled": "Очередь перемешана.",
"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}`",
"removed": "Удалено `{0}` треков из очереди.",
"forward": "Перемотать плеер вперед на **{0}**",
"rewind": "Перемотать плеер назад на **{0}**",
"replay": "Повторение текущей песни.",
"swapped": "Треки `{0}` и `{1}` обменяны местами",
"moved": "Трек `{0}` смещён на `{1}`",
"autoplay": "Режим autoplay установлен на **{0}**",
"notdj": "Вы не DJ. Текущий DJ: {0}.",
"djToMe": "Вы не можете передать роль DJ себе или боту.",
"djnotinchannel": "`{0}` не находится в голосовом канале.",
"djswap": "Вы передали роль DJ `{0}`.",
"chaptersDropdown": "Выберите эпизод для перехода ...",
"noChaptersFound": "Эпизод не найден!",
"chatpersNotSupport": "Эта команда поддерживает только видео на YouTube!",
"voicelinkQueueFull": "Извините, вы достигли максимального количества `{0}` треков в очереди!",
"voicelinkOutofList": "Пожалуйста, предоставьте действительный индекс трека!",
"voicelinkDuplicateTrack": "Извините, этот трек уже есть в очереди.",
"deocdeError": "Что-то пошло не так при декодировании файла!"
}

View File

@@ -62,7 +62,7 @@ class Vocard(commands.Bot):
await self.ipc.start()
if not func.settings.version or func.settings.version != update.__version__:
func.update_json(func.root_dir + "/settings.json", new_data={"version": update.__version__})
func.update_json("settings.json", new_data={"version": update.__version__})
await self.tree.set_translator(Translator())
await self.tree.sync()
@@ -77,6 +77,7 @@ class Vocard(commands.Bot):
print("------------------")
func.tokens.client_id = self.user.id
func.local_langs.clear()
async def on_command_error(self, ctx: commands.Context, exception, /) -> None:
error = getattr(exception, 'original', exception)
@@ -137,5 +138,5 @@ bot = Vocard(
)
if __name__ == "__main__":
update.checkVersion(withMsg=True)
update.check_version(with_msg=True)
bot.run(func.tokens.token, log_handler=None)

View File

@@ -1,4 +1,4 @@
discord.py==2.3.0
discord.py==2.3.1
pymongo==4.1.1
dnspython==2.2.1
tldextract==3.2.1

163
update.py
View File

@@ -1,59 +1,124 @@
import requests, zipfile, os, sys, shutil, traceback
import requests, zipfile, os, shutil, argparse
from io import BytesIO
root_dir = os.path.dirname(os.path.abspath(__file__))
install_pack_dir = os.path.join(root_dir, "Vocard.zip")
__version__ = "v2.6.5a"
__version__ = "v2.6.6"
def checkVersion(withMsg = False):
resp = requests.get("https://api.github.com/repos/ChocoMeow/Vocard/releases/latest")
version = resp.json().get("name", __version__)
if withMsg:
if version == __version__:
print(f"Your bot is up-to-date! - {version}")
else:
print(f"Your bot is not up-to-date! This latest version is {version} and you are currently running version {__version__}\n. Run `python update.py --start` to update your bot!")
return version
def downloadFile(version:str = None):
if not version:
version = checkVersion()
print("Downloading Vocard version: " + version)
response = requests.get("https://github.com/ChocoMeow/Vocard/archive/" + version + ".zip")
GITHUB_API_URL = "https://api.github.com/repos/ChocoMeow/Vocard/releases/latest"
VOCARD_URL = "https://github.com/ChocoMeow/Vocard/archive/"
IGNORE_FILES = ["settings.json", ".env"]
class bcolors:
WARNING = '\033[93m'
FAIL = '\033[91m'
OKGREEN = '\033[92m'
ENDC = '\033[0m'
def check_version(with_msg=False):
"""Check for the latest version of the project.
Args:
with_msg (bool): option to print the message.
Returns:
str: the latest version.
"""
response = requests.get(GITHUB_API_URL)
latest_version = response.json().get("name", __version__)
if with_msg:
msg = f"{bcolors.OKGREEN}Your bot is up-to-date! - {latest_version}{bcolors.ENDC}" if latest_version == __version__ else \
f"{bcolors.WARNING}Your bot is not up-to-date! The latest version is {latest_version} and you are currently running version {__version__}\n. Run `python update.py -l` to update your bot!{bcolors.ENDC}"
print(msg)
return latest_version
def download_file(version=None):
"""Download the latest version of the project.
Args:
version (str): the version to download. If None, download the latest version.
Returns:
BytesIO: the downloaded zip file.
"""
version = version if version else check_version()
print(f"Downloading Vocard version: {version}")
response = requests.get(VOCARD_URL + version + ".zip")
if response.status_code == 404:
print(f"{bcolors.FAIL}Warning: Version not found!{bcolors.ENDC}")
exit()
print("Download Completed")
unZip(response, version)
return response
def unZip(response, version: str):
print("Installing ...")
zfile = zipfile.ZipFile(BytesIO(response.content))
zfile.extractall(root_dir)
def install(response, version):
"""Install the downloaded version of the project.
version = version.replace("v", "")
source_dir = os.path.join(root_dir, f"Vocard-{version}")
if os.path.exists(source_dir):
for filename in os.listdir(root_dir):
if filename in ["settings.json", ".env", f"Vocard-{version}"]:
continue
filename = os.path.join(root_dir, filename)
if os.path.isdir(filename):
shutil.rmtree(filename)
else:
os.remove(filename)
for filename in os.listdir(source_dir):
shutil.move(os.path.join(source_dir, filename), os.path.join(root_dir, filename))
os.rmdir(source_dir)
Args:
response (BytesIO): the downloaded zip file.
version (str): the version to install.
"""
user_input = input(f"{bcolors.WARNING}--------------------------------------------------------------------------\n"
"Note: Before proceeding, please ensure that there are no personal files or\n" \
"sensitive information in the directory you're about to delete. This action\n" \
"is irreversible, so it's important to double-check that you're making the \n" \
f"right decision. {bcolors.ENDC} Continue with caution? (Y/n) ")
if user_input.lower() in ["y", "yes"]:
print("Installing ...")
zfile = zipfile.ZipFile(BytesIO(response.content))
zfile.extractall(root_dir)
def start():
try:
downloadFile()
if os.path.exists(install_pack_dir):
os.remove(install_pack_dir)
print("Update Successfully! Run `python main.py` to start your bot")
except Exception as e:
print(traceback.format_exc())
version = version.replace("v", "")
source_dir = os.path.join(root_dir, f"Vocard-{version}")
if os.path.exists(source_dir):
for filename in os.listdir(root_dir):
if filename in IGNORE_FILES + [f"Vocard-{version}"]:
continue
if "--start" in sys.argv:
start()
filename = os.path.join(root_dir, filename)
if os.path.isdir(filename):
shutil.rmtree(filename)
else:
os.remove(filename)
for filename in os.listdir(source_dir):
shutil.move(os.path.join(source_dir, filename), os.path.join(root_dir, filename))
os.rmdir(source_dir)
print(f"{bcolors.OKGREEN}Version {version} installed Successfully! Run `python main.py` to start your bot{bcolors.ENDC}")
else:
print("Update canceled!")
if "--check" in sys.argv:
checkVersion(withMsg = True)
def parse_args():
"""Parse command line arguments."""
parser = argparse.ArgumentParser(description='Update script for Vocard.')
parser.add_argument('-c', '--check', action='store_true', help='Check the current version of the Vocard')
parser.add_argument('-v', '--version', type=str, help='Install the specified version of the Vocard')
parser.add_argument('-l', '--latest', action='store_true', help='Install the latest version of the Vocard from Github')
parser.add_argument('-b', '--beta', action='store_true', help='Install the beta version of the Vocard from Github')
return parser.parse_args()
def main():
"""Main function."""
args = parse_args()
if args.check:
check_version(with_msg=True)
elif args.version:
version = args.version
response = download_file(version)
install(response, version)
elif args.latest:
response = download_file()
version = check_version()
install(response, version)
elif args.beta:
response = download_file("refs/heads/beta")
install(response, "beta")
pass
else:
print(f"{bcolors.FAIL}No arguments provided. Run `python update.py -h` for help.{bcolors.ENDC}")
if __name__ == "__main__":
main()

View File

@@ -38,22 +38,44 @@ from typing import Dict
def key(interaction: discord.Interaction):
return interaction.user
class Back(discord.ui.Button):
def __init__(self, player, style, row):
class ControlButton(discord.ui.Button):
def __init__(
self,
player,
label: str = None,
**kwargs
):
self.player: voicelink.Player = player
super().__init__(emoji="⏮️", label=player.get_msg('buttonBack'), style=style, disabled=False if self.player.queue.history() or not self.player.current else True, row=row)
super().__init__(label=player.get_msg(label) if label else None, **kwargs)
async def send(self, interaction: discord.Interaction, content: str, *, ephemeral: bool = False) -> None:
stay = self.player.settings.get("controller_msg", True)
return await interaction.response.send_message(
content,
delete_after=None if (not ephemeral or stay) is True else 10,
ephemeral=ephemeral
)
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
)
async def callback(self, interaction: discord.Interaction):
if not self.player.is_privileged(interaction.user):
if interaction.user in self.player.previous_votes:
return await interaction.response.send_message(self.player.get_msg("voted"), ephemeral=True)
return await self.send(interaction, self.player.get_msg("voted"), ephemeral=True)
else:
self.player.previous_votes.add(interaction.user)
if len(self.player.previous_votes) >= (required := self.player.required()):
pass
else:
return await interaction.response.send_message(self.player.get_msg("backVote").format(interaction.user, len(self.player.previous_votes), required))
return await self.send(interaction, self.player.get_msg("backVote").format(interaction.user, len(self.player.previous_votes), required))
if not self.player.is_playing:
self.player.queue.backto(1)
@@ -62,27 +84,31 @@ class Back(discord.ui.Button):
self.player.queue.backto(2)
await self.player.stop()
await interaction.response.send_message(self.player.get_msg("backed").format(interaction.user))
await self.send(interaction, self.player.get_msg("backed").format(interaction.user))
if self.player.queue._repeat.mode == voicelink.LoopType.track:
await self.player.set_repeat(voicelink.LoopType.off.name)
class Resume(discord.ui.Button):
def __init__(self, player, style, row):
self.player: voicelink.Player = player
super().__init__(emoji="⏸️", label=player.get_msg('buttonPause'), style=style, disabled=False if self.player.current else True, row=row)
class Resume(ControlButton):
def __init__(self, **kwargs):
super().__init__(
emoji="⏸️",
label="buttonPause",
disabled=not bool(kwargs["player"].current),
**kwargs
)
async def callback(self, interaction: discord.Interaction):
if self.player.is_paused:
if not self.player.is_privileged(interaction.user):
if interaction.user in self.player.resume_votes:
return await interaction.response.send_message(self.player.get_msg("voted"), ephemeral=True)
return await self.send(interaction, self.player.get_msg("voted"), ephemeral=True)
else:
self.player.resume_votes.add(interaction.user)
if len(self.player.resume_votes) >= (required := self.player.required()):
pass
else:
return await interaction.response.send_message(self.player.get_msg("resumeVote").format(interaction.user, len(self.player.resume_votes), required))
return await self.send(interaction, self.player.get_msg("resumeVote").format(interaction.user, len(self.player.resume_votes), required))
self.player.resume_votes.clear()
self.emoji = "⏸️"
@@ -92,13 +118,13 @@ class Resume(discord.ui.Button):
else:
if not self.player.is_privileged(interaction.user):
if interaction.user in self.player.pause_votes:
return await interaction.response.send_message(self.player.get_msg("voted"), ephemeral=True)
return await self.send(interaction, self.player.get_msg("voted"), ephemeral=True)
else:
self.player.pause_votes.add(interaction.user)
if len(self.player.pause_votes) >= (required := self.player.required()):
pass
else:
return await interaction.response.send_message(self.player.get_msg("pauseVote").format(interaction.user, len(self.player.pause_votes), required))
return await self.send(interaction, self.player.get_msg("pauseVote").format(interaction.user, len(self.player.pause_votes), required))
self.player.pause_votes.clear()
self.emoji = "▶️"
@@ -106,10 +132,13 @@ class Resume(discord.ui.Button):
await self.player.set_pause(True, interaction.user)
await interaction.response.edit_message(view=self.view)
class Skip(discord.ui.Button):
def __init__(self, player, style, row):
self.player: voicelink.Player = player
super().__init__(emoji="⏭️", label=player.get_msg('buttonSkip'), style=style, row=row)
class Skip(ControlButton):
def __init__(self, **kwargs):
super().__init__(
emoji="⏭️",
label="buttonSkip",
**kwargs
)
async def callback(self, interaction: discord.Interaction):
if not self.player.is_playing:
@@ -118,114 +147,131 @@ class Skip(discord.ui.Button):
if interaction.user == self.player.current.requester:
pass
elif interaction.user in self.player.skip_votes:
return await interaction.response.send_message(self.player.get_msg("voted"), ephemeral=True)
return await self.send(interaction, self.player.get_msg("voted"), ephemeral=True)
else:
self.player.skip_votes.add(interaction.user)
if len(self.player.skip_votes) >= (required := self.player.required()):
pass
else:
return await interaction.response.send_message(self.player.get_msg("skipVote").format(interaction.user, len(self.player.skip_votes), required))
return await self.send(interaction, self.player.get_msg("skipVote").format(interaction.user, len(self.player.skip_votes), required))
await interaction.response.send_message(self.player.get_msg("skipped").format(interaction.user))
await self.send(interaction, self.player.get_msg("skipped").format(interaction.user))
if self.player.queue._repeat.mode == voicelink.LoopType.track:
await self.player.set_repeat(voicelink.LoopType.off.name)
await self.player.stop()
class Stop(discord.ui.Button):
def __init__(self, player, style, row):
self.player: voicelink.Player = player
super().__init__(emoji="⏹️", label=player.get_msg('buttonLeave'), style=style, row=row)
class Stop(ControlButton):
def __init__(self, **kwargs):
super().__init__(
emoji="⏹️",
label="buttonLeave",
**kwargs
)
async def callback(self, interaction: discord.Interaction):
if not self.player.is_privileged(interaction.user):
if interaction.user in self.player.stop_votes:
return await interaction.response.send_message(self.player.get_msg("voted"), ephemeral=True)
return await self.send(interaction, self.player.get_msg("voted"), ephemeral=True)
else:
self.player.stop_votes.add(interaction.user)
if len(self.player.stop_votes) >= (required := self.player.required(leave=True)):
pass
else:
return await interaction.response.send_message(self.player.get_msg("leaveVote").format(interaction.user, len(self.player.stop_votes), required))
return await self.send(interaction, self.player.get_msg("leaveVote").format(interaction.user, len(self.player.stop_votes), required))
await interaction.response.send_message(self.player.get_msg("left").format(interaction.user))
await self.send(interaction, self.player.get_msg("left").format(interaction.user))
await self.player.teardown()
class Add(discord.ui.Button):
def __init__(self, player, style, row):
self.player: voicelink.Player = player
super().__init__(emoji="❤️", style=style, disabled=False if self.player.current else True, row=row)
class Add(ControlButton):
def __init__(self, **kwargs):
super().__init__(
emoji="❤️",
disabled=not bool(kwargs["player"].current),
**kwargs
)
async def callback(self, interaction: discord.Interaction):
track = self.player.current
if not track:
return await interaction.response.send_message(self.player.get_msg("noTrackPlaying"))
return await self.send(interaction, self.player.get_msg("noTrackPlaying"))
if track.is_stream:
return await interaction.response.send_message(self.player.get_msg("playlistAddError"))
return await self.send(interaction, self.player.get_msg("playlistAddError"))
user = await get_playlist(interaction.user.id, 'playlist')
if not user:
return await create_account(interaction)
rank, max_p, max_t = await checkroles(interaction.user.id)
if len(user['200']['tracks']) >= max_t:
return await interaction.response.send_message(self.player.get_msg("playlistlimited").format(max_t), ephemeral=True)
return await self.send(interaction, self.player.get_msg("playlistlimited").format(max_t), ephemeral=True)
if track.track_id in user['200']['tracks']:
return await interaction.response.send_message(self.player.get_msg("playlistrepeated"), ephemeral=True)
return await self.send(interaction, self.player.get_msg("playlistrepeated"), ephemeral=True)
respond = await update_playlist(interaction.user.id, {'playlist.200.tracks': track.track_id}, push=True)
if respond:
await interaction.response.send_message(self.player.get_msg("playlistAdded").format(track.title, interaction.user.mention, user['200']['name']), ephemeral=True)
await self.send(interaction, self.player.get_msg("playlistAdded").format(track.title, interaction.user.mention, user['200']['name']), ephemeral=True)
else:
await interaction.response.send_message(self.player.get_msg("playlistAddError2"), ephemeral=True)
await self.send(interaction, self.player.get_msg("playlistAddError2"), ephemeral=True)
class Loop(discord.ui.Button):
def __init__(self, player, style, row):
self.player: voicelink.Player = player
super().__init__(emoji="🔁", label=player.get_msg('buttonLoop'), style=style, row=row)
class Loop(ControlButton):
def __init__(self, **kwargs):
super().__init__(
emoji="🔁",
label="buttonLoop",
**kwargs
)
async def callback(self, interaction: discord.Interaction):
if not self.player.is_privileged(interaction.user):
return await interaction.response.send_message(self.player.get_msg('missingPerms_mode'), ephemeral=True)
return await self.send(interaction, self.player.get_msg('missingPerms_mode'), ephemeral=True)
mode = await self.player.set_repeat()
await interaction.response.send_message(self.player.get_msg('repeat').format(mode.capitalize()))
await self.send(interaction, self.player.get_msg('repeat').format(mode.capitalize()))
class VolumeUp(discord.ui.Button):
def __init__(self, player, style, row):
self.player: voicelink.Player = player
super().__init__(emoji="🔊", label=player.get_msg('buttonVolumeUp'), style=style, row=row)
class VolumeUp(ControlButton):
def __init__(self, **kwargs):
super().__init__(
emoji="🔊",
label="buttonVolumeUp",
**kwargs
)
async def callback(self, interaction: discord.Interaction):
if not self.player.is_privileged(interaction.user):
return interaction.response.send_message(self.player.get_msg("missingPerms_function"))
return await self.send(interaction, self.player.get_msg("missingPerms_function"))
value = value if (value := self.player.volume + 20) <= 150 else 150
await self.player.set_volume(value, interaction.user)
await interaction.response.send_message(self.player.get_msg('setVolume').format(value), ephemeral=True)
await self.send(interaction, self.player.get_msg('setVolume').format(value), ephemeral=True)
class VolumeDown(discord.ui.Button):
def __init__(self, player, style, row):
self.player: voicelink.Player = player
super().__init__(emoji="🔉", label=player.get_msg('buttonVolumeDown'), style=style, row=row)
class VolumeDown(ControlButton):
def __init__(self, **kwargs):
super().__init__(
emoji="🔉",
label="buttonVolumeDown",
**kwargs
)
async def callback(self, interaction: discord.Interaction):
if not self.player.is_privileged(interaction.user):
return interaction.response.send_message(self.player.get_msg("missingPerms_function"))
return await self.send(interaction, self.player.get_msg("missingPerms_function"))
value = value if (value := self.player.volume - 20) >= 0 else 0
await self.player.set_volume(value, interaction.user)
await interaction.response.send_message(self.player.get_msg('setVolume').format(value), ephemeral=True)
await self.send(interaction, self.player.get_msg('setVolume').format(value), ephemeral=True)
class VolumeMute(discord.ui.Button):
def __init__(self, player, style, row):
self.player: voicelink.Player = player
super().__init__(emoji="🔇" if player.volume else "🔈",
label=player.get_msg('buttonVolumeMute' if player.volume else "buttonVolumeUnmute"),
style=style, row=row)
class VolumeMute(ControlButton):
def __init__(self, **kwargs):
super().__init__(
emoji="🔇" if kwargs["player"].volume else "🔈",
label="buttonVolumeMute" if kwargs["player"].volume else "buttonVolumeUnmute",
**kwargs
)
async def callback(self, interaction: discord.Interaction):
if not self.player.is_privileged(interaction.user):
return interaction.response.send_message(self.player.get_msg("missingPerms_function"))
return await self.send(interaction, self.player.get_msg("missingPerms_function"))
if self.player.volume != 0:
value = 0
@@ -240,82 +286,86 @@ class VolumeMute(discord.ui.Button):
await interaction.response.edit_message(view=self.view)
class AutoPlay(discord.ui.Button):
def __init__(self, player, style, row):
self.player: voicelink.Player = player
super().__init__(emoji="💡",
label=player.get_msg('buttonAutoPlay'),
style=style, row=row)
class AutoPlay(ControlButton):
def __init__(self, **kwargs):
super().__init__(
emoji="💡",
label="buttonAutoPlay",
**kwargs
)
async def callback(self, interaction: discord.Interaction):
if not self.player.is_privileged(interaction.user):
return interaction.response.send_message(self.player.get_msg("missingPerms_autoplay"), ephemeral=True)
return await self.send(interaction, self.player.get_msg("missingPerms_autoplay"), ephemeral=True)
check = not self.player.settings.get("autoplay", False)
self.player.settings['autoplay'] = check
await interaction.response.send_message(self.player.get_msg('autoplay').format(self.player.get_msg('enabled') if check else self.player.get_msg('disabled')))
await self.send(interaction, self.player.get_msg('autoplay').format(self.player.get_msg('enabled') if check else self.player.get_msg('disabled')))
if not self.player.is_playing:
await self.player.do_next()
class Shuffle(discord.ui.Button):
def __init__(self, player, style, row):
self.player: voicelink.Player = player
super().__init__(emoji="🔀",
label=player.get_msg('buttonShuffle'),
style=style, row=row)
class Shuffle(ControlButton):
def __init__(self, **kwargs):
super().__init__(
emoji="🔀",
label="buttonShuffle",
**kwargs
)
async def callback(self, interaction: discord.Interaction):
if not self.player.is_privileged(interaction.user):
if interaction.user in self.player.shuffle_votes:
return await interaction.response.send_message(self.player.get_msg('voted'), ephemeral=True)
return await self.send(interaction, self.player.get_msg('voted'), ephemeral=True)
else:
self.player.shuffle_votes.add(interaction.user)
if len(self.player.shuffle_votes) >= (required := self.player.required()):
pass
else:
return await interaction.response.send_message(self.player.get_msg('shuffleVote').format(interaction.user, len(self.player.skip_votes), required))
return await self.send(interaction, self.player.get_msg('shuffleVote').format(interaction.user, len(self.player.skip_votes), required))
await self.player.shuffle("queue", interaction.user)
await interaction.response.send_message(self.player.get_msg('shuffled'))
await self.send(interaction, self.player.get_msg('shuffled'))
class Forward(discord.ui.Button):
def __init__(self, player, style, row):
self.player: voicelink.Player = player
super().__init__(emoji="",
label=player.get_msg('buttonForward'),
disabled=False if self.player.current else True,
style=style, row=row)
class Forward(ControlButton):
def __init__(self, **kwargs):
super().__init__(
emoji="",
label="buttonForward",
disabled=not bool(kwargs["player"].current),
**kwargs
)
async def callback(self, interaction: discord.Interaction):
if not self.player.is_privileged(interaction.user):
return await interaction.response.send_message(self.player.get_msg('missingPerms_pos'), ephemeral=True)
return await self.send(interaction, self.player.get_msg('missingPerms_pos'), ephemeral=True)
if not self.player.current:
return await interaction.response.send_message(self.player.get_msg('noTrackPlaying'), ephemeral=True)
return await self.send(interaction, self.player.get_msg('noTrackPlaying'), ephemeral=True)
await self.player.seek(self.player.position + 30000)
await interaction.response.send_message(self.player.get_msg('forward').format(func.time(self.player.position + 10000)))
await self.send(interaction, self.player.get_msg('forward').format(func.time(self.player.position + 10000)))
class Rewind(discord.ui.Button):
def __init__(self, player, style, row):
self.player: voicelink.Player = player
super().__init__(emoji="",
label=player.get_msg('buttonRewind'),
disabled=False if self.player.current else True,
style=style, row=row)
class Rewind(ControlButton):
def __init__(self, **kwargs):
super().__init__(
emoji="",
label="buttonRewind",
disabled=not bool(kwargs["player"].current)
**kwargs
)
async def callback(self, interaction: discord.Interaction):
if not self.player.is_privileged(interaction.user):
return await interaction.response.send_message(self.player.get_msg('missingPerms_pos'), ephemeral=True)
return await self.send(interaction, self.player.get_msg('missingPerms_pos'), ephemeral=True)
if not self.player.current:
return await interaction.response.send_message(self.player.get_msg('noTrackPlaying'), ephemeral=True)
return await self.send(interaction, self.player.get_msg('noTrackPlaying'), ephemeral=True)
position = 0 if (value := (self.player.position - 30000)) <= 0 else value
await self.player.seek(position)
await interaction.response.send_message(self.player.get_msg('rewind').format(func.time(position)))
await self.send(interaction, self.player.get_msg('rewind').format(func.time(position)))
class Tracks(discord.ui.Select):
def __init__(self, player, style, row):
@@ -341,7 +391,9 @@ class Tracks(discord.ui.Select):
self.player.queue.skipto(int(self.values[0].split(". ")[0]))
await self.player.stop()
await interaction.response.send_message(self.player.get_msg("skipped").format(interaction.user))
if self.player.settings.get("controller_msg", True):
await interaction.response.send_message(self.player.get_msg("skipped").format(interaction.user))
btnType = {
"back": Back,
@@ -382,7 +434,7 @@ class InteractiveController(discord.ui.View):
style = btnColor.get(color.lower(), btnColor["grey"])
if not btnClass or (self.player.queue.is_empty and btn == "tracks"):
continue
self.add_item(btnClass(player, style, row))
self.add_item(btnClass(player=player, style=style, row=row))
self.cooldown = commands.CooldownMapping.from_cooldown(2.0, 10.0, key)

View File

@@ -59,7 +59,7 @@ class HelpView(discord.ui.View):
self.add_item(discord.ui.Button(label='Support', emoji=':support:915152950471581696', url=func.settings.invite_link))
self.add_item(discord.ui.Button(label='Invite', emoji=':invite:915152589056790589', url='https://discord.com/oauth2/authorize?client_id={}&permissions=2184260928&scope=bot%20applications.commands'.format(func.tokens.client_id)))
self.add_item(discord.ui.Button(label='Github', url='https://github.com/ChocoMeow/Vocard'))
self.add_item(discord.ui.Button(label='Github', emoji=':github:1098265017268322406', url='https://github.com/ChocoMeow/Vocard'))
self.add_item(discord.ui.Button(label='Donate', emoji=':patreon:913397909024800878', url='https://www.patreon.com/Vocard'))
self.add_item(HelpDropdown(self.categorys))
@@ -89,13 +89,9 @@ class HelpView(discord.ui.View):
value="```py\n👉 News\n2. Tutorial\n{}```".format("".join(f"{i}. {c}\n" for i, c in enumerate(self.categorys, start=3))),
inline=True)
update = "> Update Contents\n" \
"➥ More text support for other languages\n" \
"➥ Add author name in playing track msg\n" \
"➥ Fixed some commands\n\n" \
"[Click Me For More Info](https://discord.com/channels/811542332678996008/811909963718459392/1063728318777671741)" \
update = "Vocard is a simple music bot. It leads to a comfortable experience which is user-friendly, It supports YouTube, Soundcloud, Spotify, Twitch and more!"
embed.add_field(name="📰 Latest News <t:1661168201:d> (<t:1661168201:R>)", value=update, inline=True)
embed.add_field(name="📰 Information:", value=update, inline=True)
embed.add_field(name="Get Started", value="```Join a voice channel and /play {Song/URL} a song. (Names, Youtube Video Links or Playlist links or Spotify links are supported on Vocard)```", inline=False)
return embed

View File

@@ -52,11 +52,11 @@ from .enums import SearchType, LoopType
from .events import VoicelinkEvent, TrackEndEvent, TrackStartEvent
from .exceptions import VoicelinkException, FilterInvalidArgument, TrackInvalidPosition, TrackLoadError, FilterTagAlreadyInUse, DuplicateTrack
from .filters import Filter, Filters
from .objects import Track
from .objects import Track, Playlist
from .pool import Node, NodePool
from .queue import Queue, FairQueue
from .placeholders import Placeholders, build_embed
from random import shuffle
from random import shuffle, choice
async def connect_channel(ctx: Union[commands.Context, Interaction], channel: VoiceChannel = None):
try:
@@ -116,6 +116,7 @@ class Player(VoiceProtocol):
self._paused: bool = False
self._is_connected: bool = False
self._ping: float = 0.0
self._track_is_stuck = False
self._position: int = 0
self._last_position: int = 0
@@ -217,8 +218,8 @@ class Player(VoiceProtocol):
def ping(self) -> float:
return round(self._ping / 1000, 2)
def get_msg(self, mKey: str, lang: str = None) -> str:
return func.langs.get(lang if lang else self.lang, func.langs["EN"])[mKey]
def get_msg(self, key: str) -> str:
return func.get_lang(self.guild.id, key)
def required(self, leave=False):
if self.settings.get('votedisable'):
@@ -318,6 +319,10 @@ class Player(VoiceProtocol):
if self._paused:
self._paused = False
if self._track_is_stuck:
await sleep(10)
self._track_is_stuck = False
if not self.guild.me.voice:
await self.connect(timeout=0.0, reconnect=True)
@@ -331,9 +336,8 @@ class Player(VoiceProtocol):
track: Track = self.queue.get()
if not track:
if self.settings.get("autoplay", False):
if await func.similar_track(self):
return await self.do_next()
if self.settings.get("autoplay", False) and await self.get_recommendations():
return await self.do_next()
else:
try:
await self.play(track, start=track.position)
@@ -445,42 +449,7 @@ class Player(VoiceProtocol):
requester=requester,
search_type=SearchType.ytsearch,
spotify_track=track,
info={
"title": track.name,
"author": track.artists,
"length": track.length,
"identifier": track.id,
"artistId": track.artistId,
"uri": track.uri,
"isStream": False,
"isSeekable": True,
"position": 0,
"thumbnail": track.image
}
)
for track in tracks ]
async def spotifyRelatedTrack(self, seed_tracks: str):
tracks = await self._node._spotify_client.similar_track(seed_tracks=seed_tracks)
return [ Track(
track_id=None,
search_type=SearchType.ytsearch,
spotify_track=track,
info={
"title": track.name,
"author": track.artists,
"length": track.length,
"identifier": track.id,
"artistId": track.artistId,
"uri": track.uri,
"isStream": False,
"isSeekable": True,
"position": 0,
"thumbnail": track.image
},
requester=self.client.user
info=track.to_dict()
)
for track in tracks ]
@@ -561,10 +530,10 @@ class Player(VoiceProtocol):
return self._current
async def add_track(self, raw_tracks: Union[Track, List[Track]], at_font: bool = False) -> int:
async def add_track(self, raw_tracks: Union[Track, List[Track]], *, at_font: bool = False, duplicate: bool = True) -> int:
tracks = []
_duplicate_tracks = () if self.queue._allow_duplicate else (track.uri for track in self.queue._queue)
_duplicate_tracks = () if self.queue._allow_duplicate and duplicate else (track.uri for track in self.queue._queue)
try:
if (isList := isinstance(raw_tracks, List)):
@@ -677,8 +646,7 @@ class Player(VoiceProtocol):
await self.seek(self.position)
async def change_node(self, identifier: str = None) -> None:
"""Change node.
"""
"""Change node."""
try:
node = NodePool.get_node(identifier=identifier)
except:
@@ -700,8 +668,43 @@ class Player(VoiceProtocol):
if self.volume != 100:
await self.set_volume(self.volume)
async def get_recommendations(self, *, track: Track = None) -> bool:
"""Get recommendations from Youtube or Spotify."""
if not track:
track = choice(self.queue.history(incTrack=True)[-5:])
if track.spotify:
spotify_tracks = await self._node._spotify_client.similar_track(seed_tracks=track.identifier)
tracks = [ Track(
track_id=None,
search_type=SearchType.ytsearch,
spotify_track=track,
info=track.to_dict(),
requester=self.client.user
)
for track in spotify_tracks ]
else:
if track.source != 'youtube':
return False
tracks = await self.get_tracks(
f"https://www.youtube.com/watch?v={track.identifier}&list=RD{track.identifier}",
requester=self.client.user
)
if tracks:
if isinstance(tracks, Playlist):
await self.add_track(tracks.tracks, duplicate=False)
else:
await self.add_track(tracks, duplicate=False)
return True
return False
async def send_ws(self, payload, requester: Member = None):
payload['guild_id'] = self.guild.id
if requester:
payload['requester_id'] = requester.id
await self.bot.ipc.send(payload)
payload['guild_id'] = self.guild.id
if requester:
payload['requester_id'] = requester.id
await self.bot.ipc.send(payload)

View File

@@ -404,18 +404,7 @@ class Node:
requester=requester,
search_type=search_type,
spotify_track=spotify_results,
info={
"title": spotify_results.name,
"author": spotify_results.artists,
"length": spotify_results.length,
"identifier": spotify_results.id,
"uri": spotify_results.uri,
"artistId": spotify_results.artistId,
"isStream": False,
"isSeekable": True,
"position": 0,
"thumbnail": spotify_results.image
}
info=spotify_results.to_dict()
)
]
@@ -425,18 +414,7 @@ class Node:
requester=requester,
search_type=search_type,
spotify_track=track,
info={
"title": track.name,
"author": track.artists,
"length": track.length,
"identifier": track.id,
"artistId": track.artistId,
"uri": track.uri,
"isStream": False,
"isSeekable": True,
"position": 0,
"thumbnail": track.image
}
info=track.to_dict()
) for track in spotify_results.tracks if track.uri
]
@@ -512,7 +490,6 @@ class Node:
for track in data["tracks"]
]
class NodePool:
"""The base class for the node pool.
This holds all the nodes that are to be used by the bot.

View File

@@ -160,5 +160,5 @@ class Client:
return Playlist(data, tracks)
async def close(self):
async def close(self) -> None:
await self.session.close()

View File

@@ -20,6 +20,20 @@ class Track:
else:
self.uri = data["external_urls"]["spotify"]
def to_dict(self) -> dict:
return {
"title": self.name,
"author": self.artists,
"length": self.length,
"identifier": self.id,
"artistId": self.artistId,
"uri": self.uri,
"isStream": False,
"isSeekable": True,
"position": 0,
"thumbnail": self.image
}
def __repr__(self) -> str:
return (
f"<Voicelink.spotify.Track name={self.name} artists={self.artists} "