import logging
import random
import re
from enum import Enum
import wavelink
import discord
from discord.ext import commands
from discord_slash.utils.manage_commands import create_option
from discord_slash import cog_ext, SlashContext, model
from .utils import static, formatting
from .utils.embed import RoEmbed

SPOTIFY_URL_REGEX = r"[\bhttps://open.\b]*spotify[\b.com\b]*[/:]*[/:]*[A-Za-z0-9?=]+"
YOUTUBE_URL_REGEX = (
    r"(?:https?:\/\/)?(?:youtu\.be\/|(?:www\.|m\.)?youtube\.com\/"
    r"(?:watch|v|embed)(?:\.php)?(?:\?.*v=|\/))([a-zA-Z0-9\_-]+)"
)

log = logging.getLogger("music-cog")


class RepeatMode(Enum):
    NONE = 0
    ONE = 1
    ALL = 2


class Track(wavelink.Track):
    """Wavelink Track object with a requester attribute."""

    __slots__ = "requester"

    def __init__(self, *args, **kwargs):
        super().__init__(*args)

        self.requester = kwargs.get("requester")


class Queue:
    def __init__(self):
        self.queue = []
        self.position = 0
        self.repeat_mode = RepeatMode.NONE

    @property
    def is_empty(self) -> bool:
        return not self.queue

    @property
    def current_track(self) -> Track:
        if not self.queue:
            return

        if self.position <= len(self.queue) - 1:
            return self.queue[self.position]

    @property
    def length(self) -> int:
        return len(self.queue)

    def add(self, *args):
        self.queue.extend(args)

    def get_next_track(self) -> Track:
        if not self.queue:
            return

        self.position += 1

        if self.position < 0:
            return None
        elif self.position > len(self.queue) - 1:
            if self.repeat_mode == RepeatMode.ALL:
                self.position = 0
            else:
                return None

        return self.queue[self.position]

    def shuffle(self):
        if not self.queue:
            return

        random.shuffle(self.queue)

    def set_repeat_mode(self, mode):
        if mode == "none":
            self.repeat_mode = RepeatMode.NONE
        elif mode == "one":
            self.repeat_mode = RepeatMode.ONE
        elif mode == "all":
            self.repeat_mode = RepeatMode.ALL

    def empty(self):
        self.queue.clear()
        self.position = 0


class Player(wavelink.Player):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.queue = Queue()

        self.user = kwargs.get("user", None)
        if self.user:
            self.dj = self.user

    async def connect(self, channel) -> discord.VoiceChannel:
        if self.is_connected:
            return

        await super().connect(channel.id)
        return channel

    async def teardown(self):
        try:
            await self.destroy()
        except KeyError:
            pass

    async def add_tracks(self, user: discord.Member, tracks: list, query: str) -> discord.Embed:

        if not tracks:
            return

        if isinstance(tracks, wavelink.TrackPlaylist):
            queue = []
            for track in tracks.tracks:
                track = Track(track.id, track.info, requester=user)
                queue.append(track)
                self.queue.add(track)
            playlist_info = tracks.data.get("playlistInfo")
            embed = RoEmbed(
                title="Playlist added to queue",
                description=f"Added playlist [{playlist_info.get('name')}]({query}) to the queue.",
            )
            embed.set_thumbnail(url=queue[0].thumb),

        else:
            track = Track(tracks[0].id, tracks[0].info, requester=user)
            self.queue.add(track)
            if self.queue.length == 0:
                embed = RoEmbed(
                    title="Song added to queue",
                    description=f"Now playing [{track.title}]({track.uri})",
                )
                embed.set_thumbnail(url=track.thumb)

            else:
                embed = RoEmbed(
                    title="Song added to queue",
                    description=f"Added [{track.title}]({track.uri}) to the queue.",
                )
                embed.set_thumbnail(url=track.thumb)

        if not self.is_playing and not self.queue.is_empty:
            await self.start_playback()

        return embed

    async def start_playback(self):
        await self.play(self.queue.current_track)

    async def advance(self):
        if (track := self.queue.get_next_track()) is not None:
            await self.play(track)

    async def repeat_track(self):
        await self.play(self.queue.current_track)


class Music(commands.Cog,wavelink.WavelinkMixin):
    def __init__(self, bot):
        self.bot = bot
        self.config = self.bot.config
        if not hasattr(bot, "wavelink"):
            self.wavelink = wavelink.Client(bot=self.bot)

        self.bot.loop.create_task(self.start_nodes())

    async def start_nodes(self):
        await self.bot.wait_until_ready()
        await self.wavelink.initiate_node(
            host=self.config.wavelink.host,
            port=self.config.wavelink.port,
            rest_uri=f"http://{self.config.wavelink.host}:{self.config.wavelink.port}",
            password=<PASSWORD>,
            identifier="TEST",
            region=self.config.wavelink.region,
        )

    @wavelink.WavelinkMixin.listener("on_track_stuck")
    @wavelink.WavelinkMixin.listener("on_track_end")
    @wavelink.WavelinkMixin.listener("on_track_exception")
    async def on_player_stop(self, node, payload):
        if payload.player.queue.repeat_mode == RepeatMode.ONE:
            await payload.player.repeat_track()
        else:
            await payload.player.advance()

    @commands.Cog.listener()
    async def on_voice_state_update(self, member, before, after):
        if not member.bot and after.channel is None:
            if not [m for m in before.channel.members if not m.bot]:
                await self.get_player(member.guild.id, member).teardown()

    def get_player(self, guild_id, user) -> Player:
        return self.wavelink.get_player(guild_id, cls=Player, user=user)

    @cog_ext.cog_slash(
        name="connect",
        description="Make the bot connect to a voice channel",
        options=[
            create_option(
                name="channel",
                description="The channel you want the bot to connect to",
                option_type=model.SlashCommandOptionType.CHANNEL,
                required=False,
            )
        ],
    )
    async def connect(self, ctx: SlashContext, channel: discord.VoiceChannel = None):
        if channel is None:
            channel = ctx.author.voice.channel
        player = self.get_player(ctx.guild_id, ctx.author)
        channel = await player.connect(channel)
        embed = RoEmbed(title=f"Connected to {channel.name}",
                        icon_url=ctx.author.avatar_url)
        await ctx.send(embeds=[embed])

    @cog_ext.cog_slash(name="disconnect",
                       description="Stop playback and make the bot leave your channel")
    async def disconnect(self, ctx: SlashContext):
        player = self.get_player(ctx.guild_id, ctx.author)
        await player.teardown()
        embed = RoEmbed(title="Disconnected",
                        icon_url=ctx.author.avatar_url)
        await ctx.send(embeds=[embed])

    @cog_ext.cog_slash(name="play",
                       description="Play a song via youtube or spotify!",
                       options=[
                           create_option(
                               name="query",
                               description="The search term or link",
                               option_type=model.SlashCommandOptionType.STRING,
                               required=True,
                           )
                       ],
                       )
    async def play(self, ctx: SlashContext, query: str):
        player = self.get_player(ctx.guild_id, ctx.author)

        if not player.is_connected:
            channel = ctx.author.voice.channel
            await player.connect(channel)

        query = query.strip("<>")

        if re.search(YOUTUBE_URL_REGEX, query):
            tracks = await self.wavelink.get_tracks(query)
            embed = await player.add_tracks(ctx.author, tracks, query)
        else:
            query = f"ytsearch:{query}"
            tracks = await self.wavelink.get_tracks(query)
            embed = await player.add_tracks(ctx.author, tracks, query)

        await ctx.send(embeds=[embed])

    @cog_ext.cog_slash(name="pause",
                       description="Pause playback")
    async def pause(self, ctx: SlashContext):
        player = self.get_player(ctx.guild_id, ctx.author)

        if player.is_paused:
            embed = RoEmbed(title="Error: Player already paused", color=static.ColorEmbedError,
                            icon_url=ctx.author.avatar_url)
        else:
            await player.set_pause(True)
            embed = RoEmbed(title="Paused player",
                            icon_url=ctx.author.avatar_url)
        await ctx.send(embeds=[embed])

    @cog_ext.cog_slash(name="resume",
                       description="Resume playback")
    async def resume(self, ctx: SlashContext):
        player = self.get_player(ctx.guild_id, ctx.author)

        if not player.is_paused:
            embed = RoEmbed(title="Error: Player not paused", color=static.ColorEmbedError,
                            icon_url=ctx.author.avatar_url)
            return
        else:
            await player.set_pause(False)
            embed = RoEmbed(title="Resumed player",
                            icon_url=ctx.author.avatar_url)
        await ctx.send(embeds=[embed])

    @cog_ext.cog_slash(name="skip",
                       description="Skip tracks",
                       options=[
                           create_option(
                               name="tracks",
                               description="Number of tracks to skip, defaults to 1",
                               option_type=model.SlashCommandOptionType.INTEGER,
                               required=False
                           )
                       ]
                       )
    async def skip(self, ctx: SlashContext, tracks: int = None):
        player = self.get_player(ctx.guild_id, ctx.author)

        if tracks is None:
            tracks = 1

        if player.queue.queue is None or len(player.queue.queue) == 0:
            embed = RoEmbed(title="Error: The queue isn't long enough", color=static.ColorEmbedError,
                            icon_url=ctx.author.avatar_url)
            await ctx.send(embeds=[embed])
            return

        if tracks > len(player.queue.queue[1:]):
            embed = RoEmbed(title="Error: The number of tracks to skip is greater than the queue", color=static.ColorEmbedError,
                            icon_url=ctx.author.avatar_url)
            await ctx.send(embeds=[embed])
            return

        embed = RoEmbed(title=f"Skipped {formatting.Plural(tracks):track}",
                        icon_url=ctx.author.avatar_url)
        await ctx.send(embeds=[embed])

    @cog_ext.cog_slash(name="queue",
                       description="Show which songs are being played next")
    async def queue(self, ctx: SlashContext):
        player = self.get_player(ctx.guild_id, ctx.author)

        if player.queue.queue is None or len(player.queue.queue) == 0:
            embed = RoEmbed(title="Error: The queue is empty", color=static.ColorEmbedError,
                            icon_url=ctx.author.avatar_url)
            await ctx.send(embeds=[embed])
            return

        final_string = ""
        titles = [track.title for track in player.queue.queue[1:11]]
        uris = [track.uri for track in player.queue.queue[1:11]]
        requester = [track.requester for track in player.queue.queue[1:11]]

        queue_length_time = 0

        for track in player.queue.queue:
            queue_length_time += track.length

        if len(titles) >= 10:
            upper_limit = 10
        else:
            upper_limit = len(titles)

        for i in range(0, upper_limit):
            final_string += (
                f"{i + 1}. [{titles[i]}]({uris[i]}) | Requested by: {requester[i]}\n\n"
            )

        embed = RoEmbed(
            title=f"Queue for {ctx.channel.name} | {len(player.queue.queue)} {formatting.Plural(len(player.queue.queue)):track}",
            description="**Now Playing:**\n\n"
                        f"[{player.queue.current_track.title}]({player.queue.current_track.uri}) | Requested by: {player.queue.current_track.requester}\n\n"
                        "**Up next:\n\n**"
                        f"{final_string}",
            icon_url=ctx.author.avatar_url
        )

        await ctx.send(embeds=[embed])

    @cog_ext.cog_slash(name="clear",
                       description="Clear the queue")
    async def clear(self, ctx: SlashContext):
        player = self.get_player(ctx.guild_id, ctx.author)

        if player.queue.queue is None or len(player.queue.queue) == 0:
            embed = RoEmbed(title="Error: Queue is already emtpy",
                            color=static.ColorEmbedError,
                            icon_url=ctx.author.avatar_url)
            await ctx.send(embeds=[embed])
            return

        player.queue.empty()

        embed = RoEmbed(title="Cleared queue",
                        color=static.ColorEmbedError,
                        icon_url=ctx.author.avatar_url)
        await ctx.send(embeds=[embed])

    @cog_ext.cog_slash(name="move",
                       description="Move a specified entry to a specified position",
                       options=[
                           create_option(
                               name="entry",
                               description="The song you want to move",
                               option_type=model.SlashCommandOptionType.INTEGER,
                               required=True,
                           ),
                           create_option(
                               name="position",
                               description="The song you want to move",
                               option_type=model.SlashCommandOptionType.INTEGER,
                               required=True,
                           ),
                       ])
    async def move(self, ctx: SlashContext, entry: int, position: int):
        player = self.get_player(ctx.guild_id, ctx.author)

        if len(player.queue.queue) == 0:
            embed = RoEmbed(title="Error: Queue is empty",
                            color=static.ColorEmbedError,
                            icon_url=ctx.author.avatar_url)
            await ctx.send(embeds=[embed])
            return
        elif entry - 1 > len(player.queue.queue) or position - 1 > len(player.queue.queue):
            embed = RoEmbed(title="Error: Entry or new position outside of current queue",
                            color=static.ColorEmbedError,
                            icon_url=ctx.author.avatar_url)
            await ctx.send(embeds=[embed])
            return

        player.queue.queue[entry], player.queue.queue[position] = player.queue.queue[position], player.queue.queue[entry]

        embed = RoEmbed(title=f"Moved {player.queue.queue[position].title} to postion {position}",
                        icon_url=ctx.author.avatar_url)
        await ctx.send(embeds=[embed])

    @cog_ext.cog_slash(name="repeat",
                       description="Set the current repeat mode",
                       options=[
                           create_option(
                               name="mode",
                               description="The repeat mode, can be either none, all or one",
                               option_type=model.SlashCommandOptionType.STRING,
                               required=True
                           )
                       ])
    async def repeat(self, ctx: SlashContext, mode: str):
        player = self.get_player(ctx.guild_id, ctx.author)

        if mode not in ("none", "one", "all"):
            embed = RoEmbed(title="Error: Invalid repeat mode given",
                            color=static.ColorEmbedError,
                            icon_url=ctx.author.avatar_url)
            await ctx.send(embeds=[embed])
            return

        player.queue.set_repeat_mode(mode)

        embed = RoEmbed(title=f"Repeat mode set to {mode}",
                        icon_url=ctx.author.avatar_url)
        await ctx.send(embeds=[embed])

def setup(bot):
    bot.add_cog(Music(bot))

import logging
import random
import re
from enum import Enum
import wavelink
import discord
from discord.ext import commands
from discord_slash.utils.manage_commands import create_option
from discord_slash import cog_ext, SlashContext, model
from .utils import static, formatting
from .utils.embed import RoEmbed

SPOTIFY_URL_REGEX = r"[\bhttps://open.\b]*spotify[\b.com\b]*[/:]*[/:]*[A-Za-z0-9?=]+"
YOUTUBE_URL_REGEX = (
    r"(?:https?:\/\/)?(?:youtu\.be\/|(?:www\.|m\.)?youtube\.com\/"
    r"(?:watch|v|embed)(?:\.php)?(?:\?.*v=|\/))([a-zA-Z0-9\_-]+)"
)

log = logging.getLogger("music-cog")


class RepeatMode(Enum):
    NONE = 0
    ONE = 1
    ALL = 2


class Track(wavelink.Track):
    """Wavelink Track object with a requester attribute."""

    __slots__ = "requester"

    def __init__(self, *args, **kwargs):
        super().__init__(*args)

        self.requester = kwargs.get("requester")


class Queue:
    def __init__(self):
        self.queue = []
        self.position = 0
        self.repeat_mode = RepeatMode.NONE

    @property
    def is_empty(self) -> bool:
        return not self.queue

    @property
    def current_track(self) -> Track:
        if not self.queue:
            return

        if self.position <= len(self.queue) - 1:
            return self.queue[self.position]

    @property
    def length(self) -> int:
        return len(self.queue)

    def add(self, *args):
        self.queue.extend(args)

    def get_next_track(self) -> Track:
        if not self.queue:
            return

        self.position += 1

        if self.position < 0:
            return None
        elif self.position > len(self.queue) - 1:
            if self.repeat_mode == RepeatMode.ALL:
                self.position = 0
            else:
                return None

        return self.queue[self.position]

    def shuffle(self):
        if not self.queue:
            return

        random.shuffle(self.queue)

    def set_repeat_mode(self, mode):
        if mode == "none":
            self.repeat_mode = RepeatMode.NONE
        elif mode == "one":
            self.repeat_mode = RepeatMode.ONE
        elif mode == "all":
            self.repeat_mode = RepeatMode.ALL

    def empty(self):
        self.queue.clear()
        self.position = 0


class Player(wavelink.Player):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.queue = Queue()

        self.user = kwargs.get("user", None)
        if self.user:
            self.dj = self.user

    async def connect(self, channel) -> discord.VoiceChannel:
        if self.is_connected:
            return

        await super().connect(channel.id)
        return channel

    async def teardown(self):
        try:
            await self.destroy()
        except KeyError:
            pass

    async def add_tracks(self, user: discord.Member, tracks: list, query: str) -> discord.Embed:

        if not tracks:
            return

        if isinstance(tracks, wavelink.TrackPlaylist):
            queue = []
            for track in tracks.tracks:
                track = Track(track.id, track.info, requester=user)
                queue.append(track)
                self.queue.add(track)
            playlist_info = tracks.data.get("playlistInfo")
            embed = RoEmbed(
                title="Playlist added to queue",
                description=f"Added playlist [{playlist_info.get('name')}]({query}) to the queue.",
            )
            embed.set_thumbnail(url=queue[0].thumb),

        else:
            track = Track(tracks[0].id, tracks[0].info, requester=user)
            self.queue.add(track)
            if self.queue.length == 0:
                embed = RoEmbed(
                    title="Song added to queue",
                    description=f"Now playing [{track.title}]({track.uri})",
                )
                embed.set_thumbnail(url=track.thumb)

            else:
                embed = RoEmbed(
                    title="Song added to queue",
                    description=f"Added [{track.title}]({track.uri}) to the queue.",
                )
                embed.set_thumbnail(url=track.thumb)

        if not self.is_playing and not self.queue.is_empty:
            await self.start_playback()

        return embed

    async def start_playback(self):
        await self.play(self.queue.current_track)

    async def advance(self):
        if (track := self.queue.get_next_track()) is not None:
            await self.play(track)

    async def repeat_track(self):
        await self.play(self.queue.current_track)


class Music(commands.Cog,wavelink.WavelinkMixin):
    def __init__(self, bot):
        self.bot = bot
        self.config = self.bot.config
        if not hasattr(bot, "wavelink"):
            self.wavelink = wavelink.Client(bot=self.bot)

        self.bot.loop.create_task(self.start_nodes())

    async def start_nodes(self):
        await self.bot.wait_until_ready()
        await self.wavelink.initiate_node(
            host=self.config.wavelink.host,
            port=self.config.wavelink.port,
            rest_uri=f"http://{self.config.wavelink.host}:{self.config.wavelink.port}",
            password=<PASSWORD>,
            identifier="TEST",
            region=self.config.wavelink.region,
        )

    @wavelink.WavelinkMixin.listener("on_track_stuck")
    @wavelink.WavelinkMixin.listener("on_track_end")
    @wavelink.WavelinkMixin.listener("on_track_exception")
    async def on_player_stop(self, node, payload):
        if payload.player.queue.repeat_mode == RepeatMode.ONE:
            await payload.player.repeat_track()
        else:
            await payload.player.advance()

    @commands.Cog.listener()
    async def on_voice_state_update(self, member, before, after):
        if not member.bot and after.channel is None:
            if not [m for m in before.channel.members if not m.bot]:
                await self.get_player(member.guild.id, member).teardown()

    def get_player(self, guild_id, user) -> Player:
        return self.wavelink.get_player(guild_id, cls=Player, user=user)

    @cog_ext.cog_slash(
        name="connect",
        description="Make the bot connect to a voice channel",
        options=[
            create_option(
                name="channel",
                description="The channel you want the bot to connect to",
                option_type=model.SlashCommandOptionType.CHANNEL,
                required=False,
            )
        ],
    )
    async def connect(self, ctx: SlashContext, channel: discord.VoiceChannel = None):
        if channel is None:
            channel = ctx.author.voice.channel
        player = self.get_player(ctx.guild_id, ctx.author)
        channel = await player.connect(channel)
        embed = RoEmbed(title=f"Connected to {channel.name}",
                        icon_url=ctx.author.avatar_url)
        await ctx.send(embeds=[embed])

    @cog_ext.cog_slash(name="disconnect",
                       description="Stop playback and make the bot leave your channel")
    async def disconnect(self, ctx: SlashContext):
        player = self.get_player(ctx.guild_id, ctx.author)
        await player.teardown()
        embed = RoEmbed(title="Disconnected",
                        icon_url=ctx.author.avatar_url)
        await ctx.send(embeds=[embed])

    @cog_ext.cog_slash(name="play",
                       description="Play a song via youtube or spotify!",
                       options=[
                           create_option(
                               name="query",
                               description="The search term or link",
                               option_type=model.SlashCommandOptionType.STRING,
                               required=True,
                           )
                       ],
                       )
    async def play(self, ctx: SlashContext, query: str):
        player = self.get_player(ctx.guild_id, ctx.author)

        if not player.is_connected:
            channel = ctx.author.voice.channel
            await player.connect(channel)

        query = query.strip("<>")

        if re.search(YOUTUBE_URL_REGEX, query):
            tracks = await self.wavelink.get_tracks(query)
            embed = await player.add_tracks(ctx.author, tracks, query)
        else:
            query = f"ytsearch:{query}"
            tracks = await self.wavelink.get_tracks(query)
            embed = await player.add_tracks(ctx.author, tracks, query)

        await ctx.send(embeds=[embed])

    @cog_ext.cog_slash(name="pause",
                       description="Pause playback")
    async def pause(self, ctx: SlashContext):
        player = self.get_player(ctx.guild_id, ctx.author)

        if player.is_paused:
            embed = RoEmbed(title="Error: Player already paused", color=static.ColorEmbedError,
                            icon_url=ctx.author.avatar_url)
        else:
            await player.set_pause(True)
            embed = RoEmbed(title="Paused player",
                            icon_url=ctx.author.avatar_url)
        await ctx.send(embeds=[embed])

    @cog_ext.cog_slash(name="resume",
                       description="Resume playback")
    async def resume(self, ctx: SlashContext):
        player = self.get_player(ctx.guild_id, ctx.author)

        if not player.is_paused:
            embed = RoEmbed(title="Error: Player not paused", color=static.ColorEmbedError,
                            icon_url=ctx.author.avatar_url)
            return
        else:
            await player.set_pause(False)
            embed = RoEmbed(title="Resumed player",
                            icon_url=ctx.author.avatar_url)
        await ctx.send(embeds=[embed])

    @cog_ext.cog_slash(name="skip",
                       description="Skip tracks",
                       options=[
                           create_option(
                               name="tracks",
                               description="Number of tracks to skip, defaults to 1",
                               option_type=model.SlashCommandOptionType.INTEGER,
                               required=False
                           )
                       ]
                       )
    async def skip(self, ctx: SlashContext, tracks: int = None):
        player = self.get_player(ctx.guild_id, ctx.author)

        if tracks is None:
            tracks = 1

        if player.queue.queue is None or len(player.queue.queue) == 0:
            embed = RoEmbed(title="Error: The queue isn't long enough", color=static.ColorEmbedError,
                            icon_url=ctx.author.avatar_url)
            await ctx.send(embeds=[embed])
            return

        if tracks > len(player.queue.queue[1:]):
            embed = RoEmbed(title="Error: The number of tracks to skip is greater than the queue", color=static.ColorEmbedError,
                            icon_url=ctx.author.avatar_url)
            await ctx.send(embeds=[embed])
            return

        embed = RoEmbed(title=f"Skipped {formatting.Plural(tracks):track}",
                        icon_url=ctx.author.avatar_url)
        await ctx.send(embeds=[embed])

    @cog_ext.cog_slash(name="queue",
                       description="Show which songs are being played next")
    async def queue(self, ctx: SlashContext):
        player = self.get_player(ctx.guild_id, ctx.author)

        if player.queue.queue is None or len(player.queue.queue) == 0:
            embed = RoEmbed(title="Error: The queue is empty", color=static.ColorEmbedError,
                            icon_url=ctx.author.avatar_url)
            await ctx.send(embeds=[embed])
            return

        final_string = ""
        titles = [track.title for track in player.queue.queue[1:11]]
        uris = [track.uri for track in player.queue.queue[1:11]]
        requester = [track.requester for track in player.queue.queue[1:11]]

        queue_length_time = 0

        for track in player.queue.queue:
            queue_length_time += track.length

        if len(titles) >= 10:
            upper_limit = 10
        else:
            upper_limit = len(titles)

        for i in range(0, upper_limit):
            final_string += (
                f"{i + 1}. [{titles[i]}]({uris[i]}) | Requested by: {requester[i]}\n\n"
            )

        embed = RoEmbed(
            title=f"Queue for {ctx.channel.name} | {len(player.queue.queue)} {formatting.Plural(len(player.queue.queue)):track}",
            description="**Now Playing:**\n\n"
                        f"[{player.queue.current_track.title}]({player.queue.current_track.uri}) | Requested by: {player.queue.current_track.requester}\n\n"
                        "**Up next:\n\n**"
                        f"{final_string}",
            icon_url=ctx.author.avatar_url
        )

        await ctx.send(embeds=[embed])

    @cog_ext.cog_slash(name="clear",
                       description="Clear the queue")
    async def clear(self, ctx: SlashContext):
        player = self.get_player(ctx.guild_id, ctx.author)

        if player.queue.queue is None or len(player.queue.queue) == 0:
            embed = RoEmbed(title="Error: Queue is already emtpy",
                            color=static.ColorEmbedError,
                            icon_url=ctx.author.avatar_url)
            await ctx.send(embeds=[embed])
            return

        player.queue.empty()

        embed = RoEmbed(title="Cleared queue",
                        color=static.ColorEmbedError,
                        icon_url=ctx.author.avatar_url)
        await ctx.send(embeds=[embed])

    @cog_ext.cog_slash(name="move",
                       description="Move a specified entry to a specified position",
                       options=[
                           create_option(
                               name="entry",
                               description="The song you want to move",
                               option_type=model.SlashCommandOptionType.INTEGER,
                               required=True,
                           ),
                           create_option(
                               name="position",
                               description="The song you want to move",
                               option_type=model.SlashCommandOptionType.INTEGER,
                               required=True,
                           ),
                       ])
    async def move(self, ctx: SlashContext, entry: int, position: int):
        player = self.get_player(ctx.guild_id, ctx.author)

        if len(player.queue.queue) == 0:
            embed = RoEmbed(title="Error: Queue is empty",
                            color=static.ColorEmbedError,
                            icon_url=ctx.author.avatar_url)
            await ctx.send(embeds=[embed])
            return
        elif entry - 1 > len(player.queue.queue) or position - 1 > len(player.queue.queue):
            embed = RoEmbed(title="Error: Entry or new position outside of current queue",
                            color=static.ColorEmbedError,
                            icon_url=ctx.author.avatar_url)
            await ctx.send(embeds=[embed])
            return

        player.queue.queue[entry], player.queue.queue[position] = player.queue.queue[position], player.queue.queue[entry]

        embed = RoEmbed(title=f"Moved {player.queue.queue[position].title} to postion {position}",
                        icon_url=ctx.author.avatar_url)
        await ctx.send(embeds=[embed])

    @cog_ext.cog_slash(name="repeat",
                       description="Set the current repeat mode",
                       options=[
                           create_option(
                               name="mode",
                               description="The repeat mode, can be either none, all or one",
                               option_type=model.SlashCommandOptionType.STRING,
                               required=True
                           )
                       ])
    async def repeat(self, ctx: SlashContext, mode: str):
        player = self.get_player(ctx.guild_id, ctx.author)

        if mode not in ("none", "one", "all"):
            embed = RoEmbed(title="Error: Invalid repeat mode given",
                            color=static.ColorEmbedError,
                            icon_url=ctx.author.avatar_url)
            await ctx.send(embeds=[embed])
            return

        player.queue.set_repeat_mode(mode)

        embed = RoEmbed(title=f"Repeat mode set to {mode}",
                        icon_url=ctx.author.avatar_url)
        await ctx.send(embeds=[embed])

def setup(bot):
    bot.add_cog(Music(bot))

Wavelink Track object with a requester attribute.