Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(member): implement member banners #1204

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/1203.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Implement new :attr:`.Member.guild_banner` property.
13 changes: 13 additions & 0 deletions disnake/asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,19 @@ def _from_guild_avatar(
animated=animated,
)

@classmethod
def _from_guild_banner(
cls, state: AnyState, guild_id: int, member_id: int, banner: str
) -> Self:
animated = banner.startswith("a_")
format = "gif" if animated else "png"
return cls(
state,
url=f"{cls.BASE}/guilds/{guild_id}/users/{member_id}/banners/{banner}.{format}?size=1024",
key=banner,
animated=animated,
)

@classmethod
def _from_icon(cls, state: AnyState, object_id: int, icon_hash: str, path: str) -> Self:
return cls(
Expand Down
27 changes: 27 additions & 0 deletions disnake/member.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ class Member(disnake.abc.Messageable, _UserTag):
"_user",
"_state",
"_avatar",
"_banner",
"_communication_disabled_until",
"_flags",
"_avatar_decoration_data",
Expand Down Expand Up @@ -340,6 +341,7 @@ def __init__(
self.nick: Optional[str] = data.get("nick")
self.pending: bool = data.get("pending", False)
self._avatar: Optional[str] = data.get("avatar")
self._banner: Optional[str] = data.get("banner")
onerandomusername marked this conversation as resolved.
Show resolved Hide resolved
timeout_datetime = utils.parse_time(data.get("communication_disabled_until"))
self._communication_disabled_until: Optional[datetime.datetime] = timeout_datetime
self._flags: int = data.get("flags", 0)
Expand Down Expand Up @@ -409,6 +411,7 @@ def _copy(cls, member: Member) -> Self:
self.activities = member.activities
self._state = member._state
self._avatar = member._avatar
self._banner = member._banner
self._communication_disabled_until = member.current_timeout
self._flags = member._flags

Expand Down Expand Up @@ -437,6 +440,7 @@ def _update(self, data: GuildMemberUpdateEvent) -> None:
self.premium_since = utils.parse_time(data.get("premium_since"))
self._roles = utils.SnowflakeList(map(int, data["roles"]))
self._avatar = data.get("avatar")
self._banner = data.get("banner")
timeout_datetime = utils.parse_time(data.get("communication_disabled_until"))
self._communication_disabled_until = timeout_datetime
self._flags = data.get("flags", 0)
Expand Down Expand Up @@ -619,6 +623,29 @@ def guild_avatar(self) -> Optional[Asset]:
return None
return Asset._from_guild_avatar(self._state, self.guild.id, self.id, self._avatar)

@property
def display_banner(self) -> Optional[Asset]:
"""Optional[:class:`Asset`]: Returns the member's display banner.

For regular members this is just their banner, but
if they have a guild specific banner then that
is returned instead.

.. versionadded:: 2.10
"""
return self.guild_banner or self._user.banner

@property
def guild_banner(self) -> Optional[Asset]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a display_banner field would be a good QOL addition, which also keeps it in line with avatars and decorations

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has no reason to exist, as it is non-functional, as the API only returns the user banners on fetched users.

Copy link
Member

@shiftinv shiftinv Sep 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The gateway doesn't send user banners, but REST does, so this works fine with guild.fetch_member which also returns user data for the requested member.
The caveat here is that the user can't be cached yet, since store_user will prefer the cached user (without banner) over the passed user data (with banner):

disnake/disnake/member.py

Lines 328 to 330 in 93de3c8

if user_data is None:
user_data = cast("MemberWithUserPayload", data)["user"]
self._user: User = state.store_user(user_data)

Improving this is out of scope here, but display_banner isn't completely useless either way.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like how it functions, given its a bit of a footgun in its current state.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given its a bit of a footgun in its current state

hmm, fair enough; it is somewhat inconsistent.
In that case, I suppose display_banner would be better suited as a TODO for the future, once the MemberWithUserPayload handling improves a bit. Bit unfortunate, but you're right.

"""Optional[:class:`Asset`]: Returns an :class:`Asset` for the guild banner
the member has. If unavailable, ``None`` is returned.

.. versionadded:: 2.10
"""
if self._banner is None:
return None
return Asset._from_guild_banner(self._state, self.guild.id, self.id, self._banner)

@property
def activity(self) -> Optional[ActivityTypes]:
"""Optional[Union[:class:`BaseActivity`, :class:`Spotify`]]: Returns the primary
Expand Down
1 change: 1 addition & 0 deletions disnake/types/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,7 @@ class GuildMemberUpdateEvent(TypedDict):
user: User
nick: NotRequired[Optional[str]]
avatar: Optional[str]
banner: Optional[str]
joined_at: Optional[str]
premium_since: NotRequired[Optional[str]]
deaf: NotRequired[bool]
Expand Down
Loading