<gh_stars>1-10
import asyncio
import io
import json
import re
from types import coroutine
from typing import List, Tuple, Union, Any, Callable, Iterable

from PIL.Image import Image
from dpy_button_utils import ButtonConfirmation, ButtonPaginator

import discord
from discord.embeds import EmptyEmbed
from discord.ext import commands
from libs.conversions import escape


def _wrap_user(user: discord.abc.User):
    return f"**{user}** "


class AoiContext(commands.Context):
    INFO = 0
    ERROR = 1
    OK = 2

    @property
    def clean_prefix(self):
        return escape(self.prefix, self)

    async def using_embeds(self):
        return (await self.bot.db.guild_setting(self.guild.id)).reply_embeds

    async def trash_reaction(self, message: discord.Message):
        if len(message.embeds) == 0:
            return

        def check(_reaction: discord.Reaction, _user: Union[discord.User, discord.Member]):
            return all([
                _user.id == self.author.id or _user.guild_permissions.manage_messages,
                _reaction.message.id == message.id,
                str(_reaction) == "🗑️"
            ])

        await message.add_reaction("🗑️")
        await asyncio.sleep(0.5)
        try:
            _, _ = await self.bot.wait_for("reaction_add", timeout=60.0, check=check)
        except asyncio.TimeoutError:
            await message.clear_reactions()
        else:
            await message.delete()

    async def confirm(self, message: str, confirmed: str, denied: str):
        conf = ButtonConfirmation(self, message, timeout=120, confirm_message=confirmed, cancel_message=denied)
        return await conf.run()

    async def confirm_coro(self, message: str, confirmed: str, denied: str, coro: coroutine):
        conf = ButtonConfirmation(self, message, timeout=120, confirm_message="...",
                                  cancel_message=denied)
        is_confirmed = await conf.run()
        if is_confirmed:
            await coro
            await (await self.fetch_message(conf.msg)).edit(content=confirmed)
        return is_confirmed

    async def done_ping(self):
        return await self.send("Done!", reference=self.message, mention_author=False)

    async def send_info(self, message: str, *, user: discord.abc.User = None,
                        title: str = None, trash: bool = False, ping: bool = False):
        if not user:
            user = self.author
        if not (await self.bot.db.guild_setting(self.guild.id)).reply_embeds:
            msg = await self.send(f":information_source: "
                                  f"{_wrap_user(user)} {title if title is not None else ''}\n{message}")
        else:
            msg = await self.send(
                user.mention if ping else None,
                embed=discord.Embed(
                    title=title or EmptyEmbed,
                    description=f"{_wrap_user(user) if user else ''}{message}",
                    colour=await self.get_color(self.INFO)
                ))
        if trash:
            await self.trash_reaction(msg)
        else:
            return msg

    async def send_ok(self, message: str, *, user: discord.abc.User = None,
                      title: str = None, trash: bool = False, ping: bool = False):
        if not (await self.bot.db.guild_setting(self.guild.id)).reply_embeds:
            return await self.send(f":white_check_mark: "
                                   f"{_wrap_user(user)} {title if title is not None else ''}\n{message}")
        if not user:
            user = self.author
        msg = await self.send(
            user.mention if ping else None,
            embed=discord.Embed(
                title=title or EmptyEmbed,
                description=f"{_wrap_user(user) if user else ''}{message}",
                colour=await self.get_color(self.OK)
            ))
        if trash:
            await self.trash_reaction(msg)
        else:
            return msg

    async def send_error(self, message: str, *, user: discord.abc.User = None,
                         title: str = None, trash: bool = False, ping: bool = False):
        if not (await self.bot.db.guild_setting(self.guild.id)).reply_embeds:
            return await self.send(f":x: {_wrap_user(user)} {title if title is not None else ''}\n{message}")
        if not user:
            user = self.author
        msg = await self.send(
            user.mention if ping else None,
            embed=discord.Embed(
                title=title or EmptyEmbed,
                description=f"{_wrap_user(user) if user else ''}{message}",
                colour=await self.get_color(self.ERROR)
            ))
        if trash:
            await self.trash_reaction(msg)
        else:
            return msg

    async def edit_message(self, message: discord.Message, content: str, *, user: discord.abc.User = None,
                           title: str = None, color: int = 0):
        if not user:
            user = self.author
        await message.edit(embed=discord.Embed(
            title=title or EmptyEmbed,
            description=f"{_wrap_user(user) if user else ''}{message}",
            colour=await self.get_color(color)
        ))
        return message

    async def get_color(self, typ: int):
        if not self.guild:
            return discord.Color([0x0000ff, 0xff0000, 0x00cc00][typ])
        if typ == 0:
            return discord.Color((await self.bot.db.guild_setting(self.guild.id)).info_color)
        if typ == 1:
            return discord.Color((await self.bot.db.guild_setting(self.guild.id)).error_color)
        if typ == 2:
            return discord.Color((await self.bot.db.guild_setting(self.guild.id)).ok_color)
        raise ValueError

    # noinspection PyDefaultArgument
    async def embed(self, *,  # noqa c901
                    author: str = None,
                    description: str = None,
                    title: str = None,
                    typ: int = INFO,
                    fields: List[Tuple[str, str]] = None,
                    thumbnail: str = None,
                    clr: discord.Colour = None,
                    image: Union[str, io.BufferedIOBase] = None,
                    footer: str = None,
                    not_inline: List[int] = [],
                    trash_reaction: bool = False):  # noqa c901
        if not (await self.bot.db.guild_setting(self.guild.id)).reply_embeds:
            msg = ""
            msg += f"**{author}**\n" if author else ""
            msg += f":{['information_source', 'x', 'white_check_mark'][typ]}: " + \
                   (f"**{title}**" if title else "") + "\n"
            msg += f"{description}\n" if description is not None else ""
            for i in fields or []:
                msg += f"**{i[0]}**\n{i[1]}\n\n"
            msg += f"**{footer}**\n" if footer else ""
            zw_sp = "​"  # not blank, there's a zero width space there, I swear
            url = ""
            if image:
                if isinstance(image, str):
                    f = None
                    url = image
                elif isinstance(image, Image):
                    buf = io.BytesIO()
                    image.save(buf, "png")
                    buf.seek(0)
                    f = discord.File(buf, filename="image.png")
                    url = ""
                else:
                    image.seek(0)
                    f = discord.File(image, filename="image.png")
                    url = ""
            else:
                f = None
            return await self.send(re.sub(r"\[(.*?)\]\((.*?)\)", rf"\1 ({zw_sp}<\2>{zw_sp})", msg) + url,
                                   file=f)  # noqa ignore the \[

        if typ and clr:
            raise ValueError("typ and clr can not be both defined")
        embed = discord.Embed(
            title=title or discord.embeds.EmptyEmbed,
            description=description or discord.embeds.EmptyEmbed,
            colour=(await self.get_color(typ) if not clr else clr)
        )
        if author:
            embed.set_author(name=author)
        if image:
            if isinstance(image, str):
                embed.set_image(url=image)
                f = None
            elif isinstance(image, Image):
                buf = io.BytesIO()
                image.save(buf, "png")
                buf.seek(0)
                f = discord.File(buf, filename="image.png")
                embed.set_image(url="attachment://image.png")
            else:
                image.seek(0)
                f = discord.File(image, filename="image.png")
                embed.set_image(url="attachment://image.png")
        else:
            f = None
        if footer:
            embed.set_footer(text=footer)
        if thumbnail:
            embed.set_thumbnail(url=thumbnail)
        for n, r in enumerate(fields or []):
            embed.add_field(name=r[0], value=r[1] or "None", inline=n not in not_inline)
        msg = await self.send(embed=embed, file=f)
        if trash_reaction:
            await self.trash_reaction(msg)
        else:
            return msg

    @staticmethod
    def group_list(lst: List[Any], n: int) -> List[List[Any]]:
        """
        Splits a list into sub-lists of n
        :param lst: the list
        :param n: the subgroup size
        :return: The list of lists
        """
        return [lst[i * n:(i + 1) * n] for i in range((len(lst) + n - 1) // n)]

    async def pages(self, lst: List[Any], n: int,
                    title: str, *, fmt: str = "%s",
                    thumbnails: List[str] = None, sep: str = "\n", color=None) \
            -> List[discord.Embed]:
        # noinspection GrazieInspection
        """
            Paginates a list into embeds to use with :class:disputils.BotEmbedPaginator

            :param lst: the list to paginate
            :param n: the number of elements per page
            :param title: the title of the embed
            :param fmt: a % string used to format the resulting page
            :param sep: the string to join the list elements with
            :param thumbnails: thumbnail
            :param color: color
            :return: a list of embeds
            """
        l: List[List[str]] = self.group_list([str(i) for i in lst], n)
        pgs = [sep.join(page) for page in l]
        if not thumbnails:
            return [
                discord.Embed(
                    title=f"{title} - {i + 1}/{len(pgs)}",
                    description=fmt % pg,
                    color=color or await self.get_color(self.OK),
                ) for i, pg in enumerate(pgs)
            ]
        else:
            return [
                discord.Embed(
                    title=f"{title} - {i + 1}/{len(pgs)}",
                    description=fmt % pg,
                    color=color or await self.get_color(self.OK),
                ).set_thumbnail(url=thumbnails[i]) for i, pg in enumerate(pgs)
            ]

    @staticmethod
    def numbered(lst: Iterable[Any], num_start=0) -> List[str]:
        """

        Returns a numbered version of a list
        """
        return [f"**{i + num_start}.** {a}" for i, a in enumerate(lst)]

    async def paginate(self, lst: Iterable[Any], n: int,
                       title: str, *, fmt: str = "%s", sep: str = "\n",
                       numbered: bool = False, thumbnails: List[str] = None,
                       num_start: int = 0):
        if numbered:
            lst = self.numbered(lst, num_start)
        paginator = ButtonPaginator(self,
                                    embeds=await self.pages(lst, n, title,
                                                            fmt=fmt, sep=sep,
                                                            thumbnails=thumbnails,
                                                            color=await self.get_color(self.INFO)))
        await paginator.run()

    async def page_predefined(self, *embeds: discord.Embed):
        paginator = ButtonPaginator(self, embeds=list(embeds))
        await paginator.run()

    async def input(self, typ: type, cancel_str: str = "cancel", ch: Callable = None, err=None, check_author=True,
                    return_author=False, del_error=60, del_response=False, timeout=60.0):
        def check(m):
            return ((m.author == self.author and m.channel == self.channel) or not check_author) and not m.author.bot

        while True:
            try:
                inp: discord.Message = await self.bot.wait_for('message', check=check, timeout=timeout)
                if del_response:
                    await inp.delete()
                if inp.content.lower() == cancel_str.lower():
                    return (None, None) if return_author else None
                res = typ(inp.content.lower())
                if ch:
                    if not ch(res):
                        raise ValueError
                return (res, inp.author) if return_author else res
            except ValueError:
                await self.send(err or "That's not a valid response, try again" +
                                ("" if not cancel_str else f" or type `{cancel_str}` to quit"), delete_after=del_error)
                continue
            except asyncio.TimeoutError:
                await self.send("You took too long to respond ): Try to start over", delete_after=del_error)
                return (None, None) if return_author else None

    async def send_json(self, msg: str):
        msg = self.bot.placeholders.replace(self, msg)
        try:
            msg = json.loads(msg)
        except json.JSONDecodeError:
            msg = {
                "plainText": msg
            }
        if isinstance(msg, str):
            msg = {
                "plainText": msg
            }
        if "plainText" in msg:
            content = msg.pop("plainText")
        else:
            content = None
        if len(msg.keys()) < 2:  # no embed here:
            return await self.send(content)
        thumbnail = msg.pop("thumbnail", None) if msg else None
        image = msg.pop("image", None) if msg else None
        msg["description"] = msg.get("description", "_ _")
        embed = discord.Embed.from_dict(msg)
        if thumbnail:
            embed.set_thumbnail(url=thumbnail)
        if image:
            embed.set_image(url=image)
        await self.send(
            content=content,
            embed=embed if embed else None
        )

    def loc(self, string: str) -> str:
        return string

import asyncio
import io
import json
import re
from types import coroutine
from typing import List, Tuple, Union, Any, Callable, Iterable

from PIL.Image import Image
from dpy_button_utils import ButtonConfirmation, ButtonPaginator

import discord
from discord.embeds import EmptyEmbed
from discord.ext import commands
from libs.conversions import escape


def _wrap_user(user: discord.abc.User):
    return f"**{user}** "


class AoiContext(commands.Context):
    INFO = 0
    ERROR = 1
    OK = 2

    @property
    def clean_prefix(self):
        return escape(self.prefix, self)

    async def using_embeds(self):
        return (await self.bot.db.guild_setting(self.guild.id)).reply_embeds

    async def trash_reaction(self, message: discord.Message):
        if len(message.embeds) == 0:
            return

        def check(_reaction: discord.Reaction, _user: Union[discord.User, discord.Member]):
            return all([
                _user.id == self.author.id or _user.guild_permissions.manage_messages,
                _reaction.message.id == message.id,
                str(_reaction) == "🗑️"
            ])

        await message.add_reaction("🗑️")
        await asyncio.sleep(0.5)
        try:
            _, _ = await self.bot.wait_for("reaction_add", timeout=60.0, check=check)
        except asyncio.TimeoutError:
            await message.clear_reactions()
        else:
            await message.delete()

    async def confirm(self, message: str, confirmed: str, denied: str):
        conf = ButtonConfirmation(self, message, timeout=120, confirm_message=confirmed, cancel_message=denied)
        return await conf.run()

    async def confirm_coro(self, message: str, confirmed: str, denied: str, coro: coroutine):
        conf = ButtonConfirmation(self, message, timeout=120, confirm_message="...",
                                  cancel_message=denied)
        is_confirmed = await conf.run()
        if is_confirmed:
            await coro
            await (await self.fetch_message(conf.msg)).edit(content=confirmed)
        return is_confirmed

    async def done_ping(self):
        return await self.send("Done!", reference=self.message, mention_author=False)

    async def send_info(self, message: str, *, user: discord.abc.User = None,
                        title: str = None, trash: bool = False, ping: bool = False):
        if not user:
            user = self.author
        if not (await self.bot.db.guild_setting(self.guild.id)).reply_embeds:
            msg = await self.send(f":information_source: "
                                  f"{_wrap_user(user)} {title if title is not None else ''}\n{message}")
        else:
            msg = await self.send(
                user.mention if ping else None,
                embed=discord.Embed(
                    title=title or EmptyEmbed,
                    description=f"{_wrap_user(user) if user else ''}{message}",
                    colour=await self.get_color(self.INFO)
                ))
        if trash:
            await self.trash_reaction(msg)
        else:
            return msg

    async def send_ok(self, message: str, *, user: discord.abc.User = None,
                      title: str = None, trash: bool = False, ping: bool = False):
        if not (await self.bot.db.guild_setting(self.guild.id)).reply_embeds:
            return await self.send(f":white_check_mark: "
                                   f"{_wrap_user(user)} {title if title is not None else ''}\n{message}")
        if not user:
            user = self.author
        msg = await self.send(
            user.mention if ping else None,
            embed=discord.Embed(
                title=title or EmptyEmbed,
                description=f"{_wrap_user(user) if user else ''}{message}",
                colour=await self.get_color(self.OK)
            ))
        if trash:
            await self.trash_reaction(msg)
        else:
            return msg

    async def send_error(self, message: str, *, user: discord.abc.User = None,
                         title: str = None, trash: bool = False, ping: bool = False):
        if not (await self.bot.db.guild_setting(self.guild.id)).reply_embeds:
            return await self.send(f":x: {_wrap_user(user)} {title if title is not None else ''}\n{message}")
        if not user:
            user = self.author
        msg = await self.send(
            user.mention if ping else None,
            embed=discord.Embed(
                title=title or EmptyEmbed,
                description=f"{_wrap_user(user) if user else ''}{message}",
                colour=await self.get_color(self.ERROR)
            ))
        if trash:
            await self.trash_reaction(msg)
        else:
            return msg

    async def edit_message(self, message: discord.Message, content: str, *, user: discord.abc.User = None,
                           title: str = None, color: int = 0):
        if not user:
            user = self.author
        await message.edit(embed=discord.Embed(
            title=title or EmptyEmbed,
            description=f"{_wrap_user(user) if user else ''}{message}",
            colour=await self.get_color(color)
        ))
        return message

    async def get_color(self, typ: int):
        if not self.guild:
            return discord.Color([0x0000ff, 0xff0000, 0x00cc00][typ])
        if typ == 0:
            return discord.Color((await self.bot.db.guild_setting(self.guild.id)).info_color)
        if typ == 1:
            return discord.Color((await self.bot.db.guild_setting(self.guild.id)).error_color)
        if typ == 2:
            return discord.Color((await self.bot.db.guild_setting(self.guild.id)).ok_color)
        raise ValueError

    # noinspection PyDefaultArgument
    async def embed(self, *,  # noqa c901
                    author: str = None,
                    description: str = None,
                    title: str = None,
                    typ: int = INFO,
                    fields: List[Tuple[str, str]] = None,
                    thumbnail: str = None,
                    clr: discord.Colour = None,
                    image: Union[str, io.BufferedIOBase] = None,
                    footer: str = None,
                    not_inline: List[int] = [],
                    trash_reaction: bool = False):  # noqa c901
        if not (await self.bot.db.guild_setting(self.guild.id)).reply_embeds:
            msg = ""
            msg += f"**{author}**\n" if author else ""
            msg += f":{['information_source', 'x', 'white_check_mark'][typ]}: " + \
                   (f"**{title}**" if title else "") + "\n"
            msg += f"{description}\n" if description is not None else ""
            for i in fields or []:
                msg += f"**{i[0]}**\n{i[1]}\n\n"
            msg += f"**{footer}**\n" if footer else ""
            zw_sp = "​"  # not blank, there's a zero width space there, I swear
            url = ""
            if image:
                if isinstance(image, str):
                    f = None
                    url = image
                elif isinstance(image, Image):
                    buf = io.BytesIO()
                    image.save(buf, "png")
                    buf.seek(0)
                    f = discord.File(buf, filename="image.png")
                    url = ""
                else:
                    image.seek(0)
                    f = discord.File(image, filename="image.png")
                    url = ""
            else:
                f = None
            return await self.send(re.sub(r"\[(.*?)\]\((.*?)\)", rf"\1 ({zw_sp}<\2>{zw_sp})", msg) + url,
                                   file=f)  # noqa ignore the \[

        if typ and clr:
            raise ValueError("typ and clr can not be both defined")
        embed = discord.Embed(
            title=title or discord.embeds.EmptyEmbed,
            description=description or discord.embeds.EmptyEmbed,
            colour=(await self.get_color(typ) if not clr else clr)
        )
        if author:
            embed.set_author(name=author)
        if image:
            if isinstance(image, str):
                embed.set_image(url=image)
                f = None
            elif isinstance(image, Image):
                buf = io.BytesIO()
                image.save(buf, "png")
                buf.seek(0)
                f = discord.File(buf, filename="image.png")
                embed.set_image(url="attachment://image.png")
            else:
                image.seek(0)
                f = discord.File(image, filename="image.png")
                embed.set_image(url="attachment://image.png")
        else:
            f = None
        if footer:
            embed.set_footer(text=footer)
        if thumbnail:
            embed.set_thumbnail(url=thumbnail)
        for n, r in enumerate(fields or []):
            embed.add_field(name=r[0], value=r[1] or "None", inline=n not in not_inline)
        msg = await self.send(embed=embed, file=f)
        if trash_reaction:
            await self.trash_reaction(msg)
        else:
            return msg

    @staticmethod
    def group_list(lst: List[Any], n: int) -> List[List[Any]]:
        """
        Splits a list into sub-lists of n
        :param lst: the list
        :param n: the subgroup size
        :return: The list of lists
        """
        return [lst[i * n:(i + 1) * n] for i in range((len(lst) + n - 1) // n)]

    async def pages(self, lst: List[Any], n: int,
                    title: str, *, fmt: str = "%s",
                    thumbnails: List[str] = None, sep: str = "\n", color=None) \
            -> List[discord.Embed]:
        # noinspection GrazieInspection
        """
            Paginates a list into embeds to use with :class:disputils.BotEmbedPaginator

            :param lst: the list to paginate
            :param n: the number of elements per page
            :param title: the title of the embed
            :param fmt: a % string used to format the resulting page
            :param sep: the string to join the list elements with
            :param thumbnails: thumbnail
            :param color: color
            :return: a list of embeds
            """
        l: List[List[str]] = self.group_list([str(i) for i in lst], n)
        pgs = [sep.join(page) for page in l]
        if not thumbnails:
            return [
                discord.Embed(
                    title=f"{title} - {i + 1}/{len(pgs)}",
                    description=fmt % pg,
                    color=color or await self.get_color(self.OK),
                ) for i, pg in enumerate(pgs)
            ]
        else:
            return [
                discord.Embed(
                    title=f"{title} - {i + 1}/{len(pgs)}",
                    description=fmt % pg,
                    color=color or await self.get_color(self.OK),
                ).set_thumbnail(url=thumbnails[i]) for i, pg in enumerate(pgs)
            ]

    @staticmethod
    def numbered(lst: Iterable[Any], num_start=0) -> List[str]:
        """

        Returns a numbered version of a list
        """
        return [f"**{i + num_start}.** {a}" for i, a in enumerate(lst)]

    async def paginate(self, lst: Iterable[Any], n: int,
                       title: str, *, fmt: str = "%s", sep: str = "\n",
                       numbered: bool = False, thumbnails: List[str] = None,
                       num_start: int = 0):
        if numbered:
            lst = self.numbered(lst, num_start)
        paginator = ButtonPaginator(self,
                                    embeds=await self.pages(lst, n, title,
                                                            fmt=fmt, sep=sep,
                                                            thumbnails=thumbnails,
                                                            color=await self.get_color(self.INFO)))
        await paginator.run()

    async def page_predefined(self, *embeds: discord.Embed):
        paginator = ButtonPaginator(self, embeds=list(embeds))
        await paginator.run()

    async def input(self, typ: type, cancel_str: str = "cancel", ch: Callable = None, err=None, check_author=True,
                    return_author=False, del_error=60, del_response=False, timeout=60.0):
        def check(m):
            return ((m.author == self.author and m.channel == self.channel) or not check_author) and not m.author.bot

        while True:
            try:
                inp: discord.Message = await self.bot.wait_for('message', check=check, timeout=timeout)
                if del_response:
                    await inp.delete()
                if inp.content.lower() == cancel_str.lower():
                    return (None, None) if return_author else None
                res = typ(inp.content.lower())
                if ch:
                    if not ch(res):
                        raise ValueError
                return (res, inp.author) if return_author else res
            except ValueError:
                await self.send(err or "That's not a valid response, try again" +
                                ("" if not cancel_str else f" or type `{cancel_str}` to quit"), delete_after=del_error)
                continue
            except asyncio.TimeoutError:
                await self.send("You took too long to respond ): Try to start over", delete_after=del_error)
                return (None, None) if return_author else None

    async def send_json(self, msg: str):
        msg = self.bot.placeholders.replace(self, msg)
        try:
            msg = json.loads(msg)
        except json.JSONDecodeError:
            msg = {
                "plainText": msg
            }
        if isinstance(msg, str):
            msg = {
                "plainText": msg
            }
        if "plainText" in msg:
            content = msg.pop("plainText")
        else:
            content = None
        if len(msg.keys()) < 2:  # no embed here:
            return await self.send(content)
        thumbnail = msg.pop("thumbnail", None) if msg else None
        image = msg.pop("image", None) if msg else None
        msg["description"] = msg.get("description", "_ _")
        embed = discord.Embed.from_dict(msg)
        if thumbnail:
            embed.set_thumbnail(url=thumbnail)
        if image:
            embed.set_image(url=image)
        await self.send(
            content=content,
            embed=embed if embed else None
        )

    def loc(self, string: str) -> str:
        return string
# noinspection PyDefaultArgument # noqa c901 # noqa c901 # not blank, there's a zero width space there, I swear # noqa ignore the \[ Splits a list into sub-lists of n         :param lst: the list         :param n: the subgroup size         :return: The list of lists # noinspection GrazieInspection Paginates a list into embeds to use with :class:disputils.BotEmbedPaginator              :param lst: the list to paginate             :param n: the number of elements per page             :param title: the title of the embed             :param fmt: a % string used to format the resulting page             :param sep: the string to join the list elements with             :param thumbnails: thumbnail             :param color: color             :return: a list of embeds Returns a numbered version of a list # no embed here: