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"