Skip to content

Commit

Permalink
Merge branch 'master' into ft/polls
Browse files Browse the repository at this point in the history
  • Loading branch information
Snipy7374 authored Aug 20, 2024
2 parents 66b780c + cebfb89 commit 18151b8
Show file tree
Hide file tree
Showing 24 changed files with 166 additions and 38 deletions.
1 change: 1 addition & 0 deletions changelog/1142.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Support fetching invites with ``null`` channel (e.g. friend invites).
1 change: 1 addition & 0 deletions changelog/1142.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add :attr:`Invite.type`.
1 change: 1 addition & 0 deletions changelog/1180.doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Adding some clarifying documentation around the type of :attr:`AuditLogEntry.extra` when the action is :attr:`~AuditLogAction.overwrite_create`.
1 change: 1 addition & 0 deletions changelog/1218.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add new :attr:`Attachment.title` attribute.
1 change: 1 addition & 0 deletions changelog/1220.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add :attr:`AppInfo.approximate_guild_count` and :attr:`AppInfo.approximate_user_install_count`.
1 change: 1 addition & 0 deletions changelog/1221.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add new :meth:`.Client.fetch_sticker_pack` method.
19 changes: 15 additions & 4 deletions disnake/appinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,7 @@ class AppInfo:
.. versionadded:: 1.3
guild_id: Optional[:class:`int`]
If this application is a game sold on Discord,
this field will be the guild to which it has been linked to.
The ID of the guild associated with the application, if any.
.. versionadded:: 1.3
Expand Down Expand Up @@ -151,6 +150,15 @@ class AppInfo:
in the guild role verification configuration.
.. versionadded:: 2.8
approximate_guild_count: :class:`int`
The approximate number of guilds the application is installed to.
.. versionadded:: 2.10
approximate_user_install_count: :class:`int`
The approximate number of users that have installed the application
(for user-installable apps).
.. versionadded:: 2.10
"""

__slots__ = (
Expand All @@ -177,6 +185,8 @@ class AppInfo:
"install_params",
"custom_install_url",
"role_connections_verification_url",
"approximate_guild_count",
"approximate_user_install_count",
)

def __init__(self, state: ConnectionState, data: AppInfoPayload) -> None:
Expand Down Expand Up @@ -218,6 +228,8 @@ def __init__(self, state: ConnectionState, data: AppInfoPayload) -> None:
self.role_connections_verification_url: Optional[str] = data.get(
"role_connections_verification_url"
)
self.approximate_guild_count: int = data.get("approximate_guild_count", 0)
self.approximate_user_install_count: int = data.get("approximate_user_install_count", 0)

def __repr__(self) -> str:
return (
Expand Down Expand Up @@ -245,8 +257,7 @@ def cover_image(self) -> Optional[Asset]:

@property
def guild(self) -> Optional[Guild]:
"""Optional[:class:`Guild`]: If this application is a game sold on Discord,
this field will be the guild to which it has been linked
"""Optional[:class:`Guild`]: The guild associated with the application, if any.
.. versionadded:: 1.3
"""
Expand Down
9 changes: 7 additions & 2 deletions disnake/audit_logs.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
DefaultReaction as DefaultReactionPayload,
PermissionOverwrite as PermissionOverwritePayload,
)
from .types.invite import Invite as InvitePayload
from .types.role import Role as RolePayload
from .types.snowflake import Snowflake
from .types.threads import ForumTag as ForumTagPayload
Expand Down Expand Up @@ -799,15 +800,19 @@ def _convert_target_invite(self, target_id: int) -> Invite:
# so figure out which change has the full invite data
changeset = self.before if self.action is enums.AuditLogAction.invite_delete else self.after

fake_payload = {
fake_payload: InvitePayload = {
"max_age": changeset.max_age,
"max_uses": changeset.max_uses,
"code": changeset.code,
"temporary": changeset.temporary,
"uses": changeset.uses,
"type": 0,
"channel": None,
}

obj = Invite(state=self._state, data=fake_payload, guild=self.guild, channel=changeset.channel) # type: ignore
obj = Invite(
state=self._state, data=fake_payload, guild=self.guild, channel=changeset.channel
)
try:
obj.inviter = changeset.inviter
except AttributeError:
Expand Down
10 changes: 6 additions & 4 deletions disnake/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -3494,7 +3494,7 @@ async def create_thread(
) -> ThreadWithMessage:
"""|coro|
Creates a thread in this channel.
Creates a thread (with an initial message) in this channel.
You must have the :attr:`~Permissions.create_forum_threads` permission to do this.
Expand All @@ -3507,6 +3507,10 @@ async def create_thread(
.. versionchanged:: 2.6
The ``content`` parameter is no longer required.
.. note::
Unlike :meth:`TextChannel.create_thread`,
this **returns a tuple** with both the created **thread and message**.
Parameters
----------
name: :class:`str`
Expand Down Expand Up @@ -3583,10 +3587,8 @@ async def create_thread(
Returns
-------
Tuple[:class:`Thread`, :class:`Message`]
:class:`ThreadWithMessage`
A :class:`~typing.NamedTuple` with the newly created thread and the message sent in it.
These values can also be accessed through the ``thread`` and ``message`` fields.
"""
from .message import Message
from .webhook.async_ import handle_message_parameters_dict
Expand Down
27 changes: 27 additions & 0 deletions disnake/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2512,6 +2512,33 @@ async def fetch_sticker(self, sticker_id: int, /) -> Union[StandardSticker, Guil
cls, _ = _sticker_factory(data["type"]) # type: ignore
return cls(state=self._connection, data=data) # type: ignore

async def fetch_sticker_pack(self, pack_id: int, /) -> StickerPack:
"""|coro|
Retrieves a :class:`.StickerPack` with the given ID.
.. versionadded:: 2.10
Parameters
----------
pack_id: :class:`int`
The ID of the sticker pack to retrieve.
Raises
------
HTTPException
Retrieving the sticker pack failed.
NotFound
Invalid sticker pack ID.
Returns
-------
:class:`.StickerPack`
The sticker pack you requested.
"""
data = await self.http.get_sticker_pack(pack_id)
return StickerPack(state=self._connection, data=data)

async def fetch_sticker_packs(self) -> List[StickerPack]:
"""|coro|
Expand Down
7 changes: 7 additions & 0 deletions disnake/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"ExpireBehavior",
"StickerType",
"StickerFormatType",
"InviteType",
"InviteTarget",
"VideoQualityMode",
"ComponentType",
Expand Down Expand Up @@ -607,6 +608,12 @@ def file_extension(self) -> str:
}


class InviteType(Enum):
guild = 0
group_dm = 1
friend = 2


class InviteTarget(Enum):
unknown = 0
stream = 1
Expand Down
6 changes: 3 additions & 3 deletions disnake/flags.py
Original file line number Diff line number Diff line change
Expand Up @@ -1446,7 +1446,7 @@ def reactions(self):

@flag_value
def guild_reactions(self):
""":class:`bool`: Whether guild message reaction related events are enabled.
""":class:`bool`: Whether guild reaction related events are enabled.
See also :attr:`dm_reactions` for DMs or :attr:`reactions` for both.
Expand Down Expand Up @@ -1502,7 +1502,7 @@ def typing(self):

@flag_value
def guild_typing(self):
""":class:`bool`: Whether guild and direct message typing related events are enabled.
""":class:`bool`: Whether guild typing related events are enabled.
See also :attr:`dm_typing` for DMs or :attr:`typing` for both.
Expand All @@ -1516,7 +1516,7 @@ def guild_typing(self):

@flag_value
def dm_typing(self):
""":class:`bool`: Whether guild and direct message typing related events are enabled.
""":class:`bool`: Whether direct message typing related events are enabled.
See also :attr:`guild_typing` for guilds or :attr:`typing` for both.
Expand Down
11 changes: 9 additions & 2 deletions disnake/guild.py
Original file line number Diff line number Diff line change
Expand Up @@ -3185,7 +3185,10 @@ async def invites(self) -> List[Invite]:
data = await self._state.http.invites_from(self.id)
result = []
for invite in data:
channel = self.get_channel(int(invite["channel"]["id"]))
if channel_data := invite.get("channel"):
channel = self.get_channel(int(channel_data["id"]))
else:
channel = None
result.append(Invite(state=self._state, data=invite, guild=self, channel=channel))

return result
Expand Down Expand Up @@ -4130,11 +4133,15 @@ async def vanity_invite(self, *, use_cached: bool = False) -> Optional[Invite]:
# reliable or a thing anymore
data = await self._state.http.get_invite(payload["code"])

channel = self.get_channel(int(data["channel"]["id"]))
if channel_data := data.get("channel"):
channel = self.get_channel(int(channel_data["id"]))
else:
channel = None
payload["temporary"] = False
payload["max_uses"] = 0
payload["max_age"] = 0
payload["uses"] = payload.get("uses", 0)
payload["type"] = 0
return Invite(state=self._state, data=payload, guild=self, channel=channel)

# TODO: use MISSING when async iterators get refactored
Expand Down
3 changes: 3 additions & 0 deletions disnake/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -1611,6 +1611,9 @@ def estimate_pruned_members(
def get_sticker(self, sticker_id: Snowflake) -> Response[sticker.Sticker]:
return self.request(Route("GET", "/stickers/{sticker_id}", sticker_id=sticker_id))

def get_sticker_pack(self, pack_id: Snowflake) -> Response[sticker.StickerPack]:
return self.request(Route("GET", "/sticker-packs/{pack_id}", pack_id=pack_id))

def list_sticker_packs(self) -> Response[sticker.ListStickerPacks]:
return self.request(Route("GET", "/sticker-packs"))

Expand Down
38 changes: 22 additions & 16 deletions disnake/invite.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from .appinfo import PartialAppInfo
from .asset import Asset
from .enums import ChannelType, InviteTarget, NSFWLevel, VerificationLevel, try_enum
from .enums import ChannelType, InviteTarget, InviteType, NSFWLevel, VerificationLevel, try_enum
from .guild_scheduled_event import GuildScheduledEvent
from .mixins import Hashable
from .object import Object
Expand Down Expand Up @@ -307,8 +307,13 @@ class Invite(Hashable):
----------
code: :class:`str`
The URL fragment used for the invite.
type: :class:`InviteType`
The type of the invite.
.. versionadded:: 2.10
guild: Optional[Union[:class:`Guild`, :class:`Object`, :class:`PartialInviteGuild`]]
The guild the invite is for. Can be ``None`` if it's from a group direct message.
The guild the invite is for. Can be ``None`` if it's not a guild invite (see :attr:`type`).
max_age: Optional[:class:`int`]
How long before the invite expires in seconds.
A value of ``0`` indicates that it doesn't expire.
Expand Down Expand Up @@ -382,6 +387,7 @@ class Invite(Hashable):
__slots__ = (
"max_age",
"code",
"type",
"guild",
"created_at",
"uses",
Expand Down Expand Up @@ -412,6 +418,7 @@ def __init__(
) -> None:
self._state: ConnectionState = state
self.code: str = data["code"]
self.type: InviteType = try_enum(InviteType, data.get("type", 0))
self.guild: Optional[InviteGuildType] = self._resolve_guild(data.get("guild"), guild)

self.max_age: Optional[int] = data.get("max_age")
Expand Down Expand Up @@ -481,15 +488,12 @@ def from_incomplete(cls, *, state: ConnectionState, data: InvitePayload) -> Self
# If it's not cached, then it has to be a partial guild
guild = PartialInviteGuild(state, guild_data, guild_id)

# todo: this is no longer true
# As far as I know, invites always need a channel
# So this should never raise.
channel: Union[PartialInviteChannel, GuildChannel] = PartialInviteChannel(
data=data["channel"], state=state
)
if guild is not None and not isinstance(guild, PartialInviteGuild):
# Upgrade the partial data if applicable
channel = guild.get_channel(channel.id) or channel
channel: Optional[Union[PartialInviteChannel, GuildChannel]] = None
if channel_data := data.get("channel"):
channel = PartialInviteChannel(data=channel_data, state=state)
if guild is not None and not isinstance(guild, PartialInviteGuild):
# Upgrade the partial data if applicable
channel = guild.get_channel(channel.id) or channel

return cls(state=state, data=data, guild=guild, channel=channel)

Expand Down Expand Up @@ -543,11 +547,13 @@ def __str__(self) -> str:
return self.url

def __repr__(self) -> str:
return (
f"<Invite code={self.code!r} guild={self.guild!r} "
f"online={self.approximate_presence_count} "
f"members={self.approximate_member_count}>"
)
s = f"<Invite code={self.code!r} type={self.type!r}"
if self.type is InviteType.guild:
s += f" guild={self.guild!r} online={self.approximate_presence_count}"
if self.type is not InviteType.friend:
s += f" members={self.approximate_member_count}"
s += ">"
return s

def __hash__(self) -> int:
return hash(self.code)
Expand Down
10 changes: 10 additions & 0 deletions disnake/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,12 @@ class Attachment(Hashable):
The attachment's width, in pixels. Only applicable to images and videos.
filename: :class:`str`
The attachment's filename.
title: Optional[:class:`str`]
The attachment title. If the filename contained special characters,
this will be set to the original filename, without filename extension.
.. versionadded:: 2.10
url: :class:`str`
The attachment URL. If the message this attachment was attached
to is deleted, then this will 404.
Expand Down Expand Up @@ -295,6 +301,7 @@ class Attachment(Hashable):
"height",
"width",
"filename",
"title",
"url",
"proxy_url",
"_http",
Expand All @@ -312,6 +319,7 @@ def __init__(self, *, data: AttachmentPayload, state: ConnectionState) -> None:
self.height: Optional[int] = data.get("height")
self.width: Optional[int] = data.get("width")
self.filename: str = data["filename"]
self.title: Optional[str] = data.get("title")
self.url: str = data["url"]
self.proxy_url: str = data["proxy_url"]
self._http = state.http
Expand Down Expand Up @@ -511,6 +519,8 @@ def to_dict(self) -> AttachmentPayload:
result["waveform"] = b64encode(self.waveform).decode("ascii")
if self._flags:
result["flags"] = self._flags
if self.title:
result["title"] = self.title
return result


Expand Down
2 changes: 2 additions & 0 deletions disnake/types/appinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ class AppInfo(BaseAppInfo):
install_params: NotRequired[InstallParams]
custom_install_url: NotRequired[str]
role_connections_verification_url: NotRequired[str]
approximate_guild_count: NotRequired[int]
approximate_user_install_count: NotRequired[int]


class PartialAppInfo(BaseAppInfo, total=False):
Expand Down
3 changes: 2 additions & 1 deletion disnake/types/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from .guild_scheduled_event import GuildScheduledEvent
from .integration import BaseIntegration
from .interactions import BaseInteraction, GuildApplicationCommandPermissions
from .invite import InviteTargetType
from .invite import InviteTargetType, InviteType
from .member import MemberWithUser
from .message import Message
from .role import Role
Expand Down Expand Up @@ -366,6 +366,7 @@ class InviteCreateEvent(TypedDict):
target_user: NotRequired[User]
target_application: NotRequired[PartialAppInfo]
temporary: bool
type: InviteType
uses: int # always 0


Expand Down
Loading

0 comments on commit 18151b8

Please sign in to comment.