From fa313034eef92d502379fea0f84076617dcb404c Mon Sep 17 00:00:00 2001 From: ToothyDev Date: Mon, 26 Jan 2026 23:25:50 +0100 Subject: [PATCH 01/14] =?UTF-8?q?=F0=9F=90=9B=20Fix=20primary=20guild=20no?= =?UTF-8?q?t=20firing=20on=5Fuser=5Fupdate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 3 +++ discord/member.py | 11 ++++++++++- discord/types/user.py | 2 ++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8210bcb658..b39301c67f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,9 @@ These changes are available on the `master` branch, but have not yet been releas - Fixed an issue where the optional parameters of the `InteractionResponse.send_message` method were not type-hinted as optional. ([#3061](https://github.com/Pycord-Development/pycord/pull/3061)) +- Fixed the update of a user's guild tag (`User.primary_guild`) not causing the + `on_user_update` event to fire. + ([#3077](https://github.com/Pycord-Development/pycord/pull/3077)) ### Removed diff --git a/discord/member.py b/discord/member.py index 72cbb912d9..2c052c0366 100644 --- a/discord/member.py +++ b/discord/member.py @@ -462,7 +462,14 @@ def _presence_update( def _update_inner_user(self, user: UserPayload) -> tuple[User, User] | None: u = self._user - original = (u.name, u._avatar, u.discriminator, u.global_name, u._public_flags) + original = ( + u.name, + u._avatar, + u.discriminator, + u.global_name, + u._public_flags, + u.primary_guild, + ) # These keys seem to always be available modified = ( user["username"], @@ -470,6 +477,7 @@ def _update_inner_user(self, user: UserPayload) -> tuple[User, User] | None: user["discriminator"], user.get("global_name", None) or None, user.get("public_flags", 0), + user.get("primary_guild"), ) if original != modified: to_return = User._copy(self._user) @@ -479,6 +487,7 @@ def _update_inner_user(self, user: UserPayload) -> tuple[User, User] | None: u.discriminator, u.global_name, u._public_flags, + u.primary_guild, ) = modified # Signal to dispatch on_user_update return to_return, u diff --git a/discord/types/user.py b/discord/types/user.py index a0383e4f04..61b60c2c3a 100644 --- a/discord/types/user.py +++ b/discord/types/user.py @@ -27,6 +27,7 @@ from typing import Literal, TypedDict +from .primary_guild import PrimaryGuild from .snowflake import Snowflake @@ -51,3 +52,4 @@ class User(PartialUser, total=False): flags: int premium_type: PremiumType public_flags: int + primary_guild: PrimaryGuild From 70673a3c0e8750b21770aef988ce1836f7bc9644 Mon Sep 17 00:00:00 2001 From: ToothyDev Date: Mon, 26 Jan 2026 23:34:46 +0100 Subject: [PATCH 02/14] Add primary guild firing the event to docs --- docs/api/events.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/api/events.rst b/docs/api/events.rst index 45d8c1e279..519350e5f0 100644 --- a/docs/api/events.rst +++ b/docs/api/events.rst @@ -736,6 +736,7 @@ Members/Users - username - discriminator - global_name + - primary_guild This requires :attr:`Intents.members` to be enabled. From 307c36f787fa81ed0491daffdb67f2c9e89a83fb Mon Sep 17 00:00:00 2001 From: ToothyDev <55001472+ToothyDev@users.noreply.github.com> Date: Mon, 26 Jan 2026 23:38:40 +0100 Subject: [PATCH 03/14] Update CHANGELOG.md Co-authored-by: Paillat Signed-off-by: ToothyDev <55001472+ToothyDev@users.noreply.github.com> --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b39301c67f..ebf31b70f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,8 +42,7 @@ These changes are available on the `master` branch, but have not yet been releas - Fixed an issue where the optional parameters of the `InteractionResponse.send_message` method were not type-hinted as optional. ([#3061](https://github.com/Pycord-Development/pycord/pull/3061)) -- Fixed the update of a user's guild tag (`User.primary_guild`) not causing the - `on_user_update` event to fire. +- Fixed the update of a user's `primary_guild` to now cause an `on_user_update` event to fire. ([#3077](https://github.com/Pycord-Development/pycord/pull/3077)) ### Removed From dd0c6e03297997a0afa717a8a537f431eea4047c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 26 Jan 2026 22:39:07 +0000 Subject: [PATCH 04/14] style(pre-commit): auto fixes from pre-commit.com hooks --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ebf31b70f6..8671533cc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,8 +42,8 @@ These changes are available on the `master` branch, but have not yet been releas - Fixed an issue where the optional parameters of the `InteractionResponse.send_message` method were not type-hinted as optional. ([#3061](https://github.com/Pycord-Development/pycord/pull/3061)) -- Fixed the update of a user's `primary_guild` to now cause an `on_user_update` event to fire. - ([#3077](https://github.com/Pycord-Development/pycord/pull/3077)) +- Fixed the update of a user's `primary_guild` to now cause an `on_user_update` event to + fire. ([#3077](https://github.com/Pycord-Development/pycord/pull/3077)) ### Removed From dab95e01a85e6a5f217c376dda95f993b6fc6242 Mon Sep 17 00:00:00 2001 From: ToothyDev Date: Tue, 27 Jan 2026 00:54:07 +0100 Subject: [PATCH 05/14] =?UTF-8?q?=E2=9A=A0=EF=B8=8F=20Current=20buggy=20st?= =?UTF-8?q?ate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- discord/member.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/discord/member.py b/discord/member.py index 2c052c0366..54c865675d 100644 --- a/discord/member.py +++ b/discord/member.py @@ -471,13 +471,19 @@ def _update_inner_user(self, user: UserPayload) -> tuple[User, User] | None: u.primary_guild, ) # These keys seem to always be available + pg_payload = user.get("primary_guild") + primary_guild = ( + PrimaryGuild(pg_payload, state=self._state) + if pg_payload and pg_payload.get("identity_enabled") + else None + ) modified = ( user["username"], user["avatar"], user["discriminator"], user.get("global_name", None) or None, user.get("public_flags", 0), - user.get("primary_guild"), + primary_guild, ) if original != modified: to_return = User._copy(self._user) From b69756641693cae2cd1dcad043d873c4993121da Mon Sep 17 00:00:00 2001 From: ToothyDev Date: Tue, 27 Jan 2026 01:21:53 +0100 Subject: [PATCH 06/14] Ugly working state --- discord/member.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/discord/member.py b/discord/member.py index 54c865675d..7664974129 100644 --- a/discord/member.py +++ b/discord/member.py @@ -471,12 +471,19 @@ def _update_inner_user(self, user: UserPayload) -> tuple[User, User] | None: u.primary_guild, ) # These keys seem to always be available - pg_payload = user.get("primary_guild") - primary_guild = ( - PrimaryGuild(pg_payload, state=self._state) - if pg_payload and pg_payload.get("identity_enabled") + pg = user.get("primary_guild") + pgid = ( + int(pg["identity_guild_id"]) + if pg and pg.get("identity_enabled") and pg.get("identity_guild_id") else None ) + + if pgid == (u.primary_guild.identity_guild_id if u.primary_guild else None): + primary_guild = u.primary_guild + elif pgid is not None: + primary_guild = PrimaryGuild(pg, state=self._state) + else: + primary_guild = None modified = ( user["username"], user["avatar"], From e362ffbdc337a803bd5308a3a83767fb7289081a Mon Sep 17 00:00:00 2001 From: ToothyDev Date: Tue, 27 Jan 2026 01:38:05 +0100 Subject: [PATCH 07/14] Sane working state --- discord/member.py | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/discord/member.py b/discord/member.py index 7664974129..a23be0dc73 100644 --- a/discord/member.py +++ b/discord/member.py @@ -472,25 +472,17 @@ def _update_inner_user(self, user: UserPayload) -> tuple[User, User] | None: ) # These keys seem to always be available pg = user.get("primary_guild") - pgid = ( - int(pg["identity_guild_id"]) - if pg and pg.get("identity_enabled") and pg.get("identity_guild_id") - else None - ) - - if pgid == (u.primary_guild.identity_guild_id if u.primary_guild else None): - primary_guild = u.primary_guild - elif pgid is not None: - primary_guild = PrimaryGuild(pg, state=self._state) - else: - primary_guild = None + primary_guild = PrimaryGuild(pg, state=self._state) + if u.primary_guild: + if u.primary_guild.identity_guild_id == primary_guild.identity_guild_id: + primary_guild = u.primary_guild modified = ( user["username"], user["avatar"], user["discriminator"], user.get("global_name", None) or None, user.get("public_flags", 0), - primary_guild, + primary_guild if pg["identity_enabled"] else None, ) if original != modified: to_return = User._copy(self._user) From 0250990c67cf7b28f1a4224fbeb0275a8be859a9 Mon Sep 17 00:00:00 2001 From: ToothyDev Date: Tue, 27 Jan 2026 01:43:44 +0100 Subject: [PATCH 08/14] Prettier working state --- discord/member.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/discord/member.py b/discord/member.py index a23be0dc73..e251767658 100644 --- a/discord/member.py +++ b/discord/member.py @@ -471,18 +471,19 @@ def _update_inner_user(self, user: UserPayload) -> tuple[User, User] | None: u.primary_guild, ) # These keys seem to always be available - pg = user.get("primary_guild") - primary_guild = PrimaryGuild(pg, state=self._state) - if u.primary_guild: - if u.primary_guild.identity_guild_id == primary_guild.identity_guild_id: - primary_guild = u.primary_guild + primary_guild = PrimaryGuild(user.get("primary_guild"), state=self._state) + if ( + u.primary_guild + and u.primary_guild.identity_guild_id == primary_guild.identity_guild_id + ): + primary_guild = u.primary_guild modified = ( user["username"], user["avatar"], user["discriminator"], user.get("global_name", None) or None, user.get("public_flags", 0), - primary_guild if pg["identity_enabled"] else None, + primary_guild if primary_guild.identity_enabled else None, ) if original != modified: to_return = User._copy(self._user) From ac35241df1b9c5484e3df0b5ede91f05edf955da Mon Sep 17 00:00:00 2001 From: ToothyDev Date: Wed, 28 Jan 2026 11:55:35 +0100 Subject: [PATCH 09/14] Implement custom equals method to clean up code --- discord/member.py | 8 ++------ discord/primary_guild.py | 10 ++++++++++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/discord/member.py b/discord/member.py index e251767658..e28ddc6359 100644 --- a/discord/member.py +++ b/discord/member.py @@ -471,12 +471,8 @@ def _update_inner_user(self, user: UserPayload) -> tuple[User, User] | None: u.primary_guild, ) # These keys seem to always be available - primary_guild = PrimaryGuild(user.get("primary_guild"), state=self._state) - if ( - u.primary_guild - and u.primary_guild.identity_guild_id == primary_guild.identity_guild_id - ): - primary_guild = u.primary_guild + new_pg = PrimaryGuild(user["primary_guild"], state=self._state) + primary_guild = u.primary_guild if u.primary_guild == new_pg else new_pg modified = ( user["username"], user["avatar"], diff --git a/discord/primary_guild.py b/discord/primary_guild.py index 27edba7e97..ffe2af41cf 100644 --- a/discord/primary_guild.py +++ b/discord/primary_guild.py @@ -62,6 +62,16 @@ def __init__(self, data: PrimaryGuildPayload, state: "ConnectionState") -> None: def __repr__(self) -> str: return f"" + def __eq__(self, other: object): + if not isinstance(other, PrimaryGuild): + return NotImplemented + + return ( + self.identity_guild_id == other.identity_guild_id + and self.identity_enabled == other.identity_enabled + and self.tag == other.tag + ) + @cached_property def badge(self) -> Asset | None: """Returns the badge asset, if available. From e104cd10dd72237b440062631277454f1ee4ff33 Mon Sep 17 00:00:00 2001 From: ToothyDev Date: Wed, 28 Jan 2026 12:05:44 +0100 Subject: [PATCH 10/14] Add missing return type --- discord/primary_guild.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/primary_guild.py b/discord/primary_guild.py index ffe2af41cf..4cf6cdaf68 100644 --- a/discord/primary_guild.py +++ b/discord/primary_guild.py @@ -62,7 +62,7 @@ def __init__(self, data: PrimaryGuildPayload, state: "ConnectionState") -> None: def __repr__(self) -> str: return f"" - def __eq__(self, other: object): + def __eq__(self, other: object) -> bool: if not isinstance(other, PrimaryGuild): return NotImplemented From f4293062997e26121857ebe2c6f9a0354e395e8c Mon Sep 17 00:00:00 2001 From: ToothyDev Date: Wed, 28 Jan 2026 13:01:47 +0100 Subject: [PATCH 11/14] Apply code review suggestions Co-authored-by: Paillat-dev --- CHANGELOG.md | 2 ++ discord/member.py | 4 +--- discord/primary_guild.py | 13 +++++++++++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8671533cc8..b0cd6f4a66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ These changes are available on the `master` branch, but have not yet been releas - Added `.extension` attribute to the `AppEmoji` and `GuildEmoji` classes. ([#3055](https://github.com/Pycord-Development/pycord/pull/3055)) +- Added the ability to compare instances of `PrimaryGuild`. + ([#3077](https://github.com/Pycord-Development/pycord/pull/3077)) ### Changed diff --git a/discord/member.py b/discord/member.py index e28ddc6359..25854dd9c9 100644 --- a/discord/member.py +++ b/discord/member.py @@ -471,15 +471,13 @@ def _update_inner_user(self, user: UserPayload) -> tuple[User, User] | None: u.primary_guild, ) # These keys seem to always be available - new_pg = PrimaryGuild(user["primary_guild"], state=self._state) - primary_guild = u.primary_guild if u.primary_guild == new_pg else new_pg modified = ( user["username"], user["avatar"], user["discriminator"], user.get("global_name", None) or None, user.get("public_flags", 0), - primary_guild if primary_guild.identity_enabled else None, + PrimaryGuild(user["primary_guild"], state=self._state), ) if original != modified: to_return = User._copy(self._user) diff --git a/discord/primary_guild.py b/discord/primary_guild.py index 4cf6cdaf68..645d3be65b 100644 --- a/discord/primary_guild.py +++ b/discord/primary_guild.py @@ -40,6 +40,19 @@ class PrimaryGuild: .. versionadded:: 2.7 + .. container:: operations + + .. describe:: x == y + + Checks if two Primary Guilds are equal. + + .. describe:: x != y + + Checks if two Primary Guilds are not equal. + + .. versionchanged:: 2.7.1 + Primary Guilds are now comparable. + Attributes ---------- identity_guild_id: int From e8e12d3bbd15941d6ed9b049a87e54a5d96ba740 Mon Sep 17 00:00:00 2001 From: ToothyDev Date: Wed, 28 Jan 2026 14:09:55 +0100 Subject: [PATCH 12/14] Fix for bot user updates --- discord/member.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/discord/member.py b/discord/member.py index 25854dd9c9..5791a27833 100644 --- a/discord/member.py +++ b/discord/member.py @@ -471,13 +471,18 @@ def _update_inner_user(self, user: UserPayload) -> tuple[User, User] | None: u.primary_guild, ) # These keys seem to always be available + new_pg = ( + PrimaryGuild(user.get("primary_guild"), state=self._state) + if user.get("primary_guild") + else None + ) modified = ( user["username"], user["avatar"], user["discriminator"], user.get("global_name", None) or None, user.get("public_flags", 0), - PrimaryGuild(user["primary_guild"], state=self._state), + new_pg if new_pg and new_pg.identity_enabled else None, ) if original != modified: to_return = User._copy(self._user) From 5b0a4ce9fce44d7d35eb51de544947241c416400 Mon Sep 17 00:00:00 2001 From: ToothyDev Date: Wed, 28 Jan 2026 14:34:39 +0100 Subject: [PATCH 13/14] Apply code review suggestions Co-authored-by: Paillat-dev --- discord/member.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/discord/member.py b/discord/member.py index 5791a27833..26515fa396 100644 --- a/discord/member.py +++ b/discord/member.py @@ -471,18 +471,20 @@ def _update_inner_user(self, user: UserPayload) -> tuple[User, User] | None: u.primary_guild, ) # These keys seem to always be available - new_pg = ( - PrimaryGuild(user.get("primary_guild"), state=self._state) - if user.get("primary_guild") - else None - ) + new_primary_guild_data = user.get("primary_guild") + if new_primary_guild_data and new_primary_guild_data.get("identity_enabled"): + new_primary_guild: PrimaryGuild | None = PrimaryGuild( + new_primary_guild_data, state=self._state + ) + else: + new_primary_guild = None modified = ( user["username"], user["avatar"], user["discriminator"], user.get("global_name", None) or None, user.get("public_flags", 0), - new_pg if new_pg and new_pg.identity_enabled else None, + new_primary_guild, ) if original != modified: to_return = User._copy(self._user) From 93efc5bc7f3c8c1aa5aac4ac4059aa337d01bc2a Mon Sep 17 00:00:00 2001 From: ToothyDev Date: Wed, 28 Jan 2026 14:37:56 +0100 Subject: [PATCH 14/14] Apply code review suggestions #2 Co-authored-by: Paillat-dev --- discord/member.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/discord/member.py b/discord/member.py index 26515fa396..8210d129bf 100644 --- a/discord/member.py +++ b/discord/member.py @@ -471,8 +471,9 @@ def _update_inner_user(self, user: UserPayload) -> tuple[User, User] | None: u.primary_guild, ) # These keys seem to always be available - new_primary_guild_data = user.get("primary_guild") - if new_primary_guild_data and new_primary_guild_data.get("identity_enabled"): + if ( + new_primary_guild_data := user.get("primary_guild") + ) and new_primary_guild_data.get("identity_enabled"): new_primary_guild: PrimaryGuild | None = PrimaryGuild( new_primary_guild_data, state=self._state )