Skip to content

Commit

Permalink
Merge pull request #12 from ChocoMeow/beta
Browse files Browse the repository at this point in the history
Vocard v2.6.6 Update: new features, bug fixes, and code clean-up
  • Loading branch information
ChocoMeow authored Aug 3, 2023
2 parents be2901c + e8323e0 commit 608e67a
Show file tree
Hide file tree
Showing 23 changed files with 700 additions and 381 deletions.
2 changes: 0 additions & 2 deletions .env Example
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,17 @@ MONGODB_NAME = Vocard
* For `default_controller` you can set custom embeds and buttons in controller, [Example Here](https://github.com/ChocoMeow/Vocard/blob/main/PLACEHOLDERS.md#controller-embeds)

## How to update? (For Windows and Linux)
1. Run `python update.py --check` to check if your bot is up to date
2. Run `python update.py --start` to start update your bot <br/>
***Note: Make sure there are no personal files in the directory! Otherwise it will be deleted.***
```sh
# Check the current version
python update.py -c

# Install the latest version
python update.py -l

# Install the specified version
python update.py -v VERSION

# Install the beta version
python update.py -b
```
1 change: 0 additions & 1 deletion addons/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
15 changes: 14 additions & 1 deletion cogs/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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:
Expand Down
43 changes: 38 additions & 5 deletions cogs/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand All @@ -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):
Expand Down
7 changes: 3 additions & 4 deletions cogs/listeners.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
178 changes: 69 additions & 109 deletions function.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {}
Expand All @@ -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:
Expand Down Expand Up @@ -168,98 +222,4 @@ async def update_inbox(userid:int, data:dict) -> None:
async def checkroles(userid:int):
rank, max_p, max_t = 'Normal', 5, 500

return rank, max_p, max_t

async def similar_track(player) -> bool:
trackids = [ track.identifier for track in player.queue.history(incTrack=True) if track.source == 'youtube' ]
randomTrack = choice(player.queue.history(incTrack=True)[-10:])
tracks = []

if randomTrack.spotify:
tracks = await player.spotifyRelatedTrack(randomTrack.identifier)
else:
if randomTrack.source != 'youtube':
return False

if not tokens.youtube_api_key:
return False

request_url = "https://youtube.googleapis.com/youtube/v3/search?part={part}&relatedToVideoId={videoId}&type={type}&videoCategoryId={videoCategoryId}&key={key}".format(
part="snippet",
videoId=randomTrack.identifier,
type="video",
videoCategoryId="10",
key=tokens.youtube_api_key
)

try:
data = await requests_api(request_url)
if not data:
return False

for item in data['items']:
if 'snippet' not in item:
continue
if item['id']['videoId'] not in trackids:
tracks = await player.get_tracks(f"https://www.youtube.com/watch?v={item['id']['videoId']}", requester=player._bot.user)
break
except:
return False

if tracks:
await player.add_track(tracks)
return True

return False

def time(millis:int) -> str:
seconds=(millis/1000)%60
minutes=(millis/(1000*60))%60
hours=(millis/(1000*60*60))%24
if hours > 1:
return "%02d:%02d:%02d" % (hours, minutes, seconds)
else:
return "%02d:%02d" % (minutes, seconds)

def formatTime(number:str) -> Optional[int]:
try:
try:
num = strptime(number, '%M:%S')
except ValueError:
try:
num = strptime(number, '%S')
except ValueError:
num = strptime(number, '%H:%M:%S')
except:
return None

return (int(num.tm_hour) * 3600 + int(num.tm_min) * 60 + int(num.tm_sec)) * 1000

def emoji_source(emoji:str):
return settings.emoji_source_raw.get(emoji.lower(), "🔗")

def gen_report() -> Optional[discord.File]:
if error_log:
errorText = ""
for guild_id, error in error_log.items():
errorText += f"Guild ID: {guild_id}\n" + "-" * 30 + "\n"
for index, (key, value) in enumerate(error.items() , start=1):
errorText += f"Error No: {index}, Time: {datetime.fromtimestamp(key)}\n" + value + "-" * 30 + "\n\n"

buffer = BytesIO(errorText.encode('utf-8'))
file = discord.File(buffer, filename='report.txt')
buffer.close()

return file
return None

def cooldown_check(ctx: commands.Context) -> Optional[commands.Cooldown]:
if ctx.author.id in settings.bot_access_user:
return None
cooldown = settings.cooldowns_settings.get(f"{ctx.command.parent.qualified_name} {ctx.command.name}" if ctx.command.parent else ctx.command.name)
if not cooldown:
return None
return commands.Cooldown(cooldown[0], cooldown[1])

def get_aliases(name: str) -> list:
return settings.aliases_settings.get(name, [])
return rank, max_p, max_t
Loading

0 comments on commit 608e67a

Please sign in to comment.