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:
@@ -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
|
||||
|
||||
15
README.md
15
README.md
@@ -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
|
||||
```
|
||||
|
||||
@@ -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")
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
194
function.py
194
function.py
@@ -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
|
||||
@@ -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}",
|
||||
|
||||
@@ -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}",
|
||||
|
||||
@@ -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}",
|
||||
|
||||
@@ -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}",
|
||||
|
||||
@@ -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}",
|
||||
|
||||
@@ -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
192
langs/RU.json
Normal 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": "Что-то пошло не так при декодировании файла!"
|
||||
}
|
||||
5
main.py
5
main.py
@@ -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)
|
||||
@@ -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
163
update.py
@@ -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()
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -160,5 +160,5 @@ class Client:
|
||||
|
||||
return Playlist(data, tracks)
|
||||
|
||||
async def close(self):
|
||||
async def close(self) -> None:
|
||||
await self.session.close()
|
||||
|
||||
@@ -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} "
|
||||
|
||||
Reference in New Issue
Block a user