Pycord-Development / pycord

Pycord is a modern, easy to use, feature-rich, and async ready API wrapper for Discord written in Python
https://docs.pycord.dev
MIT License
2.72k stars 459 forks source link

Thread member cache not accurate #1296

Closed erincerys closed 8 months ago

erincerys commented 2 years ago

Summary

Thread member cache not accurate

Reproduction Steps

Craft an event listener for on_thread_join:

Craft an event listener for on_thread_update:

Craft an event listener for on_thread_remove:

Start the bot

In your Discord client on a guild where the bot is present:

Minimal Reproducible Code

import logging
from typing import (Optional, Union)
from discord import (Thread, Guild)
from discord.ext import commands
from bot import Bot

logger = logging.getLogger(__name__)

class ThreadManager(commands.Cog):
    """Manages the bot's presence in threads as it receives events about them."""

    def __init__(self, bot: Bot) -> None:
        super().__init__()
        self.bot = bot

    async def _get_thread_details(self, event: str, thread: Union[int, Thread], guild: Optional[Guild] = None) -> Thread:
        text = f">>> {event}: Cached thread details; "
        if guild and isinstance(thread, int):
            thread: Thread = guild.get_thread(thread)
        text += f"'{thread.name}' ({thread.id}); thread.me={thread.me}, "
        text += f"Thread.members:List[ThreadMember]={thread.members}"
        if thread.members:
            members = [thread.guild.get_member(m.id) for m in thread.members]
            text += f", Thread.members:List[Member]={members}"
        logger.debug(text)
        return thread

    async def _join_thread(self, thread: Thread) -> None:
        await thread.join()
        logger.debug(f">>> THREAD_JOIN: Joined thread '{thread.name}' ({thread.id})")

    @commands.Cog.listener("on_thread_join")
    async def create_handler(self, thread: Thread) -> None:
        thread = await self._get_thread_details("THREAD_JOIN", thread, guild=after.guild)
        if not thread.me:
            await self._join_thread(thread)

    @commands.Cog.listener("on_thread_update")
    async def update_handler(self, before: Thread, after: Thread) -> None:
        thread = await self._get_thread_details("THREAD_UPDATE", after.id, guild=after.guild)
        if (
            (before.archived and not after.archived)
            and not thread.me
        ):
            await self._join_thread(thread)

    @commands.Cog.listener("on_thread_remove")
    async def remove_handler(self, thread: Thread) -> None:
        await self._get_thread_details("THREAD_REMOVE", thread, guild=after.guild)

def setup(bot: Bot):
    bot.add_cog(ThreadManager(bot))

Expected Results

On handling the THREAD_MEMBERS_UPDATE websocket event and subsequent thread_remove PyCord event:

On handling the thread_update event:

Actual Results

On handling the THREAD_MEMBERS_UPDATE websocket event and subsequent thread_remove PyCord event:

On handling the thread_update event:

Intents

intents = discord.Intents.default()
intents.members = True

System Information

- Python v3.10.4-final
- py-cord v2.0.0-beta
    - py-cord pkg_resources: v2.0.0b3
- aiohttp v3.8.1
- system info: Linux 5.17.4-zen1-1-zen #1 ZEN SMP PREEMPT Wed, 20 Apr 2022 18:29:30 +0000

Checklist

Additional Context

This is the bot's logs when the steps to reproduced are followed with my own bot, which includes code that isn't in the minimal reproduction code provided. I've heavily redacted identifiers, but left those that are important for discerning unique members.

2022-04-26 10:39:53 discord.gateway      DEBUG    For Shard ID None: WebSocket Event: {'t': 'THREAD_CREATE', 's': 16, 'op': 0, 'd': {'type': 11, 'thread_metadata': {'locked': False, 'create_timestamp': '2022-04-26T17:39:53.535000+00:00', 'auto_archive_duration': 10080, 'archived': False, 'archive_timestamp': '2022-04-26T17:39:53.535000+00:00'}, 'rate_limit_per_user': 0, 'parent_id': '[redacted]', 'owner_id': '[redacted]', 'newly_created': True, 'name': 'test', 'message_count': 0, 'member_count': 1, 'last_message_id': None, 'id': '[redacted]', 'guild_id': '[redacted]', 'flags': 0}}
2022-04-26 10:39:53 discord.client       DEBUG    Dispatching event socket_event_type
2022-04-26 10:39:53 discord.client       DEBUG    Dispatching event thread_join
2022-04-26 10:39:53 bot.events.threads DEBUG    !!! THREAD_JOIN: Handling event
2022-04-26 10:39:53 bot.events.threads DEBUG    >>> THREAD_JOIN: Event details; 'test' ([redacted]); me=None, Thread.members:List[ThreadMember]=[]
2022-04-26 10:39:53 bot.events.threads DEBUG    >>> THREAD_JOIN: Cached thread details; 'test' ([redacted]); thread.me=None, Thread.members:List[ThreadMember]=[]
2022-04-26 10:39:53 discord.gateway      DEBUG    For Shard ID None: WebSocket Event: {'t': 'THREAD_MEMBERS_UPDATE', 's': 17, 'op': 0, 'd': {'member_count': 2, 'id': '[redacted]', 'added_members': [{'user_id': '890400000000000000', 'presence': {'user': {'username': 'bot', 'public_flags': 0, 'id': '890400000000000000', 'discriminator': '[redacted]', 'bot': True, 'avatar': '7f3b35011a5cdefa9b73c07aa36d3fdd'}, 'status': 'online', 'game': {'type': 0, 'session_id': None, 'name': 'in buggy waters', 'id': 'ec0b28a579ecb4bd', 'created_at': 1650423664256}, 'client_status': {'web': 'online'}, 'activities': [{'type': 0, 'name': 'in buggy waters', 'id': 'ec0b28a579ecb4bd', 'created_at': 1650423664256}]}, 'member': {'user': {'username': 'bot', 'public_flags': 0, 'id': '890400000000000000', 'discriminator': '[redacted]', 'bot': True, 'avatar': '7f3b35011a5cdefa9b73c07aa36d3fdd'}, 'roles': ['[redacted]', '[redacted]'], 'mute': False, 'joined_at': '2022-02-03T21:40:31.159000+00:00', 'hoisted_role': '[redacted]', 'flags': 0, 'deaf': False}, 'join_timestamp': '2022-04-26T17:39:53.757651+00:00', 'id': '[redacted]', 'flags': 0}], 'guild_id': '[redacted]'}}
2022-04-26 10:39:53 discord.client       DEBUG    Dispatching event socket_event_type
2022-04-26 10:39:53 discord.client       DEBUG    Dispatching event thread_member_join
2022-04-26 10:39:53 bot.events.threads DEBUG    !!! THREAD_MEMBER_JOIN: Handling event
2022-04-26 10:39:53 bot.events.threads DEBUG    >>> THREAD_MEMBER_JOIN: Event details; 'test' ([redacted]); ThreadMember=<ThreadMember id=890400000000000000 thread_id=[redacted] joined_at=datetime.datetime(2022, 4, 26, 17, 39, 53, 757651, tzinfo=datetime.timezone.utc)>, Member=bot#[redacted]
2022-04-26 10:39:53 bot.events.threads DEBUG    >>> THREAD_MEMBER_JOIN: Cached thread details; 'test' ([redacted]); thread.me=None, Thread.members:List[ThreadMember]=[<ThreadMember id=890400000000000000 thread_id=[redacted] joined_at=datetime.datetime(2022, 4, 26, 17, 39, 53, 757651, tzinfo=datetime.timezone.utc)>], Thread.members:List[Member]=[<Member id=890400000000000000 name='bot' discriminator='[redacted]' bot=True nick=None guild=<Guild id=[redacted] name="bot development" shard_id=0 chunked=True member_count=11>>]
2022-04-26 10:39:53 discord.gateway      DEBUG    For Shard ID None: WebSocket Event: {'t': 'THREAD_MEMBERS_UPDATE', 's': 18, 'op': 0, 'd': {'member_count': 3, 'id': '[redacted]', 'added_members': [{'user_id': '897100000000000000', 'presence': {'user': {'username': 'gay bot', 'id': '897100000000000000', 'discriminator': '[redacted]', 'bot': True, 'avatar': '39801670320babab422e028653bd7225'}, 'status': 'online', 'game': {'type': 0, 'session_id': None, 'name': 'in buggy waters', 'id': 'ec0b28a579ecb4bd', 'created_at': 1650994751513}, 'client_status': {'web': 'online'}, 'activities': [{'type': 0, 'name': 'in buggy waters', 'id': 'ec0b28a579ecb4bd', 'created_at': 1650994751513}]}, 'muted': False, 'mute_config': None, 'member': {'user': {'username': 'gay bot', 'id': '897100000000000000', 'discriminator': '[redacted]', 'bot': True, 'avatar': '39801670320babab422e028653bd7225'}, 'roles': ['[redacted]', '[redacted]'], 'mute': False, 'joined_at': '2022-04-21T02:55:43.620000+00:00', 'hoisted_role': '[redacted]', 'flags': 1, 'deaf': False}, 'join_timestamp': '2022-04-26T17:39:53.846437+00:00', 'id': '[redacted]', 'flags': 1}], 'guild_id': '[redacted]'}}
2022-04-26 10:39:53 discord.client       DEBUG    Dispatching event socket_event_type
2022-04-26 10:39:53 discord.client       DEBUG    Dispatching event thread_join
2022-04-26 10:39:53 discord.gateway      DEBUG    For Shard ID None: WebSocket Event: {'t': 'THREAD_CREATE', 's': 19, 'op': 0, 'd': {'type': 11, 'thread_metadata': {'locked': False, 'create_timestamp': '2022-04-26T17:39:53.535000+00:00', 'auto_archive_duration': 10080, 'archived': False, 'archive_timestamp': '2022-04-26T17:39:53.535000+00:00'}, 'rate_limit_per_user': 0, 'parent_id': '[redacted]', 'owner_id': '[redacted]', 'name': 'test', 'message_count': 0, 'member_count': 3, 'member': {'user_id': '897100000000000000', 'muted': False, 'mute_config': None, 'join_timestamp': '2022-04-26T17:39:53.846437+00:00', 'id': '[redacted]', 'flags': 1}, 'last_message_id': None, 'id': '[redacted]', 'guild_id': '[redacted]', 'flags': 0}}
2022-04-26 10:39:53 discord.client       DEBUG    Dispatching event socket_event_type
2022-04-26 10:39:53 bot.events.threads DEBUG    !!! THREAD_JOIN: Handling event
2022-04-26 10:39:53 bot.events.threads DEBUG    >>> THREAD_JOIN: Event details; 'test' ([redacted]); me=<ThreadMember id=897100000000000000 thread_id=[redacted] joined_at=datetime.datetime(2022, 4, 26, 17, 39, 53, 846437, tzinfo=datetime.timezone.utc)>, Thread.members:List[ThreadMember]=[<ThreadMember id=890400000000000000 thread_id=[redacted] joined_at=datetime.datetime(2022, 4, 26, 17, 39, 53, 757651, tzinfo=datetime.timezone.utc)>]
2022-04-26 10:39:53 bot.events.threads DEBUG    >>> THREAD_JOIN: Cached thread details; 'test' ([redacted]); thread.me=<ThreadMember id=897100000000000000 thread_id=[redacted] joined_at=datetime.datetime(2022, 4, 26, 17, 39, 53, 846437, tzinfo=datetime.timezone.utc)>, Thread.members:List[ThreadMember]=[]
2022-04-26 10:39:53 discord.http         DEBUG    POST https://discord.com/api/v9/channels/[redacted]/thread-members/@me with None has returned 204
2022-04-26 10:39:53 discord.http         DEBUG    POST https://discord.com/api/v9/channels/[redacted]/thread-members/@me has received 
2022-04-26 10:39:53 bot.events.threads DEBUG    >>> THREAD_JOIN: Joined thread 'test' ([redacted])
2022-04-26 10:39:53 bot.events.threads DEBUG    >>> THREAD_JOIN: Cached thread details; 'test' ([redacted]); thread.me=<ThreadMember id=897100000000000000 thread_id=[redacted] joined_at=datetime.datetime(2022, 4, 26, 17, 39, 53, 846437, tzinfo=datetime.timezone.utc)>, Thread.members:List[ThreadMember]=[]
2022-04-26 10:39:53 discord.gateway      DEBUG    For Shard ID None: WebSocket Event: {'t': 'MESSAGE_CREATE', 's': 20, 'op': 0, 'd': {'type': 0, 'tts': False, 'timestamp': '2022-04-26T17:39:53.913000+00:00', 'referenced_message': None, 'pinned': False, 'nonce': '968567108596137984', 'mentions': [], 'mention_roles': [], 'mention_everyone': False, 'member': {'roles': ['[redacted]'], 'mute': False, 'joined_at': '2021-10-08T01:35:51.577000+00:00', 'hoisted_role': None, 'flags': 0, 'deaf': False}, 'id': '[redacted]', 'flags': 0, 'embeds': [], 'edited_timestamp': None, 'content': 'test', 'components': [], 'channel_id': '[redacted]', 'author': {'username': '[redacted]', 'public_flags': 0, 'id': '[reacted]', 'discriminator': '[redacted]', 'avatar_decoration': None, 'avatar': '8f6be73f6c4854b0785fff75adf80076'}, 'attachments': [], 'guild_id': '[redacted]'}}
2022-04-26 10:39:53 discord.client       DEBUG    Dispatching event socket_event_type
2022-04-26 10:39:53 discord.client       DEBUG    Dispatching event message
2022-04-26 10:39:54 discord.http         DEBUG    POST https://discord.com/api/v9/channels/[redacted]/thread-members/@me with None has returned 204
2022-04-26 10:39:54 discord.http         DEBUG    POST https://discord.com/api/v9/channels/[redacted]/thread-members/@me has received 
2022-04-26 10:39:54 bot.events.threads DEBUG    >>> THREAD_MEMBER_JOIN: Joined thread 'test' ([redacted])
2022-04-26 10:39:54 bot.events.threads DEBUG    >>> THREAD_MEMBER_JOIN: Cached thread details; 'test' ([redacted]); thread.me=<ThreadMember id=897100000000000000 thread_id=[redacted] joined_at=datetime.datetime(2022, 4, 26, 17, 39, 53, 846437, tzinfo=datetime.timezone.utc)>, Thread.members:List[ThreadMember]=[]
2022-04-26 10:39:58 discord.gateway      DEBUG    For Shard ID None: WebSocket Event: {'t': 'THREAD_MEMBERS_UPDATE', 's': 21, 'op': 0, 'd': {'removed_member_ids': ['897100000000000000'], 'member_count': 2, 'id': '[redacted]', 'guild_id': '[redacted]'}}
2022-04-26 10:39:58 discord.client       DEBUG    Dispatching event socket_event_type
2022-04-26 10:39:58 discord.client       DEBUG    Dispatching event thread_remove
2022-04-26 10:39:58 bot.events.threads DEBUG    !!! Handling thread_remove event
2022-04-26 10:39:58 bot.events.threads DEBUG    >>> THREAD_REMOVE: Event before payload; 'test' ([redacted]); me=<ThreadMember id=897100000000000000 thread_id=[redacted] joined_at=datetime.datetime(2022, 4, 26, 17, 39, 53, 846437, tzinfo=datetime.timezone.utc)>, Thread.members:List[ThreadMember]=[]
2022-04-26 10:39:58 bot.events.threads DEBUG    >>> THREAD_REMOVE: Cached thread details; 'test' ([redacted]); thread.me=<ThreadMember id=897100000000000000 thread_id=[redacted] joined_at=datetime.datetime(2022, 4, 26, 17, 39, 53, 846437, tzinfo=datetime.timezone.utc)>, Thread.members:List[ThreadMember]=[]
2022-04-26 10:39:58 discord.gateway      DEBUG    For Shard ID None: WebSocket Event: {'t': 'MESSAGE_CREATE', 's': 22, 'op': 0, 'd': {'type': 2, 'tts': False, 'timestamp': '2022-04-26T17:39:58.639000+00:00', 'pinned': False, 'mentions': [{'username': 'gay bot', 'public_flags': 0, 'member': {'roles': ['[redacted]', '[redacted]'], 'mute': False, 'joined_at': '2022-04-21T02:55:43.620000+00:00', 'hoisted_role': '[redacted]', 'flags': 1, 'deaf': False}, 'id': '897100000000000000', 'discriminator': '[redacted]', 'bot': True, 'avatar_decoration': None, 'avatar': '39801670320babab422e028653bd7225'}], 'mention_roles': [], 'mention_everyone': False, 'member': {'roles': ['[redacted]'], 'mute': False, 'joined_at': '2021-10-08T01:35:51.577000+00:00', 'hoisted_role': None, 'flags': 0, 'deaf': False}, 'id': '[redacted]', 'flags': 0, 'embeds': [], 'edited_timestamp': None, 'content': '', 'components': [], 'channel_id': '[redacted]', 'author': {'username': '[redacted]', 'public_flags': 0, 'id': '[redacted]', 'discriminator': '[redacted]', 'avatar_decoration': None, 'avatar': '8f6be73f6c4854b0785fff75adf80076'}, 'attachments': [], 'guild_id': '[redacted]'}}
2022-04-26 10:39:58 discord.client       DEBUG    Dispatching event socket_event_type
2022-04-26 10:39:58 discord.client       DEBUG    Dispatching event message
2022-04-26 10:40:02 discord.gateway      DEBUG    For Shard ID None: WebSocket Event: {'t': 'THREAD_UPDATE', 's': 23, 'op': 0, 'd': {'type': 11, 'thread_metadata': {'locked': False, 'create_timestamp': '2022-04-26T17:39:53.535000+00:00', 'auto_archive_duration': 10080, 'archived': True, 'archive_timestamp': '2022-04-26T17:40:02.737778+00:00'}, 'rate_limit_per_user': 0, 'parent_id': '[redacted]', 'owner_id': '[redacted]', 'name': 'test', 'message_count': 2, 'member_count': 2, 'last_message_id': '[redacted]', 'id': '[redacted]', 'guild_id': '[redacted]', 'flags': 0}}
2022-04-26 10:40:02 discord.client       DEBUG    Dispatching event socket_event_type
2022-04-26 10:40:02 discord.client       DEBUG    Dispatching event thread_update
2022-04-26 10:40:02 bot.events.threads DEBUG    !!! Handling thread_update event
2022-04-26 10:40:02 bot.events.threads DEBUG    >>> THREAD_UPDATE: Event before payload; 'test' ([redacted]); archived=False, Thread.members:List[ThreadMember]=[], Thread.members:List[Member]=[]
2022-04-26 10:40:02 bot.events.threads DEBUG    >>> THREAD_UPDATE: Event after payload; 'test' ([redacted]); archived=True, Thread.members:List[ThreadMember]=[], Thread.members:List[Member]=[]
2022-04-26 10:40:02 bot.events.threads DEBUG    >>> THREAD_UPDATE: Cached thread details; 'test' ([redacted]); thread.me=<ThreadMember id=897100000000000000 thread_id=[redacted] joined_at=datetime.datetime(2022, 4, 26, 17, 39, 53, 846437, tzinfo=datetime.timezone.utc)>, Thread.members:List[ThreadMember]=[]
2022-04-26 10:40:05 discord.gateway      DEBUG    For Shard ID None: WebSocket Event: {'t': 'THREAD_UPDATE', 's': 24, 'op': 0, 'd': {'type': 11, 'thread_metadata': {'locked': False, 'create_timestamp': '2022-04-26T17:39:53.535000+00:00', 'auto_archive_duration': 10080, 'archived': False, 'archive_timestamp': '2022-04-26T17:40:04.983287+00:00'}, 'rate_limit_per_user': 0, 'parent_id': '[redacted]', 'owner_id': '[redacted]', 'name': 'test', 'message_count': 2, 'member_count': 2, 'last_message_id': '[redacted]', 'id': '[redacted]', 'guild_id': '[redacted]', 'flags': 0}}
2022-04-26 10:40:05 discord.client       DEBUG    Dispatching event socket_event_type
2022-04-26 10:40:05 discord.client       DEBUG    Dispatching event thread_update
2022-04-26 10:40:05 bot.events.threads DEBUG    !!! Handling thread_update event
2022-04-26 10:40:05 bot.events.threads DEBUG    >>> THREAD_UPDATE: Event before payload; 'test' ([redacted]); archived=True, Thread.members:List[ThreadMember]=[], Thread.members:List[Member]=[]
2022-04-26 10:40:05 bot.events.threads DEBUG    >>> THREAD_UPDATE: Event after payload; 'test' ([redacted]); archived=False, Thread.members:List[ThreadMember]=[], Thread.members:List[Member]=[]
2022-04-26 10:40:05 bot.events.threads DEBUG    >>> THREAD_UPDATE: Cached thread details; 'test' ([redacted]); thread.me=<ThreadMember id=897100000000000000 thread_id=[redacted] joined_at=datetime.datetime(2022, 4, 26, 17, 39, 53, 846437, tzinfo=datetime.timezone.utc)>, Thread.members:List[ThreadMember]=[]

There are other events that are impacted by the same issue outlined here, as can be seen from the log output above. I believe y'all are aware of this behavior, in part, as it's documented that, most of the time, Thread.members is empty due to the gateway not returning anything.

Minimally, I'd like to see the documentation updated for Thread.me. It's misleading, saying that it might not be available, but not that it might be wrong. Ideally, the framework would call Thread.fetch_members() to populate these properties with correct information.

Lulalaby commented 2 years ago

Big up for this detailed report. We'll investigate this as fast as we can.

Thank you!

Lulalaby commented 2 years ago

Status Update please @Pycord-Development/maintainers