Skip to content

Commit

Permalink
Merge branch 'master' into feat/forward
Browse files Browse the repository at this point in the history
  • Loading branch information
Snipy7374 authored Nov 18, 2024
2 parents ffe679f + ac5a936 commit 774be89
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 12 deletions.
1 change: 1 addition & 0 deletions changelog/1184.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add the possibility to pass :class:`disnake.File` objects to :meth:`Embed.set_author` and :meth:`~Embed.set_footer`.
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
7 changes: 6 additions & 1 deletion disnake/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1272,7 +1272,12 @@ def run(self, *args: Any, **kwargs: Any) -> None:
This function must be the last function to call due to the fact that it
is blocking. That means that registration of events or anything being
called after this function call will not execute until it returns.
called after this function call will not execute until it returns
Parameters
----------
token: :class:`str`
The discord token of the bot that is being ran.
"""
loop = self.loop

Expand Down
87 changes: 76 additions & 11 deletions disnake/embeds.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ class _EmbedAuthorProxy(Sized, Protocol):
icon_url: Optional[str]
proxy_icon_url: Optional[str]

_FileKey = Literal["image", "thumbnail"]
_FileKey = Literal["image", "thumbnail", "footer", "author"]


class Embed:
Expand Down Expand Up @@ -385,12 +385,32 @@ def footer(self) -> _EmbedFooterProxy:
"""
return cast("_EmbedFooterProxy", EmbedProxy(self._footer))

def set_footer(self, *, text: Any, icon_url: Optional[Any] = None) -> Self:
@overload
def set_footer(self, *, text: Any, icon_url: Optional[Any] = ...) -> Self:
...

@overload
def set_footer(self, *, text: Any, icon_file: File = ...) -> Self:
...

def set_footer(
self, *, text: Any, icon_url: Optional[Any] = MISSING, icon_file: File = MISSING
) -> Self:
"""Sets the footer for the embed content.
This function returns the class instance to allow for fluent-style
chaining.
At most one of ``icon_url`` or ``icon_file`` may be passed.
.. warning::
Passing a :class:`disnake.File` object will make the embed not
reusable.
.. warning::
If used with the other ``set_*`` methods, you must ensure
that the :attr:`.File.filename` is unique to avoid duplication.
Parameters
----------
text: :class:`str`
Expand All @@ -401,13 +421,18 @@ def set_footer(self, *, text: Any, icon_url: Optional[Any] = None) -> Self:
icon_url: Optional[:class:`str`]
The URL of the footer icon. Only HTTP(S) is supported.
icon_file: :class:`File`
The file to use as the footer icon.
.. versionadded:: 2.10
"""
self._footer = {
"text": str(text),
}

if icon_url is not None:
self._footer["icon_url"] = str(icon_url)
result = self._handle_resource(icon_url, icon_file, key="footer", required=False)
if result is not None:
self._footer["icon_url"] = result

return self

Expand Down Expand Up @@ -457,6 +482,10 @@ def set_image(self, url: Optional[Any] = MISSING, *, file: File = MISSING) -> Se
Passing a :class:`disnake.File` object will make the embed not
reusable.
.. warning::
If used with the other ``set_*`` methods, you must ensure
that the :attr:`.File.filename` is unique to avoid duplication.
.. versionchanged:: 1.4
Passing ``None`` removes the image.
Expand Down Expand Up @@ -508,6 +537,10 @@ def set_thumbnail(self, url: Optional[Any] = MISSING, *, file: File = MISSING) -
Passing a :class:`disnake.File` object will make the embed not
reusable.
.. warning::
If used with the other ``set_*`` methods, you must ensure
that the :attr:`.File.filename` is unique to avoid duplication.
.. versionchanged:: 1.4
Passing ``None`` removes the thumbnail.
Expand Down Expand Up @@ -559,18 +592,39 @@ def author(self) -> _EmbedAuthorProxy:
"""
return cast("_EmbedAuthorProxy", EmbedProxy(self._author))

@overload
def set_author(
self, *, name: Any, url: Optional[Any] = ..., icon_url: Optional[Any] = ...
) -> Self:
...

@overload
def set_author(self, *, name: Any, url: Optional[Any] = ..., icon_file: File = ...) -> Self:
...

def set_author(
self,
*,
name: Any,
url: Optional[Any] = None,
icon_url: Optional[Any] = None,
icon_url: Optional[Any] = MISSING,
icon_file: File = MISSING,
) -> Self:
"""Sets the author for the embed content.
This function returns the class instance to allow for fluent-style
chaining.
At most one of ``icon_url`` or ``icon_file`` may be passed.
.. warning::
Passing a :class:`disnake.File` object will make the embed not
reusable.
.. warning::
If used with the other ``set_*`` methods, you must ensure
that the :attr:`.File.filename` is unique to avoid duplication.
Parameters
----------
name: :class:`str`
Expand All @@ -579,6 +633,10 @@ def set_author(
The URL for the author.
icon_url: Optional[:class:`str`]
The URL of the author icon. Only HTTP(S) is supported.
icon_file: :class:`File`
The file to use as the author icon.
.. versionadded:: 2.10
"""
self._author = {
"name": str(name),
Expand All @@ -587,8 +645,9 @@ def set_author(
if url is not None:
self._author["url"] = str(url)

if icon_url is not None:
self._author["icon_url"] = str(icon_url)
result = self._handle_resource(icon_url, icon_file, key="author", required=False)
if result is not None:
self._author["icon_url"] = result

return self

Expand Down Expand Up @@ -821,9 +880,15 @@ def get_default_colour(cls) -> Optional[Colour]:

get_default_color = get_default_colour

def _handle_resource(self, url: Optional[Any], file: File, *, key: _FileKey) -> Optional[str]:
if not (url is MISSING) ^ (file is MISSING):
raise TypeError("Exactly one of url or file must be provided")
def _handle_resource(
self, url: Optional[Any], file: Optional[File], *, key: _FileKey, required: bool = True
) -> Optional[str]:
if required:
if not (url is MISSING) ^ (file is MISSING):
raise TypeError("Exactly one of url or file must be provided")
else:
if url is not MISSING and file is not MISSING:
raise TypeError("At most one of url or file may be provided, not both.")

if file:
if file.filename is None:
Expand All @@ -832,7 +897,7 @@ def _handle_resource(self, url: Optional[Any], file: File, *, key: _FileKey) ->
return f"attachment://{file.filename}"
else:
self._files.pop(key, None)
return str(url) if url is not None else None
return str(url) if url else None

def check_limits(self) -> None:
"""Checks if this embed fits within the limits dictated by Discord.
Expand Down
19 changes: 19 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")
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 @@ -413,6 +415,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 @@ -441,6 +444,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 @@ -623,6 +627,21 @@ def guild_avatar(self) -> Optional[Asset]:
return None
return Asset._from_guild_avatar(self._state, self.guild.id, self.id, self._avatar)

# TODO
# implement a `display_banner` property
# for more info on why this wasn't implemented read this discussion
# https://github.com/DisnakeDev/disnake/pull/1204#discussion_r1685773429
@property
def guild_banner(self) -> Optional[Asset]:
"""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 @@ -453,6 +453,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

0 comments on commit 774be89

Please sign in to comment.