diff --git a/discord/app_commands/models.py b/discord/app_commands/models.py index ef2e1849d..3e9d250b2 100644 --- a/discord/app_commands/models.py +++ b/discord/app_commands/models.py @@ -673,7 +673,7 @@ class AppCommandThread(Hashable): archiver_id: Optional[:class:`int`] The user's ID that archived this thread. auto_archive_duration: :class:`int` - The duration in minutes until the thread is automatically archived due to inactivity. + The duration in minutes until the thread is automatically hidden from the channel list. Usually a value of 60, 1440, 4320 and 10080. archive_timestamp: :class:`datetime.datetime` An aware timestamp of when the thread's archived status was last updated in UTC. diff --git a/discord/audit_logs.py b/discord/audit_logs.py index fa5b2ff21..3c1aa6eca 100644 --- a/discord/audit_logs.py +++ b/discord/audit_logs.py @@ -76,6 +76,7 @@ if TYPE_CHECKING: from .types.onboarding import Prompt as PromptPayload, PromptOption as PromptOptionPayload from .user import User from .app_commands import AppCommand + from .webhook import Webhook TargetType = Union[ Guild, @@ -91,6 +92,9 @@ if TYPE_CHECKING: Object, PartialIntegration, AutoModRule, + ScheduledEvent, + Webhook, + AppCommand, None, ] @@ -597,6 +601,7 @@ class AuditLogEntry(Hashable): integrations: Mapping[int, PartialIntegration], app_commands: Mapping[int, AppCommand], automod_rules: Mapping[int, AutoModRule], + webhooks: Mapping[int, Webhook], data: AuditLogEntryPayload, guild: Guild, ): @@ -606,6 +611,7 @@ class AuditLogEntry(Hashable): self._integrations: Mapping[int, PartialIntegration] = integrations self._app_commands: Mapping[int, AppCommand] = app_commands self._automod_rules: Mapping[int, AutoModRule] = automod_rules + self._webhooks: Mapping[int, Webhook] = webhooks self._from_data(data) def _from_data(self, data: AuditLogEntryPayload) -> None: @@ -744,12 +750,11 @@ class AuditLogEntry(Hashable): if self.action.target_type is None: return None - if self._target_id is None: - return None - try: converter = getattr(self, '_convert_target_' + self.action.target_type) except AttributeError: + if self._target_id is None: + return None return Object(id=self._target_id) else: return converter(self._target_id) @@ -782,7 +787,12 @@ class AuditLogEntry(Hashable): def _convert_target_channel(self, target_id: int) -> Union[abc.GuildChannel, Object]: return self.guild.get_channel(target_id) or Object(id=target_id) - def _convert_target_user(self, target_id: int) -> Union[Member, User, Object]: + def _convert_target_user(self, target_id: Optional[int]) -> Optional[Union[Member, User, Object]]: + # For some reason the member_disconnect and member_move action types + # do not have a non-null target_id so safeguard against that + if target_id is None: + return None + return self._get_member(target_id) or Object(id=target_id, type=Member) def _convert_target_role(self, target_id: int) -> Union[Role, Object]: @@ -862,3 +872,9 @@ class AuditLogEntry(Hashable): def _convert_target_auto_moderation(self, target_id: int) -> Union[AutoModRule, Object]: return self._automod_rules.get(target_id) or Object(target_id, type=AutoModRule) + + def _convert_target_webhook(self, target_id: int) -> Union[Webhook, Object]: + # circular import + from .webhook import Webhook + + return self._webhooks.get(target_id) or Object(target_id, type=Webhook) diff --git a/discord/channel.py b/discord/channel.py index 3c93832f3..cd5490b2e 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -737,7 +737,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable): If ``None`` is passed then a private thread is created. Defaults to ``None``. auto_archive_duration: :class:`int` - The duration in minutes before a thread is automatically archived for inactivity. + The duration in minutes before a thread is automatically hidden from the channel list. If not provided, the channel's default auto archive duration is used. Must be one of ``60``, ``1440``, ``4320``, or ``10080``, if provided. @@ -2607,7 +2607,7 @@ class ForumChannel(discord.abc.GuildChannel, Hashable): name: :class:`str` The name of the thread. auto_archive_duration: :class:`int` - The duration in minutes before a thread is automatically archived for inactivity. + The duration in minutes before a thread is automatically hidden from the channel list. If not provided, the channel's default auto archive duration is used. Must be one of ``60``, ``1440``, ``4320``, or ``10080``, if provided. diff --git a/discord/ext/commands/core.py b/discord/ext/commands/core.py index f3850a224..8140dca00 100644 --- a/discord/ext/commands/core.py +++ b/discord/ext/commands/core.py @@ -2036,7 +2036,7 @@ def has_role(item: Union[int, str], /) -> Check[Any]: # ctx.guild is None doesn't narrow ctx.author to Member if isinstance(item, int): - role = discord.utils.get(ctx.author.roles, id=item) # type: ignore + role = ctx.author.get_role(item) # type: ignore else: role = discord.utils.get(ctx.author.roles, name=item) # type: ignore if role is None: @@ -2083,8 +2083,12 @@ def has_any_role(*items: Union[int, str]) -> Callable[[T], T]: raise NoPrivateMessage() # ctx.guild is None doesn't narrow ctx.author to Member - getter = functools.partial(discord.utils.get, ctx.author.roles) - if any(getter(id=item) is not None if isinstance(item, int) else getter(name=item) is not None for item in items): + if any( + ctx.author.get_role(item) is not None + if isinstance(item, int) + else discord.utils.get(ctx.author.roles, name=item) is not None + for item in items + ): return True raise MissingAnyRole(list(items)) @@ -2113,11 +2117,10 @@ def bot_has_role(item: int, /) -> Callable[[T], T]: if ctx.guild is None: raise NoPrivateMessage() - me = ctx.me if isinstance(item, int): - role = discord.utils.get(me.roles, id=item) + role = ctx.me.get_role(item) else: - role = discord.utils.get(me.roles, name=item) + role = discord.utils.get(ctx.me.roles, name=item) if role is None: raise BotMissingRole(item) return True @@ -2144,8 +2147,10 @@ def bot_has_any_role(*items: int) -> Callable[[T], T]: raise NoPrivateMessage() me = ctx.me - getter = functools.partial(discord.utils.get, me.roles) - if any(getter(id=item) is not None if isinstance(item, int) else getter(name=item) is not None for item in items): + if any( + me.get_role(item) is not None if isinstance(item, int) else discord.utils.get(me.roles, name=item) is not None + for item in items + ): return True raise BotMissingAnyRole(list(items)) diff --git a/discord/flags.py b/discord/flags.py index 1dbb7c3c3..1b0005bd2 100644 --- a/discord/flags.py +++ b/discord/flags.py @@ -25,6 +25,7 @@ DEALINGS IN THE SOFTWARE. from __future__ import annotations from functools import reduce +from operator import or_ from typing import TYPE_CHECKING, Any, Callable, ClassVar, Dict, Iterator, List, Optional, Tuple, Type, TypeVar, overload from .enums import UserFlags @@ -1566,7 +1567,15 @@ class ArrayFlags(BaseFlags): @classmethod def _from_value(cls: Type[Self], value: List[int]) -> Self: self = cls.__new__(cls) - self.value = reduce(lambda a, b: a | (1 << b - 1), value, 0) + # This is a micro-optimization given the frequency this object can be created. + # (1).__lshift__ is used in place of lambda x: 1 << x + # prebinding to a method of a constant rather than define a lambda. + # Pairing this with map, is essentially equivalent to (1 << x for x in value) + # reduction using operator.or_ instead of defining a lambda each call + # Discord sends these starting with a value of 1 + # Rather than subtract 1 from each element prior to left shift, + # we shift right by 1 once at the end. + self.value = reduce(or_, map((1).__lshift__, value), 0) >> 1 return self def to_array(self) -> List[int]: diff --git a/discord/guild.py b/discord/guild.py index ea488c0ab..63d8ac5c1 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -319,9 +319,9 @@ class Guild(Hashable): ) _PREMIUM_GUILD_LIMITS: ClassVar[Dict[Optional[int], _GuildLimit]] = { - None: _GuildLimit(emoji=50, stickers=5, bitrate=96e3, filesize=8388608), - 0: _GuildLimit(emoji=50, stickers=5, bitrate=96e3, filesize=8388608), - 1: _GuildLimit(emoji=100, stickers=15, bitrate=128e3, filesize=8388608), + None: _GuildLimit(emoji=50, stickers=5, bitrate=96e3, filesize=26214400), + 0: _GuildLimit(emoji=50, stickers=5, bitrate=96e3, filesize=26214400), + 1: _GuildLimit(emoji=100, stickers=15, bitrate=128e3, filesize=26214400), 2: _GuildLimit(emoji=150, stickers=30, bitrate=256e3, filesize=52428800), 3: _GuildLimit(emoji=250, stickers=60, bitrate=384e3, filesize=104857600), } @@ -3861,6 +3861,7 @@ class Guild(Hashable): # avoid circular import from .app_commands import AppCommand + from .webhook import Webhook while True: retrieve = 100 if limit is None else min(limit, 100) @@ -3887,6 +3888,9 @@ class Guild(Hashable): ) automod_rule_map = {rule.id: rule for rule in automod_rules} + webhooks = (Webhook.from_state(data=raw_webhook, state=self._state) for raw_webhook in data.get('webhooks', [])) + webhook_map = {webhook.id: webhook for webhook in webhooks} + count = 0 for count, raw_entry in enumerate(raw_entries, 1): @@ -3900,6 +3904,7 @@ class Guild(Hashable): integrations=integration_map, app_commands=app_command_map, automod_rules=automod_rule_map, + webhooks=webhook_map, guild=self, ) diff --git a/discord/message.py b/discord/message.py index 8c9b732f5..c96c7e022 100644 --- a/discord/message.py +++ b/discord/message.py @@ -1215,7 +1215,7 @@ class PartialMessage(Hashable): name: :class:`str` The name of the thread. auto_archive_duration: :class:`int` - The duration in minutes before a thread is automatically archived for inactivity. + The duration in minutes before a thread is automatically hidden from the channel list. If not provided, the channel's default auto archive duration is used. Must be one of ``60``, ``1440``, ``4320``, or ``10080``, if provided. diff --git a/discord/state.py b/discord/state.py index 68ca61f36..6da0379ca 100644 --- a/discord/state.py +++ b/discord/state.py @@ -1108,6 +1108,7 @@ class ConnectionState(Generic[ClientT]): integrations={}, app_commands={}, automod_rules={}, + webhooks={}, data=data, guild=guild, ) diff --git a/discord/threads.py b/discord/threads.py index c5551a55a..b47c189d2 100644 --- a/discord/threads.py +++ b/discord/threads.py @@ -122,7 +122,7 @@ class Thread(Messageable, Hashable): archiver_id: Optional[:class:`int`] The user's ID that archived this thread. auto_archive_duration: :class:`int` - The duration in minutes until the thread is automatically archived due to inactivity. + The duration in minutes until the thread is automatically hidden from the channel list. Usually a value of 60, 1440, 4320 and 10080. archive_timestamp: :class:`datetime.datetime` An aware timestamp of when the thread's archived status was last updated in UTC. @@ -608,7 +608,7 @@ class Thread(Messageable, Hashable): Whether non-moderators can add other non-moderators to this thread. Only available for private threads. auto_archive_duration: :class:`int` - The new duration in minutes before a thread is automatically archived for inactivity. + The new duration in minutes before a thread is automatically hidden from the channel list. Must be one of ``60``, ``1440``, ``4320``, or ``10080``. slowmode_delay: :class:`int` Specifies the slowmode rate limit for user in this thread, in seconds. diff --git a/discord/utils.py b/discord/utils.py index 6b4e14347..fd711387b 100644 --- a/discord/utils.py +++ b/discord/utils.py @@ -900,7 +900,7 @@ _MARKDOWN_ESCAPE_REGEX = re.compile(fr'(?P{_MARKDOWN_ESCAPE_SUBREGEX}| _URL_REGEX = r'(?P<[^: >]+:\/[^ >]+>|(?:https?|steam):\/\/[^\s<]+[^<.,:;\"\'\]\s])' -_MARKDOWN_STOCK_REGEX = fr'(?P[_\\~|\*`]|{_MARKDOWN_ESCAPE_COMMON})' +_MARKDOWN_STOCK_REGEX = fr'(?P[_\\~|\*`#-]|{_MARKDOWN_ESCAPE_COMMON})' def remove_markdown(text: str, *, ignore_links: bool = True) -> str: diff --git a/docs/api.rst b/docs/api.rst index f63eb38a8..e2ae7d8d5 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -3477,6 +3477,12 @@ AuditLogDiff :type: :class:`str` + .. attribute:: guild + + The guild of something. + + :type: :class:`Guild` + .. attribute:: icon A guild's or role's icon. See also :attr:`Guild.icon` or :attr:`Role.icon`. @@ -4051,6 +4057,38 @@ AuditLogDiff :type: :class:`ChannelFlags` + .. attribute:: default_thread_slowmode_delay + + The default slowmode delay for threads created in this text channel or forum. + + See also :attr:`TextChannel.default_thread_slowmode_delay` and :attr:`ForumChannel.default_thread_slowmode_delay` + + :type: :class:`int` + + .. attribute:: applied_tags + + The applied tags of a forum post. + + See also :attr:`Thread.applied_tags` + + :type: List[Union[:class:`ForumTag`, :class:`Object`]] + + .. attribute:: available_tags + + The available tags of a forum. + + See also :attr:`ForumChannel.available_tags` + + :type: Sequence[:class:`ForumTag`] + + .. attribute:: default_reaction_emoji + + The default_reaction_emoji for forum posts. + + See also :attr:`ForumChannel.default_reaction_emoji` + + :type: :class:`default_reaction_emoji` + .. attribute:: options The onboarding prompt options associated with this onboarding prompt. diff --git a/docs/discord.rst b/docs/discord.rst index ac12417f0..63485138e 100644 --- a/docs/discord.rst +++ b/docs/discord.rst @@ -21,12 +21,7 @@ Creating a Bot account is a pretty straightforward process. .. image:: /images/discord_create_app_form.png :alt: The new application form filled in. -5. Create a Bot User by navigating to the "Bot" tab and clicking "Add Bot". - - - Click "Yes, do it!" to continue. - - .. image:: /images/discord_create_bot_user.png - :alt: The Add Bot button. +5. Navigate to the "Bot" tab to configure it. 6. Make sure that **Public Bot** is ticked if you want others to invite your bot. - You should also make sure that **Require OAuth2 Code Grant** is unchecked unless you diff --git a/docs/images/discord_create_bot_user.png b/docs/images/discord_create_bot_user.png deleted file mode 100644 index b66e64c75..000000000 Binary files a/docs/images/discord_create_bot_user.png and /dev/null differ