Skip to content

Commit

Permalink
Merge pull request #10 from ChocoMeow/beta
Browse files Browse the repository at this point in the history
Vocard v2.6.5 Update: added four commands and bugs fixed
  • Loading branch information
ChocoMeow authored Jul 16, 2023
2 parents 4bca89e + b2ed17f commit ef55050
Show file tree
Hide file tree
Showing 13 changed files with 197 additions and 29 deletions.
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
</a>

# 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/) <br>
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?
<a href="https://www.patreon.com/Vocard">
<img src="https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fshieldsio-patreon.vercel.app%2Fapi%3Fusername%3Dendel%26type%3Dpatrons&style=for-the-badge" alt="Patreon">
</a>

# 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)
Expand Down
77 changes: 76 additions & 1 deletion cogs/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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."
Expand All @@ -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):
"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)

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):
"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)

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()
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'))

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, ephemeral=True)

except:
return await ctx.send(player.get_msg("decodeError"), ephemeral=True)

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):
Expand Down
82 changes: 81 additions & 1 deletion cogs/playlist.py
Original file line number Diff line number Diff line change
@@ -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 (
Expand Down Expand Up @@ -205,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):
Expand Down Expand Up @@ -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))
22 changes: 11 additions & 11 deletions function.py
Original file line number Diff line number Diff line change
Expand Up @@ -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})
Expand All @@ -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():
Expand All @@ -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)
Expand All @@ -83,26 +83,26 @@ 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:
return False

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))
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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})
Expand All @@ -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):
Expand Down
4 changes: 3 additions & 1 deletion langs/CH.json
Original file line number Diff line number Diff line change
Expand Up @@ -186,5 +186,7 @@

"voicelinkQueueFull": "抱歉,您已達到隊列中 `{0}` 首歌曲的最大數量!",
"voicelinkOutofList": "請提供有效的歌曲索引!",
"voicelinkDuplicateTrack": "抱歉,此歌曲已在隊列中。"
"voicelinkDuplicateTrack": "抱歉,此歌曲已在隊列中。",

"deocdeError": "解碼文件時出現問題!"
}
4 changes: 3 additions & 1 deletion langs/DE.json
Original file line number Diff line number Diff line change
Expand Up @@ -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!"
}
4 changes: 3 additions & 1 deletion langs/EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -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!"
}
4 changes: 3 additions & 1 deletion langs/ES.json
Original file line number Diff line number Diff line change
Expand Up @@ -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!"
}
4 changes: 3 additions & 1 deletion langs/JA.json
Original file line number Diff line number Diff line change
Expand Up @@ -186,5 +186,7 @@

"voicelinkQueueFull": "申し訳ありませんが、キュー内の曲数が最大値の`{0}`に達しました!",
"voicelinkOutofList": "有効なトラックインデックスを指定してください!",
"voicelinkDuplicateTrack": "申し訳ありませんが、このトラックは既にキューに存在します。"
"voicelinkDuplicateTrack": "申し訳ありませんが、このトラックは既にキューに存在します。",

"deocdeError": "ファイルのデコード中に問題が発生しました!"
}
4 changes: 3 additions & 1 deletion langs/KO.json
Original file line number Diff line number Diff line change
Expand Up @@ -186,5 +186,7 @@

"voicelinkQueueFull": "죄송합니다. 큐에 `{0}`개의 트랙을 모두 추가하셨습니다!",
"voicelinkOutofList": "유효한 트랙 인덱스를 제공해주세요!",
"voicelinkDuplicateTrack": "죄송합니다. 이 트랙은 이미 큐에 있습니다."
"voicelinkDuplicateTrack": "죄송합니다. 이 트랙은 이미 큐에 있습니다.",

"deocdeError": "파일 디코딩 중 문제가 발생했습니다!"
}
2 changes: 1 addition & 1 deletion main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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) + "```"
Expand Down
2 changes: 1 addition & 1 deletion update.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Loading

0 comments on commit ef55050

Please sign in to comment.