dpytest icon indicating copy to clipboard operation
dpytest copied to clipboard

Support for reactions

Open Circlepuller opened this issue 4 years ago • 7 comments

Hi! I've began using this package for testing with my Discord bot that utilizes the BotEmbedPaginator from @LiBa001's disputils. I noticed when I try to test any commands that utilize that class, I get the following error:

NotImplementedError: Operation occured that isn't captured by the tests framework.
https://discordapp.com/api/v7/channels/721295729460641849/messages/721295751816282172/reactions/%E2%8F%AE/@me {}

I can guess from the URL that this is related to disputils' use of reactions for pagination buttons and dpytest's lack of current support for them. Is there any plans to address support for reactions in the near future?

Circlepuller avatar Jun 13 '20 09:06 Circlepuller

I don't know anything about dpytest so I can only guess, but it might be related to you using the canary API endpoint v7. Idk, just an idea.

LiBa001 avatar Jun 13 '20 12:06 LiBa001

... it might be related to you using the canary API endpoint v7.

Actually, this is what discord.py v1.3.3 seems to use for the API, believe it or not.

Circlepuller avatar Jun 13 '20 19:06 Circlepuller

I've hacked in some code for this, but really aren't testing the actual use of the reactions yet. I just did it so it wouldn't crash when my bot tries to add reactions to a message. I had to make the fakeHTTP class have the proper function, and then created a callback for when the event is dispatched. So far it keeps it from crashing :)

bravosierra99 avatar Jun 19 '20 23:06 bravosierra99

Implemented starting support for reactions, please try updating to the most recent version. If the issue still isn't fixed, I need to add the more specific reaction handlers.

CraftSpider avatar Jul 10 '20 03:07 CraftSpider

Definitely making progress. Looks like it has issues handling content when it's set to None?

______________________________ test_search_anime _______________________________

args = (<plugins.myanimelist.MyAnimeList object at 0x7f23ada70c70>, <discord.ext.commands.context.Context object at 0x7f23ad9fb550>, 'jojo', 'anime')
kwargs = {}

    @functools.wraps(coro)
    async def wrapped(*args, **kwargs):
        try:
>           ret = await coro(*args, **kwargs)

bot-env/lib/python3.8/site-packages/discord/ext/commands/core.py:83: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <plugins.myanimelist.MyAnimeList object at 0x7f23ada70c70>
ctx = <discord.ext.commands.context.Context object at 0x7f23ad9fb550>
query = 'jojo', category = 'anime'

    @command(help='Searches for anime and manga based on a given term')
    async def search(self, ctx: Context, query: str, category: str = 'anime') -> None:
        async with AioJikan() as client:
            try:
                response = await client.search(category, query)
            except APIException:
                await ctx.send(ERROR_MESSAGE)
                return
    
        sorted_results = sorted(response['results'], key=itemgetter('members'), reverse=True)
        embeds = []
    
        for result in sorted_results:
            e = Embed(
                title=result['title'],
                description=is_na(result['synopsis']),
                url=result['url'],
                timestamp=datetime.fromisoformat(result['start_date'])
            )
            e.set_thumbnail(url=result['image_url'])
    
            footer = f"{result['type']} | Score: {is_na(result['score'], float)} | {result['members']} members"
    
            if category == 'anime':
                footer += f" | Episodes: {is_na(result['episodes'], int)} | {result['rated']}"
    
            e.set_footer(text=footer)
    
            embeds.append(e)
    
        paginator = BotEmbedPaginator(ctx, embeds)
>       await paginator.run()

plugins/myanimelist.py:69: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <disputils.pagination.BotEmbedPaginator object at 0x7f23ad9fb6d0>
channel = <TextChannel id=731820112646111297 name='Channel_0' position=1 nsfw=False news=False category_id=None>
users = [<Member id=731820112646111298 name='TestUser' discriminator='0001' bot=False nick=None guild=<Guild id=731820112646111296 name='Test Guild 0' shard_id=None chunked=False member_count=3>>]

    async def run(self, channel: discord.TextChannel = None, users: List[discord.User] = None):
        """
        Runs the paginator.
    
        :type channel: Optional[discord.TextChannel]
        :param channel:
            The text channel to send the embed to.
            Default is the context channel.
    
        :type users: Optional[List[discord.User]]
        :param users:
            A list of :class:`discord.User` that can control the pagination.
            Default is the context author.
            Passing an empty list will grant access to all users. (Not recommended.)
    
        :return: None
        """
    
        if users is None:
            users = [self._ctx.author]
    
        if self.message is None and channel is None:
            channel = self._ctx.channel
    
>       await super().run(users, channel)

bot-env/lib/python3.8/site-packages/disputils/pagination.py:170: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <disputils.pagination.BotEmbedPaginator object at 0x7f23ad9fb6d0>
users = [<Member id=731820112646111298 name='TestUser' discriminator='0001' bot=False nick=None guild=<Guild id=731820112646111296 name='Test Guild 0' shard_id=None chunked=False member_count=3>>]
channel = <TextChannel id=731820112646111297 name='Channel_0' position=1 nsfw=False news=False category_id=None>

    async def run(self, users: List[discord.User], channel: discord.TextChannel = None):
        """
        Runs the paginator.
    
        :type users: List[discord.User]
        :param users:
            A list of :class:`discord.User` that can control the pagination.
            Passing an empty list will grant access to all users. (Not recommended.)
    
        :type channel: Optional[discord.TextChannel]
        :param channel:
            The text channel to send the embed to.
            Must only be specified if `self.message` is `None`.
    
        :return: None
        """
    
        if channel is None and self.message is not None:
            channel = self.message.channel
        elif channel is None:
            raise TypeError("Missing argument. You need to specify a target channel.")
    
        self._embed = self.pages[0]
    
        if len(self.pages) == 1:  # no pagination needed in this case
            self.message = await channel.send(embed=self._embed)
            return
    
>       self.message = await channel.send(embed=self.formatted_pages[0])

bot-env/lib/python3.8/site-packages/disputils/pagination.py:69: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <TextChannel id=731820112646111297 name='Channel_0' position=1 nsfw=False news=False category_id=None>
content = None

    async def send(self, content=None, *, tts=False, embed=None, file=None, files=None, delete_after=None, nonce=None):
        """|coro|
    
        Sends a message to the destination with the content given.
    
        The content must be a type that can convert to a string through ``str(content)``.
        If the content is set to ``None`` (the default), then the ``embed`` parameter must
        be provided.
    
        To upload a single file, the ``file`` parameter should be used with a
        single :class:`~discord.File` object. To upload multiple files, the ``files``
        parameter should be used with a :class:`list` of :class:`~discord.File` objects.
        **Specifying both parameters will lead to an exception**.
    
        If the ``embed`` parameter is provided, it must be of type :class:`~discord.Embed` and
        it must be a rich embed type.
    
        Parameters
        ------------
        content: :class:`str`
            The content of the message to send.
        tts: :class:`bool`
            Indicates if the message should be sent using text-to-speech.
        embed: :class:`~discord.Embed`
            The rich embed for the content.
        file: :class:`~discord.File`
            The file to upload.
        files: List[:class:`~discord.File`]
            A list of files to upload. Must be a maximum of 10.
        nonce: :class:`int`
            The nonce to use for sending this message. If the message was successfully sent,
            then the message will have a nonce with this value.
        delete_after: :class:`float`
            If provided, the number of seconds to wait in the background
            before deleting the message we just sent. If the deletion fails,
            then it is silently ignored.
    
        Raises
        --------
        ~discord.HTTPException
            Sending the message failed.
        ~discord.Forbidden
            You do not have the proper permissions to send the message.
        ~discord.InvalidArgument
            The ``files`` list is not of the appropriate size or
            you specified both ``file`` and ``files``.
    
        Returns
        ---------
        :class:`~discord.Message`
            The message that was sent.
        """
    
        channel = await self._get_channel()
        state = self._state
        content = str(content) if content is not None else None
        if embed is not None:
            embed = embed.to_dict()
    
        if file is not None and files is not None:
            raise InvalidArgument('cannot pass both file and files parameter to send()')
    
        if file is not None:
            if not isinstance(file, File):
                raise InvalidArgument('file parameter must be File')
    
            try:
                data = await state.http.send_files(channel.id, files=[file],
                                                   content=content, tts=tts, embed=embed, nonce=nonce)
            finally:
                file.close()
    
        elif files is not None:
            if len(files) > 10:
                raise InvalidArgument('files parameter must be a list of up to 10 elements')
            elif not all(isinstance(file, File) for file in files):
                raise InvalidArgument('files parameter must be a list of File')
    
            try:
                data = await state.http.send_files(channel.id, files=files, content=content, tts=tts,
                                                   embed=embed, nonce=nonce)
            finally:
                for f in files:
                    f.close()
        else:
>           data = await state.http.send_message(channel.id, content, tts=tts, embed=embed, nonce=nonce)

bot-env/lib/python3.8/site-packages/discord/abc.py:856: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <discord.ext.test.backend.FakeHttp object at 0x7f23ada70a30>
channel_id = 731820112646111297, content = None

    async def send_message(self, channel_id, content, *, tts=False, embed=None, nonce=None):
        locs = self._get_higher_locs(1)
        channel = locs.get("channel", None)
    
        embeds = []
        if embed:
            embeds = [discord.Embed.from_dict(embed)]
        user = self.state.user
        if hasattr(channel, "guild"):
            perm = channel.permissions_for(channel.guild.get_member(user.id))
        else:
            perm = channel.permissions_for(user)
        if not ((perm.send_messages and perm.read_messages) or perm.administrator):
            raise discord.errors.Forbidden(FakeRequest(403, "missing send_messages"), "send_messages")
    
>       message = make_message(
            channel=channel, author=self.state.user, content=content, tts=tts, embeds=embeds, nonce=nonce
        )

bot-env/lib/python3.8/site-packages/discord/ext/test/backend.py:83: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

content = None
author = <ClientUser id=731820112646111295 name='FakeApp' discriminator='0001' bot=False verified=False mfa_enabled=False>
channel = <TextChannel id=731820112646111297 name='Channel_0' position=1 nsfw=False news=False category_id=None>
tts = False, embeds = [<discord.embeds.Embed object at 0x7f23ad170c10>]
attachments = None, nonce = None, id_num = -1

    def make_message(content, author, channel, tts=False, embeds=None, attachments=None, nonce=None, id_num=-1):
        guild = channel.guild if hasattr(channel, "guild") else None
        guild_id = guild.id if guild else None
    
>       mentions = find_user_mentions(content, guild)

bot-env/lib/python3.8/site-packages/discord/ext/test/backend.py:562: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

content = None
guild = <Guild id=731820112646111296 name='Test Guild 0' shard_id=None chunked=False member_count=3>

    def find_user_mentions(content, guild):
        if guild is None:
            return []  # TODO: Check for dm user mentions
>       matches = re.findall(MEMBER_MENTION, content)

bot-env/lib/python3.8/site-packages/discord/ext/test/backend.py:593: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

pattern = re.compile('<@!?[0-9]{17,21}>', re.MULTILINE), string = None
flags = 0

    def findall(pattern, string, flags=0):
        """Return a list of all non-overlapping matches in the string.
    
        If one or more capturing groups are present in the pattern, return
        a list of groups; this will be a list of tuples if the pattern
        has more than one group.
    
        Empty matches are included in the result."""
>       return _compile(pattern, flags).findall(string)
E       TypeError: expected string or bytes-like object

/usr/lib/python3.8/re.py:239: TypeError

The above exception was the direct cause of the following exception:

bot = <discord.ext.commands.bot.Bot object at 0x7f23add84790>

    @pytest.mark.asyncio
    async def test_search_anime(bot: Bot) -> None:
>       await dpytest.message('%search jojo')

It's pretty obvious, but it's a Cog for handling MyAnimeList content, haha.

Circlepuller avatar Jul 12 '20 10:07 Circlepuller

Failure when content is None is #26, introduced due to changes on how the backend handless messages. That should be fixed soon, apologies on the regression

CraftSpider avatar Jul 12 '20 23:07 CraftSpider

Not a problem, https://github.com/CraftSpider/dpytest/issues/26 looks like a pretty good fix for it.

Circlepuller avatar Jul 15 '20 00:07 Circlepuller

Closing it.

Sergeileduc avatar Dec 08 '22 09:12 Sergeileduc