Merge pull request #14 from ChocoMeow/beta
Vocard v2.6.7 Update: new features, bug fixes, and code clean-up
This commit is contained in:
@@ -34,7 +34,7 @@ Click on the image below to watch the tutorial on Youtube.
|
||||
## Requirements
|
||||
* [Python 3.10+](https://www.python.org/downloads/)
|
||||
* [Modules in requirements](https://github.com/ChocoMeow/Vocard/blob/main/requirements.txt)
|
||||
* [Lavalink Server (Requires 3.7.0+)](https://github.com/freyacodes/Lavalink)
|
||||
* [Lavalink Server (Requires 4.0.0+)](https://github.com/freyacodes/Lavalink)
|
||||
|
||||
## Quick Start
|
||||
```sh
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import aiohttp, random, bs4, re
|
||||
import function as func
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from urllib.parse import quote
|
||||
from math import floor
|
||||
from importlib import import_module
|
||||
@@ -46,7 +47,12 @@ Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_6; en-US) AppleWebKit/530.9 (KHTM
|
||||
Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_6; en-US) AppleWebKit/530.6 (KHTML, like Gecko) Chrome/ Safari/530.6
|
||||
Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_6; en-US) AppleWebKit/530.5 (KHTML, like Gecko) Chrome/ Safari/530.5'''
|
||||
|
||||
class A_ZLyrics():
|
||||
class LyricsPlatform(ABC):
|
||||
@abstractmethod
|
||||
async def getLyrics():
|
||||
...
|
||||
|
||||
class A_ZLyrics(LyricsPlatform):
|
||||
async def get(self, url):
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
@@ -166,7 +172,7 @@ class A_ZLyrics():
|
||||
|
||||
return text
|
||||
|
||||
class Genius():
|
||||
class Genius(LyricsPlatform):
|
||||
def __init__(self) -> None:
|
||||
self.module = import_module("lyricsgenius")
|
||||
self.genius = self.module.Genius(func.tokens.genius_token)
|
||||
@@ -178,7 +184,7 @@ class Genius():
|
||||
|
||||
return {"default": song.lyrics}
|
||||
|
||||
lyricsPlatform = {
|
||||
lyricsPlatform: dict[str, LyricsPlatform] = {
|
||||
"a_zlyrics": A_ZLyrics,
|
||||
"genius": Genius
|
||||
}
|
||||
@@ -13,42 +13,9 @@ class Settings:
|
||||
self.emoji_source_raw = settings.get("emoji_source_raw", {})
|
||||
self.cooldowns_settings = settings.get("cooldowns", {})
|
||||
self.aliases_settings = settings.get("aliases", {})
|
||||
self.controller = settings.get("default_controller",
|
||||
{
|
||||
"embeds": {
|
||||
"active": {
|
||||
"description": "**Now Playing: ```[@@track_name@@]```\nLink: [Click Me](@@track_url@@) | Requester: @@requester@@ | DJ: @@dj@@**",
|
||||
"footer": {
|
||||
"text": "Queue Length: @@queue_length@@ | Duration: @@duration@@ | Volume: @@volume@@% {{loop_mode!=Off ?? | Repeat: @@loop_mode@@}}",
|
||||
},
|
||||
"image": "@@track_thumbnail@@",
|
||||
"author": {
|
||||
"name": "Music Controller | @@channel_name@@",
|
||||
"icon_url": "@@bot_icon@@"
|
||||
},
|
||||
"color": "@@default_embed_color@@"
|
||||
},
|
||||
"inactive": {
|
||||
"title": {
|
||||
"name": "There are no songs playing right now"
|
||||
},
|
||||
"description": "[Support](@@server_invite_link@@) | [Invite](@@invite_link@@) | [Questionnaire](https://forms.gle/Qm8vjBfg2kp13YGD7)",
|
||||
"image": "https://i.imgur.com/dIFBwU7.png",
|
||||
"color": "@@default_embed_color@@"
|
||||
}
|
||||
},
|
||||
"default_buttons": [
|
||||
["back", "resume", "skip", {"stop": "red"}, "add"],
|
||||
["tracks"]
|
||||
]
|
||||
})
|
||||
self.controller = settings.get("default_controller", {})
|
||||
self.lyrics_platform = settings.get("lyrics_platform", "A_ZLyrics").lower()
|
||||
self.ipc_server = settings.get("ipc_server", {
|
||||
"host": "127.0.0.1",
|
||||
"port": 8000,
|
||||
"enable": False
|
||||
}
|
||||
)
|
||||
self.ipc_server = settings.get("ipc_server", {})
|
||||
self.version = settings.get("version", "")
|
||||
|
||||
class TOKENS:
|
||||
|
||||
@@ -1,3 +1,26 @@
|
||||
"""MIT License
|
||||
|
||||
Copyright (c) 2023 - present Vocard Development
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
import discord
|
||||
import voicelink
|
||||
import re
|
||||
@@ -33,22 +56,20 @@ async def nowplay(ctx: commands.Context, player: voicelink.Player):
|
||||
if not track:
|
||||
return await ctx.send(player.get_msg('noTrackPlaying'), ephemeral=True)
|
||||
|
||||
upnext = "\n".join(f"`{index}.` `[{track.formatLength}]` [{track.title[:30]}]({track.uri})" for index, track in enumerate(
|
||||
player.queue.tracks()[:2], start=2))
|
||||
embed = discord.Embed(description=player.get_msg(
|
||||
'nowplayingDesc').format(track.title), color=settings.embed_color)
|
||||
embed.set_author(name=track.requester if track.requester else ctx.bot,
|
||||
icon_url=track.requester.display_avatar.url if track.requester else ctx.me.display_avatar.url)
|
||||
upnext = "\n".join(f"`{index}.` `[{track.formatted_length}]` [{track.title[:30]}]({track.uri})" for index, track in enumerate(player.queue.tracks()[:2], start=2))
|
||||
embed = discord.Embed(description=player.get_msg('nowplayingDesc').format(track.title), color=settings.embed_color)
|
||||
embed.set_author(
|
||||
name=track.requester,
|
||||
icon_url=track.requester.display_avatar.url
|
||||
)
|
||||
embed.set_thumbnail(url=track.thumbnail)
|
||||
|
||||
if upnext:
|
||||
embed.add_field(name=player.get_msg('nowplayingField'), value=upnext)
|
||||
pbar = "".join(":radio_button:" if i == round(
|
||||
player.position // round(track.length // 15)) else "▬" for i in range(15))
|
||||
icon = ":red_circle:" if track.is_stream else (
|
||||
":pause_button:" if player.is_paused else ":arrow_forward:")
|
||||
|
||||
embed.add_field(
|
||||
name="\u2800", value=f"{icon} {pbar} **[{ctime(player.position)}/{track.formatLength}]**", inline=False)
|
||||
pbar = "".join(":radio_button:" if i == round(player.position // round(track.length // 15)) else "▬" for i in range(15))
|
||||
icon = ":red_circle:" if track.is_stream else (":pause_button:" if player.is_paused else ":arrow_forward:")
|
||||
embed.add_field(name="\u2800", value=f"{icon} {pbar} **[{ctime(player.position)}/{track.formatted_length}]**", inline=False)
|
||||
|
||||
return await ctx.send(embed=embed, view=LinkView(player.get_msg('nowplayingLink').format(track.source), track.emoji, track.uri))
|
||||
|
||||
@@ -63,8 +84,7 @@ class Basic(commands.Cog):
|
||||
self.bot.tree.add_command(self.ctx_menu)
|
||||
|
||||
async def cog_unload(self) -> None:
|
||||
self.bot.tree.remove_command(
|
||||
self.ctx_menu.name, type=self.ctx_menu.type)
|
||||
self.bot.tree.remove_command(self.ctx_menu.name, type=self.ctx_menu.type)
|
||||
|
||||
async def help_autocomplete(self, interaction: discord.Interaction, current: str) -> list:
|
||||
return [app_commands.Choice(name=c.capitalize(), value=c) for c in self.bot.cogs if c not in ["Nodes", "Task"] and current in c]
|
||||
@@ -103,7 +123,7 @@ class Basic(commands.Cog):
|
||||
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].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)
|
||||
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].formatted_length, 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].formatted_length)), allowed_mentions=False)
|
||||
except voicelink.QueueFull as e:
|
||||
await ctx.send(e)
|
||||
finally:
|
||||
@@ -143,7 +163,7 @@ class Basic(commands.Cog):
|
||||
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].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)
|
||||
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].formatted_length, 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].formatted_length)), allowed_mentions=False)
|
||||
except voicelink.QueueFull as e:
|
||||
await interaction.response.send_message(e)
|
||||
|
||||
@@ -190,20 +210,20 @@ class Basic(commands.Cog):
|
||||
return await ctx.send(player.get_msg('noTrackFound'))
|
||||
|
||||
query_track = "\n".join(
|
||||
f"`{index}.` `[{track.formatLength}]` **{track.title[:35]}**" for index, track in enumerate(tracks[0:10], start=1))
|
||||
f"`{index}.` `[{track.formatted_length}]` **{track.title[:35]}**" for index, track in enumerate(tracks[0:10], start=1))
|
||||
embed = discord.Embed(title=player.get_msg('searchTitle').format(query), description=player.get_msg(
|
||||
'searchDesc').format(emoji_source(platform), platform, len(tracks[0:10]), query_track), color=settings.embed_color)
|
||||
view = SearchView(tracks=tracks[0:10], lang=player.lang)
|
||||
message = await ctx.send(embed=embed, view=view, ephemeral=True)
|
||||
view.response = message
|
||||
view = SearchView(tracks=tracks[0:10], lang=player.get_msg)
|
||||
view.response = await ctx.send(embed=embed, view=view, ephemeral=True)
|
||||
|
||||
await view.wait()
|
||||
if view.values is not None:
|
||||
msg = ""
|
||||
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.uri, track.author, track.formatLength,
|
||||
position) if position >= 1 else player.get_msg('trackLoad').format(track.title, track.uri, 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.formatted_length,
|
||||
position) if position >= 1 else player.get_msg('trackLoad').format(track.title, track.uri, track.author, track.formatted_length)))
|
||||
await ctx.send(msg, allowed_mentions=False)
|
||||
|
||||
if not player.is_playing:
|
||||
@@ -231,7 +251,7 @@ class Basic(commands.Cog):
|
||||
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].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)
|
||||
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].formatted_length, 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].formatted_length)), allowed_mentions=False)
|
||||
|
||||
except voicelink.QueueFull as e:
|
||||
await ctx.send(e)
|
||||
@@ -262,7 +282,7 @@ class Basic(commands.Cog):
|
||||
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)
|
||||
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].formatted_length), allowed_mentions=False)
|
||||
|
||||
except voicelink.QueueFull as e:
|
||||
await ctx.send(e)
|
||||
@@ -419,6 +439,7 @@ class Basic(commands.Cog):
|
||||
@commands.hybrid_group(
|
||||
name="queue",
|
||||
aliases=get_aliases("queue"),
|
||||
fallback="list",
|
||||
invoke_without_command=True
|
||||
)
|
||||
@commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild)
|
||||
@@ -434,8 +455,7 @@ class Basic(commands.Cog):
|
||||
if player.queue.is_empty:
|
||||
return await nowplay(ctx, player)
|
||||
view = ListView(player=player, author=ctx.author)
|
||||
message = await ctx.send(embed=view.build_embed(), view=view)
|
||||
view.response = message
|
||||
view.response = await ctx.send(embed=view.build_embed(), view=view)
|
||||
|
||||
@queue.command(name="export", aliases=get_aliases("export"))
|
||||
@commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild)
|
||||
@@ -522,8 +542,7 @@ class Basic(commands.Cog):
|
||||
return await nowplay(ctx, player)
|
||||
|
||||
view = ListView(player=player, author=ctx.author, isQueue=False)
|
||||
message = await ctx.send(embed=view.build_embed(), view=view)
|
||||
view.response = message
|
||||
view.response = await ctx.send(embed=view.build_embed(), view=view)
|
||||
|
||||
@commands.hybrid_command(name="leave", aliases=get_aliases("leave"))
|
||||
@commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild)
|
||||
@@ -773,13 +792,12 @@ class Basic(commands.Cog):
|
||||
name = player.current.title + " " + player.current.author
|
||||
await ctx.defer()
|
||||
|
||||
song = await lyricsPlatform.get(settings.lyrics_platform)().getLyrics(name)
|
||||
song: dict[str, str] = await lyricsPlatform.get(settings.lyrics_platform)().getLyrics(name)
|
||||
if not song:
|
||||
return await ctx.send(get_lang(ctx.guild.id, 'lyricsNotFound'), ephemeral=True)
|
||||
|
||||
view = LyricsView(name=name, source={_: re.findall(r'.*\n(?:.*\n){,22}', v) for _, v in song.items()}, author=ctx.author)
|
||||
message = await ctx.send(embed=view.build_embed(), view=view)
|
||||
view.response = message
|
||||
view.response = await ctx.send(embed=view.build_embed(), view=view)
|
||||
|
||||
@commands.hybrid_command(name="swapdj", aliases=get_aliases("swapdj"))
|
||||
@app_commands.describe(member="Choose a member to transfer the dj role.")
|
||||
@@ -841,8 +859,7 @@ class Basic(commands.Cog):
|
||||
return await ctx.send(player.get_msg('noChaptersFound'), ephemeral=True)
|
||||
|
||||
view = ChapterView(player, chapters, author=ctx.author)
|
||||
message = await ctx.send(view=view)
|
||||
view.response = message
|
||||
view.response = await ctx.send(view=view)
|
||||
|
||||
@commands.hybrid_command(name="autoplay", aliases=get_aliases("autoplay"))
|
||||
@commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild)
|
||||
@@ -871,8 +888,7 @@ class Basic(commands.Cog):
|
||||
category = "News"
|
||||
view = HelpView(self.bot, ctx.author)
|
||||
embed = view.build_embed(category)
|
||||
message = await ctx.send(embed=embed, view=view)
|
||||
view.response = message
|
||||
view.response = await ctx.send(embed=embed, view=view)
|
||||
|
||||
@commands.hybrid_command(name="ping", aliases=get_aliases("ping"))
|
||||
@commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild)
|
||||
|
||||
@@ -1,3 +1,26 @@
|
||||
"""MIT License
|
||||
|
||||
Copyright (c) 2023 - present Vocard Development
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
import discord
|
||||
import voicelink
|
||||
|
||||
|
||||
@@ -1,3 +1,26 @@
|
||||
"""MIT License
|
||||
|
||||
Copyright (c) 2023 - present Vocard Development
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
import voicelink
|
||||
import asyncio
|
||||
import discord
|
||||
@@ -5,7 +28,7 @@ import function as func
|
||||
|
||||
from discord.ext import commands
|
||||
|
||||
class Nodes(commands.Cog):
|
||||
class Listeners(commands.Cog):
|
||||
"""Music Cog."""
|
||||
|
||||
def __init__(self, bot: commands.Bot):
|
||||
@@ -36,10 +59,10 @@ class Nodes(commands.Cog):
|
||||
await player.do_next()
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_voicelink_track_exception(self, player: voicelink.Player, track, _):
|
||||
async def on_voicelink_track_exception(self, player: voicelink.Player, track, error: dict):
|
||||
try:
|
||||
player._track_is_stuck = True
|
||||
await player.context.send(f"{_} Please wait for 5 seconds.", delete_after=10)
|
||||
await player.context.send(f"{error['message']}! The next song will begin in the next 5 seconds.", delete_after=10)
|
||||
except:
|
||||
pass
|
||||
|
||||
@@ -73,7 +96,7 @@ class Nodes(commands.Cog):
|
||||
"op": "updateGuild",
|
||||
"user": {
|
||||
"user_id": member.id,
|
||||
"avatar_url": member.avatar.url,
|
||||
"avatar_url": member.display_avatar.url,
|
||||
"name": member.name,
|
||||
},
|
||||
"channel_name": member.voice.channel.name if is_joined else "",
|
||||
@@ -82,4 +105,4 @@ class Nodes(commands.Cog):
|
||||
})
|
||||
|
||||
async def setup(bot: commands.Bot) -> None:
|
||||
await bot.add_cog(Nodes(bot))
|
||||
await bot.add_cog(Listeners(bot))
|
||||
|
||||
@@ -1,3 +1,26 @@
|
||||
"""MIT License
|
||||
|
||||
Copyright (c) 2023 - present Vocard Development
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
import discord
|
||||
import voicelink
|
||||
|
||||
@@ -8,11 +31,11 @@ from function import (
|
||||
time as ctime,
|
||||
get_playlist,
|
||||
create_account,
|
||||
checkroles,
|
||||
check_roles,
|
||||
update_playlist,
|
||||
update_inbox,
|
||||
get_lang,
|
||||
playlist_name,
|
||||
PLAYLIST_NAME,
|
||||
settings,
|
||||
get_aliases,
|
||||
cooldown_check
|
||||
@@ -70,10 +93,10 @@ class Playlists(commands.Cog, name="playlist"):
|
||||
self.description = "This is the Vocard playlist system. You can save your favorites and use Vocard to play on any server."
|
||||
|
||||
async def playlist_autocomplete(self, interaction: discord.Interaction, current: str) -> list:
|
||||
playlists = playlist_name.get(str(interaction.user.id), None)
|
||||
playlists = PLAYLIST_NAME.get(str(interaction.user.id), None)
|
||||
if not playlists:
|
||||
playlists_raw = await get_playlist(interaction.user.id, 'playlist')
|
||||
playlists = playlist_name[str(interaction.user.id)] = [
|
||||
playlists = PLAYLIST_NAME[str(interaction.user.id)] = [
|
||||
value['name'] for value in playlists_raw.values()] if playlists_raw else []
|
||||
if current:
|
||||
return [app_commands.Choice(name=p, value=p) for p in playlists if current in p]
|
||||
@@ -87,8 +110,7 @@ class Playlists(commands.Cog, name="playlist"):
|
||||
async def playlist(self, ctx: commands.Context):
|
||||
view = HelpView(self.bot, ctx.author)
|
||||
embed = view.build_embed(self.qualified_name)
|
||||
message = await ctx.send(embed=embed, view=view)
|
||||
view.response = message
|
||||
view.response = await ctx.send(embed=embed, view=view)
|
||||
|
||||
@playlist.command(name="play", aliases=get_aliases("play"))
|
||||
@app_commands.describe(
|
||||
@@ -104,7 +126,7 @@ class Playlists(commands.Cog, name="playlist"):
|
||||
return await create_account(ctx)
|
||||
if not result['playlist']:
|
||||
return await ctx.send(get_lang(ctx.guild.id, 'playlistNotFound').format(name), ephemeral=True)
|
||||
rank, max_p, max_t = await checkroles(ctx.author.id)
|
||||
rank, max_p, max_t = check_roles()
|
||||
if result['position'] > max_p:
|
||||
return await ctx.send(get_lang(ctx.guild.id, 'playlistNotAccess'), ephemeral=True)
|
||||
|
||||
@@ -142,7 +164,7 @@ class Playlists(commands.Cog, name="playlist"):
|
||||
user = await check_playlist(ctx, full=True)
|
||||
if not user:
|
||||
return await create_account(ctx)
|
||||
rank, max_p, max_t = await checkroles(ctx.author.id)
|
||||
rank, max_p, max_t = check_roles()
|
||||
|
||||
results = []
|
||||
for index, data in enumerate(user, start=1):
|
||||
@@ -156,7 +178,7 @@ class Playlists(commands.Cog, name="playlist"):
|
||||
if share := playlist['type'] == 'share':
|
||||
playlist = await check_playlist_perms(ctx.author.id, playlist['user'], playlist['referId'])
|
||||
if not playlist:
|
||||
await update_playlist(ctx.author.id, {f"playlist.{data}": 1}, mode=False)
|
||||
await update_playlist(ctx.author.id, {f"playlist.{data}": 1}, mode="unset")
|
||||
continue
|
||||
if playlist['type'] == 'link':
|
||||
tracks = await search_playlist(playlist['uri'], requester=ctx.author)
|
||||
@@ -169,7 +191,7 @@ class Playlists(commands.Cog, name="playlist"):
|
||||
init.append(dt)
|
||||
playlist['tracks'] = init
|
||||
results.append({'emoji': ('🔒' if max_p < index else ('🤝' if share else '❤️')), 'id': data, 'time': ctime(time), 'name': user[data]['name'], 'tracks': playlist['tracks'], 'perms': playlist['perms'], 'owner': user[data].get('user', None), 'type': user[data]['type']})
|
||||
|
||||
|
||||
except Exception as e:
|
||||
results.append({'emoji': '⛔', 'id': data, 'time': '00:00', 'name': 'Error', 'tracks': [], 'type': 'error'})
|
||||
|
||||
@@ -182,8 +204,7 @@ class Playlists(commands.Cog, name="playlist"):
|
||||
embed.set_footer(text=get_lang(ctx.guild.id, 'playlistFooter'))
|
||||
|
||||
view = PlaylistView(embed, results, ctx.author)
|
||||
messsage = await ctx.send(embed=embed, view=view, ephemeral=True)
|
||||
view.response = messsage
|
||||
view.response = await ctx.send(embed=embed, view=view, ephemeral=True)
|
||||
|
||||
@playlist.command(name="create", aliases=get_aliases("create"))
|
||||
@app_commands.describe(
|
||||
@@ -196,7 +217,7 @@ class Playlists(commands.Cog, name="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)
|
||||
rank, max_p, max_t = check_roles()
|
||||
user = await check_playlist(ctx, full=True)
|
||||
if not user:
|
||||
return await create_account(ctx)
|
||||
@@ -212,9 +233,8 @@ class Playlists(commands.Cog, name="playlist"):
|
||||
if not isinstance(tracks, voicelink.Playlist):
|
||||
return await ctx.send(get_lang(ctx.guild.id, 'playlistNotInvaildUrl'), ephemeral=True)
|
||||
|
||||
playlist_name.pop(str(ctx.author.id), None)
|
||||
data = {'uri': link, 'perms': {'read': []}, 'name': name, 'type': 'link'} if link else {'tracks': [], '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 update_playlist(ctx.author.id, {f"playlist.{assign_playlistId([data for data in user])}": data}, update_cache=True)
|
||||
await ctx.send(get_lang(ctx.guild.id, 'playlistCreated').format(name))
|
||||
|
||||
@playlist.command(name="delete", aliases=get_aliases("delete"))
|
||||
@@ -232,10 +252,9 @@ class Playlists(commands.Cog, name="playlist"):
|
||||
return await ctx.send(get_lang(ctx.guild.id, 'playlistDeleteError'), ephemeral=True)
|
||||
|
||||
if result['playlist']['type'] == 'share':
|
||||
await update_playlist(result['playlist']['user'], {f"playlist.{result['playlist']['referId']}.perms.read": ctx.author.id}, pull=True, mode=False)
|
||||
await update_playlist(result['playlist']['user'], {f"playlist.{result['playlist']['referId']}.perms.read": ctx.author.id}, mode="pull")
|
||||
|
||||
playlist_name.pop(str(ctx.author.id), None)
|
||||
await update_playlist(ctx.author.id, {f"playlist.{result['id']}": 1}, mode=False)
|
||||
await update_playlist(ctx.author.id, {f"playlist.{result['id']}": 1}, mode="unset", update_cache=True)
|
||||
return await ctx.send(get_lang(ctx.guild.id, 'playlistRemove').format(result['playlist']['name']))
|
||||
|
||||
@playlist.command(name="share", aliases=get_aliases("share"))
|
||||
@@ -300,8 +319,7 @@ class Playlists(commands.Cog, name="playlist"):
|
||||
if not found:
|
||||
return await ctx.send(get_lang(ctx.guild.id, 'playlistNotFound').format(name), ephemeral=True)
|
||||
|
||||
playlist_name.pop(str(ctx.author.id), None)
|
||||
await update_playlist(ctx.author.id, {f'playlist.{id}.name': newname})
|
||||
await update_playlist(ctx.author.id, {f'playlist.{id}.name': newname}, update_cache=True)
|
||||
await ctx.send(get_lang(ctx.guild.id, 'playlistRenamed').format(name, newname))
|
||||
|
||||
@playlist.command(name="inbox", aliases=get_aliases("inbox"))
|
||||
@@ -316,8 +334,7 @@ class Playlists(commands.Cog, name="playlist"):
|
||||
|
||||
inbox = user['inbox'].copy()
|
||||
view = InboxView(ctx.author, user['inbox'])
|
||||
message = await ctx.send(embed=view.build_embed(), view=view, ephemeral=True)
|
||||
view.response = message
|
||||
view.response = await ctx.send(embed=view.build_embed(), view=view, ephemeral=True)
|
||||
await view.wait()
|
||||
|
||||
if inbox == user['inbox']:
|
||||
@@ -325,13 +342,12 @@ class Playlists(commands.Cog, name="playlist"):
|
||||
updateData, dId = {}, {dId for dId in user["playlist"]}
|
||||
for data in view.newplaylist[:(5 - len(user['playlist']))]:
|
||||
addId = assign_playlistId(dId)
|
||||
await update_playlist(data['sender'], {f"playlist.{data['referId']}.perms.read": ctx.author.id}, push=True)
|
||||
await update_playlist(data['sender'], {f"playlist.{data['referId']}.perms.read": ctx.author.id}, mode="push")
|
||||
updateData[f'playlist.{addId}'] = {'user': data['sender'], 'referId': data['referId'],
|
||||
'name': f"Share{data['time'].strftime('%M%S')}", 'type': 'share'}
|
||||
dId.add(addId)
|
||||
|
||||
playlist_name.pop(str(ctx.author.id), None)
|
||||
await update_playlist(ctx.author.id, updateData | {'inbox': view.inbox})
|
||||
await update_playlist(ctx.author.id, updateData | {'inbox': view.inbox}, update_cache=True)
|
||||
|
||||
@playlist.command(name="add", aliases=get_aliases("add"))
|
||||
@commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild)
|
||||
@@ -350,7 +366,7 @@ class Playlists(commands.Cog, name="playlist"):
|
||||
if result['playlist']['type'] in ['share', 'link']:
|
||||
return await ctx.send(get_lang(ctx.guild.id, 'playlistNotAllow'), ephemeral=True)
|
||||
|
||||
rank, max_p, max_t = await checkroles(ctx.author.id)
|
||||
rank, max_p, max_t = check_roles()
|
||||
if len(result['playlist']['tracks']) >= max_t:
|
||||
return await ctx.send(get_lang(ctx.guild.id, 'playlistLimitTrack').format(max_t), ephemeral=True)
|
||||
|
||||
@@ -364,7 +380,7 @@ class Playlists(commands.Cog, name="playlist"):
|
||||
if results[0].is_stream:
|
||||
return await ctx.send(get_lang(ctx.guild.id, 'playlistStream'), ephemeral=True)
|
||||
|
||||
await update_playlist(ctx.author.id, {f'playlist.{result["id"]}.tracks': results[0].track_id}, push=True)
|
||||
await update_playlist(ctx.author.id, {f'playlist.{result["id"]}.tracks': results[0].track_id}, mode="push")
|
||||
await ctx.send(get_lang(ctx.guild.id, 'playlistAdded').format(results[0].title, ctx.author, result['playlist']['name']))
|
||||
|
||||
@playlist.command(name="remove", aliases=get_aliases("remove"))
|
||||
@@ -386,7 +402,7 @@ class Playlists(commands.Cog, name="playlist"):
|
||||
if not 0 < position <= len(result['playlist']['tracks']):
|
||||
return await ctx.send(get_lang(ctx.guild.id, 'playlistPositionNotFound').format(position, name))
|
||||
|
||||
await update_playlist(ctx.author.id, {f'playlist.{result["id"]}.tracks': result['playlist']['tracks'][position - 1]}, pull=True, mode=False)
|
||||
await update_playlist(ctx.author.id, {f'playlist.{result["id"]}.tracks': result['playlist']['tracks'][position - 1]}, mode="pull")
|
||||
|
||||
track = voicelink.decode(result['playlist']['tracks'][position - 1])
|
||||
await ctx.send(get_lang(ctx.guild.id, 'playlistRemoved').format(track.get("title"), ctx.author, name))
|
||||
@@ -462,7 +478,7 @@ class Playlists(commands.Cog, name="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)
|
||||
rank, max_p, max_t = check_roles()
|
||||
user = await check_playlist(ctx, full=True)
|
||||
if not user:
|
||||
return await create_account(ctx)
|
||||
@@ -479,9 +495,8 @@ class Playlists(commands.Cog, name="playlist"):
|
||||
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 update_playlist(ctx.author.id, {f"playlist.{assign_playlistId([data for data in user])}": data}, update_cache=True)
|
||||
await ctx.send(get_lang(ctx.guild.id, 'playlistCreated').format(name))
|
||||
|
||||
except:
|
||||
|
||||
@@ -1,16 +1,36 @@
|
||||
"""MIT License
|
||||
|
||||
Copyright (c) 2023 - present Vocard Development
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
import discord
|
||||
import voicelink
|
||||
import io
|
||||
import contextlib
|
||||
import textwrap
|
||||
import traceback
|
||||
import psutil
|
||||
import function as func
|
||||
|
||||
from typing import Tuple
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from function import (
|
||||
langs,
|
||||
LANGS,
|
||||
update_settings,
|
||||
get_settings,
|
||||
get_lang,
|
||||
@@ -19,11 +39,18 @@ from function import (
|
||||
get_aliases,
|
||||
cooldown_check
|
||||
)
|
||||
from views import DebugModal, HelpView, EmbedBuilderView
|
||||
from views import DebugView, HelpView, EmbedBuilderView
|
||||
|
||||
class Admin(commands.Cog, name="settings"):
|
||||
def formatBytes(bytes: int, unit: bool = False):
|
||||
if bytes <= 1_000_000_000:
|
||||
return f"{bytes / (1024 ** 2):.1f}" + ("MB" if unit else "")
|
||||
|
||||
else:
|
||||
return f"{bytes / (1024 ** 3):.1f}" + ("GB" if unit else "")
|
||||
|
||||
class Settings(commands.Cog, name="settings"):
|
||||
def __init__(self, bot) -> None:
|
||||
self.bot = bot
|
||||
self.bot: commands.Bot = bot
|
||||
self.description = "This category is only available to admin permissions on the server."
|
||||
|
||||
def get_settings(self, ctx: commands.Context) -> Tuple[voicelink.Player, dict]:
|
||||
@@ -43,8 +70,7 @@ class Admin(commands.Cog, name="settings"):
|
||||
async def settings(self, ctx: commands.Context):
|
||||
view = HelpView(self.bot, ctx.author)
|
||||
embed = view.build_embed(self.qualified_name)
|
||||
message = await ctx.send(embed=embed, view=view)
|
||||
view.response = message
|
||||
view.response = await ctx.send(embed=embed, view=view)
|
||||
|
||||
@settings.command(name="prefix", aliases=get_aliases("prefix"))
|
||||
@commands.has_permissions(manage_guild=True)
|
||||
@@ -60,21 +86,17 @@ class Admin(commands.Cog, name="settings"):
|
||||
async def language(self, ctx: commands.Context, language: str):
|
||||
"You can choose your preferred language, the bot message will change to the language you set."
|
||||
language = language.upper()
|
||||
if language not in langs:
|
||||
if language not in LANGS:
|
||||
return await ctx.send(get_lang(ctx.guild.id, "languageNotFound"))
|
||||
|
||||
player, settings = self.get_settings(ctx)
|
||||
if player:
|
||||
player.lang = language
|
||||
|
||||
update_settings(ctx.guild.id, {'lang': language})
|
||||
await ctx.send(get_lang(ctx.guild.id, 'changedLanguage').format(language))
|
||||
|
||||
@language.autocomplete('language')
|
||||
async def autocomplete_callback(self, interaction: discord.Interaction, current: str) -> list:
|
||||
if current:
|
||||
return [app_commands.Choice(name=lang, value=lang) for lang in langs.keys() if current.upper() in lang]
|
||||
return [app_commands.Choice(name=lang, value=lang) for lang in langs.keys()]
|
||||
return [app_commands.Choice(name=lang, value=lang) for lang in LANGS.keys() if current.upper() in lang]
|
||||
return [app_commands.Choice(name=lang, value=lang) for lang in LANGS.keys()]
|
||||
|
||||
@settings.command(name="dj", aliases=get_aliases("dj"))
|
||||
@commands.has_permissions(manage_guild=True)
|
||||
@@ -86,7 +108,7 @@ class Admin(commands.Cog, name="settings"):
|
||||
if not role:
|
||||
if player:
|
||||
player.settings.pop('dj', None)
|
||||
update_settings(ctx.guild.id, {'dj': ''}, mode="Unset")
|
||||
update_settings(ctx.guild.id, {'dj': ''}, mode="unset")
|
||||
else:
|
||||
if player:
|
||||
player.settings['dj'] = role.id
|
||||
@@ -229,8 +251,7 @@ class Admin(commands.Cog, name="settings"):
|
||||
controller_settings = settings.get("default_controller", func.settings.controller)
|
||||
|
||||
view = EmbedBuilderView(ctx.author, controller_settings.get("embeds").copy())
|
||||
message = await ctx.send(embed=view.build_embed(), view=view)
|
||||
view.response = message
|
||||
view.response = await ctx.send(embed=view.build_embed(), view=view)
|
||||
|
||||
@settings.command(name="controllermsg", aliases=get_aliases("controllermsg"))
|
||||
@commands.has_permissions(manage_guild=True)
|
||||
@@ -250,53 +271,41 @@ class Admin(commands.Cog, name="settings"):
|
||||
if interaction.user.id not in func.settings.bot_access_user:
|
||||
return await interaction.response.send_message("You are not able to use this command!")
|
||||
|
||||
def clear_code(content: str):
|
||||
if content.startswith("```") and content.endswith("```"):
|
||||
return "\n".join(content.split("\n")[1:])[:-3]
|
||||
else:
|
||||
return content
|
||||
memory = psutil.virtual_memory()
|
||||
disk = psutil.disk_usage('/')
|
||||
|
||||
modal = DebugModal(title="Debug Panel")
|
||||
await interaction.response.send_modal(modal)
|
||||
await modal.wait()
|
||||
available_memory, total_memory = memory.available, memory.total
|
||||
used_disk_space, total_disk_space = disk.used, disk.total
|
||||
embed = discord.Embed(title="📄 Debug Panel", color=func.settings.embed_color)
|
||||
embed.description = "```== System Info ==\n" \
|
||||
f"• CPU: {psutil.cpu_freq().current}Mhz ({psutil.cpu_percent()}%)\n" \
|
||||
f"• RAM: {formatBytes(total_memory - available_memory)}/{formatBytes(total_memory, True)} ({memory.percent}%)\n" \
|
||||
f"• DISK: {formatBytes(total_disk_space - used_disk_space)}/{formatBytes(total_disk_space, True)} ({disk.percent}%)```"
|
||||
|
||||
if modal.values is None:
|
||||
return
|
||||
embed.add_field(
|
||||
name="🤖 Bot Information",
|
||||
value=f"```• LATENCY: {self.bot.latency:.2f}ms\n" \
|
||||
f"• GUILDS: {len(self.bot.guilds)}\n" \
|
||||
f"• USERS: {sum([guild.member_count for guild in self.bot.guilds])}\n" \
|
||||
f"• PLAYERS: {len(self.bot.voice_clients)}```",
|
||||
inline=False
|
||||
)
|
||||
|
||||
e = None
|
||||
|
||||
local_variables = {
|
||||
"discord": discord,
|
||||
"commands": commands,
|
||||
"voicelink": voicelink,
|
||||
"bot": self.bot,
|
||||
"interaction": interaction,
|
||||
"channel": interaction.channel,
|
||||
"author": interaction.user,
|
||||
"guild": interaction.guild,
|
||||
"message": interaction.message,
|
||||
"input": None
|
||||
}
|
||||
|
||||
code = clear_code(modal.values)
|
||||
str_obj = io.StringIO() # Retrieves a stream of data
|
||||
try:
|
||||
with contextlib.redirect_stdout(str_obj):
|
||||
exec(
|
||||
f"async def func():\n{textwrap.indent(code, ' ')}", local_variables)
|
||||
obj = await local_variables["func"]()
|
||||
result = f"{str_obj.getvalue()}\n-- {obj}\n"
|
||||
except Exception as e:
|
||||
errormsg = ''.join(
|
||||
traceback.format_exception(e, e, e.__traceback__))
|
||||
return await interaction.followup.send(f"```py\n{errormsg}```")
|
||||
|
||||
string = result.split("\n")
|
||||
text = ""
|
||||
for index, i in enumerate(string, start=1):
|
||||
text += f"{'%03d' % index} | {i}\n"
|
||||
return await interaction.followup.send(f"```{text}```")
|
||||
node: voicelink.Node
|
||||
for name, node in voicelink.NodePool._nodes.items():
|
||||
total_memory = node.stats.used + node.stats.free
|
||||
embed.add_field(
|
||||
name=f"{name} Node - " + ("🟢 Connected" if node._available else "🔴 Disconnected"),
|
||||
value=f"```• ADDRESS: {node._host}:{node._port}\n" \
|
||||
f"• PLAYERS: {len(node._players)}\n" \
|
||||
f"• CPU: {node.stats.cpu_process_load:.1f}%\n" \
|
||||
f"• RAM: {formatBytes(node.stats.free)}/{formatBytes(total_memory, True)} ({(node.stats.free/total_memory) * 100:.1f}%)\n"
|
||||
f"• LATENCY: {node.latency:.2f}ms\n" \
|
||||
f"• UPTIME: {func.time(node.stats.uptime)}```",
|
||||
inline=True
|
||||
)
|
||||
|
||||
await interaction.response.send_message(embed=embed, view=DebugView(self.bot), ephemeral=True)
|
||||
|
||||
async def setup(bot: commands.Bot) -> None:
|
||||
await bot.add_cog(Admin(bot))
|
||||
await bot.add_cog(Settings(bot))
|
||||
29
cogs/task.py
29
cogs/task.py
@@ -1,3 +1,26 @@
|
||||
"""MIT License
|
||||
|
||||
Copyright (c) 2023 - present Vocard Development
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
import voicelink
|
||||
import discord
|
||||
import function as func
|
||||
@@ -87,8 +110,8 @@ class Task(commands.Cog):
|
||||
|
||||
@tasks.loop(hours=12.0)
|
||||
async def cache_cleaner(self):
|
||||
func.guild_settings.clear()
|
||||
func.playlist_name.clear()
|
||||
func.GUILD_SETTINGS.clear()
|
||||
func.PLAYLIST_NAME.clear()
|
||||
|
||||
errorFile = func.gen_report()
|
||||
if errorFile:
|
||||
@@ -98,7 +121,7 @@ class Task(commands.Cog):
|
||||
await report_channel.send(content=f"Report Before: <t:{round(datetime.timestamp(datetime.now()))}:F>", file=errorFile)
|
||||
except Exception as e:
|
||||
print(f"Report could not be sent (Reason: {e})")
|
||||
func.error_log.clear()
|
||||
func.ERROR_LOGS.clear()
|
||||
|
||||
async def setup(bot: commands.Bot):
|
||||
await bot.add_cog(Task(bot))
|
||||
|
||||
123
function.py
123
function.py
@@ -8,16 +8,16 @@ from datetime import datetime
|
||||
from time import strptime
|
||||
from io import BytesIO
|
||||
from pymongo import MongoClient
|
||||
from typing import Optional, Union
|
||||
from typing import Optional, Union, Any
|
||||
from addons import Settings, TOKENS
|
||||
|
||||
root_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
if not os.path.exists(os.path.join(root_dir, "settings.json")):
|
||||
if not os.path.exists(os.path.join(ROOT_DIR, "settings.json")):
|
||||
raise Exception("Settings file not set!")
|
||||
|
||||
#-------------- API Clients --------------
|
||||
tokens: TOKENS = TOKENS();
|
||||
tokens: TOKENS = TOKENS()
|
||||
|
||||
if not (tokens.mongodb_name and tokens.mongodb_url):
|
||||
raise Exception("MONGODB_NAME and MONGODB_URL can't not be empty in .env")
|
||||
@@ -32,45 +32,47 @@ try:
|
||||
except Exception as e:
|
||||
raise Exception("Not able to connect MongoDB! Reason:", e)
|
||||
|
||||
collection = mongodb[tokens.mongodb_name]['Settings']
|
||||
Playlist = mongodb[tokens.mongodb_name]['Playlist']
|
||||
SETTINGS_DB = mongodb[tokens.mongodb_name]['Settings']
|
||||
PLAYLISTS_DB = mongodb[tokens.mongodb_name]['Playlist']
|
||||
|
||||
#--------------- Cache Var ---------------
|
||||
settings: Settings
|
||||
error_log = {} #Stores error that not a Voicelink Exception
|
||||
langs = {} #Stores all the languages in ./langs
|
||||
guild_settings = {} #Cache guild language
|
||||
local_langs = {} #Stores all the localization languages in ./local_langs
|
||||
playlist_name = {} #Cache the user's playlist name
|
||||
ERROR_LOGS: dict[int, dict[int, str]] = {} #Stores error that not a Voicelink Exception
|
||||
LANGS: dict[str, dict[str, str]] = {} #Stores all the languages in ./langs
|
||||
GUILD_SETTINGS: dict[int, dict[str, Any]] = {} #Cache guild language
|
||||
LOCAL_LANGS: dict[str, dict[str, str]] = {} #Stores all the localization languages in ./local_langs
|
||||
PLAYLIST_NAME: dict[str, list[str]] = {} #Cache the user's playlist name
|
||||
|
||||
#-------------- Vocard Functions --------------
|
||||
def get_settings(guild_id:int) -> dict:
|
||||
settings = guild_settings.get(guild_id, None)
|
||||
settings = GUILD_SETTINGS.get(guild_id, None)
|
||||
if not settings:
|
||||
settings = collection.find_one({"_id":guild_id})
|
||||
settings = SETTINGS_DB.find_one({"_id":guild_id})
|
||||
if not settings:
|
||||
collection.insert_one({"_id":guild_id})
|
||||
settings = {}
|
||||
guild_settings[guild_id] = settings
|
||||
SETTINGS_DB.insert_one({"_id":guild_id})
|
||||
|
||||
GUILD_SETTINGS[guild_id] = settings or {}
|
||||
return settings
|
||||
|
||||
def update_settings(guild_id:int, data: dict, mode="Set") -> None:
|
||||
def update_settings(guild_id:int, data: dict, mode="set") -> bool:
|
||||
settings = get_settings(guild_id)
|
||||
if mode == "Set":
|
||||
for key, value in data.items():
|
||||
if settings.get(key) != value:
|
||||
guild_settings[guild_id][key] = value
|
||||
collection.update_one({"_id":guild_id}, {"$set":data})
|
||||
elif mode == "Delete":
|
||||
for key, value in data.items():
|
||||
if settings.get(key) != value:
|
||||
del guild_settings[guild_id][key]
|
||||
collection.update_one({"_id":guild_id}, {"$unset":data})
|
||||
return
|
||||
|
||||
for key, value in data.items():
|
||||
if settings.get(key) != value:
|
||||
match mode:
|
||||
case "set":
|
||||
GUILD_SETTINGS[guild_id][key] = value
|
||||
case "unset":
|
||||
GUILD_SETTINGS[guild_id].pop(key)
|
||||
case _:
|
||||
return False
|
||||
|
||||
result = SETTINGS_DB.update_one({"_id":guild_id}, {f"${mode}":data})
|
||||
return result.modified_count > 0
|
||||
|
||||
def open_json(path: str) -> dict:
|
||||
try:
|
||||
with open(os.path.join(root_dir, 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 {}
|
||||
@@ -82,15 +84,15 @@ def update_json(path: str, new_data: dict) -> None:
|
||||
|
||||
data.update(new_data)
|
||||
|
||||
with open(os.path.join(root_dir, 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")
|
||||
if lang in langs and not langs[lang]:
|
||||
langs[lang] = open_json(os.path.join("langs", f"{lang}.json"))
|
||||
if lang in LANGS and not LANGS[lang]:
|
||||
LANGS[lang] = open_json(os.path.join("langs", f"{lang}.json"))
|
||||
|
||||
return langs.get(lang, {}).get(key, "Language pack not found!")
|
||||
return LANGS.get(lang, {}).get(key, "Language pack not found!")
|
||||
|
||||
def init() -> None:
|
||||
global settings
|
||||
@@ -100,13 +102,13 @@ def init() -> None:
|
||||
settings = Settings(json)
|
||||
|
||||
def langs_setup() -> None:
|
||||
for language in os.listdir(os.path.join(root_dir, "langs")):
|
||||
for language in os.listdir(os.path.join(ROOT_DIR, "langs")):
|
||||
if language.endswith('.json'):
|
||||
langs[language[:-5]] = {}
|
||||
LANGS[language[:-5]] = {}
|
||||
|
||||
for language in os.listdir(os.path.join(root_dir, "local_langs")):
|
||||
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("local_langs", language))
|
||||
LOCAL_LANGS[language[:-5]] = open_json(os.path.join("local_langs", language))
|
||||
|
||||
return
|
||||
|
||||
@@ -133,13 +135,13 @@ def formatTime(number:str) -> Optional[int]:
|
||||
|
||||
return (int(num.tm_hour) * 3600 + int(num.tm_min) * 60 + int(num.tm_sec)) * 1000
|
||||
|
||||
def emoji_source(emoji:str):
|
||||
def emoji_source(emoji:str) -> str:
|
||||
return settings.emoji_source_raw.get(emoji.lower(), "🔗")
|
||||
|
||||
def gen_report() -> Optional[discord.File]:
|
||||
if error_log:
|
||||
if ERROR_LOGS:
|
||||
errorText = ""
|
||||
for guild_id, error in error_log.items():
|
||||
for guild_id, error in ERROR_LOGS.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"
|
||||
@@ -162,6 +164,9 @@ def cooldown_check(ctx: commands.Context) -> Optional[commands.Cooldown]:
|
||||
def get_aliases(name: str) -> list:
|
||||
return settings.aliases_settings.get(name, [])
|
||||
|
||||
def check_roles() -> tuple[str, int, int]:
|
||||
return 'Normal', 5, 500
|
||||
|
||||
async def requests_api(url: str) -> dict:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
resp = await session.get(url)
|
||||
@@ -183,20 +188,19 @@ async def create_account(ctx: Union[commands.Context, discord.Interaction]) -> N
|
||||
" ➥ You have the right to immediately stop the services we offer to you\n"
|
||||
" ➥ Please do not abuse our services, such as affecting other users\n", inline=False)
|
||||
if isinstance(ctx, commands.Context):
|
||||
message = await ctx.reply(embed=embed, view=view, ephemeral=True)
|
||||
view.response = await ctx.reply(embed=embed, view=view, ephemeral=True)
|
||||
else:
|
||||
message = await ctx.response.send_message(embed=embed, view=view, ephemeral=True)
|
||||
|
||||
view.response = message
|
||||
view.response = await ctx.response.send_message(embed=embed, view=view, ephemeral=True)
|
||||
|
||||
await view.wait()
|
||||
if view.value:
|
||||
try:
|
||||
Playlist.insert_one({'_id':author.id, 'playlist': {'200':{'tracks':[],'perms':{ 'read': [], 'write':[], 'remove': []},'name':'Favourite', 'type':'playlist' }},'inbox':[] })
|
||||
PLAYLISTS_DB.insert_one({'_id':author.id, 'playlist': {'200':{'tracks':[],'perms':{ 'read': [], 'write':[], 'remove': []},'name':'Favourite', 'type':'playlist' }},'inbox':[] })
|
||||
except:
|
||||
pass
|
||||
|
||||
async def get_playlist(userid:int, dType:str=None, dId:str=None) -> dict:
|
||||
user = Playlist.find_one({"_id":userid}, {"_id": 0})
|
||||
async def get_playlist(user_id:int, dType:str=None, dId:str=None) -> bool:
|
||||
user = PLAYLISTS_DB.find_one({"_id":user_id}, {"_id": 0})
|
||||
if not user:
|
||||
return None
|
||||
if dType:
|
||||
@@ -205,21 +209,12 @@ async def get_playlist(userid:int, dType:str=None, dId:str=None) -> dict:
|
||||
return user[dType]
|
||||
return user
|
||||
|
||||
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})
|
||||
Playlist.update_one({"_id":userid}, {"$set": data})
|
||||
else:
|
||||
if pull:
|
||||
return Playlist.update_one({"_id":userid}, {"$pull": data})
|
||||
Playlist.update_one({"_id":userid}, {"$unset": data})
|
||||
return
|
||||
async def update_playlist(user_id:int, data:dict, *, mode:str="set", update_cache: bool=False) -> None:
|
||||
if update_cache:
|
||||
PLAYLIST_NAME.pop(str(user_id), None)
|
||||
result = PLAYLISTS_DB.update_one({"_id":user_id}, {f"${mode}": data})
|
||||
return result.modified_count > 0
|
||||
|
||||
async def update_inbox(userid:int, data:dict) -> None:
|
||||
return Playlist.update_one({"_id":userid}, {"$push":{'inbox':data}})
|
||||
|
||||
async def checkroles(userid:int):
|
||||
rank, max_p, max_t = 'Normal', 5, 500
|
||||
|
||||
return rank, max_p, max_t
|
||||
async def update_inbox(user_id:int, data:dict) -> bool:
|
||||
result = PLAYLISTS_DB.update_one({"_id":user_id}, {"$push":{'inbox':data}})
|
||||
return result.modified_count > 0
|
||||
192
langs/UA.json
Normal file
192
langs/UA.json
Normal file
@@ -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:42",
|
||||
"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": "Вперед",
|
||||
"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": "Щось пішло не так під час декодування файлу!"
|
||||
}
|
||||
25
main.py
25
main.py
@@ -21,8 +21,8 @@ class Translator(discord.app_commands.Translator):
|
||||
print("Unload Translator")
|
||||
|
||||
async def translate(self, string: discord.app_commands.locale_str, locale: discord.Locale, context: discord.app_commands.TranslationContext):
|
||||
if str(locale) in func.local_langs:
|
||||
return func.local_langs[str(locale)].get(string.message, None)
|
||||
if str(locale) in func.LOCAL_LANGS:
|
||||
return func.LOCAL_LANGS[str(locale)].get(string.message, None)
|
||||
return None
|
||||
|
||||
class Vocard(commands.Bot):
|
||||
@@ -50,7 +50,7 @@ class Vocard(commands.Bot):
|
||||
|
||||
async def setup_hook(self):
|
||||
func.langs_setup()
|
||||
for module in os.listdir(func.root_dir + '/cogs'):
|
||||
for module in os.listdir(func.ROOT_DIR + '/cogs'):
|
||||
if module.endswith('.py'):
|
||||
try:
|
||||
await self.load_extension(f"cogs.{module[:-3]}")
|
||||
@@ -77,7 +77,7 @@ class Vocard(commands.Bot):
|
||||
print("------------------")
|
||||
|
||||
func.tokens.client_id = self.user.id
|
||||
func.local_langs.clear()
|
||||
func.LOCAL_LANGS.clear()
|
||||
|
||||
async def on_command_error(self, ctx: commands.Context, exception, /) -> None:
|
||||
error = getattr(exception, 'original', exception)
|
||||
@@ -90,15 +90,22 @@ class Vocard(commands.Bot):
|
||||
pass
|
||||
|
||||
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}"
|
||||
command = f"{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) + "```"
|
||||
description = f"**Correct Usage:**\n```{command}\n" + " " * position + "^" * len(ctx.current_parameter.name) + "```\n"
|
||||
if ctx.command.aliases:
|
||||
description += f"**Aliases:**\n`{', '.join([f'{ctx.prefix}{alias}' for alias in ctx.command.aliases])}`\n\n"
|
||||
description += f"**Description:**\n{ctx.command.help}\n\u200b"
|
||||
|
||||
embed = discord.Embed(description=description, color=func.settings.embed_color)
|
||||
embed.set_footer(icon_url=ctx.me.display_avatar.url, text=f"More Help: {func.settings.invite_link}")
|
||||
return await ctx.reply(embed=embed)
|
||||
|
||||
elif not issubclass(error.__class__, VoicelinkException):
|
||||
error = func.get_lang(ctx.guild.id, "unknownException") + func.settings.invite_link
|
||||
if (guildId := ctx.guild.id) not in func.error_log:
|
||||
func.error_log[guildId] = {}
|
||||
func.error_log[guildId][round(datetime.timestamp(datetime.now()))] = "".join(traceback.format_exception(type(exception), exception, exception.__traceback__))
|
||||
if (guildId := ctx.guild.id) not in func.ERROR_LOGS:
|
||||
func.ERROR_LOGS[guildId] = {}
|
||||
func.ERROR_LOGS[guildId][round(datetime.timestamp(datetime.now()))] = "".join(traceback.format_exception(type(exception), exception, exception.__traceback__))
|
||||
|
||||
try:
|
||||
return await ctx.reply(error, ephemeral=True)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
discord.py==2.3.1
|
||||
pymongo==4.1.1
|
||||
discord.py==2.3.2
|
||||
pymongo==4.5.0
|
||||
dnspython==2.2.1
|
||||
tldextract==3.2.1
|
||||
validators==0.18.2
|
||||
@@ -9,3 +9,4 @@ beautifulsoup4==4.11.1
|
||||
websockets==10.4
|
||||
Flask==2.2.3
|
||||
Flask-SocketIO==5.3.2
|
||||
psutil==5.9.5
|
||||
|
||||
14
update.py
14
update.py
@@ -1,8 +1,8 @@
|
||||
import requests, zipfile, os, shutil, argparse
|
||||
from io import BytesIO
|
||||
|
||||
root_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
__version__ = "v2.6.6"
|
||||
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
__version__ = "v2.6.7b6"
|
||||
|
||||
GITHUB_API_URL = "https://api.github.com/repos/ChocoMeow/Vocard/releases/latest"
|
||||
VOCARD_URL = "https://github.com/ChocoMeow/Vocard/archive/"
|
||||
@@ -65,22 +65,22 @@ def install(response, version):
|
||||
if user_input.lower() in ["y", "yes"]:
|
||||
print("Installing ...")
|
||||
zfile = zipfile.ZipFile(BytesIO(response.content))
|
||||
zfile.extractall(root_dir)
|
||||
zfile.extractall(ROOT_DIR)
|
||||
|
||||
version = version.replace("v", "")
|
||||
source_dir = os.path.join(root_dir, f"Vocard-{version}")
|
||||
source_dir = os.path.join(ROOT_DIR, f"Vocard-{version}")
|
||||
if os.path.exists(source_dir):
|
||||
for filename in os.listdir(root_dir):
|
||||
for filename in os.listdir(ROOT_DIR):
|
||||
if filename in IGNORE_FILES + [f"Vocard-{version}"]:
|
||||
continue
|
||||
|
||||
filename = os.path.join(root_dir, filename)
|
||||
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))
|
||||
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:
|
||||
|
||||
@@ -13,5 +13,5 @@ from .chapter import ChapterView
|
||||
from .playlist import PlaylistView, CreateView
|
||||
from .inbox import InboxView
|
||||
from .link import LinkView
|
||||
from .debug import DebugModal
|
||||
from .debug import DebugView
|
||||
from .embedBuilder import EmbedBuilderView
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""MIT License
|
||||
|
||||
Copyright (c) 2023 Vocard Development
|
||||
Copyright (c) 2023 - present Vocard Development
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -20,28 +20,30 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import discord
|
||||
|
||||
from function import formatTime
|
||||
from typing import TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from voicelink import Player
|
||||
|
||||
class Dropdown(discord.ui.Select):
|
||||
def __init__(self, player, chapters):
|
||||
def __init__(self, player: Player, chapters):
|
||||
self.view: ChapterView
|
||||
|
||||
self.player = player
|
||||
self.chapters = chapters
|
||||
self.current = player.current.uri
|
||||
|
||||
options = [
|
||||
discord.SelectOption(label=f"{index}. {title[:30]}",
|
||||
description=time)
|
||||
for index, (time, title) in enumerate(self.chapters, start=1)
|
||||
]
|
||||
self.player: Player = player
|
||||
self.chapters: list[str] = chapters
|
||||
self.current: str = player.current.uri
|
||||
|
||||
super().__init__(
|
||||
placeholder=self.player.get_msg('chaptersDropdown'),
|
||||
min_values=1, max_values=1,
|
||||
options=options[:25],
|
||||
options=[
|
||||
discord.SelectOption(label=f"{index}. {title[:30]}", description=time)
|
||||
for index, (time, title) in enumerate(self.chapters, start=1)
|
||||
][:25]
|
||||
)
|
||||
|
||||
async def callback(self, interaction: discord.Interaction):
|
||||
@@ -55,13 +57,18 @@ class Dropdown(discord.ui.Select):
|
||||
await interaction.response.send_message(self.player.get_msg('seek').format(formatTime(position)))
|
||||
|
||||
class ChapterView(discord.ui.View):
|
||||
def __init__(self, player, chapters, author):
|
||||
def __init__(
|
||||
self,
|
||||
player: Player,
|
||||
chapters: list[str],
|
||||
author: discord.Member
|
||||
) -> None:
|
||||
super().__init__(timeout=180)
|
||||
|
||||
self.player = player
|
||||
self.chapters = chapters
|
||||
self.author = author
|
||||
self.response = None
|
||||
self.player: Player = player
|
||||
self.chapters: list[str] = chapters
|
||||
self.author: discord.Member = author
|
||||
self.response: discord.Message = None
|
||||
self.add_item(Dropdown(player, chapters))
|
||||
|
||||
async def on_error(self, error: Exception, item, interaction) -> None:
|
||||
@@ -78,7 +85,5 @@ class ChapterView(discord.ui.View):
|
||||
async def on_timeout(self) -> None:
|
||||
await self.stop_view()
|
||||
|
||||
async def interaction_check(self, interaction):
|
||||
if interaction.user == self.author:
|
||||
return True
|
||||
return False
|
||||
async def interaction_check(self, interaction: discord.Interaction):
|
||||
return interaction.user == self.author
|
||||
@@ -1,6 +1,6 @@
|
||||
"""MIT License
|
||||
|
||||
Copyright (c) 2023 Vocard Development
|
||||
Copyright (c) 2023 - present Vocard Development
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -31,7 +31,7 @@ from function import (
|
||||
get_playlist,
|
||||
update_playlist,
|
||||
create_account,
|
||||
checkroles
|
||||
check_roles
|
||||
)
|
||||
|
||||
from typing import Dict
|
||||
@@ -94,7 +94,7 @@ class Resume(ControlButton):
|
||||
super().__init__(
|
||||
emoji="⏸️",
|
||||
label="buttonPause",
|
||||
disabled=not bool(kwargs["player"].current),
|
||||
disabled=kwargs["player"].current is None,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
@@ -187,7 +187,7 @@ class Add(ControlButton):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(
|
||||
emoji="❤️",
|
||||
disabled=not bool(kwargs["player"].current),
|
||||
disabled=kwargs["player"].current is None,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
@@ -200,13 +200,13 @@ class Add(ControlButton):
|
||||
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)
|
||||
rank, max_p, max_t = check_roles()
|
||||
if len(user['200']['tracks']) >= max_t:
|
||||
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 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)
|
||||
respond = await update_playlist(interaction.user.id, {'playlist.200.tracks': track.track_id}, mode="push")
|
||||
if respond:
|
||||
await self.send(interaction, self.player.get_msg("playlistAdded").format(track.title, interaction.user.mention, user['200']['name']), ephemeral=True)
|
||||
else:
|
||||
@@ -332,7 +332,7 @@ class Forward(ControlButton):
|
||||
super().__init__(
|
||||
emoji="⏩",
|
||||
label="buttonForward",
|
||||
disabled=not bool(kwargs["player"].current),
|
||||
disabled=kwargs["player"].current is None,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
@@ -351,7 +351,7 @@ class Rewind(ControlButton):
|
||||
super().__init__(
|
||||
emoji="⏪",
|
||||
label="buttonRewind",
|
||||
disabled=not bool(kwargs["player"].current)
|
||||
disabled=kwargs["player"].current is None,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
@@ -376,7 +376,7 @@ class Tracks(discord.ui.Select):
|
||||
for index, track in enumerate(self.player.queue.tracks(), start=1):
|
||||
if index > 10:
|
||||
break
|
||||
options.append(discord.SelectOption(label=f"{index}. {track.title[:40]}", description=f"{track.author[:30]} · " + ("Live" if track.is_stream else track.formatLength), emoji=track.emoji))
|
||||
options.append(discord.SelectOption(label=f"{index}. {track.title[:40]}", description=f"{track.author[:30]} · " + ("Live" if track.is_stream else track.formatted_length), emoji=track.emoji))
|
||||
|
||||
super().__init__(
|
||||
placeholder=player.get_msg("playerDropdown"),
|
||||
|
||||
160
views/debug.py
160
views/debug.py
@@ -1,6 +1,6 @@
|
||||
"""MIT License
|
||||
|
||||
Copyright (c) 2023 Vocard Development
|
||||
Copyright (c) 2023 - present Vocard Development
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -22,20 +22,168 @@ SOFTWARE.
|
||||
"""
|
||||
|
||||
import discord
|
||||
import function
|
||||
import io
|
||||
import contextlib
|
||||
import textwrap
|
||||
import traceback
|
||||
|
||||
class DebugModal(discord.ui.Modal):
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
from discord.ext import commands
|
||||
|
||||
class ExceuteModal(discord.ui.Modal):
|
||||
def __init__(self, code: str, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
self.values = None
|
||||
self.code: str = code
|
||||
|
||||
self.add_item(
|
||||
discord.ui.TextInput(
|
||||
label="Code Runner",
|
||||
placeholder="Input Your Code",
|
||||
style=discord.TextStyle.long,
|
||||
default=self.code
|
||||
)
|
||||
)
|
||||
|
||||
async def on_submit(self, interaction: discord.Interaction):
|
||||
self.values = self.children[0].value
|
||||
self.stop()
|
||||
await interaction.response.defer()
|
||||
self.code = self.children[0].value
|
||||
self.stop()
|
||||
|
||||
class CogsDropdown(discord.ui.Select):
|
||||
def __init__(self, bot: commands.Bot):
|
||||
self.bot: commands.Bot = bot
|
||||
|
||||
super().__init__(
|
||||
placeholder="Select a cog to reload...",
|
||||
min_values=1, max_values=1,
|
||||
options=[discord.SelectOption(label="All", description="All the cogs")] +
|
||||
[
|
||||
discord.SelectOption(label=name.capitalize(), description=cog.description[:50])
|
||||
for name, cog in bot.cogs.items()
|
||||
],
|
||||
)
|
||||
|
||||
async def callback(self, interaction: discord.Interaction) -> None:
|
||||
selected = self.values[0].lower()
|
||||
try:
|
||||
if selected == "all":
|
||||
for name in self.bot.cogs.keys():
|
||||
await self.bot.reload_extension(f"cogs.{name.lower()}")
|
||||
else:
|
||||
await self.bot.reload_extension(f"cogs.{selected}")
|
||||
except Exception as e:
|
||||
return await interaction.response.send_message(f"Unable to reload `{selected}`! Reason: {e}", ephemeral=True)
|
||||
|
||||
await interaction.response.send_message(f"Reloaded `{selected}` sucessfully!", ephemeral=True)
|
||||
|
||||
class ExceutePanel(discord.ui.View):
|
||||
def __init__(self, bot, *, timeout = 180):
|
||||
self.bot: commands.Bot = bot
|
||||
|
||||
self.message: discord.WebhookMessage = None
|
||||
self.code: str = None
|
||||
self._error: Exception = None
|
||||
|
||||
super().__init__(timeout=timeout)
|
||||
|
||||
def toggle_button(self, name: str, status: bool):
|
||||
child: discord.ui.Button
|
||||
for child in self.children:
|
||||
if child.label == name:
|
||||
child.disabled = status
|
||||
break
|
||||
|
||||
def clear_code(self, content: str):
|
||||
"""Automatically removes code blocks from the code."""
|
||||
if content.startswith('```') and content.endswith('```'):
|
||||
return '\n'.join(content.split('\n')[1:-1])
|
||||
|
||||
return content.strip('` \n')
|
||||
|
||||
async def on_timeout(self) -> None:
|
||||
for child in self.children:
|
||||
child.disabled = True
|
||||
if self.message:
|
||||
await self.message.edit(view=self)
|
||||
|
||||
async def execute(self, interaction: discord.Interaction):
|
||||
modal = ExceuteModal(self.code, title="Enter Your Code")
|
||||
await interaction.response.send_modal(modal)
|
||||
await modal.wait()
|
||||
|
||||
if not (code := modal.code):
|
||||
return
|
||||
|
||||
self._error = None
|
||||
text = ""
|
||||
|
||||
local_variables = {
|
||||
"discord": discord,
|
||||
"bot": self.bot,
|
||||
"interaction": interaction,
|
||||
"input": None
|
||||
}
|
||||
|
||||
self.code = self.clear_code(code)
|
||||
str_obj = io.StringIO() #Retrieves a stream of data
|
||||
try:
|
||||
with contextlib.redirect_stdout(str_obj):
|
||||
exec(f"async def func():\n{textwrap.indent(self.code, ' ')}", local_variables)
|
||||
obj = await local_variables["func"]()
|
||||
result = f"{str_obj.getvalue()}\n-- {obj}\n"
|
||||
except Exception as e:
|
||||
text = f"{e.__class__.__name__}: {e}"
|
||||
self._error = e
|
||||
|
||||
if not self._error:
|
||||
text = "\n".join([f"{'%03d' % index} | {i}" for index, i in enumerate(result.split("\n"), start=1)])
|
||||
|
||||
self.toggle_button("Error", True if self._error is None else False)
|
||||
|
||||
if not self.message:
|
||||
self.message = await interaction.followup.send(f"```{text}```", view=self, ephemeral=True)
|
||||
else:
|
||||
await self.message.edit(content=f"```{text}```", view=self)
|
||||
|
||||
@discord.ui.button(label="End", emoji="🗑️", custom_id="end")
|
||||
async def end(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
if self.message:
|
||||
await self.message.delete()
|
||||
self.stop()
|
||||
|
||||
@discord.ui.button(label="Rerun", emoji="🔄", custom_id="rerun")
|
||||
async def rerun(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
await self.execute(interaction)
|
||||
|
||||
@discord.ui.button(label="Error", emoji="👾", custom_id="Error")
|
||||
async def error(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
result = ''.join(traceback.format_exception(self._error, self._error, self._error.__traceback__))
|
||||
await self.message.edit(content=f"```py\n{result}```")
|
||||
|
||||
class CogsView(discord.ui.View):
|
||||
def __init__(self, bot, *, timeout: float | None = 180):
|
||||
super().__init__(timeout=timeout)
|
||||
|
||||
self.add_item(CogsDropdown(bot))
|
||||
|
||||
class DebugView(discord.ui.View):
|
||||
def __init__(self, bot, *, timeout: float | None = 180):
|
||||
self.bot: commands.Bot = bot
|
||||
self.panel: ExceutePanel = ExceutePanel(bot)
|
||||
|
||||
super().__init__(timeout=timeout)
|
||||
|
||||
@discord.ui.button(label='Command', emoji="▶️", style=discord.ButtonStyle.green)
|
||||
async def run_command(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
await self.panel.execute(interaction)
|
||||
|
||||
@discord.ui.button(label='Cogs', emoji="🔃")
|
||||
async def reload_cog(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
return await interaction.response.send_message("Reload Cogs", view=CogsView(self.bot), ephemeral=True)
|
||||
|
||||
@discord.ui.button(label='Send Logs', emoji="📥", style=discord.ButtonStyle.red)
|
||||
async def send_error_logs(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
if not function.ERROR_LOGS:
|
||||
return await interaction.response.send_message("Sorry there are not error logs!", ephemeral=True)
|
||||
|
||||
await interaction.response.send_message(file=function.gen_report(), ephemeral=True)
|
||||
@@ -59,10 +59,8 @@ class EmbedBuilderView(discord.ui.View):
|
||||
except:
|
||||
pass
|
||||
|
||||
async def interaction_check(self, interaction):
|
||||
if interaction.user == self.author:
|
||||
return True
|
||||
return False
|
||||
async def interaction_check(self, interaction: discord.Interaction):
|
||||
return interaction.user == self.author
|
||||
|
||||
@discord.ui.button(label="Edit Content", style=discord.ButtonStyle.blurple)
|
||||
async def edit_content(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""MIT License
|
||||
|
||||
Copyright (c) 2023 Vocard Development
|
||||
Copyright (c) 2023 - present Vocard Development
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -28,34 +28,33 @@ import function as func
|
||||
|
||||
class HelpDropdown(discord.ui.Select):
|
||||
def __init__(self, categorys:list):
|
||||
options = [
|
||||
discord.SelectOption(emoji="🆕", label="News", description="View new updates of Vocard."),
|
||||
discord.SelectOption(emoji="🕹️", label="Tutorial", description="How to use Vocard."),
|
||||
]
|
||||
cog_emojis = ["1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣"]
|
||||
for category, emoji in zip(categorys, cog_emojis):
|
||||
options.append(discord.SelectOption(emoji=emoji,
|
||||
label=f"{category} Commands",
|
||||
description=f"This is {category.lower()} Category."))
|
||||
|
||||
self.view: HelpView
|
||||
|
||||
super().__init__(
|
||||
placeholder="Select Category!",
|
||||
min_values=1, max_values=1,
|
||||
options=options, custom_id="select"
|
||||
options=[
|
||||
discord.SelectOption(emoji="🆕", label="News", description="View new updates of Vocard."),
|
||||
discord.SelectOption(emoji="🕹️", label="Tutorial", description="How to use Vocard."),
|
||||
] + [
|
||||
discord.SelectOption(emoji=emoji, label=f"{category} Commands", description=f"This is {category.lower()} Category.")
|
||||
for category, emoji in zip(categorys, ["1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣"])
|
||||
],
|
||||
custom_id="select"
|
||||
)
|
||||
|
||||
async def callback(self, interaction: discord.Interaction):
|
||||
async def callback(self, interaction: discord.Interaction) -> None:
|
||||
embed = self.view.build_embed(self.values[0].split(" ")[0])
|
||||
await interaction.response.edit_message(embed=embed)
|
||||
|
||||
class HelpView(discord.ui.View):
|
||||
def __init__(self, bot: commands.Bot, author: discord.Member):
|
||||
def __init__(self, bot: commands.Bot, author: discord.Member) -> None:
|
||||
super().__init__(timeout=60)
|
||||
|
||||
self.author = author
|
||||
self.bot = bot
|
||||
self.response = None
|
||||
self.categorys = [ name.capitalize() for name, cog in bot.cogs.items() if len([c for c in cog.walk_commands()]) ]
|
||||
self.author: discord.Member = author
|
||||
self.bot: commands.Bot = bot
|
||||
self.response: discord.Message = None
|
||||
self.categorys: list[str] = [ name.capitalize() for name, cog in bot.cogs.items() if len([c for c in cog.walk_commands()]) ]
|
||||
|
||||
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)))
|
||||
@@ -63,10 +62,10 @@ class HelpView(discord.ui.View):
|
||||
self.add_item(discord.ui.Button(label='Donate', emoji=':patreon:913397909024800878', url='https://www.patreon.com/Vocard'))
|
||||
self.add_item(HelpDropdown(self.categorys))
|
||||
|
||||
async def on_error(self, error, item, interaction):
|
||||
async def on_error(self, error, item, interaction) -> None:
|
||||
return
|
||||
|
||||
async def on_timeout(self):
|
||||
async def on_timeout(self) -> None:
|
||||
for child in self.children:
|
||||
if child.custom_id == "select":
|
||||
child.disabled = True
|
||||
@@ -75,22 +74,20 @@ class HelpView(discord.ui.View):
|
||||
except:
|
||||
pass
|
||||
|
||||
async def interaction_check(self, interaction):
|
||||
if interaction.user == self.author:
|
||||
return True
|
||||
return False
|
||||
async def interaction_check(self, interaction: discord.Interaction) -> None:
|
||||
return interaction.user == self.author
|
||||
|
||||
def build_embed(self, category: str):
|
||||
def build_embed(self, category: str) -> discord.Embed:
|
||||
category = category.lower()
|
||||
if category == "news":
|
||||
embed = discord.Embed(title="Vocard Help Menu", url="https://discord.com/channels/811542332678996008/811909963718459392/1069971173116481636", color=func.settings.embed_color)
|
||||
|
||||
embed.add_field(name=f"Available Categories: [{2 + len(self.categorys)}]",
|
||||
value="```py\n👉 News\n2. Tutorial\n{}```".format("".join(f"{i}. {c}\n" for i, c in enumerate(self.categorys, start=3))),
|
||||
inline=True)
|
||||
embed.add_field(
|
||||
name=f"Available Categories: [{2 + len(self.categorys)}]",
|
||||
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 = "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="📰 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)
|
||||
|
||||
@@ -107,7 +104,9 @@ class HelpView(discord.ui.View):
|
||||
|
||||
commands = [command for command in cog.walk_commands()]
|
||||
embed.description = cog.description
|
||||
embed.add_field(name=f"{category} Commands: [{len(commands)}]",
|
||||
value="```{}```".format("".join(f"/{command.qualified_name}\n" for command in commands if not command.qualified_name == cog.qualified_name)))
|
||||
embed.add_field(
|
||||
name=f"{category} Commands: [{len(commands)}]",
|
||||
value="```{}```".format("".join(f"/{command.qualified_name}\n" for command in commands if not command.qualified_name == cog.qualified_name))
|
||||
)
|
||||
|
||||
return embed
|
||||
@@ -1,6 +1,6 @@
|
||||
"""MIT License
|
||||
|
||||
Copyright (c) 2023 Vocard Development
|
||||
Copyright (c) 2023 - present Vocard Development
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -24,8 +24,11 @@ SOFTWARE.
|
||||
import discord
|
||||
import function as func
|
||||
|
||||
from typing import Any
|
||||
|
||||
class Select_message(discord.ui.Select):
|
||||
def __init__(self, inbox):
|
||||
self.view: InboxView
|
||||
options = [discord.SelectOption(label=f"{index}. {mail['title'][:50]}", description=mail['type'], emoji='✉️' if mail['type'] == 'invite' else '📢') for index, mail in enumerate(inbox, start=1) ]
|
||||
|
||||
super().__init__(
|
||||
@@ -38,25 +41,27 @@ class Select_message(discord.ui.Select):
|
||||
await self.view.button_change(interaction)
|
||||
|
||||
class InboxView(discord.ui.View):
|
||||
def __init__(self, author, inbox):
|
||||
def __init__(self, author: discord.Member, inbox: list[dict[str, Any]]):
|
||||
super().__init__(timeout=60)
|
||||
self.author: discord.Member = author
|
||||
self.inbox = inbox
|
||||
self.response = None
|
||||
self.inbox: list[dict[str, Any]] = inbox
|
||||
self.newplaylist = []
|
||||
|
||||
self.author: discord.Member = author
|
||||
self.response: discord.Message = None
|
||||
self.current = None
|
||||
|
||||
self.add_item(Select_message(inbox))
|
||||
|
||||
async def interaction_check(self, interaction: discord.Interaction):
|
||||
if interaction.user == self.author:
|
||||
return True
|
||||
return False
|
||||
return interaction.user == self.author
|
||||
|
||||
def build_embed(self) -> discord.Embed:
|
||||
embed=discord.Embed(
|
||||
title=f"📭 All {self.author.name}'s Inbox",
|
||||
description=f'Max Messages: {len(self.inbox)}/10' + '```%0s %2s %20s\n' % (" ", "ID:", "Title:") + '\n'.join('%0s %2s. %35s'% ('✉️' if mail['type'] == 'invite' else '📢', index, mail['title'][:35] + "...") for index, mail in enumerate(self.inbox, start=1)) + '```',
|
||||
color=func.settings.embed_color
|
||||
)
|
||||
|
||||
def build_embed(self):
|
||||
embed=discord.Embed(title=f"📭 All {self.author.name}'s Inbox",
|
||||
description=f'Max Messages: {len(self.inbox)}/10' + '```%0s %2s %20s\n' % (" ", "ID:", "Title:") + '\n'.join('%0s %2s. %35s'% ('✉️' if mail['type'] == 'invite' else '📢', index, mail['title'][:35] + "...") for index, mail in enumerate(self.inbox, start=1)) + '```',
|
||||
color=func.settings.embed_color)
|
||||
if self.current:
|
||||
embed.add_field(name="Message Info:", value=f"```{self.current['description']}\nSender ID: {self.current['sender']}\nPlaylist ID: {self.current['referId']}\nInvite Time: {self.current['time'].strftime('%d-%m %H:%M:%S')}```")
|
||||
return embed
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""MIT License
|
||||
|
||||
Copyright (c) 2023 Vocard Development
|
||||
Copyright (c) 2023 - present Vocard Development
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""MIT License
|
||||
|
||||
Copyright (c) 2023 Vocard Development
|
||||
Copyright (c) 2023 - present Vocard Development
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -25,26 +25,38 @@ import discord
|
||||
import function as func
|
||||
|
||||
from math import ceil
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from voicelink import Player, Track
|
||||
|
||||
class ListView(discord.ui.View):
|
||||
def __init__(self, player, author, isQueue = True):
|
||||
def __init__(
|
||||
self,
|
||||
player: "Player",
|
||||
author: discord.Member,
|
||||
isQueue = True
|
||||
) -> None:
|
||||
super().__init__(timeout=60)
|
||||
self.player = player
|
||||
self.player: Player = player
|
||||
|
||||
self.name: str = player.get_msg('queueTitle') if isQueue else player.get_msg('historyTitle')
|
||||
self.tracks: list[Track] = player.queue.tracks() if isQueue else player.queue.history()
|
||||
self.response: discord.Message = None
|
||||
|
||||
self.name = player.get_msg('queueTitle') if isQueue else player.get_msg('historyTitle')
|
||||
self.tracks = player.queue.tracks() if isQueue else player.queue.history()
|
||||
if not isQueue:
|
||||
self.tracks.reverse()
|
||||
self.author = author
|
||||
self.author: discord.Member = author
|
||||
|
||||
self.page: int = ceil(len(self.tracks) / 7)
|
||||
self.current_page: int = 1
|
||||
|
||||
self.page = ceil(len(self.tracks) / 7)
|
||||
self.current_page = 1
|
||||
try:
|
||||
self.time = func.time(sum([track.length for track in self.tracks]))
|
||||
except:
|
||||
self.time: str = func.time(sum([track.length for track in self.tracks]))
|
||||
except Exception as _:
|
||||
self.time = "∞"
|
||||
|
||||
async def on_timeout(self):
|
||||
async def on_timeout(self) -> None:
|
||||
for child in self.children:
|
||||
child.disabled = True
|
||||
try:
|
||||
@@ -52,54 +64,57 @@ class ListView(discord.ui.View):
|
||||
except:
|
||||
pass
|
||||
|
||||
async def on_error(self, error, item, interaction):
|
||||
async def on_error(self, error, item, interaction) -> None:
|
||||
return
|
||||
|
||||
async def interaction_check(self, interaction):
|
||||
if interaction.user == self.author:
|
||||
return True
|
||||
return False
|
||||
async def interaction_check(self, interaction: discord.Interaction) -> bool:
|
||||
return interaction.user == self.author
|
||||
|
||||
def build_embed(self):
|
||||
offset = self.current_page * 7
|
||||
tracks = self.tracks[(offset-7):offset]
|
||||
def build_embed(self) -> discord.Embed:
|
||||
offset: int = self.current_page * 7
|
||||
tracks: list[Track] = self.tracks[(offset-7):offset]
|
||||
|
||||
embed = discord.Embed(title=self.player.get_msg('viewTitle'), color=func.settings.embed_color)
|
||||
embed.description=self.player.get_msg('viewDesc').format(self.player.current.uri, f"```{self.player.current.title}```") if self.player.current else self.player.get_msg('nowplayingDesc').format("None")
|
||||
queueText = ""
|
||||
for index, track in enumerate(tracks, start=offset - 6):
|
||||
queueText += f"{track.emoji} `{index}.` `[" + (self.player.get_msg("live") if track.is_stream else func.time(track.length)) + f'`] **{track.title[:30]}** ' + (track.requester.mention if track.requester else self.player.client.id) + "\n"
|
||||
|
||||
queueText = "\n".join([
|
||||
f"{track.emoji} `{i}.` `[" + (self.player.get_msg("live") if track.is_stream else func.time(track.length)) + f'`] **{track.title[:30]}** ' + (track.requester.mention)
|
||||
for i, track in enumerate(tracks, start=offset-6)
|
||||
])
|
||||
embed.add_field(name=self.name, value=queueText)
|
||||
embed.set_footer(text=self.player.get_msg('viewFooter').format(self.current_page, self.page, self.time))
|
||||
|
||||
return embed
|
||||
|
||||
@discord.ui.button(label='<<', style=discord.ButtonStyle.grey)
|
||||
async def fast_back_button(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
async def fast_back_button(self, interaction: discord.Interaction, button: discord.ui.Button) -> None:
|
||||
if self.current_page != 1:
|
||||
self.current_page = 1
|
||||
await interaction.response.edit_message(embed=self.build_embed())
|
||||
|
||||
return await interaction.response.edit_message(embed=self.build_embed())
|
||||
await interaction.response.defer()
|
||||
|
||||
@discord.ui.button(label='Back', style=discord.ButtonStyle.blurple)
|
||||
async def back_button(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
async def back_button(self, interaction: discord.Interaction, button: discord.ui.Button) -> None:
|
||||
if self.current_page > 1:
|
||||
self.current_page -= 1
|
||||
await interaction.response.edit_message(embed=self.build_embed())
|
||||
|
||||
return await interaction.response.edit_message(embed=self.build_embed())
|
||||
await interaction.response.defer()
|
||||
|
||||
@discord.ui.button(label='Next', style=discord.ButtonStyle.blurple)
|
||||
async def next_button(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
async def next_button(self, interaction: discord.Interaction, button: discord.ui.Button) -> None:
|
||||
if self.current_page < self.page:
|
||||
self.current_page += 1
|
||||
await interaction.response.edit_message(embed=self.build_embed())
|
||||
return await interaction.response.edit_message(embed=self.build_embed())
|
||||
await interaction.response.defer()
|
||||
|
||||
@discord.ui.button(label='>>', style=discord.ButtonStyle.grey)
|
||||
async def fast_next_button(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
async def fast_next_button(self, interaction: discord.Interaction, button: discord.ui.Button) -> None:
|
||||
if self.current_page != self.page:
|
||||
self.current_page = self.page
|
||||
await interaction.response.edit_message(embed=self.build_embed())
|
||||
|
||||
return await interaction.response.edit_message(embed=self.build_embed())
|
||||
await interaction.response.defer()
|
||||
|
||||
@discord.ui.button(emoji='🗑️', style=discord.ButtonStyle.red)
|
||||
async def stop_button(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
async def stop_button(self, interaction: discord.Interaction, button: discord.ui.Button) -> None:
|
||||
await self.response.delete()
|
||||
self.stop()
|
||||
@@ -1,6 +1,6 @@
|
||||
"""MIT License
|
||||
|
||||
Copyright (c) 2023 Vocard Development
|
||||
Copyright (c) 2023 - present Vocard Development
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -25,41 +25,40 @@ import discord
|
||||
import function as func
|
||||
|
||||
class LyricsDropdown(discord.ui.Select):
|
||||
def __init__(self, langs: list):
|
||||
options = [discord.SelectOption(label=lang) for lang in langs]
|
||||
def __init__(self, langs: list[str]) -> None:
|
||||
self.view: LyricsView
|
||||
|
||||
super().__init__(
|
||||
placeholder="Select A Lyrics Translation",
|
||||
min_values=1, max_values=1,
|
||||
options=options, custom_id="selectLyricsLangs"
|
||||
options=[discord.SelectOption(label=lang) for lang in langs],
|
||||
custom_id="selectLyricsLangs"
|
||||
)
|
||||
|
||||
async def callback(self, interaction: discord.Interaction):
|
||||
async def callback(self, interaction: discord.Interaction) -> None:
|
||||
self.view.lang = self.values[0]
|
||||
self.view.current_page = 1
|
||||
self.view.pages = len(self.view.source.get(self.values[0]))
|
||||
await interaction.response.edit_message(embed=self.view.build_embed())
|
||||
|
||||
class LyricsView(discord.ui.View):
|
||||
def __init__(self, name: str, source: dict, author: discord.Member):
|
||||
def __init__(self, name: str, source: dict, author: discord.Member) -> None:
|
||||
super().__init__(timeout=60)
|
||||
|
||||
self.name = name
|
||||
self.source = source
|
||||
self.lang = list(source.keys())[0]
|
||||
self.author = author
|
||||
self.name: str = name
|
||||
self.source: dict[str, list[str]] = source
|
||||
self.lang: list[str] = list(source.keys())[0]
|
||||
self.author: discord.Member = author
|
||||
|
||||
self.response = None
|
||||
self.pages = len(self.source.get(self.lang))
|
||||
self.current_page = 1
|
||||
self.response: discord.Message = None
|
||||
self.pages: int = len(self.source.get(self.lang))
|
||||
self.current_page: int = 1
|
||||
self.add_item(LyricsDropdown(list(source.keys())))
|
||||
|
||||
async def interaction_check(self, interaction):
|
||||
if interaction.user == self.author:
|
||||
return True
|
||||
return False
|
||||
async def interaction_check(self, interaction: discord.Interaction) -> bool:
|
||||
return interaction.user == self.author
|
||||
|
||||
async def on_timeout(self):
|
||||
async def on_timeout(self) -> None:
|
||||
for child in self.children:
|
||||
child.disabled = True
|
||||
try:
|
||||
@@ -67,10 +66,10 @@ class LyricsView(discord.ui.View):
|
||||
except:
|
||||
pass
|
||||
|
||||
async def on_error(self, error, item, interaction):
|
||||
async def on_error(self, error, item, interaction) -> None:
|
||||
return
|
||||
|
||||
def build_embed(self):
|
||||
def build_embed(self) -> discord.Embed:
|
||||
chunk = self.source.get(self.lang)[self.current_page - 1]
|
||||
embed=discord.Embed(description=chunk, color=func.settings.embed_color)
|
||||
embed.set_author(name=f"Searching Query: {self.name}", icon_url=self.author.display_avatar.url)
|
||||
@@ -78,30 +77,34 @@ class LyricsView(discord.ui.View):
|
||||
return embed
|
||||
|
||||
@discord.ui.button(label='<<', style=discord.ButtonStyle.grey)
|
||||
async def fast_back_button(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
async def fast_back_button(self, interaction: discord.Interaction, button: discord.ui.Button) -> None:
|
||||
if self.current_page != 1:
|
||||
self.current_page = 1
|
||||
await interaction.response.edit_message(embed=self.build_embed())
|
||||
|
||||
return await interaction.response.edit_message(embed=self.build_embed())
|
||||
await interaction.response.defer()
|
||||
|
||||
@discord.ui.button(label='Back', style=discord.ButtonStyle.blurple)
|
||||
async def back_button(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
async def back_button(self, interaction: discord.Interaction, button: discord.ui.Button) -> None:
|
||||
if self.current_page > 1:
|
||||
self.current_page -= 1
|
||||
await interaction.response.edit_message(embed=self.build_embed())
|
||||
|
||||
return await interaction.response.edit_message(embed=self.build_embed())
|
||||
await interaction.response.defer()
|
||||
|
||||
@discord.ui.button(label='Next', style=discord.ButtonStyle.blurple)
|
||||
async def next_button(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
async def next_button(self, interaction: discord.Interaction, button: discord.ui.Button) -> None:
|
||||
if self.current_page < self.pages:
|
||||
self.current_page += 1
|
||||
await interaction.response.edit_message(embed=self.build_embed())
|
||||
return await interaction.response.edit_message(embed=self.build_embed())
|
||||
await interaction.response.defer()
|
||||
|
||||
@discord.ui.button(label='>>', style=discord.ButtonStyle.grey)
|
||||
async def fast_next_button(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
async def fast_next_button(self, interaction: discord.Interaction, button: discord.ui.Button) -> None:
|
||||
if self.current_page != self.pages:
|
||||
self.current_page = self.pages
|
||||
await interaction.response.edit_message(embed=self.build_embed())
|
||||
return await interaction.response.edit_message(embed=self.build_embed())
|
||||
await interaction.response.defer()
|
||||
|
||||
@discord.ui.button(emoji='🗑️', style=discord.ButtonStyle.red)
|
||||
async def stop_button(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
async def stop_button(self, interaction: discord.Interaction, button: discord.ui.Button) -> None:
|
||||
await self.response.delete()
|
||||
self.stop()
|
||||
@@ -1,6 +1,6 @@
|
||||
"""MIT License
|
||||
|
||||
Copyright (c) 2023 Vocard Development
|
||||
Copyright (c) 2023 - present Vocard Development
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -26,33 +26,40 @@ import function as func
|
||||
|
||||
from math import ceil
|
||||
from tldextract import extract
|
||||
from typing import Any, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from voicelink import Track
|
||||
|
||||
class Select_playlist(discord.ui.Select):
|
||||
def __init__(self, results):
|
||||
options = [discord.SelectOption(emoji='🌎', label='All Playlist')]
|
||||
for index, playlist in enumerate(results, start=1):
|
||||
if playlist['type'] != 'error':
|
||||
options.append(discord.SelectOption(emoji=playlist['emoji'], label=f'{index}. {playlist["name"]}', description=f"{playlist['time']} · {playlist['type']}"))
|
||||
self.view: PlaylistView
|
||||
|
||||
super().__init__(
|
||||
placeholder="Select a playlist to view ..",
|
||||
options=options
|
||||
options=[discord.SelectOption(emoji='🌎', label='All Playlist')] +
|
||||
[
|
||||
discord.SelectOption(emoji=playlist['emoji'], label=f'{index}. {playlist["name"]}', description=f"{playlist['time']} · {playlist['type']}")
|
||||
for index, playlist in enumerate(results, start=1) if playlist['type'] != 'error'
|
||||
]
|
||||
)
|
||||
|
||||
async def callback(self, interaction: discord.Interaction):
|
||||
async def callback(self, interaction: discord.Interaction) -> None:
|
||||
if self.values[0] == 'All Playlist':
|
||||
self.view.current = None
|
||||
return await interaction.response.edit_message(embed=self.view.viewEmbed)
|
||||
|
||||
self.view.current = self.view.results[int(self.values[0].split(". ")[0]) - 1]
|
||||
self.view.page = ceil(len(self.view.current['tracks']) / 7)
|
||||
self.view.current_page = 1
|
||||
await interaction.response.edit_message(embed=self.view.build_embed())
|
||||
|
||||
class agree(discord.ui.Button):
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
self.view: CreateView
|
||||
super().__init__(label="Agree", style=discord.ButtonStyle.green)
|
||||
|
||||
async def callback(self, interaction: discord.Interaction):
|
||||
async def callback(self, interaction: discord.Interaction) -> None:
|
||||
self.label = "Created"
|
||||
self.disabled = True
|
||||
self.style=discord.ButtonStyle.primary
|
||||
@@ -62,57 +69,61 @@ class agree(discord.ui.Button):
|
||||
self.view.stop()
|
||||
|
||||
class PlaylistView(discord.ui.View):
|
||||
def __init__(self, viewEmbed, results, author):
|
||||
def __init__(
|
||||
self,
|
||||
viewEmbed: discord.Embed,
|
||||
results: list[dict[str, Any]],
|
||||
author: discord.Message
|
||||
) -> None:
|
||||
super().__init__(timeout=60)
|
||||
self.viewEmbed = viewEmbed
|
||||
self.results = results
|
||||
self.author = author
|
||||
self.guildID = author.guild.id
|
||||
self.response = None
|
||||
|
||||
self.current = None
|
||||
self.page = 0
|
||||
self.current_page = 1
|
||||
self.viewEmbed: discord.Embed = viewEmbed
|
||||
self.results: list[dict[str, Any]] = results
|
||||
self.author: discord.Member = author
|
||||
self.response: discord.Message = None
|
||||
|
||||
self.current: dict[str, Any] = None
|
||||
self.page: int = 0
|
||||
self.current_page: int = 1
|
||||
|
||||
self.add_item(Select_playlist(results))
|
||||
|
||||
async def interaction_check(self, interaction):
|
||||
if interaction.user == self.author:
|
||||
return True
|
||||
return False
|
||||
async def interaction_check(self, interaction: discord.Interaction) -> bool:
|
||||
return interaction.user == self.author
|
||||
|
||||
async def on_error(self, error, item, interaction):
|
||||
async def on_error(self, error, item, interaction) -> None:
|
||||
return
|
||||
|
||||
def build_embed(self):
|
||||
offset = self.current_page * 7
|
||||
tracks = self.current['tracks'][(offset-7):offset]
|
||||
def build_embed(self) -> discord.Embed:
|
||||
offset: int = self.current_page * 7
|
||||
tracks: list[Track] = self.current['tracks'][(offset-7):offset]
|
||||
guild_id = self.author.id
|
||||
|
||||
embed = discord.Embed(title=func.get_lang(self.guildID, 'playlistView'), color=func.settings.embed_color)
|
||||
embed = discord.Embed(title=func.get_lang(guild_id, 'playlistView'), color=func.settings.embed_color)
|
||||
|
||||
embed.description= func.get_lang(self.guildID, 'playlistViewDesc').format(self.current['name'], self.current['id'], len(self.current['tracks']), owner if (owner := self.current.get('owner')) else f"{self.author.id} (You)", self.current['type'])
|
||||
embed.description= func.get_lang(guild_id, 'playlistViewDesc').format(self.current['name'], self.current['id'], len(self.current['tracks']), owner if (owner := self.current.get('owner')) else f"{self.author.id} (You)", self.current['type'])
|
||||
|
||||
perms = self.current['perms']
|
||||
permsStr = func.get_lang(self.guildID, 'settingsPermTitle')
|
||||
permsStr = func.get_lang(guild_id, 'settingsPermTitle')
|
||||
if self.current['type'] == 'share':
|
||||
embed.add_field(name=permsStr, value=func.get_lang(self.guildID, 'playlistViewPermsValue').format('✓' if 'write' in perms and self.author.id in perms['write'] else '✘', '✓' if 'remove' in perms and self.author.id in perms['remove'] else '✘'))
|
||||
embed.add_field(name=permsStr, value=func.get_lang(guild_id, 'playlistViewPermsValue').format('✓' if 'write' in perms and self.author.id in perms['write'] else '✘', '✓' if 'remove' in perms and self.author.id in perms['remove'] else '✘'))
|
||||
else:
|
||||
embed.add_field(name=permsStr, value=func.get_lang(self.guildID, 'playlistViewPermsValue2').format(', '.join(f'<@{user}>' for user in perms['read'])))
|
||||
embed.add_field(name=permsStr, value=func.get_lang(guild_id, 'playlistViewPermsValue2').format(', '.join(f'<@{user}>' for user in perms['read'])))
|
||||
|
||||
trackStr = func.get_lang(self.guildID, 'playlistViewTrack')
|
||||
trackStr = func.get_lang(guild_id, 'playlistViewTrack')
|
||||
if tracks:
|
||||
if self.current.get("type") == "playlist":
|
||||
embed.add_field(name=trackStr, value="\n".join(f"{func.emoji_source(track['sourceName'])} `{index}.` `[{func.time(track['length'])}]` **{track['title'][:30]}**" for index, track in enumerate(tracks, start=offset - 6)), inline=False)
|
||||
else:
|
||||
embed.add_field(name=trackStr, value='\n'.join(f"{func.emoji_source(extract(track.info['uri']).domain)} `{index}.` `[{func.time(track.length)}]` **{track.title[:30]}** " for index, track in enumerate(tracks, start=offset - 6)), inline=False)
|
||||
else:
|
||||
embed.add_field(name=trackStr, value=func.get_lang(self.guildID, 'playlistNoTrack').format(self.current['name']), inline=False)
|
||||
embed.add_field(name=trackStr, value=func.get_lang(guild_id, 'playlistNoTrack').format(self.current['name']), inline=False)
|
||||
|
||||
embed.set_footer(text=func.get_lang(self.guildID, 'playlistViewPage').format(self.current_page, self.page, self.current['time']))
|
||||
embed.set_footer(text=func.get_lang(guild_id, 'playlistViewPage').format(self.current_page, self.page, self.current['time']))
|
||||
|
||||
return embed
|
||||
|
||||
async def on_timeout(self):
|
||||
async def on_timeout(self) -> None:
|
||||
for child in self.children:
|
||||
child.disabled = True
|
||||
try:
|
||||
@@ -121,51 +132,56 @@ class PlaylistView(discord.ui.View):
|
||||
pass
|
||||
|
||||
@discord.ui.button(label='<<', style=discord.ButtonStyle.grey)
|
||||
async def fast_back_button(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
async def fast_back_button(self, interaction: discord.Interaction, button: discord.ui.Button) -> None:
|
||||
if not self.current:
|
||||
return
|
||||
if self.current_page != 1:
|
||||
self.current_page = 1
|
||||
await interaction.response.edit_message(embed=self.build_embed())
|
||||
|
||||
return await interaction.response.edit_message(embed=self.build_embed())
|
||||
await interaction.response.defer()
|
||||
|
||||
@discord.ui.button(label='Back', style=discord.ButtonStyle.blurple)
|
||||
async def back_button(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
async def back_button(self, interaction: discord.Interaction, button: discord.ui.Button) -> None:
|
||||
if not self.current:
|
||||
return
|
||||
if self.current_page > 1:
|
||||
self.current_page -= 1
|
||||
await interaction.response.edit_message(embed=self.build_embed())
|
||||
|
||||
return await interaction.response.edit_message(embed=self.build_embed())
|
||||
await interaction.response.defer()
|
||||
|
||||
@discord.ui.button(label='Next', style=discord.ButtonStyle.blurple)
|
||||
async def next_button(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
async def next_button(self, interaction: discord.Interaction, button: discord.ui.Button) -> None:
|
||||
if not self.current:
|
||||
return
|
||||
if self.current_page < self.page:
|
||||
self.current_page += 1
|
||||
await interaction.response.edit_message(embed=self.build_embed())
|
||||
return await interaction.response.edit_message(embed=self.build_embed())
|
||||
await interaction.response.defer()
|
||||
|
||||
@discord.ui.button(label='>>', style=discord.ButtonStyle.grey)
|
||||
async def fast_next_button(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
async def fast_next_button(self, interaction: discord.Interaction, button: discord.ui.Button) -> None:
|
||||
if not self.current:
|
||||
return
|
||||
if self.current_page != self.page:
|
||||
self.current_page = self.page
|
||||
await interaction.response.edit_message(embed=self.build_embed())
|
||||
return await interaction.response.edit_message(embed=self.build_embed())
|
||||
await interaction.response.defer()
|
||||
|
||||
@discord.ui.button(emoji='🗑️', style=discord.ButtonStyle.red)
|
||||
async def stop_button(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
async def stop_button(self, interaction: discord.Interaction, button: discord.ui.Button) -> None:
|
||||
await self.response.delete()
|
||||
self.stop()
|
||||
|
||||
class CreateView(discord.ui.View):
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(timeout=20)
|
||||
self.value = None
|
||||
self.response = None
|
||||
self.value: bool = None
|
||||
self.response: discord.Message = None
|
||||
|
||||
self.add_item(agree())
|
||||
self.add_item(discord.ui.Button(label='Support', emoji=':support:915152950471581696', url=func.settings.invite_link))
|
||||
|
||||
async def on_timeout(self):
|
||||
async def on_timeout(self) -> None:
|
||||
for child in self.children:
|
||||
child.disabled = True
|
||||
try:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""MIT License
|
||||
|
||||
Copyright (c) 2023 Vocard Development
|
||||
Copyright (c) 2023 - present Vocard Development
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -20,36 +20,41 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import discord
|
||||
|
||||
from function import langs
|
||||
from typing import TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from voicelink import Track
|
||||
|
||||
class SearchDropdown(discord.ui.Select):
|
||||
def __init__(self, tracks, lang):
|
||||
self.tracks = tracks
|
||||
self.lang = lang
|
||||
options = []
|
||||
for index, track in enumerate(self.tracks, start=1):
|
||||
options.append(discord.SelectOption(label=f"{index}. {track.title[:50]}", description=f"{track.author[:50]} · {track.formatLength}"))
|
||||
|
||||
super().__init__(placeholder=langs[lang]['searchWait'],
|
||||
min_values=1, max_values=len(tracks),
|
||||
options=options
|
||||
)
|
||||
def __init__(self, tracks: list[Track], get_msg: callable) -> None:
|
||||
self.view: SearchView
|
||||
self.get_msg: callable = get_msg
|
||||
|
||||
async def callback(self, interaction: discord.Interaction):
|
||||
super().__init__(
|
||||
placeholder=get_msg('searchWait'),
|
||||
min_values=1, max_values=len(tracks),
|
||||
options=[
|
||||
discord.SelectOption(label=f"{i}. {track.title[:50]}", description=f"{track.author[:50]} · {track.formatted_length}")
|
||||
for i, track in enumerate(tracks, start=1)
|
||||
]
|
||||
)
|
||||
|
||||
async def callback(self, interaction: discord.Interaction) -> None:
|
||||
self.disabled = True
|
||||
self.placeholder = langs[self.lang]['searchSuccess']
|
||||
self.placeholder = self.get_msg('searchSuccess')
|
||||
await interaction.response.edit_message(view=self.view)
|
||||
self.view.values = self.values
|
||||
self.view.stop()
|
||||
|
||||
class SearchView(discord.ui.View):
|
||||
def __init__(self, tracks, lang):
|
||||
def __init__(self, tracks: list[Track], lang: callable) -> None:
|
||||
super().__init__(timeout=60)
|
||||
self.response = None
|
||||
self.values = None
|
||||
|
||||
self.response: discord.Message = None
|
||||
self.values: list[str] = None
|
||||
self.add_item(SearchDropdown(tracks, lang))
|
||||
|
||||
async def on_error(self, error, item, interaction):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""MIT License
|
||||
|
||||
Copyright (c) 2023 Vocard Development
|
||||
Copyright (c) 2023 - present Vocard Development
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -21,7 +21,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
__version__ = "1.3"
|
||||
__version__ = "1.4"
|
||||
__author__ = 'Vocard Development, Choco'
|
||||
__license__ = "MIT"
|
||||
__copyright__ = "Copyright 2023 (c) Vocard Development, Choco"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""MIT License
|
||||
|
||||
Copyright (c) 2023 Vocard Development
|
||||
Copyright (c) 2023 - present Vocard Development
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""MIT License
|
||||
|
||||
Copyright (c) 2023 Vocard Development
|
||||
Copyright (c) 2023 - present Vocard Development
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -108,12 +108,11 @@ class TrackExceptionEvent(VoicelinkEvent):
|
||||
def __init__(self, data: dict, player):
|
||||
self.player = player
|
||||
self.track = self.player._ending_track
|
||||
if data.get('error'):
|
||||
# User is running Lavalink <= 3.3
|
||||
self.exception: str = data["error"]
|
||||
else:
|
||||
# User is running Lavalink >=3.4
|
||||
self.exception: str = data["exception"]
|
||||
self.exception: dict = data.get("exception", {
|
||||
"severity": "",
|
||||
"message": "",
|
||||
"cause": ""
|
||||
})
|
||||
|
||||
# on_voicelink_track_exception(player, track, error)
|
||||
self.handler_args = self.player, self.track, self.exception
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""MIT License
|
||||
|
||||
Copyright (c) 2023 Vocard Development
|
||||
Copyright (c) 2023 - present Vocard Development
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""MIT License
|
||||
|
||||
Copyright (c) 2023 Vocard Development
|
||||
Copyright (c) 2023 - present Vocard Development
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import base64, io, abc, struct, dataclasses
|
||||
|
||||
from typing import Union, BinaryIO, Optional
|
||||
from typing import Union, BinaryIO, Optional, TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from .objects import Track
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class Codec:
|
||||
@@ -180,13 +184,14 @@ class TrackDecoder:
|
||||
"identifier": body_reader.read_utf(),
|
||||
"is_stream": body_reader.read_bool(),
|
||||
"uri": body_reader.read_optional_utf(),
|
||||
"thumbnail": None if version != 0 else body_reader.read_optional_utf(),
|
||||
"artworkUrl": None if version not in [0, 3] else body_reader.read_optional_utf(),
|
||||
"isrc": None if version != 3 else body_reader.read_optional_utf(),
|
||||
"sourceName": body_reader.read_utf(),
|
||||
"position": body_reader.read_long()
|
||||
}
|
||||
|
||||
class TrackEncoder:
|
||||
def encode(self, stream: MessageOutput, track) -> None:
|
||||
def encode(self, stream: MessageOutput, track: Track) -> None:
|
||||
body_writer = stream.start()
|
||||
|
||||
body_writer.write_byte(0)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""MIT License
|
||||
|
||||
Copyright (c) 2023 Vocard Development
|
||||
Copyright (c) 2023 - present Vocard Development
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -41,6 +41,28 @@ class Track:
|
||||
You can also pass in commands.Context to get a discord.py Context object in your track.
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
"_track_id",
|
||||
"info",
|
||||
"identifier",
|
||||
"title",
|
||||
"author",
|
||||
"uri",
|
||||
"source",
|
||||
"spotify",
|
||||
"artist_id",
|
||||
"original",
|
||||
"_search_type",
|
||||
"spotify_track",
|
||||
"thumbnail",
|
||||
"emoji",
|
||||
"length",
|
||||
"requester",
|
||||
"is_stream",
|
||||
"is_seekable",
|
||||
"position"
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@@ -50,73 +72,81 @@ class Track:
|
||||
search_type: SearchType = SearchType.ytsearch,
|
||||
spotify_track = None,
|
||||
):
|
||||
self.track_id = track_id
|
||||
self.info = info
|
||||
self._track_id: Optional[str] = track_id
|
||||
self.info: dict = info
|
||||
|
||||
self.identifier = info.get("identifier")
|
||||
self.title = info.get("title", "Unknown")
|
||||
self.author = info.get("author", "Unknown")
|
||||
self.uri = info.get("uri", "https://discord.com/application-directory/605618911471468554")
|
||||
self.source = info.get("sourceName", extract(self.uri).domain)
|
||||
self.spotify = True if self.source == "spotify" else False
|
||||
self.identifier: str = info.get("identifier")
|
||||
self.title: str = info.get("title", "Unknown")
|
||||
self.author: str = info.get("author", "Unknown")
|
||||
self.uri: str = info.get("uri", "https://discord.com/application-directory/605618911471468554")
|
||||
self.source: str = info.get("sourceName", extract(self.uri).domain)
|
||||
self.spotify: bool = self.source == "spotify"
|
||||
if self.spotify:
|
||||
self.artistId: Optional[list] = info.get("artistId")
|
||||
self.artist_id: Optional[list] = info.get("artist_id")
|
||||
|
||||
self.original: Optional[Track] = None if self.spotify else self
|
||||
self._search_type = SearchType.ytmsearch if self.spotify else search_type
|
||||
self.spotify_track = spotify_track
|
||||
self._search_type: SearchType = SearchType.ytmsearch if self.spotify else search_type
|
||||
self.spotify_track: Track = spotify_track
|
||||
|
||||
self.thumbnail = None
|
||||
self.thumbnail: str = info.get("artworkUrl")
|
||||
if not self.thumbnail and YOUTUBE_REGEX.match(self.uri):
|
||||
self.thumbnail = f"https://img.youtube.com/vi/{self.identifier}/maxresdefault.jpg"
|
||||
|
||||
self.emoji = emoji_source(self.source)
|
||||
self.emoji: str = emoji_source(self.source)
|
||||
self.length: float = 3000 if self.source == "soundcloud" and "/preview/" in self.identifier else info.get("length")
|
||||
|
||||
if info.get("thumbnail"):
|
||||
self.thumbnail = info.get("thumbnail")
|
||||
elif YOUTUBE_REGEX.match(self.uri):
|
||||
self.thumbnail = f"https://img.youtube.com/vi/{self.identifier}/hqdefault.jpg"
|
||||
self.requester: Member = requester
|
||||
self.is_stream: bool = info.get("isStream", False)
|
||||
self.is_seekable: bool = info.get("isSeekable", True)
|
||||
self.position: int = info.get("position", 0)
|
||||
|
||||
if self.source == "soundcloud" and "/preview/" in self.identifier:
|
||||
self.length = 30000
|
||||
else:
|
||||
self.length = info.get("length")
|
||||
|
||||
self.formatLength = ctime(self.length)
|
||||
self.requester = requester
|
||||
self.is_stream = info.get("isStream", False)
|
||||
self.is_seekable = info.get("isSeekable", True)
|
||||
self.position = info.get("position", 0)
|
||||
|
||||
if not track_id:
|
||||
self.track_id = encode(self)
|
||||
|
||||
def toDict(self):
|
||||
return {
|
||||
"track_id": self.track_id,
|
||||
"info": self.info,
|
||||
"thumbnail": self.thumbnail
|
||||
}
|
||||
|
||||
def encode(self):
|
||||
return encode(self)
|
||||
|
||||
def __eq__(self, other):
|
||||
def __eq__(self, other) -> bool:
|
||||
if not isinstance(other, Track):
|
||||
return False
|
||||
|
||||
return other.track_id == self.track_id
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
return self.title
|
||||
|
||||
def __repr__(self):
|
||||
def __repr__(self) -> str:
|
||||
return f"<Voicelink.track title={self.title!r} uri=<{self.uri!r}> length={self.length}>"
|
||||
|
||||
def toDict(self) -> dict:
|
||||
return {
|
||||
"track_id": self.track_id,
|
||||
"info": self.info,
|
||||
"thumbnail": self.thumbnail
|
||||
}
|
||||
|
||||
@property
|
||||
def track_id(self) -> str:
|
||||
if not self._track_id:
|
||||
self._track_id = encode(self)
|
||||
|
||||
return self._track_id
|
||||
|
||||
@property
|
||||
def formatted_length(self) -> str:
|
||||
return ctime(self.length)
|
||||
|
||||
class Playlist:
|
||||
"""The base playlist object.
|
||||
Returns critical playlist information needed for parsing by Lavalink.
|
||||
You can also pass in commands.Context to get a discord.py Context object in your tracks.
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
"playlist_info",
|
||||
"tracks_raw",
|
||||
"spotify",
|
||||
"name",
|
||||
"spotify_playlist",
|
||||
"_thumbnail",
|
||||
"_uri",
|
||||
"tracks"
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@@ -141,18 +171,16 @@ class Playlist:
|
||||
self._uri = self.spotify_playlist.uri
|
||||
else:
|
||||
self.tracks = [
|
||||
Track(track_id=track["track"], info=track["info"], requester=requester)
|
||||
Track(track_id=track["encoded"], info=track["info"], requester=requester)
|
||||
for track in self.tracks_raw
|
||||
]
|
||||
self._thumbnail = None
|
||||
self._uri = None
|
||||
|
||||
self.track_count = len(self.tracks)
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
return self.name
|
||||
|
||||
def __repr__(self):
|
||||
def __repr__(self) -> str:
|
||||
return f"<Voicelink.playlist name={self.name!r} track_count={len(self.tracks)}>"
|
||||
|
||||
@property
|
||||
@@ -164,3 +192,7 @@ class Playlist:
|
||||
def thumbnail(self) -> Optional[str]:
|
||||
"""Spotify album/playlist thumbnail, or None if not a Spotify object."""
|
||||
return self._thumbnail
|
||||
|
||||
@property
|
||||
def track_count(self) -> int:
|
||||
return len(self.tracks)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""MIT License
|
||||
|
||||
Copyright (c) 2023 Vocard Development
|
||||
Copyright (c) 2023 - present Vocard Development
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -96,8 +96,8 @@ class Player(VoiceProtocol):
|
||||
channel: Optional[VoiceChannel] = None,
|
||||
ctx: Union[commands.Context, Interaction] = None,
|
||||
):
|
||||
self.client = client
|
||||
self._bot = client
|
||||
self.client: Client = client
|
||||
self._bot: Client = client
|
||||
self.context = ctx
|
||||
self.dj: Member = ctx.user if isinstance(ctx, Interaction) else ctx.author
|
||||
self.channel: VoiceChannel = channel
|
||||
@@ -107,7 +107,6 @@ class Player(VoiceProtocol):
|
||||
self.settings: dict = func.get_settings(ctx.guild.id)
|
||||
self.joinTime: float = round(time.time())
|
||||
self._volume: int = self.settings.get('volume', 100)
|
||||
self.lang: dict = self.settings.get('lang', 'EN') if self.settings.get('lang', 'EN') in func.langs else "EN"
|
||||
self.queue: Queue = eval(self.settings.get("queueType", "Queue"))(self.settings.get("maxQueue", func.settings.max_queue), self.settings.get("duplicateTrack", True), self.get_msg)
|
||||
|
||||
self._node = NodePool.get_node()
|
||||
@@ -304,7 +303,7 @@ class Player(VoiceProtocol):
|
||||
event_type = data.get("type")
|
||||
event: VoicelinkEvent = getattr(events, event_type)(data, self)
|
||||
|
||||
if isinstance(event, TrackEndEvent) and event.reason != "REPLACED":
|
||||
if isinstance(event, TrackEndEvent) and event.reason != "replaced":
|
||||
self._current = None
|
||||
|
||||
event.dispatch(self._bot)
|
||||
@@ -441,7 +440,7 @@ class Player(VoiceProtocol):
|
||||
|
||||
try:
|
||||
tracks = await self._node._spotify_client.trackSearch(query=query)
|
||||
except:
|
||||
except Exception as _:
|
||||
raise TrackLoadError("Not able to find the provided Spotify entity, is it private?")
|
||||
|
||||
return [ Track(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""MIT License
|
||||
|
||||
Copyright (c) 2023 Vocard Development
|
||||
Copyright (c) 2023 - present Vocard Development
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -66,7 +66,7 @@ URL_REGEX = re.compile(
|
||||
r"https?://(?:www\.)?.+"
|
||||
)
|
||||
|
||||
NODE_VERSION = "v3"
|
||||
NODE_VERSION = "v4"
|
||||
CALL_METHOD = ["PATCH", "DELETE"]
|
||||
|
||||
def exception_catch_callback(task):
|
||||
@@ -394,27 +394,27 @@ class Node:
|
||||
|
||||
try:
|
||||
spotify_results = await self._spotify_client.search(query=query)
|
||||
except:
|
||||
except Exception as _:
|
||||
raise TrackLoadError("Not able to find the provided Spotify entity, is it private?")
|
||||
|
||||
if isinstance(spotify_results, spotify.Track):
|
||||
return [
|
||||
Track(
|
||||
track_id=None,
|
||||
info=spotify_results.to_dict(),
|
||||
requester=requester,
|
||||
search_type=search_type,
|
||||
spotify_track=spotify_results,
|
||||
info=spotify_results.to_dict()
|
||||
)
|
||||
]
|
||||
|
||||
tracks = [
|
||||
Track(
|
||||
track_id=None,
|
||||
info=track.to_dict(),
|
||||
requester=requester,
|
||||
search_type=search_type,
|
||||
spotify_track=track,
|
||||
info=track.to_dict()
|
||||
) for track in spotify_results.tracks if track.uri
|
||||
]
|
||||
|
||||
@@ -466,28 +466,40 @@ class Node:
|
||||
if not load_type:
|
||||
raise TrackLoadError("There was an error while trying to load this track.")
|
||||
|
||||
elif load_type == "LOAD_FAILED":
|
||||
exception = data["exception"]
|
||||
elif load_type == "error":
|
||||
exception = data["data"]
|
||||
raise TrackLoadError(f"{exception['message']} [{exception['severity']}]")
|
||||
|
||||
elif load_type == "NO_MATCHES":
|
||||
elif load_type == "empty":
|
||||
return None
|
||||
|
||||
elif load_type == "PLAYLIST_LOADED":
|
||||
elif load_type == "playlist":
|
||||
data = data.get("data")
|
||||
|
||||
return Playlist(
|
||||
playlist_info=data["playlistInfo"],
|
||||
playlist_info=data["info"],
|
||||
tracks=data["tracks"],
|
||||
requester=requester
|
||||
)
|
||||
|
||||
elif load_type == "SEARCH_RESULT" or load_type == "TRACK_LOADED":
|
||||
elif load_type == "search":
|
||||
return [
|
||||
Track(
|
||||
track_id=track["track"],
|
||||
track_id=track["encoded"],
|
||||
info=track["info"],
|
||||
requester=requester
|
||||
)
|
||||
for track in data["data"]
|
||||
]
|
||||
|
||||
elif load_type == "track":
|
||||
track = data["data"]
|
||||
return [
|
||||
Track(
|
||||
track_id=track["encoded"],
|
||||
info=track["info"],
|
||||
requester=requester
|
||||
)
|
||||
for track in data["tracks"]
|
||||
]
|
||||
|
||||
class NodePool:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""MIT License
|
||||
|
||||
Copyright (c) 2023 Vocard Development
|
||||
Copyright (c) 2023 - present Vocard Development
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -53,12 +53,12 @@ class LoopTypeCycle:
|
||||
|
||||
class Queue:
|
||||
def __init__(self, size: int, allow_duplicate: bool, get_msg: Callable[[str], str]) -> None:
|
||||
self._queue = []
|
||||
self._position = 0
|
||||
self._size = size
|
||||
self._repeat = LoopTypeCycle()
|
||||
self._repeat_position = 0
|
||||
self._allow_duplicate = allow_duplicate
|
||||
self._queue: List[Track] = []
|
||||
self._position: int = 0
|
||||
self._size: int = size
|
||||
self._repeat: LoopTypeCycle = LoopTypeCycle()
|
||||
self._repeat_position: int = 0
|
||||
self._allow_duplicate: bool = allow_duplicate
|
||||
|
||||
self.get_msg = get_msg
|
||||
|
||||
|
||||
@@ -23,12 +23,12 @@ SOFTWARE.
|
||||
|
||||
import re
|
||||
import time
|
||||
from base64 import b64encode
|
||||
|
||||
import aiohttp
|
||||
|
||||
from base64 import b64encode
|
||||
from typing import List, Union
|
||||
from .objects import Track, Album, Artist, Playlist
|
||||
from .exceptions import InvalidSpotifyURL, SpotifyRequestException
|
||||
from .exceptions import InvalidSpotifyURL, SpotifyRequestException
|
||||
|
||||
GRANT_URL = "https://accounts.spotify.com/api/token"
|
||||
REQUEST_URL = "https://api.spotify.com/v1/{type}s/{id}"
|
||||
@@ -71,7 +71,7 @@ class Client:
|
||||
self._expiry = time.time() + (int(data["expires_in"]) - 10)
|
||||
self._bearer_headers = {"Authorization": f"Bearer {self._bearer_token}"}
|
||||
|
||||
async def trackSearch(self, query: str, track: str = "track", limit: int = 10) -> list:
|
||||
async def trackSearch(self, query: str, track: str = "track", limit: int = 10) -> List[Track]:
|
||||
if not self._bearer_token or time.time() >= self._expiry:
|
||||
await self._fetch_bearer_token()
|
||||
|
||||
@@ -87,7 +87,7 @@ class Client:
|
||||
|
||||
return [ Track(track) for track in data['tracks']['items'] ]
|
||||
|
||||
async def similar_track(self, seed_tracks: str, *, limit: int = 5) -> list:
|
||||
async def similar_track(self, seed_tracks: str, *, limit: int = 5) -> List[Track]:
|
||||
if not self._bearer_token or time.time() >= self._expiry:
|
||||
await self._fetch_bearer_token()
|
||||
|
||||
@@ -103,7 +103,7 @@ class Client:
|
||||
|
||||
return [ Track(track) for track in data['tracks'] ]
|
||||
|
||||
async def search(self, *, query: str):
|
||||
async def search(self, *, query: str) -> Union[Track, Album, Playlist]:
|
||||
if not self._bearer_token or time.time() >= self._expiry:
|
||||
await self._fetch_bearer_token()
|
||||
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
from typing import List
|
||||
|
||||
class Track:
|
||||
"""The base class for a Spotify Track"""
|
||||
|
||||
__slots__ = (
|
||||
"name",
|
||||
"artists",
|
||||
"artist_id",
|
||||
"length",
|
||||
"id",
|
||||
"image",
|
||||
"uri"
|
||||
)
|
||||
|
||||
def __init__(self, data: dict, image=None) -> None:
|
||||
self.name = data.get('name', 'Unknown')
|
||||
self.artists = ", ".join(artist["name"] for artist in data.get('artists'))
|
||||
self.artistId = [artist['id'] for artist in data.get('artists')]
|
||||
self.length = data.get('duration_ms')
|
||||
self.id = data.get('id')
|
||||
|
||||
if data.get("album") and data["album"].get("images"):
|
||||
self.image = data["album"]["images"][0]["url"]
|
||||
else:
|
||||
self.image = image
|
||||
|
||||
if data["is_local"]:
|
||||
self.uri = None
|
||||
else:
|
||||
self.uri = data["external_urls"]["spotify"]
|
||||
self.name: str = data.get('name', 'Unknown')
|
||||
self.artists: str = ", ".join(artist["name"] for artist in data.get('artists'))
|
||||
self.artist_id: list[str] = [artist['id'] for artist in data.get('artists')]
|
||||
self.length: int = data.get('duration_ms')
|
||||
self.id: str = data.get('id')
|
||||
self.image: str = images[0]["url"] if (images := data.get("album", {}).get("images")) else image
|
||||
self.uri: str = None if data["is_local"] else data["external_urls"]["spotify"]
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
@@ -26,12 +26,12 @@ class Track:
|
||||
"author": self.artists,
|
||||
"length": self.length,
|
||||
"identifier": self.id,
|
||||
"artistId": self.artistId,
|
||||
"artist_id": self.artist_id,
|
||||
"uri": self.uri,
|
||||
"isStream": False,
|
||||
"isSeekable": True,
|
||||
"position": 0,
|
||||
"thumbnail": self.image
|
||||
"artworkUrl": self.image
|
||||
}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
@@ -43,14 +43,24 @@ class Track:
|
||||
class Album:
|
||||
"""The base class for a Spotify album"""
|
||||
|
||||
__slots__ = (
|
||||
"name",
|
||||
"artists",
|
||||
"image",
|
||||
"tracks",
|
||||
"total_tracks",
|
||||
"id",
|
||||
"uri"
|
||||
)
|
||||
|
||||
def __init__(self, data: dict) -> None:
|
||||
self.name = data.get('name', 'Unknown')
|
||||
self.artists = ", ".join(artist["name"] for artist in data.get('artists'))
|
||||
self.image = data["images"][0]["url"]
|
||||
self.tracks = [Track(track, image=self.image) for track in data["tracks"]["items"]]
|
||||
self.total_tracks = data["total_tracks"]
|
||||
self.id = data.get('id')
|
||||
self.uri = data["external_urls"]["spotify"]
|
||||
self.name: str = data.get('name', 'Unknown')
|
||||
self.artists: str = ", ".join(artist["name"] for artist in data.get('artists'))
|
||||
self.image: str = data["images"][0]["url"]
|
||||
self.tracks: list[Track] = [Track(track, image=self.image) for track in data["tracks"]["items"]]
|
||||
self.total_tracks: int = data["total_tracks"]
|
||||
self.id: str = data.get('id')
|
||||
self.uri: str = data["external_urls"]["spotify"]
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
@@ -61,15 +71,24 @@ class Album:
|
||||
class Artist:
|
||||
"""The base class for a Spotify playlist"""
|
||||
|
||||
__slots__ = (
|
||||
"tracks",
|
||||
"image",
|
||||
"total_tracks",
|
||||
"owner",
|
||||
"id",
|
||||
"uri",
|
||||
"name"
|
||||
)
|
||||
def __init__(self, data: dict) -> None:
|
||||
self.tracks = [Track(track) for track in data['tracks']]
|
||||
self.tracks: list[Track] = [Track(track) for track in data['tracks']]
|
||||
if self.tracks:
|
||||
self.image = self.tracks[0].image
|
||||
self.total_tracks = len(self.tracks)
|
||||
self.owner = self.tracks[0].artists
|
||||
self.id = self.tracks[0].artistId
|
||||
self.uri = data['tracks'][0]['album']['artists'][0]['external_urls']['spotify']
|
||||
self.name = f"Top tracks - {self.owner}"
|
||||
self.image: str = self.tracks[0].image
|
||||
self.total_tracks: int = len(self.tracks)
|
||||
self.owner: str = self.tracks[0].artists
|
||||
self.id: str = self.tracks[0].artist_id
|
||||
self.uri: str = data['tracks'][0]['album']['artists'][0]['external_urls']['spotify']
|
||||
self.name: str = f"Top tracks - {self.owner}"
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
@@ -80,17 +99,24 @@ class Artist:
|
||||
class Playlist:
|
||||
"""The base class for a Spotify playlist"""
|
||||
|
||||
def __init__(self, data: dict, tracks: List[Track]) -> None:
|
||||
self.name = data.get('name', 'Unknown')
|
||||
self.tracks = tracks
|
||||
self.owner = data["owner"]["display_name"]
|
||||
self.total_tracks = data["tracks"]["total"]
|
||||
self.id = data.get('id')
|
||||
if data.get("images") and len(data["images"]):
|
||||
self.image = data["images"][0]["url"]
|
||||
else:
|
||||
self.image = None
|
||||
self.uri = data["external_urls"]["spotify"]
|
||||
__slots__ = (
|
||||
"name",
|
||||
"tracks",
|
||||
"owner",
|
||||
"total_tracks",
|
||||
"id",
|
||||
"image",
|
||||
"uri"
|
||||
)
|
||||
|
||||
def __init__(self, data: dict, tracks: list[Track]) -> None:
|
||||
self.name: str = data.get('name', 'Unknown')
|
||||
self.tracks: list[Track] = tracks
|
||||
self.owner: str = data["owner"]["display_name"]
|
||||
self.total_tracks: int = data["tracks"]["total"]
|
||||
self.id: str = data.get('id')
|
||||
self.image: str = data["images"][0]["url"] if len(data.get("images", [])) else None
|
||||
self.uri: str = data["external_urls"]["spotify"]
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""MIT License
|
||||
|
||||
Copyright (c) 2023 Vocard Development
|
||||
Copyright (c) 2023 - present Vocard Development
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -279,7 +279,7 @@ async def getPlaylists(member: Member, data: dict):
|
||||
playlist = await func.get_playlist(pList["user"], "playlist", pList["referId"])
|
||||
if playlist:
|
||||
if member.id not in playlist["perms"]["read"]:
|
||||
await func.update_playlist(member.id, {f"playlist.{pId}": 1}, mode=False)
|
||||
await func.update_playlist(member.id, {f"playlist.{pId}": 1}, mode="unset")
|
||||
del playlists[pId]
|
||||
continue
|
||||
|
||||
@@ -304,9 +304,9 @@ async def removePlaylist(member: Member, data:dict):
|
||||
|
||||
if isShare:
|
||||
refer_user = data.get("refer_user")
|
||||
await func.update_playlist(refer_user, {f"playlist.{pId}.perms.read": member.id}, pull=True, mode=False)
|
||||
await func.update_playlist(refer_user, {f"playlist.{pId}.perms.read": member.id}, mode="pull")
|
||||
|
||||
await func.update_playlist(member.id, {f'playlist.{pId}': 1}, mode=False)
|
||||
await func.update_playlist(member.id, {f'playlist.{pId}': 1}, mode="unset")
|
||||
|
||||
async def addPlaylistTrack(member: Member, data: dict):
|
||||
track_id = data.get("track_id")
|
||||
@@ -321,14 +321,14 @@ async def addPlaylistTrack(member: Member, data: dict):
|
||||
if playlist["type"] != "playlist":
|
||||
return error_msg(func.get_lang(member.guild.id, 'playlistNotAllow'), user_id=member.id)
|
||||
|
||||
rank, max_p, max_t = await func.checkroles(member.id)
|
||||
rank, max_p, max_t = func.check_roles()
|
||||
if len(playlist["tracks"]) >= max_t:
|
||||
return error_msg(func.get_lang(member.guild.id, "playlistlimited").format(max_t), user_id=member.id)
|
||||
|
||||
if track_id in playlist['tracks']:
|
||||
return error_msg(func.get_lang(member.guild.id, "playlistrepeated"), user_id=member.id)
|
||||
|
||||
await func.update_playlist(member.id, {f'playlist.{pId}.tracks': track_id}, push=True)
|
||||
await func.update_playlist(member.id, {f'playlist.{pId}.tracks': track_id}, mode="push")
|
||||
|
||||
async def removePlaylistTrack(member: Member, data: dict):
|
||||
track_id = data.get("track_id")
|
||||
@@ -336,7 +336,7 @@ async def removePlaylistTrack(member: Member, data: dict):
|
||||
if not track_id or not pId:
|
||||
return
|
||||
|
||||
await func.update_playlist(member.id, {f'playlist.{pId}.tracks': track_id }, pull=True, mode=False)
|
||||
await func.update_playlist(member.id, {f'playlist.{pId}.tracks': track_id }, mode="pull")
|
||||
|
||||
methods = {
|
||||
"initPlayer": [initPlayer, False],
|
||||
|
||||
@@ -130,6 +130,19 @@ const decoders = [
|
||||
const source = input.readUTF();
|
||||
|
||||
return { track_id, title, author, length, identifier, isStream, uri, thumbnail: null, source, position: 0n };
|
||||
},
|
||||
(input, track_id) => {
|
||||
const title = input.readUTF();
|
||||
const author = input.readUTF();
|
||||
const length = input.readLong();
|
||||
const identifier = input.readUTF();
|
||||
const isStream = input.readBoolean();
|
||||
const uri = input.readBoolean() ? input.readUTF() : null;
|
||||
const thumbnail = input.readBoolean() ? input.readUTF() : null;
|
||||
const isrc = input.readBoolean() ? input.readUTF() : null;
|
||||
const source = input.readUTF();
|
||||
|
||||
return { track_id, title, author, length, identifier, isStream, uri, thumbnail, source, position: 0n };
|
||||
}
|
||||
]
|
||||
function decode(track_id) {
|
||||
@@ -481,7 +494,7 @@ class Player {
|
||||
return;
|
||||
}
|
||||
let position = tempPosition / 500 * this.currentTrack.length;
|
||||
this.send({ "op": "updatePosition", "position": position });
|
||||
this.send({ "op": "updatePosition", "position": Math.trunc(position) });
|
||||
}
|
||||
|
||||
shuffle() {
|
||||
|
||||
Reference in New Issue
Block a user