diff --git a/cogs/admin.py b/cogs/admin.py index cdb777c..d1e5321 100644 --- a/cogs/admin.py +++ b/cogs/admin.py @@ -6,6 +6,7 @@ import traceback import function as func +from typing import Tuple from discord import app_commands from discord.ext import commands from function import ( @@ -25,7 +26,7 @@ def __init__(self, bot) -> None: self.bot = bot self.description = "This category is only available to admin permissions on the server." - def get_settings(self, ctx: commands.Context) -> dict: + def get_settings(self, ctx: commands.Context) -> Tuple[voicelink.Player, dict]: player: voicelink.Player = ctx.guild.voice_client if not player: settings = get_settings(ctx.guild.id) @@ -34,9 +35,11 @@ def get_settings(self, ctx: commands.Context) -> dict: return player, settings - @commands.hybrid_group(name="settings", - aliases=get_aliases("settings"), - invoke_without_command=True) + @commands.hybrid_group( + name="settings", + aliases=get_aliases("settings"), + invoke_without_command=True + ) async def settings(self, ctx: commands.Context): view = HelpView(self.bot, ctx.author) embed = view.build_embed(self.qualified_name) @@ -101,8 +104,8 @@ async def dj(self, ctx: commands.Context, role: discord.Role = None): async def queue(self, ctx: commands.Context, mode: str): "Change to another type of queue mode." player, settings = self.get_settings(ctx) - if mode.capitalize() not in ["FairQueue", "Queue"]: - mode = "Queue" + + mode = "FairQueue" if mode.lower() == "fairqueue" else "Queue" settings["queueType"] = mode update_settings(ctx.guild.id, {"queueType": mode}) await ctx.send(get_lang(ctx.guild.id, "setqueue").format(mode)) @@ -211,10 +214,10 @@ async def duplicatetrack(self, ctx: commands.Context): player, settings = self.get_settings(ctx) toggle = settings.get('duplicateTrack', False) if player: - player.queue._duplicateTrack = not toggle + player.queue._allow_duplicate = not toggle update_settings(ctx.guild.id, {'duplicateTrack': not toggle}) - toggle = get_lang(ctx.guild.id, "enabled" if not toggle else "disabled") + toggle = get_lang(ctx.guild.id, "enabled" if toggle else "disabled") return await ctx.send(get_lang(ctx.guild.id, "toggleDuplicateTrack").format(toggle)) @settings.command(name="customcontroller", aliases=get_aliases("customcontroller")) @@ -234,7 +237,7 @@ async def debug(self, interaction: discord.Interaction): 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): + def clear_code(content: str): if content.startswith("```") and content.endswith("```"): return "\n".join(content.split("\n")[1:])[:-3] else: diff --git a/cogs/basic.py b/cogs/basic.py index ccd6a68..ab802d6 100644 --- a/cogs/basic.py +++ b/cogs/basic.py @@ -322,8 +322,9 @@ async def skip(self, ctx: commands.Context, index: int = 0): await ctx.send(player.get_msg('skipped').format(ctx.author)) - if player.queue._repeat == 1: - await player.queue.set_repeat("off") + if player.queue._repeat.mode == voicelink.LoopType.track: + await player.set_repeat(voicelink.LoopType.off.name) + await player.stop() @commands.hybrid_command(name="back", aliases=get_aliases("back")) @@ -357,8 +358,8 @@ async def back(self, ctx: commands.Context, index: int = 1): await ctx.send(player.get_msg('backed').format(ctx.author)) - if player.queue._repeat == 1: - await player.queue.set_repeat("off") + if player.queue._repeat.mode == voicelink.LoopType.track: + await player.set_repeat(voicelink.LoopType.off.name) @commands.hybrid_command(name="seek", aliases=get_aliases("seek")) @app_commands.describe(position="Input position. Exmaple: 1:20.") @@ -456,7 +457,7 @@ async def _import(self, ctx: commands.Context, attachment: discord.Attachment): track_ids = bytes.split(b"\n")[-1] track_ids = track_ids.decode().split(",") - tracks = [voicelink.Track(track_id=track_id, info=voicelink.decode(track_id), requester=ctx.author) for track_id in track_ids] + tracks = (voicelink.Track(track_id=track_id, info=voicelink.decode(track_id), requester=ctx.author) for track_id in track_ids) if not tracks: return await ctx.send(player.get_msg('noTrackFound')) diff --git a/function.py b/function.py index c4f1063..05e1e12 100644 --- a/function.py +++ b/function.py @@ -26,7 +26,10 @@ try: mongodb = MongoClient(host=tokens.mongodb_url, serverSelectionTimeoutMS=5000) mongodb.server_info() + if tokens.mongodb_name not in mongodb.list_database_names(): + raise Exception(f"{tokens.mongodb_name} does not exist in your mongoDB!") print("Successfully connected to MongoDB!") + except Exception as e: raise Exception("Not able to connect MongoDB! Reason:", e) diff --git a/main.py b/main.py index bf9f8f9..6cd5ade 100644 --- a/main.py +++ b/main.py @@ -88,7 +88,7 @@ async def on_command_error(self, ctx: commands.Context, exception, /) -> None: elif isinstance(error, (commands.CommandOnCooldown, commands.MissingPermissions, commands.RangeError, commands.BadArgument)): pass - elif isinstance(error, commands.MissingRequiredArgument, commands.MissingRequiredAttachment): + elif isinstance(error, (commands.MissingRequiredArgument, commands.MissingRequiredAttachment)): command = f" Correct Usage: {ctx.prefix}" + (f"{ctx.command.parent.qualified_name} " if ctx.command.parent else "") + f"{ctx.command.name} {ctx.command.signature}" position = command.find(f"<{ctx.current_parameter.name}>") + 1 error = f"```css\n[You are missing argument!]\n{command}\n" + " " * position + "^" * len(ctx.current_parameter.name) + "```" diff --git a/update.py b/update.py index 33d6741..ad96d4b 100644 --- a/update.py +++ b/update.py @@ -3,7 +3,7 @@ root_dir = os.path.dirname(os.path.abspath(__file__)) install_pack_dir = os.path.join(root_dir, "Vocard.zip") -__version__ = "v2.6.5" +__version__ = "v2.6.5a" def checkVersion(withMsg = False): resp = requests.get("https://api.github.com/repos/ChocoMeow/Vocard/releases/latest") diff --git a/views/controller.py b/views/controller.py index b436c09..52edf74 100644 --- a/views/controller.py +++ b/views/controller.py @@ -22,6 +22,7 @@ """ import discord +import voicelink import function as func from discord.ext import commands @@ -40,7 +41,7 @@ def key(interaction: discord.Interaction): class Back(discord.ui.Button): def __init__(self, player, style, row): - self.player = player + self.player: voicelink.Player = player super().__init__(emoji="⏮️", label=player.get_msg('buttonBack'), style=style, disabled=False if self.player.queue.history() or not self.player.current else True, row=row) async def callback(self, interaction: discord.Interaction): @@ -63,12 +64,12 @@ async def callback(self, interaction: discord.Interaction): await interaction.response.send_message(self.player.get_msg("backed").format(interaction.user)) - if self.player.queue._repeat == 1: - await self.player.set_repeat("off") + if self.player.queue._repeat.mode == voicelink.LoopType.track: + await self.player.set_repeat(voicelink.LoopType.off.name) class Resume(discord.ui.Button): def __init__(self, player, style, row): - self.player = player + self.player: voicelink.Player = player super().__init__(emoji="⏸️", label=player.get_msg('buttonPause'), style=style, disabled=False if self.player.current else True, row=row) async def callback(self, interaction: discord.Interaction): @@ -107,7 +108,7 @@ async def callback(self, interaction: discord.Interaction): class Skip(discord.ui.Button): def __init__(self, player, style, row): - self.player = player + self.player: voicelink.Player = player super().__init__(emoji="⏭️", label=player.get_msg('buttonSkip'), style=style, row=row) async def callback(self, interaction: discord.Interaction): @@ -127,13 +128,13 @@ async def callback(self, interaction: discord.Interaction): await interaction.response.send_message(self.player.get_msg("skipped").format(interaction.user)) - if self.player.queue._repeat == 1: - await self.player.set_repeat("off") + if self.player.queue._repeat.mode == voicelink.LoopType.track: + await self.player.set_repeat(voicelink.LoopType.off.name) await self.player.stop() class Stop(discord.ui.Button): def __init__(self, player, style, row): - self.player = player + self.player: voicelink.Player = player super().__init__(emoji="⏹️", label=player.get_msg('buttonLeave'), style=style, row=row) async def callback(self, interaction: discord.Interaction): if not self.player.is_privileged(interaction.user): @@ -151,7 +152,7 @@ async def callback(self, interaction: discord.Interaction): class Add(discord.ui.Button): def __init__(self, player, style, row): - self.player = player + self.player: voicelink.Player = player super().__init__(emoji="❤️", style=style, disabled=False if self.player.current else True, row=row) async def callback(self, interaction: discord.Interaction): @@ -177,7 +178,7 @@ async def callback(self, interaction: discord.Interaction): class Loop(discord.ui.Button): def __init__(self, player, style, row): - self.player = player + self.player: voicelink.Player = player super().__init__(emoji="🔁", label=player.get_msg('buttonLoop'), style=style, row=row) async def callback(self, interaction: discord.Interaction): @@ -189,7 +190,7 @@ async def callback(self, interaction: discord.Interaction): class VolumeUp(discord.ui.Button): def __init__(self, player, style, row): - self.player = player + self.player: voicelink.Player = player super().__init__(emoji="🔊", label=player.get_msg('buttonVolumeUp'), style=style, row=row) async def callback(self, interaction: discord.Interaction): @@ -203,7 +204,7 @@ async def callback(self, interaction: discord.Interaction): class VolumeDown(discord.ui.Button): def __init__(self, player, style, row): - self.player = player + self.player: voicelink.Player = player super().__init__(emoji="🔉", label=player.get_msg('buttonVolumeDown'), style=style, row=row) async def callback(self, interaction: discord.Interaction): @@ -217,7 +218,7 @@ async def callback(self, interaction: discord.Interaction): class VolumeMute(discord.ui.Button): def __init__(self, player, style, row): - self.player = player + self.player: voicelink.Player = player super().__init__(emoji="🔇" if player.volume else "🔈", label=player.get_msg('buttonVolumeMute' if player.volume else "buttonVolumeUnmute"), style=style, row=row) @@ -241,7 +242,7 @@ async def callback(self, interaction: discord.Interaction): class AutoPlay(discord.ui.Button): def __init__(self, player, style, row): - self.player = player + self.player: voicelink.Player = player super().__init__(emoji="💡", label=player.get_msg('buttonAutoPlay'), style=style, row=row) @@ -259,7 +260,7 @@ async def callback(self, interaction: discord.Interaction): class Shuffle(discord.ui.Button): def __init__(self, player, style, row): - self.player = player + self.player: voicelink.Player = player super().__init__(emoji="🔀", label=player.get_msg('buttonShuffle'), style=style, row=row) @@ -280,7 +281,7 @@ async def callback(self, interaction: discord.Interaction): class Forward(discord.ui.Button): def __init__(self, player, style, row): - self.player = player + self.player: voicelink.Player = player super().__init__(emoji="⏩", label=player.get_msg('buttonForward'), disabled=False if self.player.current else True, @@ -298,7 +299,7 @@ async def callback(self, interaction: discord.Interaction): class Rewind(discord.ui.Button): def __init__(self, player, style, row): - self.player = player + self.player: voicelink.Player = player super().__init__(emoji="⏪", label=player.get_msg('buttonRewind'), disabled=False if self.player.current else True, @@ -319,7 +320,7 @@ async def callback(self, interaction: discord.Interaction): class Tracks(discord.ui.Select): def __init__(self, player, style, row): - self.player = player + self.player: voicelink.Player = player options = [] for index, track in enumerate(self.player.queue.tracks(), start=1): @@ -370,7 +371,7 @@ class InteractiveController(discord.ui.View): def __init__(self, player): super().__init__(timeout=None) - self.player = player + self.player: voicelink.Player = player for row, btnRow in enumerate(func.settings.controller.get("default_buttons")): for btn in btnRow: color = "" @@ -385,7 +386,7 @@ def __init__(self, player): self.cooldown = commands.CooldownMapping.from_cooldown(2.0, 10.0, key) - async def interaction_check(self, interaction): + async def interaction_check(self, interaction: discord.Interaction): if not self.player.node._available: await interaction.response.send_message(self.player.get_msg("nodeReconnect"), ephemeral=True) return False diff --git a/voicelink/__init__.py b/voicelink/__init__.py index c7753ba..f82f387 100644 --- a/voicelink/__init__.py +++ b/voicelink/__init__.py @@ -26,7 +26,7 @@ __license__ = "MIT" __copyright__ = "Copyright 2023 (c) Vocard Development, Choco" -from .enums import SearchType +from .enums import SearchType, LoopType from .events import * from .exceptions import * from .filters import * diff --git a/voicelink/enums.py b/voicelink/enums.py index 8d16f4c..d9e4a00 100644 --- a/voicelink/enums.py +++ b/voicelink/enums.py @@ -23,6 +23,18 @@ from enum import Enum, auto +class LoopType(Enum): + """The enum for the different loop types for Voicelink + + LoopType.off: 1 + LoopType.track: 2 + LoopType.queue: 3 + + """ + + off = auto() + track = auto() + queue = auto() class SearchType(Enum): """The enum for the different search types for Voicelink. diff --git a/voicelink/player.py b/voicelink/player.py index 10f049d..7cf68db 100644 --- a/voicelink/player.py +++ b/voicelink/player.py @@ -39,10 +39,8 @@ Client, Guild, VoiceChannel, - VoiceProtocol, - StageChannel, + VoiceProtocol, Member, - Embed, ui, Message, Interaction @@ -50,9 +48,9 @@ from discord.ext import commands from . import events -from .enums import SearchType +from .enums import SearchType, LoopType from .events import VoicelinkEvent, TrackEndEvent, TrackStartEvent -from .exceptions import VoicelinkException, FilterInvalidArgument, TrackInvalidPosition, TrackLoadError, FilterTagAlreadyInUse +from .exceptions import VoicelinkException, FilterInvalidArgument, TrackInvalidPosition, TrackLoadError, FilterTagAlreadyInUse, DuplicateTrack from .filters import Filter, Filters from .objects import Track from .pool import Node, NodePool @@ -565,12 +563,20 @@ async def play( async def add_track(self, raw_tracks: Union[Track, List[Track]], at_font: bool = False) -> int: tracks = [] + + _duplicate_tracks = () if self.queue._allow_duplicate else (track.uri for track in self.queue._queue) + try: if (isList := isinstance(raw_tracks, List)): for track in raw_tracks: + if track.uri in _duplicate_tracks: + continue self.queue.put_at_front(track) if at_font else self.queue.put(track) tracks.append(track) else: + if raw_tracks.uri in _duplicate_tracks: + raise DuplicateTrack(self.get_msg("voicelinkDuplicateTrack")) + position = self.queue.put_at_front(raw_tracks) if at_font else self.queue.put(raw_tracks) tracks.append(raw_tracks) finally: @@ -605,7 +611,7 @@ async def set_volume(self, volume: int, requester: Member = None) -> int: self._volume = volume return self._volume - async def shuffle(self, queue_type: str, requester: Member = None): + async def shuffle(self, queue_type: str, requester: Member = None) -> None: replacement = self.queue.tracks() if queue_type == "queue" else self.queue.history() if len(replacement) < 3: raise VoicelinkException(self.get_msg('shuffleError')) @@ -623,19 +629,17 @@ async def shuffle(self, queue_type: str, requester: Member = None): } }, requester) - async def set_repeat(self, mode:str = None): + async def set_repeat(self, mode: str = None) -> str: if not mode: - mode = self.queue._repeat_mode.get((self.queue._repeat + 1)%len(self.queue._repeat_mode), 'off') - + mode = self.queue._repeat.next().name + is_found = False - for i, m in self.queue._repeat_mode.items(): - if m == mode.lower(): + for type in LoopType: + if type.name.lower() == mode.lower(): + self.queue._repeat.set_mode(type) is_found = True - self.queue._repeat = i - if i == 2: - self._repeat_position = self._position - 1 break - + if not is_found: raise VoicelinkException("Invalid repeat mode.") diff --git a/voicelink/queue.py b/voicelink/queue.py index 716fa86..4672401 100644 --- a/voicelink/queue.py +++ b/voicelink/queue.py @@ -21,41 +21,60 @@ SOFTWARE. """ -from .exceptions import QueueFull, OutofList, DuplicateTrack +from .exceptions import QueueFull, OutofList from .objects import Track +from .enums import LoopType + +from typing import Optional, Tuple, List, Callable +from itertools import cycle from discord import Member +class LoopTypeCycle: + def __init__(self) -> None: + self._cycle = cycle(LoopType) + self.current = next(self._cycle) + + def next(self) -> LoopType: + self.current = next(self._cycle) + return self.current + + def set_mode(self, value: LoopType) -> LoopType: + while next(self._cycle) != value: + pass + self.current = value + return value + + @property + def mode(self) -> LoopType: + return self.current + + def __str__(self) -> str: + return self.current.name.capitalize() + class Queue: - def __init__(self, size: int, duplicate_track: bool, get_msg): + def __init__(self, size: int, allow_duplicate: bool, get_msg: Callable[[str], str]) -> None: self._queue = [] self._position = 0 self._size = size - self._repeat = 0 + self._repeat = LoopTypeCycle() self._repeat_position = 0 - self._duplicate_track = duplicate_track - - self._repeat_mode = { - 0: "off", - 1: "track", - 2: "queue", - } + self._allow_duplicate = allow_duplicate self.get_msg = get_msg - def get(self): + def get(self) -> Optional[Track]: track = None try: - track = self._queue[self._position - - 1 if self._repeat == 1 else self._position] - if self._repeat != 1: + track = self._queue[self._position - 1 if self._repeat.mode == LoopType.track else self._position] + if self._repeat.mode != LoopType.track: self._position += 1 except: - if self._repeat == 2: + if self._repeat.mode == LoopType.queue: try: track = self._queue[self._repeat_position] self._position = self._repeat_position + 1 except IndexError: - self._repeat = 0 + self._repeat.set_mode(LoopType.off) return track @@ -63,61 +82,49 @@ def put(self, item: Track) -> int: if self.count >= self._size: raise QueueFull(self.get_msg("voicelinkQueueFull").format(self._size)) - if not self._duplicate_track: - if item.uri in [track.uri for track in self._queue]: - raise DuplicateTrack(self.get_msg("voicelinkDuplicateTrack")) - self._queue.append(item) return self.count - def put_at_front(self, item: Track): + def put_at_front(self, item: Track) -> int: if self.count >= self._size: raise QueueFull(self.get_msg("voicelinkQueueFull").format(self._size)) - if not self._duplicate_track: - if item.uri in [track.uri for track in self._queue]: - raise DuplicateTrack(self.get_msg("voicelinkDuplicateTrack")) - self._queue.insert(self._position, item) return 1 - def put_at_index(self, index: int, item: Track): + def put_at_index(self, index: int, item: Track) -> None: if self.count >= self._size: raise QueueFull(self.get_msg("voicelinkQueueFull").format(self._size)) - if not self._duplicate_track: - if item.uri in [track.uri for track in self._queue]: - raise DuplicateTrack(self.get_msg("voicelinkDuplicateTrack")) - return self._queue.insert(self._position - 1 + index, item) - def skipto(self, index: int): + def skipto(self, index: int) -> None: if not 0 < index <= self.count: raise OutofList(self.get_msg("voicelinkOutofList")) else: self._position += index - 1 - def backto(self, index: int): + def backto(self, index: int) -> None: if not self._position - index >= 0: raise OutofList(self.get_msg("voicelinkOutofList")) else: self._position -= index - def history_clear(self, is_playing: bool): + def history_clear(self, is_playing: bool) -> None: self._queue[:self._position - 1 if is_playing else self._position] = [] self._position = 1 if is_playing else 0 - def clear(self): + def clear(self) -> None: del self._queue[self._position:] - def replace(self, queue_type: str, replacement: list): + def replace(self, queue_type: str, replacement: list) -> None: if queue_type == "queue": self.clear() self._queue += replacement elif queue_type == "history": self._queue[:self._position] = replacement - def swap(self, num1: int, num2: int): + def swap(self, num1: int, num2: int) -> Tuple[Track, Track]: try: pos = self._position - 1 self._queue[pos + num1], self._queue[pos + num2] = self._queue[pos + num2], self._queue[pos + num1] @@ -125,7 +132,7 @@ def swap(self, num1: int, num2: int): except IndexError: raise OutofList(self.get_msg("voicelinkOutofList")) - def move(self, target: int, to: int): + def move(self, target: int, to: int) -> Optional[Track]: if not 0 < target <= self.count or not 0 < to: raise OutofList(self.get_msg("voicelinkOutofList")) @@ -137,20 +144,18 @@ def move(self, target: int, to: int): except: raise OutofList(self.get_msg("voicelinkOutofList")) - def remove(self, index: int, index2: int = None, member: Member = None): + def remove(self, index: int, index2: int = None, member: Member = None) -> Optional[List[Track]]: pos = self._position - 1 if index2 is None: index2 = index - if index2 < index: - temp = index - index = index2 - index2 = temp + elif index2 < index: + index, index2 = index2, index try: count = [] - for i, track in enumerate(self._queue[pos + index: pos + index2 + 1], start=0): + for i, track in enumerate(self._queue[pos + index: pos + index2 + 1]): if member: if track.requester != member: continue @@ -162,46 +167,41 @@ def remove(self, index: int, index2: int = None, member: Member = None): except: raise OutofList(self.get_msg("voicelinkOutofList")) - def history(self, incTrack: bool = False) -> list: + def history(self, incTrack: bool = False) -> List[Track]: if incTrack: return self._queue[:self._position] return self._queue[:self._position - 1] - def tracks(self, incTrack: bool = False): + def tracks(self, incTrack: bool = False) -> List[Track]: if incTrack: return self._queue[self._position - 1:] return self._queue[self._position:] @property - def count(self): + def count(self) -> int: return len(self._queue[self._position:]) - + @property - def repeat(self): - return self._repeat_mode.get(self._repeat, "Off").capitalize() + def repeat(self) -> str: + return self._repeat.mode.name.capitalize() @property - def is_empty(self): + def is_empty(self) -> bool: try: self._queue[self._position] except: return True return False - class FairQueue(Queue): - def __init__(self, size: int, duplicate_track: bool, get_msg): - super().__init__(size, duplicate_track, get_msg) + def __init__(self, size: int, allow_duplicate: bool, get_msg) -> None: + super().__init__(size, allow_duplicate, get_msg) self._set = set() - async def put(self, item: Track) -> int: + def put(self, item: Track) -> int: if len(self._queue) >= self._size: raise QueueFull(self.get_msg("voicelinkQueueFull").format(self._size)) - if not self._duplicate_track: - if item.uri in [track.uri for track in self._queue]: - raise DuplicateTrack(self.get_msg("voicelinkDuplicateTrack")) - tracks = self.tracks(incTrack=True) lastIndex = len(tracks) for track in reversed(tracks): @@ -214,5 +214,6 @@ async def put(self, item: Track) -> int: break lastIndex += 1 self._set.add(track.requester) - await self.put_at_index(lastIndex, item) + + self.put_at_index(lastIndex, item) return lastIndex diff --git a/web/ipc/methods.py b/web/ipc/methods.py index 9a4cfab..3f598da 100644 --- a/web/ipc/methods.py +++ b/web/ipc/methods.py @@ -2,7 +2,7 @@ from discord import Member, VoiceChannel from discord.ext import commands -from voicelink import Player, Track, Playlist, NodePool, connect_channel, decode +from voicelink import Player, Track, Playlist, NodePool, connect_channel, decode, LoopType class TempCtx(): def __init__(self, author: Member, channel: VoiceChannel) -> None: @@ -76,8 +76,8 @@ async def skipTo(player: Player, member: Member, data: dict): if index > 1: player.queue.skipto(index) - if player.queue._repeat == 1: - await player.set_repeat("off") + if player.queue._repeat.mode == LoopType.track: + await player.set_repeat(LoopType.off.name) await player.stop() async def backTo(player: Player, member: Member, data: dict):