From 61a2d1d974f5db25110544adfb7f5f4c4ddcbcab Mon Sep 17 00:00:00 2001 From: Choco <94597336+ChocoMeow@users.noreply.github.com> Date: Sun, 9 Jul 2023 20:19:55 +0800 Subject: [PATCH 1/9] Added queue import and export command --- cogs/basic.py | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++- main.py | 2 +- 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/cogs/basic.py b/cogs/basic.py index 82de4ad..695f456 100644 --- a/cogs/basic.py +++ b/cogs/basic.py @@ -2,6 +2,7 @@ import voicelink import re +from io import StringIO from discord import app_commands from discord.ext import commands from function import ( @@ -381,7 +382,11 @@ async def seek(self, ctx: commands.Context, position: str): await player.seek(num, ctx.author) await ctx.send(player.get_msg('seek').format(position)) - @commands.hybrid_command(name="queue", aliases=get_aliases("queue")) + @commands.hybrid_group( + name="queue", + aliases=get_aliases("queue"), + invoke_without_command=True + ) @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) async def queue(self, ctx: commands.Context): "Display the players queue songs in your queue." @@ -398,6 +403,76 @@ async def queue(self, ctx: commands.Context): message = await ctx.send(embed=view.build_embed(), view=view) view.response = message + @queue.command(name="export", aliases=get_aliases("export")) + @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) + async def export(self, ctx: commands.Context): + "Export the queue into a txt file." + player: voicelink.Player = ctx.guild.voice_client + if not player: + return await ctx.send(get_lang(ctx.guild.id, "noPlayer"), ephemeral=True) + + if not player.is_user_join(ctx.author): + return await ctx.send(player.get_msg('notInChannel').format(ctx.author.mention, player.channel.mention), ephemeral=True) + + if player.queue.is_empty and not player.current: + return await ctx.send(player.get_msg('noTrackPlaying'), ephemeral=True) + + await ctx.defer() + + tracks = player.queue.tracks(True) + temp = "" + raw = "----------->Raw Info<-----------\n" + + total_length = 0 + for index, track in enumerate(tracks, start=1): + temp += f"{index}. {track.title} [{ctime(track.length)}]\n" + raw += track.track_id + if index != len(tracks): + raw += "," + total_length += track.length + + temp = "!Remember do not change this file!\n------------->Info<-------------\nGuild: {} ({})\nRequester: {} ({})\nTracks: {} - {}\n------------>Tracks<------------\n".format( + ctx.guild.name, ctx.guild.id, + ctx.author.name, ctx.author.id, + len(tracks), ctime(total_length) + ) + temp + temp += raw + + await ctx.reply(content="", file=discord.File(StringIO(temp), filename=f"{ctx.guild.id}_Full_Queue.txt")) + + @queue.command(name="import", aliases=get_aliases("import")) + @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) + async def _import(self, ctx: commands.Context, attachment: discord.Attachment): + "Import the track into the queue." + player: voicelink.Player = ctx.guild.voice_client + if not player: + player = await voicelink.connect_channel(ctx) + + if not player.is_user_join(ctx.author): + return await ctx.send(player.get_msg('notInChannel').format(ctx.author.mention, player.channel.mention), ephemeral=True) + + try: + bytes = await attachment.read() + last_line = bytes.split(b"\n")[-1] + track_ids = last_line.decode().split(",") + + tracks = [voicelink.Track(track_id=track_id, info=voicelink.decode(track_id), requester=ctx.author) for track_id in track_ids] + if not tracks: + return await ctx.send(player.get_msg('noTrackFound')) + + index = await player.add_track(tracks) + await ctx.send(player.get_msg('playlistLoad').format(attachment.filename, index)) + + except voicelink.QueueFull as e: + return await ctx.send(e) + + except Exception as e: + await ctx.send("Something went wrong while decoding the file!") + + finally: + if not player.is_playing: + await player.do_next() + @commands.hybrid_command(name="history", aliases=get_aliases("history")) @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) async def history(self, ctx: commands.Context): diff --git a/main.py b/main.py index df7753c..bf9f8f9 100644 --- a/main.py +++ b/main.py @@ -88,7 +88,7 @@ async def on_command_error(self, ctx: commands.Context, exception, /) -> None: elif isinstance(error, (commands.CommandOnCooldown, commands.MissingPermissions, commands.RangeError, commands.BadArgument)): pass - elif isinstance(error, commands.MissingRequiredArgument): + elif isinstance(error, commands.MissingRequiredArgument, commands.MissingRequiredAttachment): command = f" Correct Usage: {ctx.prefix}" + (f"{ctx.command.parent.qualified_name} " if ctx.command.parent else "") + f"{ctx.command.name} {ctx.command.signature}" position = command.find(f"<{ctx.current_parameter.name}>") + 1 error = f"```css\n[You are missing argument!]\n{command}\n" + " " * position + "^" * len(ctx.current_parameter.name) + "```" From 3ef66e77051f1258f1572ba9b3a6804c9a227f50 Mon Sep 17 00:00:00 2001 From: Choco <94597336+ChocoMeow@users.noreply.github.com> Date: Sun, 9 Jul 2023 20:20:16 +0800 Subject: [PATCH 2/9] Fixed node connect method --- voicelink/pool.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/voicelink/pool.py b/voicelink/pool.py index fb40329..d62bfd9 100644 --- a/voicelink/pool.py +++ b/voicelink/pool.py @@ -31,8 +31,6 @@ from discord import Client, Member from typing import Dict, Optional, TYPE_CHECKING, Union from urllib.parse import quote -import function as func - from . import ( __version__, @@ -122,7 +120,7 @@ def __init__( self._headers = { "Authorization": self._password, - "User-Id": str(func.tokens.client_id), + "User-Id": str(self.bot.user.id), "Client-Name": f"Voicelink/{__version__}", 'Resume-Key': self.resume_key } From b185c9da92bdb9913174e1846c2c7f3d53ae4a56 Mon Sep 17 00:00:00 2001 From: Choco <94597336+ChocoMeow@users.noreply.github.com> Date: Sun, 16 Jul 2023 15:38:00 +0800 Subject: [PATCH 3/9] Optimize two new commands --- cogs/basic.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/cogs/basic.py b/cogs/basic.py index 695f456..ccd6a68 100644 --- a/cogs/basic.py +++ b/cogs/basic.py @@ -406,7 +406,7 @@ async def queue(self, ctx: commands.Context): @queue.command(name="export", aliases=get_aliases("export")) @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) async def export(self, ctx: commands.Context): - "Export the queue into a txt file." + "Exports the entire queue to a text file" player: voicelink.Player = ctx.guild.voice_client if not player: return await ctx.send(get_lang(ctx.guild.id, "noPlayer"), ephemeral=True) @@ -443,7 +443,7 @@ async def export(self, ctx: commands.Context): @queue.command(name="import", aliases=get_aliases("import")) @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) async def _import(self, ctx: commands.Context, attachment: discord.Attachment): - "Import the track into the queue." + "Imports the text file and adds the track to the current queue." player: voicelink.Player = ctx.guild.voice_client if not player: player = await voicelink.connect_channel(ctx) @@ -453,9 +453,9 @@ async def _import(self, ctx: commands.Context, attachment: discord.Attachment): try: bytes = await attachment.read() - last_line = bytes.split(b"\n")[-1] - track_ids = last_line.decode().split(",") - + track_ids = bytes.split(b"\n")[-1] + track_ids = track_ids.decode().split(",") + tracks = [voicelink.Track(track_id=track_id, info=voicelink.decode(track_id), requester=ctx.author) for track_id in track_ids] if not tracks: return await ctx.send(player.get_msg('noTrackFound')) @@ -464,10 +464,10 @@ async def _import(self, ctx: commands.Context, attachment: discord.Attachment): await ctx.send(player.get_msg('playlistLoad').format(attachment.filename, index)) except voicelink.QueueFull as e: - return await ctx.send(e) + return await ctx.send(e, ephemeral=True) - except Exception as e: - await ctx.send("Something went wrong while decoding the file!") + except: + return await ctx.send(player.get_msg("decodeError"), ephemeral=True) finally: if not player.is_playing: From ddb1f10841da9b80ebab1ed0e17f21f4baf56a2c Mon Sep 17 00:00:00 2001 From: Choco <94597336+ChocoMeow@users.noreply.github.com> Date: Sun, 16 Jul 2023 15:38:26 +0800 Subject: [PATCH 4/9] Added playlist export and import commands --- cogs/playlist.py | 80 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/cogs/playlist.py b/cogs/playlist.py index 93656c1..365470a 100644 --- a/cogs/playlist.py +++ b/cogs/playlist.py @@ -1,6 +1,7 @@ import discord import voicelink +from io import StringIO from discord import app_commands from discord.ext import commands from function import ( @@ -407,5 +408,84 @@ async def clear(self, ctx: commands.Context, name: str) -> None: await update_playlist(ctx.author.id, {f'playlist.{result["id"]}.tracks': []}) await ctx.send(get_lang(ctx.guild.id, 'playlistClear').format(name)) + @playlist.command(name="export", aliases=get_aliases("export")) + @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) + @app_commands.autocomplete(name=playlist_autocomplete) + async def export(self, ctx: commands.Context, name: str) -> None: + "Exports the entire playlist to a text file" + result = await check_playlist(ctx, name.lower()) + if not result: + return await create_account(ctx) + if not result['playlist']: + return await ctx.send(get_lang(ctx.guild.id, 'playlistNotFound').format(name), ephemeral=True) + + if result['playlist']['type'] == 'link': + tracks = await search_playlist(result['playlist']['uri'], ctx.author, timeNeed=False) + else: + if not result['playlist']['tracks']: + return await ctx.send(get_lang(ctx.guild.id, 'playlistNoTrack').format(result['playlist']['name']), ephemeral=True) + + playtrack = [] + for track in result['playlist']['tracks']: + playtrack.append(voicelink.Track(track_id=track, info=voicelink.decode(track), requester=ctx.author)) + + tracks = {"name": result['playlist']['name'], "tracks": playtrack} + + if not tracks: + return await ctx.send(get_lang(ctx.guild.id, 'playlistNoTrack').format(result['playlist']['name']), ephemeral=True) + + temp = "" + raw = "----------->Raw Info<-----------\n" + + total_length = 0 + for index, track in enumerate(tracks['tracks'], start=1): + temp += f"{index}. {track.title} [{ctime(track.length)}]\n" + raw += track.track_id + if index != len(tracks['tracks']): + raw += "," + total_length += track.length + + temp = "!Remember do not change this file!\n------------->Info<-------------\nPlaylist: {} ({})\nRequester: {} ({})\nTracks: {} - {}\n------------>Tracks<------------\n".format( + tracks['name'], result['playlist']['type'], + ctx.author.name, ctx.author.id, + len(tracks['tracks']), ctime(total_length) + ) + temp + temp += raw + + await ctx.send(content="", file=discord.File(StringIO(temp), filename=f"{tracks['name']}_playlist.txt")) + + @playlist.command(name="import", aliases=get_aliases("import")) + @app_commands.describe(name="Give a name to your playlist.") + @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) + async def _import(self, ctx: commands.Context, name: str, attachment: discord.Attachment): + "Create your custom playlist." + if len(name) > 10: + return await ctx.send(get_lang(ctx.guild.id, 'playlistOverText'), ephemeral=True) + + rank, max_p, max_t = await checkroles(ctx.author.id) + user = await check_playlist(ctx, full=True) + if not user: + return await create_account(ctx) + + if len(user) >= max_p: + return await ctx.send(get_lang(ctx.guild.id, 'overPlaylistCreation').format(max_p), ephemeral=True) + + for data in user: + if user[data]['name'].lower() == name.lower(): + return await ctx.send(get_lang(ctx.guild.id, 'playlistExists').format(name), ephemeral=True) + + try: + bytes = await attachment.read() + track_ids = bytes.split(b"\n")[-1] + track_ids = track_ids.decode().split(",") + + playlist_name.pop(str(ctx.author.id), None) + data = {'tracks': track_ids, 'perms': {'read': [], 'write': [], 'remove': []}, 'name': name, 'type': 'playlist'} + await update_playlist(ctx.author.id, {f"playlist.{assign_playlistId([data for data in user])}": data}) + await ctx.send(get_lang(ctx.guild.id, 'playlistCreated').format(name)) + + except: + return await ctx.send(get_lang(ctx.guild.id, "decodeError"), ephemeral=True) + async def setup(bot: commands.Bot) -> None: await bot.add_cog(Playlists(bot)) From 339b2b940f324877354825a5a18b2d5eae10cb5d Mon Sep 17 00:00:00 2001 From: Choco <94597336+ChocoMeow@users.noreply.github.com> Date: Sun, 16 Jul 2023 15:38:42 +0800 Subject: [PATCH 5/9] Fixed error message in command --- cogs/playlist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cogs/playlist.py b/cogs/playlist.py index 365470a..5147851 100644 --- a/cogs/playlist.py +++ b/cogs/playlist.py @@ -206,7 +206,7 @@ async def create(self, ctx: commands.Context, name: str, link: str = None): for data in user: if user[data]['name'].lower() == name.lower(): - return await ctx.send(get_lang(ctx.guild.id, 'playlistExists'), ephemeral=True) + return await ctx.send(get_lang(ctx.guild.id, 'playlistExists').format(name), ephemeral=True) if link: tracks = await voicelink.NodePool.get_node().get_tracks(link, requester=ctx.author) if not isinstance(tracks, voicelink.Playlist): From 456d56cf0d34f0774e0f78cf13e2f5774decc351 Mon Sep 17 00:00:00 2001 From: Choco <94597336+ChocoMeow@users.noreply.github.com> Date: Sun, 16 Jul 2023 15:39:29 +0800 Subject: [PATCH 6/9] Added a return type to the function --- function.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/function.py b/function.py index 5b76d7b..c4f1063 100644 --- a/function.py +++ b/function.py @@ -42,7 +42,7 @@ playlist_name = {} #Cache the user's playlist name #-------------- Vocard Functions -------------- -def get_settings(guild_id:int): +def get_settings(guild_id:int) -> dict: settings = guild_settings.get(guild_id, None) if not settings: settings = collection.find_one({"_id":guild_id}) @@ -52,7 +52,7 @@ def get_settings(guild_id:int): guild_settings[guild_id] = settings return settings -def update_settings(guild_id:int, data: dict, mode="Set"): +def update_settings(guild_id:int, data: dict, mode="Set") -> None: settings = get_settings(guild_id) if mode == "Set": for key, value in data.items(): @@ -66,7 +66,7 @@ def update_settings(guild_id:int, data: dict, mode="Set"): collection.update_one({"_id":guild_id}, {"$unset":data}) return -def open_json(path: str): +def open_json(path: str) -> dict: try: with open(path, encoding="utf8") as json_file: return json.load(json_file) @@ -83,11 +83,11 @@ def update_json(path: str, new_data: dict) -> None: with open(path, "w") as json_file: json.dump(data, json_file, indent=4) -def get_lang(guild_id:int, key:str): +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): +async def requests_api(url: str) -> dict: async with aiohttp.ClientSession() as session: resp = await session.get(url) if resp.status != 200: @@ -95,14 +95,14 @@ async def requests_api(url: str): return await resp.json(encoding="utf-8") -def init(): +def init() -> None: global settings json = open_json(os.path.join(root_dir, "settings.json")) if json is not None: settings = Settings(json) -def langs_setup(): +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)) @@ -113,7 +113,7 @@ def langs_setup(): return -async def create_account(ctx: Union[commands.Context, discord.Interaction]): +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: return @@ -138,7 +138,7 @@ async def create_account(ctx: Union[commands.Context, discord.Interaction]): except: pass -async def get_playlist(userid:int, dType:str=None, dId:str=None): +async def get_playlist(userid:int, dType:str=None, dId:str=None) -> dict: user = Playlist.find_one({"_id":userid}, {"_id": 0}) if not user: return None @@ -148,7 +148,7 @@ async def get_playlist(userid:int, dType:str=None, dId:str=None): return user[dType] return user -async def update_playlist(userid:int, data:dict=None, push=False, pull=False, mode=True): +async def update_playlist(userid:int, data:dict=None, push=False, pull=False, mode=True) -> None: if mode is True: if push: return Playlist.update_one({"_id":userid}, {"$push": data}) @@ -159,7 +159,7 @@ async def update_playlist(userid:int, data:dict=None, push=False, pull=False, mo Playlist.update_one({"_id":userid}, {"$unset": data}) return -async def update_inbox(userid:int, data:dict): +async def update_inbox(userid:int, data:dict) -> None: return Playlist.update_one({"_id":userid}, {"$push":{'inbox':data}}) async def checkroles(userid:int): From f96690fa0bfd22a8baad7b1566206a47baca4f29 Mon Sep 17 00:00:00 2001 From: Choco <94597336+ChocoMeow@users.noreply.github.com> Date: Sun, 16 Jul 2023 15:39:46 +0800 Subject: [PATCH 7/9] Added decodeError message --- langs/CH.json | 4 +++- langs/DE.json | 4 +++- langs/EN.json | 4 +++- langs/ES.json | 4 +++- langs/JA.json | 4 +++- langs/KO.json | 4 +++- 6 files changed, 18 insertions(+), 6 deletions(-) diff --git a/langs/CH.json b/langs/CH.json index 1299922..4e6d71e 100644 --- a/langs/CH.json +++ b/langs/CH.json @@ -186,5 +186,7 @@ "voicelinkQueueFull": "抱歉,您已達到隊列中 `{0}` 首歌曲的最大數量!", "voicelinkOutofList": "請提供有效的歌曲索引!", - "voicelinkDuplicateTrack": "抱歉,此歌曲已在隊列中。" + "voicelinkDuplicateTrack": "抱歉,此歌曲已在隊列中。", + + "deocdeError": "解碼文件時出現問題!" } \ No newline at end of file diff --git a/langs/DE.json b/langs/DE.json index c11b8e1..ec83805 100644 --- a/langs/DE.json +++ b/langs/DE.json @@ -186,5 +186,7 @@ "voicelinkQueueFull": "Entschuldigung, du hast das Maximum von `{0}` Tracks in der Warteschlange erreicht!", "voicelinkOutofList": "Bitte gib einen gültigen Track-Index an!", - "voicelinkDuplicateTrack": "Entschuldigung, dieser Track ist bereits in der Warteschlange." + "voicelinkDuplicateTrack": "Entschuldigung, dieser Track ist bereits in der Warteschlange.", + + "deocdeError": "Beim Dekodieren der Datei ist etwas schief gelaufen!" } \ No newline at end of file diff --git a/langs/EN.json b/langs/EN.json index 1f06658..902eb0e 100644 --- a/langs/EN.json +++ b/langs/EN.json @@ -186,5 +186,7 @@ "voicelinkQueueFull": "Sorry, you have reached the maximum of `{0}` tracks in the queue!", "voicelinkOutofList": "Please provide a valid track index!", - "voicelinkDuplicateTrack": "Sorry, this track is already in the queue." + "voicelinkDuplicateTrack": "Sorry, this track is already in the queue.", + + "deocdeError": "Something went wrong while decoding the file!" } \ No newline at end of file diff --git a/langs/ES.json b/langs/ES.json index b7da52d..eab8a0c 100644 --- a/langs/ES.json +++ b/langs/ES.json @@ -186,5 +186,7 @@ "voicelinkQueueFull": "Lo siento, ¡ha alcanzado el máximo de `{0}` canciones en la cola!", "voicelinkOutofList": "¡Proporcione un índice de pista válido!", - "voicelinkDuplicateTrack": "Lo siento, esta canción ya está en la cola." + "voicelinkDuplicateTrack": "Lo siento, esta canción ya está en la cola.", + + "deocdeError": "¡Algo salió mal al decodificar el archivo!" } \ No newline at end of file diff --git a/langs/JA.json b/langs/JA.json index 2d178b7..3e738b1 100644 --- a/langs/JA.json +++ b/langs/JA.json @@ -186,5 +186,7 @@ "voicelinkQueueFull": "申し訳ありませんが、キュー内の曲数が最大値の`{0}`に達しました!", "voicelinkOutofList": "有効なトラックインデックスを指定してください!", - "voicelinkDuplicateTrack": "申し訳ありませんが、このトラックは既にキューに存在します。" + "voicelinkDuplicateTrack": "申し訳ありませんが、このトラックは既にキューに存在します。", + + "deocdeError": "ファイルのデコード中に問題が発生しました!" } \ No newline at end of file diff --git a/langs/KO.json b/langs/KO.json index 2d72440..27bd892 100644 --- a/langs/KO.json +++ b/langs/KO.json @@ -186,5 +186,7 @@ "voicelinkQueueFull": "죄송합니다. 큐에 `{0}`개의 트랙을 모두 추가하셨습니다!", "voicelinkOutofList": "유효한 트랙 인덱스를 제공해주세요!", - "voicelinkDuplicateTrack": "죄송합니다. 이 트랙은 이미 큐에 있습니다." + "voicelinkDuplicateTrack": "죄송합니다. 이 트랙은 이미 큐에 있습니다.", + + "deocdeError": "파일 디코딩 중 문제가 발생했습니다!" } \ No newline at end of file From 12d2e6fcfc6978fc68bed724d0e199167467f3fa Mon Sep 17 00:00:00 2001 From: Choco <94597336+ChocoMeow@users.noreply.github.com> Date: Sun, 16 Jul 2023 15:40:11 +0800 Subject: [PATCH 8/9] Fixed lavalink connection header --- voicelink/pool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/voicelink/pool.py b/voicelink/pool.py index d62bfd9..8bdebd4 100644 --- a/voicelink/pool.py +++ b/voicelink/pool.py @@ -120,7 +120,7 @@ def __init__( self._headers = { "Authorization": self._password, - "User-Id": str(self.bot.user.id), + "User-Id": str(bot.user.id), "Client-Name": f"Voicelink/{__version__}", 'Resume-Key': self.resume_key } From b2ed17ff50ea2e314e6237d5b6bfd455f7009982 Mon Sep 17 00:00:00 2001 From: Choco <94597336+ChocoMeow@users.noreply.github.com> Date: Sun, 16 Jul 2023 16:01:16 +0800 Subject: [PATCH 9/9] Ready for v2.6.5 --- README.md | 13 ++++++++----- update.py | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 0b7e8cf..a3db67a 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,16 @@ # Vocard (Discord Music Bot) -> Vocard is a simple custom Disocrd Music Bot built with Python & [discord.py](https://discordpy.readthedocs.io/en/stable/) - -Demo: -[Discord Bot Demo](https://discord.com/api/oauth2/authorize?client_id=890399639008866355&permissions=36708608&scope=bot%20applications.commands), +> Vocard is a simple custom Disocrd Music Bot built with Python & [discord.py](https://discordpy.readthedocs.io/en/stable/)
+Demo: [Discord Bot Demo](https://discord.com/api/oauth2/authorize?client_id=890399639008866355&permissions=36708608&scope=bot%20applications.commands), [Dashboard Demo](https://vocard.xyz) -## Tutorial +# Host for you? + + Patreon + + +# Tutorial Click on the image below to watch the tutorial on Youtube. [![Discord Music Bot](https://img.youtube.com/vi/f_Z0RLRZzWw/maxresdefault.jpg)](https://www.youtube.com/watch?v=f_Z0RLRZzWw) diff --git a/update.py b/update.py index 80515e7..33d6741 100644 --- a/update.py +++ b/update.py @@ -3,7 +3,7 @@ root_dir = os.path.dirname(os.path.abspath(__file__)) install_pack_dir = os.path.join(root_dir, "Vocard.zip") -__version__ = "v2.6.4a" +__version__ = "v2.6.5" def checkVersion(withMsg = False): resp = requests.get("https://api.github.com/repos/ChocoMeow/Vocard/releases/latest")