diff --git a/.env Example b/.env Example
index 654e9a0..697b709 100644
--- a/.env Example
+++ b/.env Example
@@ -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
diff --git a/README.md b/README.md
index a3db67a..aeee6d4 100644
--- a/README.md
+++ b/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
***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
+```
diff --git a/addons/settings.py b/addons/settings.py
index c022e32..38f5c50 100644
--- a/addons/settings.py
+++ b/addons/settings.py
@@ -62,7 +62,6 @@ def __init__(self) -> None:
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")
\ No newline at end of file
diff --git a/cogs/admin.py b/cogs/admin.py
index d1e5321..6b8017d 100644
--- a/cogs/admin.py
+++ b/cogs/admin.py
@@ -223,7 +223,7 @@ async def duplicatetrack(self, ctx: commands.Context):
@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 @@ async def customController(self, ctx: commands.Context):
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:
diff --git a/cogs/basic.py b/cogs/basic.py
index ab802d6..5c79b30 100644
--- a/cogs/basic.py
+++ b/cogs/basic.py
@@ -103,7 +103,7 @@ async def play(self, ctx: commands.Context, *, query: str) -> None:
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 @@ async def _play(self, interaction: discord.Interaction, message: discord.Message
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 @@ async def search(self, ctx: commands.Context, *, query: str, platform: str = "Yo
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 @@ async def playtop(self, ctx: commands.Context, query: str):
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 @@ async def playtop(self, ctx: commands.Context, query: str):
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):
diff --git a/cogs/listeners.py b/cogs/listeners.py
index 42df4b8..336a32e 100644
--- a/cogs/listeners.py
+++ b/cogs/listeners.py
@@ -32,17 +32,16 @@ async def on_voicelink_track_end(self, player: voicelink.Player, track, _):
@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):
diff --git a/function.py b/function.py
index 05e1e12..b684331 100644
--- a/function.py
+++ b/function.py
@@ -4,7 +4,6 @@
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,39 +82,94 @@ 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]
-
-async def requests_api(url: str) -> dict:
- async with aiohttp.ClientSession() as session:
- resp = await session.get(url)
- if resp.status != 200:
- return False
+ if lang in langs and not langs[lang]:
+ langs[lang] = open_json(os.path.join("langs", f"{lang}.json"))
- return await resp.json(encoding="utf-8")
+ return langs.get(lang, {}).get(key, "Language pack not found!")
def init() -> None:
global settings
- json = open_json(os.path.join(root_dir, "settings.json"))
+ 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]] = open_json(os.path.join(root_dir, "langs", language))
+ 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(root_dir, "local_langs", language))
+ 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)
+ if resp.status != 200:
+ return False
+
+ return await resp.json(encoding="utf-8")
+
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, [])
\ No newline at end of file
+ return rank, max_p, max_t
\ No newline at end of file
diff --git a/langs/CH.json b/langs/CH.json
index 4e6d71e..256a226 100644
--- a/langs/CH.json
+++ b/langs/CH.json
@@ -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}",
diff --git a/langs/DE.json b/langs/DE.json
index ec83805..2d6cf28 100644
--- a/langs/DE.json
+++ b/langs/DE.json
@@ -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}",
diff --git a/langs/EN.json b/langs/EN.json
index 902eb0e..2ebd9ca 100644
--- a/langs/EN.json
+++ b/langs/EN.json
@@ -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}",
diff --git a/langs/ES.json b/langs/ES.json
index eab8a0c..3efe57e 100644
--- a/langs/ES.json
+++ b/langs/ES.json
@@ -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}",
diff --git a/langs/JA.json b/langs/JA.json
index 3e738b1..11214a1 100644
--- a/langs/JA.json
+++ b/langs/JA.json
@@ -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}",
diff --git a/langs/KO.json b/langs/KO.json
index 27bd892..b5e010b 100644
--- a/langs/KO.json
+++ b/langs/KO.json
@@ -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}",
diff --git a/langs/RU.json b/langs/RU.json
new file mode 100644
index 0000000..b64b5ad
--- /dev/null
+++ b/langs/RU.json
@@ -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": "Что-то пошло не так при декодировании файла!"
+}
\ No newline at end of file
diff --git a/main.py b/main.py
index 6cd5ade..beb3263 100644
--- a/main.py
+++ b/main.py
@@ -62,7 +62,7 @@ async def setup_hook(self):
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 @@ async def on_ready(self):
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 @@ async def get_prefix(bot, message: discord.Message):
)
if __name__ == "__main__":
- update.checkVersion(withMsg=True)
+ update.check_version(with_msg=True)
bot.run(func.tokens.token, log_handler=None)
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index bfccd7a..9c997df 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -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
diff --git a/update.py b/update.py
index ad96d4b..538a2ae 100644
--- a/update.py
+++ b/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"
-
-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")
+__version__ = "v2.6.6"
+
+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)
-
-def unZip(response, version: str):
- print("Installing ...")
- zfile = zipfile.ZipFile(BytesIO(response.content))
- zfile.extractall(root_dir)
-
- 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)
-
-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())
-
-if "--start" in sys.argv:
- start()
-
-if "--check" in sys.argv:
- checkVersion(withMsg = True)
\ No newline at end of file
+ return response
+
+def install(response, version):
+ """Install the downloaded version of the project.
+
+ 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)
+
+ 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
+
+ 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!")
+
+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()
\ No newline at end of file
diff --git a/views/controller.py b/views/controller.py
index 52edf74..06c087e 100644
--- a/views/controller.py
+++ b/views/controller.py
@@ -38,22 +38,44 @@
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 @@ async def callback(self, interaction: discord.Interaction):
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 @@ async def callback(self, interaction: discord.Interaction):
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 @@ async def callback(self, interaction: discord.Interaction):
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 @@ async def callback(self, interaction: discord.Interaction):
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 @@ async def callback(self, interaction: discord.Interaction):
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 @@ async def callback(self, interaction: discord.Interaction):
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 @@ def __init__(self, player):
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)
diff --git a/views/help.py b/views/help.py
index bfd4d27..7fa0dd0 100644
--- a/views/help.py
+++ b/views/help.py
@@ -59,7 +59,7 @@ def __init__(self, bot: commands.Bot, author: discord.Member):
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 @@ def build_embed(self, category: str):
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 ()", 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
diff --git a/voicelink/player.py b/voicelink/player.py
index 7cf68db..6b3b5ea 100644
--- a/voicelink/player.py
+++ b/voicelink/player.py
@@ -52,11 +52,11 @@
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 @@ def __init__(
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 @@ def is_dead(self) -> bool:
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 @@ async def do_next(self):
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 @@ async def do_next(self):
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 @@ async def spotifySearch(self, query: str, *, requester: Member) -> list:
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 @@ async def play(
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 @@ async def reset_filter(self, *, fast_apply=False) -> None:
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 @@ async def change_node(self, identifier: str = None) -> None:
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)
diff --git a/voicelink/pool.py b/voicelink/pool.py
index 8bdebd4..7a2e543 100644
--- a/voicelink/pool.py
+++ b/voicelink/pool.py
@@ -404,18 +404,7 @@ async def get_tracks(
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 @@ async def get_tracks(
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 @@ async def get_tracks(
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.
diff --git a/voicelink/spotify/client.py b/voicelink/spotify/client.py
index d2ccd03..016b7e0 100644
--- a/voicelink/spotify/client.py
+++ b/voicelink/spotify/client.py
@@ -160,5 +160,5 @@ async def search(self, *, query: str):
return Playlist(data, tracks)
- async def close(self):
+ async def close(self) -> None:
await self.session.close()
diff --git a/voicelink/spotify/objects.py b/voicelink/spotify/objects.py
index 5608b82..94b65f9 100644
--- a/voicelink/spotify/objects.py
+++ b/voicelink/spotify/objects.py
@@ -20,6 +20,20 @@ def __init__(self, data: dict, image=None) -> None:
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"