diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 436b5ccc0..79b7ac8ec 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -38,7 +38,7 @@ jobs: - name: Run Pyright uses: jakebailey/pyright-action@v1 with: - version: '1.1.289' + version: '1.1.394' warnings: false no-comments: ${{ matrix.python-version != '3.x' }} diff --git a/.readthedocs.yml b/.readthedocs.yml index f67ef5268..68c792379 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -2,7 +2,9 @@ version: 2 formats: [] build: - image: latest + os: "ubuntu-22.04" + tools: + python: "3.8" sphinx: configuration: docs/conf.py @@ -10,7 +12,6 @@ sphinx: builder: html python: - version: 3.8 install: - method: pip path: . diff --git a/MANIFEST.in b/MANIFEST.in index e623df089..8e93fd092 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,5 @@ include README.rst include LICENSE include requirements.txt -include discord/bin/*.dll +include discord/bin/* include discord/py.typed diff --git a/README.rst b/README.rst index 621a69500..b2112f9d6 100644 --- a/README.rst +++ b/README.rst @@ -27,6 +27,13 @@ Installing To install the library without full voice support, you can just run the following command: +.. note:: + + A `Virtual Environment `__ is recommended to install + the library, especially on Linux where the system Python is externally managed and restricts which + packages you can install on it. + + .. code:: sh # Linux/macOS diff --git a/discord/__init__.py b/discord/__init__.py index 014615354..48fe10925 100644 --- a/discord/__init__.py +++ b/discord/__init__.py @@ -13,7 +13,7 @@ __title__ = 'discord' __author__ = 'Rapptz' __license__ = 'MIT' __copyright__ = 'Copyright 2015-present Rapptz' -__version__ = '2.2.0a' +__version__ = '2.6.0a' __path__ = __import__('pkgutil').extend_path(__path__, __name__) @@ -41,6 +41,7 @@ from .integrations import * from .invite import * from .template import * from .welcome_screen import * +from .sku import * from .widget import * from .object import * from .reaction import * @@ -68,6 +69,10 @@ from .interactions import * from .components import * from .threads import * from .automod import * +from .poll import * +from .soundboard import * +from .subscription import * +from .presences import * class VersionInfo(NamedTuple): @@ -78,8 +83,14 @@ class VersionInfo(NamedTuple): serial: int -version_info: VersionInfo = VersionInfo(major=2, minor=2, micro=0, releaselevel='alpha', serial=0) +version_info: VersionInfo = VersionInfo(major=2, minor=6, micro=0, releaselevel='alpha', serial=0) logging.getLogger(__name__).addHandler(logging.NullHandler()) +# This is a backwards compatibility hack and should be removed in v3 +# Essentially forcing the exception to have different base classes +# In the future, this should only inherit from ClientException +if len(MissingApplicationID.__bases__) == 1: + MissingApplicationID.__bases__ = (app_commands.AppCommandError, ClientException) + del logging, NamedTuple, Literal, VersionInfo diff --git a/discord/__main__.py b/discord/__main__.py index 6e34be54c..f8556fcdc 100644 --- a/discord/__main__.py +++ b/discord/__main__.py @@ -28,7 +28,7 @@ from typing import Optional, Tuple, Dict import argparse import sys -from pathlib import Path +from pathlib import Path, PurePath, PureWindowsPath import discord import importlib.metadata @@ -157,7 +157,7 @@ _cog_extras = ''' async def cog_command_error(self, ctx, error): # error handling to every command in here pass - + async def cog_app_command_error(self, interaction, error): # error handling to every application command in here pass @@ -225,8 +225,14 @@ def to_path(parser: argparse.ArgumentParser, name: str, *, replace_spaces: bool ) if len(name) <= 4 and name.upper() in forbidden: parser.error('invalid directory name given, use a different one') + path = PurePath(name) + if isinstance(path, PureWindowsPath) and path.drive: + drive, rest = path.parts[0], path.parts[1:] + transformed = tuple(map(lambda p: p.translate(_translation_table), rest)) + name = drive + '\\'.join(transformed) - name = name.translate(_translation_table) + else: + name = name.translate(_translation_table) if replace_spaces: name = name.replace(' ', '-') return Path(name) diff --git a/discord/abc.py b/discord/abc.py index 6e3125f40..713398a7d 100644 --- a/discord/abc.py +++ b/discord/abc.py @@ -26,6 +26,7 @@ from __future__ import annotations import copy import time +import secrets import asyncio from datetime import datetime from typing import ( @@ -48,8 +49,8 @@ from typing import ( from .object import OLDEST_OBJECT, Object from .context_managers import Typing -from .enums import ChannelType -from .errors import ClientException +from .enums import ChannelType, InviteTarget +from .errors import ClientException, NotFound from .mentions import AllowedMentions from .permissions import PermissionOverwrite, Permissions from .role import Role @@ -59,6 +60,7 @@ from .http import handle_message_parameters from .voice_client import VoiceClient, VoiceProtocol from .sticker import GuildSticker, StickerItem from . import utils +from .flags import InviteFlags __all__ = ( 'Snowflake', @@ -83,9 +85,17 @@ if TYPE_CHECKING: from .channel import CategoryChannel from .embeds import Embed from .message import Message, MessageReference, PartialMessage - from .channel import TextChannel, DMChannel, GroupChannel, PartialMessageable, VoiceChannel + from .channel import ( + TextChannel, + DMChannel, + GroupChannel, + PartialMessageable, + VocalGuildChannel, + VoiceChannel, + StageChannel, + ) + from .poll import Poll from .threads import Thread - from .enums import InviteTarget from .ui.view import View from .types.channel import ( PermissionOverwrite as PermissionOverwritePayload, @@ -93,11 +103,14 @@ if TYPE_CHECKING: GuildChannel as GuildChannelPayload, OverwriteType, ) + from .types.guild import ( + ChannelPositionUpdate, + ) from .types.snowflake import ( SnowflakeList, ) - PartialMessageableChannel = Union[TextChannel, VoiceChannel, Thread, DMChannel, PartialMessageable] + PartialMessageableChannel = Union[TextChannel, VoiceChannel, StageChannel, Thread, DMChannel, PartialMessageable] MessageableChannel = Union[PartialMessageableChannel, GroupChannel] SnowflakeTime = Union["Snowflake", datetime] @@ -114,11 +127,18 @@ _undefined: Any = _Undefined() async def _single_delete_strategy(messages: Iterable[Message], *, reason: Optional[str] = None): for m in messages: - await m.delete() + try: + await m.delete() + except NotFound as exc: + if exc.code == 10008: + continue # bulk deletion ignores not found messages, single deletion does not. + # several other race conditions with deletion should fail without continuing, + # such as the channel being deleted and not found. + raise async def _purge_helper( - channel: Union[Thread, TextChannel, VoiceChannel], + channel: Union[Thread, TextChannel, VocalGuildChannel], *, limit: Optional[int] = 100, check: Callable[[Message], bool] = MISSING, @@ -211,7 +231,9 @@ class User(Snowflake, Protocol): name: :class:`str` The user's username. discriminator: :class:`str` - The user's discriminator. + The user's discriminator. This is a legacy concept that is no longer used. + global_name: Optional[:class:`str`] + The user's global nickname. bot: :class:`bool` If the user is a bot account. system: :class:`bool` @@ -220,6 +242,7 @@ class User(Snowflake, Protocol): name: str discriminator: str + global_name: Optional[str] bot: bool system: bool @@ -238,9 +261,25 @@ class User(Snowflake, Protocol): """Optional[:class:`~discord.Asset`]: Returns an Asset that represents the user's avatar, if present.""" raise NotImplementedError + @property + def avatar_decoration(self) -> Optional[Asset]: + """Optional[:class:`~discord.Asset`]: Returns an Asset that represents the user's avatar decoration, if present. + + .. versionadded:: 2.4 + """ + raise NotImplementedError + + @property + def avatar_decoration_sku_id(self) -> Optional[int]: + """Optional[:class:`int`]: Returns an integer that represents the user's avatar decoration SKU ID, if present. + + .. versionadded:: 2.4 + """ + raise NotImplementedError + @property def default_avatar(self) -> Asset: - """:class:`~discord.Asset`: Returns the default avatar for a given user. This is calculated by the user's discriminator.""" + """:class:`~discord.Asset`: Returns the default avatar for a given user.""" raise NotImplementedError @property @@ -490,6 +529,13 @@ class GuildChannel: raise TypeError('type field must be of type ChannelType') options['type'] = ch_type.value + try: + status = options.pop('status') + except KeyError: + pass + else: + await self._state.http.edit_voice_channel_status(status, channel_id=self.id, reason=reason) + if options: return await self._state.http.edit_channel(self.id, reason=reason, **options) @@ -665,6 +711,7 @@ class GuildChannel: - Member overrides - Implicit permissions - Member timeout + - User installed app If a :class:`~discord.Role` is passed, then it checks the permissions someone with that role would have, which is essentially: @@ -680,6 +727,12 @@ class GuildChannel: .. versionchanged:: 2.0 ``obj`` parameter is now positional-only. + .. versionchanged:: 2.4 + User installed apps are now taken into account. + The permissions returned for a user installed app mirrors the + permissions Discord returns in :attr:`~discord.Interaction.app_permissions`, + though it is recommended to use that attribute instead. + Parameters ---------- obj: Union[:class:`~discord.Member`, :class:`~discord.Role`] @@ -711,6 +764,13 @@ class GuildChannel: return Permissions.all() default = self.guild.default_role + if default is None: + + if self._state.self_id == obj.id: + return Permissions._user_installed_permissions(in_guild=True) + else: + return Permissions.none() + base = Permissions(default.permissions.value) # Handle the role case first @@ -935,8 +995,6 @@ class GuildChannel: if len(permissions) > 0: raise TypeError('Cannot mix overwrite and keyword arguments.') - # TODO: wait for event - if overwrite is None: await http.delete_channel_permissions(self.id, target.id, reason=reason) elif isinstance(overwrite, PermissionOverwrite): @@ -952,11 +1010,15 @@ class GuildChannel: base_attrs: Dict[str, Any], *, name: Optional[str] = None, + category: Optional[CategoryChannel] = None, reason: Optional[str] = None, ) -> Self: base_attrs['permission_overwrites'] = [x._asdict() for x in self._overwrites] base_attrs['parent_id'] = self.category_id base_attrs['name'] = name or self.name + if category is not None: + base_attrs['parent_id'] = category.id + guild_id = self.guild.id cls = self.__class__ data = await self._state.http.create_channel(guild_id, self.type.value, reason=reason, **base_attrs) @@ -966,7 +1028,13 @@ class GuildChannel: self.guild._channels[obj.id] = obj # type: ignore # obj is a GuildChannel return obj - async def clone(self, *, name: Optional[str] = None, reason: Optional[str] = None) -> Self: + async def clone( + self, + *, + name: Optional[str] = None, + category: Optional[CategoryChannel] = None, + reason: Optional[str] = None, + ) -> Self: """|coro| Clones this channel. This creates a channel with the same properties @@ -981,6 +1049,11 @@ class GuildChannel: name: Optional[:class:`str`] The name of the new channel. If not provided, defaults to this channel name. + category: Optional[:class:`~discord.CategoryChannel`] + The category the new channel belongs to. + This parameter is ignored if cloning a category channel. + + .. versionadded:: 2.5 reason: Optional[:class:`str`] The reason for cloning this channel. Shows up on the audit log. @@ -1077,10 +1150,10 @@ class GuildChannel: channel list (or category if given). This is mutually exclusive with ``beginning``, ``before``, and ``after``. before: :class:`~discord.abc.Snowflake` - The channel that should be before our current channel. + Whether to move the channel before the given channel. This is mutually exclusive with ``beginning``, ``end``, and ``after``. after: :class:`~discord.abc.Snowflake` - The channel that should be after our current channel. + Whether to move the channel after the given channel. This is mutually exclusive with ``beginning``, ``end``, and ``before``. offset: :class:`int` The number of channels to offset the move by. For example, @@ -1163,11 +1236,11 @@ class GuildChannel: raise ValueError('Could not resolve appropriate move position') channels.insert(max((index + offset), 0), self) - payload = [] + payload: List[ChannelPositionUpdate] = [] lock_permissions = kwargs.get('sync_permissions', False) reason = kwargs.get('reason') for index, channel in enumerate(channels): - d = {'id': channel.id, 'position': index} + d: ChannelPositionUpdate = {'id': channel.id, 'position': index} if parent_id is not MISSING and channel.id == self.id: d.update(parent_id=parent_id, lock_permissions=lock_permissions) payload.append(d) @@ -1185,6 +1258,7 @@ class GuildChannel: target_type: Optional[InviteTarget] = None, target_user: Optional[User] = None, target_application_id: Optional[int] = None, + guest: bool = False, ) -> Invite: """|coro| @@ -1223,6 +1297,10 @@ class GuildChannel: The id of the embedded application for the invite, required if ``target_type`` is :attr:`.InviteTarget.embedded_application`. .. versionadded:: 2.0 + guest: :class:`bool` + Whether the invite is a guest invite. + + .. versionadded:: 2.6 Raises ------- @@ -1237,6 +1315,13 @@ class GuildChannel: :class:`~discord.Invite` The invite that was created. """ + if target_type is InviteTarget.unknown: + raise ValueError('Cannot create invite with an unknown target type') + + flags: Optional[InviteFlags] = None + if guest: + flags = InviteFlags._from_value(0) + flags.guest = True data = await self._state.http.create_invite( self.id, @@ -1248,6 +1333,7 @@ class GuildChannel: target_type=target_type.value if target_type else None, target_user_id=target_user.id if target_user else None, target_application_id=target_application_id, + flags=flags.value if flags else None, ) return Invite.from_incomplete(data=data, state=self._state) @@ -1284,6 +1370,7 @@ class Messageable: - :class:`~discord.TextChannel` - :class:`~discord.VoiceChannel` + - :class:`~discord.StageChannel` - :class:`~discord.DMChannel` - :class:`~discord.GroupChannel` - :class:`~discord.PartialMessageable` @@ -1316,6 +1403,7 @@ class Messageable: view: View = ..., suppress_embeds: bool = ..., silent: bool = ..., + poll: Poll = ..., ) -> Message: ... @@ -1336,6 +1424,7 @@ class Messageable: view: View = ..., suppress_embeds: bool = ..., silent: bool = ..., + poll: Poll = ..., ) -> Message: ... @@ -1356,6 +1445,7 @@ class Messageable: view: View = ..., suppress_embeds: bool = ..., silent: bool = ..., + poll: Poll = ..., ) -> Message: ... @@ -1376,6 +1466,7 @@ class Messageable: view: View = ..., suppress_embeds: bool = ..., silent: bool = ..., + poll: Poll = ..., ) -> Message: ... @@ -1397,6 +1488,7 @@ class Messageable: view: Optional[View] = None, suppress_embeds: bool = False, silent: bool = False, + poll: Optional[Poll] = None, ) -> Message: """|coro| @@ -1454,10 +1546,11 @@ class Messageable: .. versionadded:: 1.4 reference: Union[:class:`~discord.Message`, :class:`~discord.MessageReference`, :class:`~discord.PartialMessage`] - A reference to the :class:`~discord.Message` to which you are replying, this can be created using - :meth:`~discord.Message.to_reference` or passed directly as a :class:`~discord.Message`. You can control - whether this mentions the author of the referenced message using the :attr:`~discord.AllowedMentions.replied_user` - attribute of ``allowed_mentions`` or by setting ``mention_author``. + A reference to the :class:`~discord.Message` to which you are referencing, this can be created using + :meth:`~discord.Message.to_reference` or passed directly as a :class:`~discord.Message`. + In the event of a replying reference, you can control whether this mentions the author of the referenced + message using the :attr:`~discord.AllowedMentions.replied_user` attribute of ``allowed_mentions`` or by + setting ``mention_author``. .. versionadded:: 1.6 @@ -1482,6 +1575,10 @@ class Messageable: in the UI, but will not actually send a notification. .. versionadded:: 2.2 + poll: :class:`~discord.Poll` + The poll to send with this message. + + .. versionadded:: 2.4 Raises -------- @@ -1489,6 +1586,9 @@ class Messageable: Sending the message failed. ~discord.Forbidden You do not have the proper permissions to send the message. + ~discord.NotFound + You sent a message with the same nonce as one that has been explicitly + deleted shortly earlier. ValueError The ``files`` or ``embeds`` list is not of the appropriate size. TypeError @@ -1533,6 +1633,9 @@ class Messageable: else: flags = MISSING + if nonce is None: + nonce = secrets.randbits(64) + with handle_message_parameters( content=content, tts=tts, @@ -1548,6 +1651,7 @@ class Messageable: stickers=sticker_ids, view=view, flags=flags, + poll=poll, ) as params: data = await state.http.send_message(channel.id, params=params) @@ -1555,6 +1659,9 @@ class Messageable: if view and not view.is_finished(): state.store_view(view, ret.id) + if poll: + poll._update(ret) + if delete_after is not None: await ret.delete(delay=delete_after) return ret @@ -1713,12 +1820,12 @@ class Messageable: async def _around_strategy(retrieve: int, around: Optional[Snowflake], limit: Optional[int]): if not around: - return [] + return [], None, 0 around_id = around.id if around else None data = await self._state.http.logs_from(channel.id, retrieve, around=around_id) - return data, None, limit + return data, None, 0 async def _after_strategy(retrieve: int, after: Optional[Snowflake], limit: Optional[int]): after_id = after.id if after else None @@ -1831,7 +1938,7 @@ class Connectable(Protocol): async def connect( self, *, - timeout: float = 60.0, + timeout: float = 30.0, reconnect: bool = True, cls: Callable[[Client, Connectable], T] = VoiceClient, self_deaf: bool = False, @@ -1847,7 +1954,7 @@ class Connectable(Protocol): Parameters ----------- timeout: :class:`float` - The timeout in seconds to wait for the voice endpoint. + The timeout in seconds to wait the connection to complete. reconnect: :class:`bool` Whether the bot should automatically attempt a reconnect if a part of the handshake fails diff --git a/discord/activity.py b/discord/activity.py index 5e9730065..0fc0faa64 100644 --- a/discord/activity.py +++ b/discord/activity.py @@ -162,6 +162,10 @@ class Activity(BaseActivity): The user's current state. For example, "In Game". details: Optional[:class:`str`] The detail of the user's current activity. + platform: Optional[:class:`str`] + The user's current platform. + + .. versionadded:: 2.4 timestamps: :class:`dict` A dictionary of timestamps. It contains the following optional keys: @@ -197,6 +201,7 @@ class Activity(BaseActivity): 'state', 'details', 'timestamps', + 'platform', 'assets', 'party', 'flags', @@ -215,6 +220,7 @@ class Activity(BaseActivity): self.state: Optional[str] = kwargs.pop('state', None) self.details: Optional[str] = kwargs.pop('details', None) self.timestamps: ActivityTimestamps = kwargs.pop('timestamps', {}) + self.platform: Optional[str] = kwargs.pop('platform', None) self.assets: ActivityAssets = kwargs.pop('assets', {}) self.party: ActivityParty = kwargs.pop('party', {}) self.application_id: Optional[int] = _get_as_snowflake(kwargs, 'application_id') @@ -238,6 +244,7 @@ class Activity(BaseActivity): ('type', self.type), ('name', self.name), ('url', self.url), + ('platform', self.platform), ('details', self.details), ('application_id', self.application_id), ('session_id', self.session_id), @@ -266,7 +273,7 @@ class Activity(BaseActivity): def start(self) -> Optional[datetime.datetime]: """Optional[:class:`datetime.datetime`]: When the user started doing this activity in UTC, if applicable.""" try: - timestamp = self.timestamps['start'] / 1000 + timestamp = self.timestamps['start'] / 1000 # pyright: ignore[reportTypedDictNotRequiredAccess] except KeyError: return None else: @@ -276,7 +283,7 @@ class Activity(BaseActivity): def end(self) -> Optional[datetime.datetime]: """Optional[:class:`datetime.datetime`]: When the user will stop doing this activity in UTC, if applicable.""" try: - timestamp = self.timestamps['end'] / 1000 + timestamp = self.timestamps['end'] / 1000 # pyright: ignore[reportTypedDictNotRequiredAccess] except KeyError: return None else: @@ -286,7 +293,7 @@ class Activity(BaseActivity): def large_image_url(self) -> Optional[str]: """Optional[:class:`str`]: Returns a URL pointing to the large image asset of this activity, if applicable.""" try: - large_image = self.assets['large_image'] + large_image = self.assets['large_image'] # pyright: ignore[reportTypedDictNotRequiredAccess] except KeyError: return None else: @@ -296,7 +303,7 @@ class Activity(BaseActivity): def small_image_url(self) -> Optional[str]: """Optional[:class:`str`]: Returns a URL pointing to the small image asset of this activity, if applicable.""" try: - small_image = self.assets['small_image'] + small_image = self.assets['small_image'] # pyright: ignore[reportTypedDictNotRequiredAccess] except KeyError: return None else: @@ -351,13 +358,30 @@ class Game(BaseActivity): ----------- name: :class:`str` The game's name. + platform: Optional[:class:`str`] + Where the user is playing from (ie. PS5, Xbox). + + .. versionadded:: 2.4 + + assets: :class:`dict` + A dictionary representing the images and their hover text of a game. + It contains the following optional keys: + + - ``large_image``: A string representing the ID for the large image asset. + - ``large_text``: A string representing the text when hovering over the large image asset. + - ``small_image``: A string representing the ID for the small image asset. + - ``small_text``: A string representing the text when hovering over the small image asset. + + .. versionadded:: 2.4 """ - __slots__ = ('name', '_end', '_start') + __slots__ = ('name', '_end', '_start', 'platform', 'assets') def __init__(self, name: str, **extra: Any) -> None: super().__init__(**extra) self.name: str = name + self.platform: Optional[str] = extra.get('platform') + self.assets: ActivityAssets = extra.get('assets', {}) or {} try: timestamps: ActivityTimestamps = extra['timestamps'] @@ -394,7 +418,7 @@ class Game(BaseActivity): return str(self.name) def __repr__(self) -> str: - return f'' + return f'' def to_dict(self) -> Dict[str, Any]: timestamps: Dict[str, Any] = {} @@ -408,6 +432,8 @@ class Game(BaseActivity): 'type': ActivityType.playing.value, 'name': str(self.name), 'timestamps': timestamps, + 'platform': str(self.platform) if self.platform else None, + 'assets': self.assets, } def __eq__(self, other: object) -> bool: @@ -488,7 +514,7 @@ class Streaming(BaseActivity): return str(self.name) def __repr__(self) -> str: - return f'' + return f'' @property def twitch_name(self) -> Optional[str]: @@ -499,7 +525,7 @@ class Streaming(BaseActivity): """ try: - name = self.assets['large_image'] + name = self.assets['large_image'] # pyright: ignore[reportTypedDictNotRequiredAccess] except KeyError: return None else: @@ -732,10 +758,12 @@ class CustomActivity(BaseActivity): __slots__ = ('name', 'emoji', 'state') - def __init__(self, name: Optional[str], *, emoji: Optional[PartialEmoji] = None, **extra: Any) -> None: + def __init__( + self, name: Optional[str], *, emoji: Optional[Union[PartialEmoji, Dict[str, Any], str]] = None, **extra: Any + ) -> None: super().__init__(**extra) self.name: Optional[str] = name - self.state: Optional[str] = extra.pop('state', None) + self.state: Optional[str] = extra.pop('state', name) if self.name == 'Custom Status': self.name = self.state diff --git a/discord/app_commands/__init__.py b/discord/app_commands/__init__.py index 971461713..a338cab75 100644 --- a/discord/app_commands/__init__.py +++ b/discord/app_commands/__init__.py @@ -16,5 +16,6 @@ from .tree import * from .namespace import * from .transformers import * from .translator import * +from .installs import * from . import checks as checks from .checks import Cooldown as Cooldown diff --git a/discord/app_commands/checks.py b/discord/app_commands/checks.py index f6c09481d..5c17b951c 100644 --- a/discord/app_commands/checks.py +++ b/discord/app_commands/checks.py @@ -186,7 +186,7 @@ class Cooldown: :class:`Cooldown` A new instance of this cooldown. """ - return Cooldown(self.rate, self.per) + return self.__class__(self.rate, self.per) def __repr__(self) -> str: return f'' diff --git a/discord/app_commands/commands.py b/discord/app_commands/commands.py index fd6388443..d5b8d93b2 100644 --- a/discord/app_commands/commands.py +++ b/discord/app_commands/commands.py @@ -46,8 +46,10 @@ from typing import ( ) import re +from copy import copy as shallow_copy from ..enums import AppCommandOptionType, AppCommandType, ChannelType, Locale +from .installs import AppCommandContext, AppInstallationType from .models import Choice from .transformers import annotation_to_parameter, CommandParameter, NoneType from .errors import AppCommandError, CheckFailure, CommandInvokeError, CommandSignatureMismatch, CommandAlreadyRegistered @@ -64,6 +66,8 @@ if TYPE_CHECKING: from ..abc import Snowflake from .namespace import Namespace from .models import ChoiceT + from .tree import CommandTree + from .._types import ClientT # Generally, these two libraries are supposed to be separate from each other. # However, for type hinting purposes it's unfortunately necessary for one to @@ -86,6 +90,12 @@ __all__ = ( 'autocomplete', 'guilds', 'guild_only', + 'dm_only', + 'private_channel_only', + 'allowed_contexts', + 'guild_install', + 'user_install', + 'allowed_installs', 'default_permissions', ) @@ -617,6 +627,16 @@ class Command(Generic[GroupT, P, T]): Whether the command should only be usable in guild contexts. Due to a Discord limitation, this does not work on subcommands. + allowed_contexts: Optional[:class:`~discord.app_commands.AppCommandContext`] + The contexts that the command is allowed to be used in. + Overrides ``guild_only`` if this is set. + + .. versionadded:: 2.4 + allowed_installs: Optional[:class:`~discord.app_commands.AppInstallationType`] + The installation contexts that the command is allowed to be installed + on. + + .. versionadded:: 2.4 nsfw: :class:`bool` Whether the command is NSFW and should only work in NSFW channels. @@ -637,6 +657,8 @@ class Command(Generic[GroupT, P, T]): nsfw: bool = False, parent: Optional[Group] = None, guild_ids: Optional[List[int]] = None, + allowed_contexts: Optional[AppCommandContext] = None, + allowed_installs: Optional[AppInstallationType] = None, auto_locale_strings: bool = True, extras: Dict[Any, Any] = MISSING, ): @@ -671,6 +693,13 @@ class Command(Generic[GroupT, P, T]): callback, '__discord_app_commands_default_permissions__', None ) self.guild_only: bool = getattr(callback, '__discord_app_commands_guild_only__', False) + self.allowed_contexts: Optional[AppCommandContext] = allowed_contexts or getattr( + callback, '__discord_app_commands_contexts__', None + ) + self.allowed_installs: Optional[AppInstallationType] = allowed_installs or getattr( + callback, '__discord_app_commands_installation_types__', None + ) + self.nsfw: bool = nsfw self.extras: Dict[Any, Any] = extras or {} @@ -707,33 +736,18 @@ class Command(Generic[GroupT, P, T]): ) -> Command: bindings = {} if bindings is MISSING else bindings - cls = self.__class__ - copy = cls.__new__(cls) - copy.name = self.name - copy._locale_name = self._locale_name - copy._guild_ids = self._guild_ids - copy.checks = self.checks - copy.description = self.description - copy._locale_description = self._locale_description - copy.default_permissions = self.default_permissions - copy.guild_only = self.guild_only - copy.nsfw = self.nsfw - copy._attr = self._attr - copy._callback = self._callback - copy.on_error = self.on_error + copy = shallow_copy(self) copy._params = self._params.copy() - copy.module = self.module copy.parent = parent copy.binding = bindings.get(self.binding) if self.binding is not None else binding - copy.extras = self.extras if copy._attr and set_on_binding: setattr(copy.binding, copy._attr, copy) return copy - async def get_translated_payload(self, translator: Translator) -> Dict[str, Any]: - base = self.to_dict() + async def get_translated_payload(self, tree: CommandTree[ClientT], translator: Translator) -> Dict[str, Any]: + base = self.to_dict(tree) name_localizations: Dict[str, str] = {} description_localizations: Dict[str, str] = {} @@ -759,7 +773,7 @@ class Command(Generic[GroupT, P, T]): ] return base - def to_dict(self) -> Dict[str, Any]: + def to_dict(self, tree: CommandTree[ClientT]) -> Dict[str, Any]: # If we have a parent then our type is a subcommand # Otherwise, the type falls back to the specific command type (e.g. slash command or context menu) option_type = AppCommandType.chat_input.value if self.parent is None else AppCommandOptionType.subcommand.value @@ -774,6 +788,8 @@ class Command(Generic[GroupT, P, T]): base['nsfw'] = self.nsfw base['dm_permission'] = not self.guild_only base['default_member_permissions'] = None if self.default_permissions is None else self.default_permissions.value + base['contexts'] = tree.allowed_contexts._merge_to_array(self.allowed_contexts) + base['integration_types'] = tree.allowed_installs._merge_to_array(self.allowed_installs) return base @@ -887,7 +903,7 @@ class Command(Generic[GroupT, P, T]): predicates = getattr(param.autocomplete, '__discord_app_commands_checks__', []) if predicates: try: - passed = await async_all(f(interaction) for f in predicates) + passed = await async_all(f(interaction) for f in predicates) # type: ignore except Exception: passed = False @@ -990,7 +1006,7 @@ class Command(Generic[GroupT, P, T]): if self.binding is not None: check: Optional[Check] = getattr(self.binding, 'interaction_check', None) if check: - ret = await maybe_coroutine(check, interaction) # type: ignore # Probable pyright bug + ret = await maybe_coroutine(check, interaction) if not ret: return False @@ -998,7 +1014,7 @@ class Command(Generic[GroupT, P, T]): if not predicates: return True - return await async_all(f(interaction) for f in predicates) + return await async_all(f(interaction) for f in predicates) # type: ignore def error(self, coro: Error[GroupT]) -> Error[GroupT]: """A decorator that registers a coroutine as a local error handler. @@ -1082,7 +1098,7 @@ class Command(Generic[GroupT, P, T]): def decorator(coro: AutocompleteCallback[GroupT, ChoiceT]) -> AutocompleteCallback[GroupT, ChoiceT]: if not inspect.iscoroutinefunction(coro): - raise TypeError('The error handler must be a coroutine.') + raise TypeError('The autocomplete callback must be a coroutine function.') try: param = self._params[name] @@ -1181,6 +1197,16 @@ class ContextMenu: guild_only: :class:`bool` Whether the command should only be usable in guild contexts. Defaults to ``False``. + allowed_contexts: Optional[:class:`~discord.app_commands.AppCommandContext`] + The contexts that this context menu is allowed to be used in. + Overrides ``guild_only`` if set. + + .. versionadded:: 2.4 + allowed_installs: Optional[:class:`~discord.app_commands.AppInstallationType`] + The installation contexts that the command is allowed to be installed + on. + + .. versionadded:: 2.4 nsfw: :class:`bool` Whether the command is NSFW and should only work in NSFW channels. Defaults to ``False``. @@ -1203,6 +1229,8 @@ class ContextMenu: type: AppCommandType = MISSING, nsfw: bool = False, guild_ids: Optional[List[int]] = None, + allowed_contexts: Optional[AppCommandContext] = None, + allowed_installs: Optional[AppInstallationType] = None, auto_locale_strings: bool = True, extras: Dict[Any, Any] = MISSING, ): @@ -1228,6 +1256,12 @@ class ContextMenu: ) self.nsfw: bool = nsfw self.guild_only: bool = getattr(callback, '__discord_app_commands_guild_only__', False) + self.allowed_contexts: Optional[AppCommandContext] = allowed_contexts or getattr( + callback, '__discord_app_commands_contexts__', None + ) + self.allowed_installs: Optional[AppInstallationType] = allowed_installs or getattr( + callback, '__discord_app_commands_installation_types__', None + ) self.checks: List[Check] = getattr(callback, '__discord_app_commands_checks__', []) self.extras: Dict[Any, Any] = extras or {} @@ -1245,8 +1279,8 @@ class ContextMenu: """:class:`str`: Returns the fully qualified command name.""" return self.name - async def get_translated_payload(self, translator: Translator) -> Dict[str, Any]: - base = self.to_dict() + async def get_translated_payload(self, tree: CommandTree[ClientT], translator: Translator) -> Dict[str, Any]: + base = self.to_dict(tree) context = TranslationContext(location=TranslationContextLocation.command_name, data=self) if self._locale_name: name_localizations: Dict[str, str] = {} @@ -1258,11 +1292,13 @@ class ContextMenu: base['name_localizations'] = name_localizations return base - def to_dict(self) -> Dict[str, Any]: + def to_dict(self, tree: CommandTree[ClientT]) -> Dict[str, Any]: return { 'name': self.name, 'type': self.type.value, 'dm_permission': not self.guild_only, + 'contexts': tree.allowed_contexts._merge_to_array(self.allowed_contexts), + 'integration_types': tree.allowed_installs._merge_to_array(self.allowed_installs), 'default_member_permissions': None if self.default_permissions is None else self.default_permissions.value, 'nsfw': self.nsfw, } @@ -1272,7 +1308,7 @@ class ContextMenu: if not predicates: return True - return await async_all(f(interaction) for f in predicates) + return await async_all(f(interaction) for f in predicates) # type: ignore def _has_any_error_handlers(self) -> bool: return self.on_error is not None @@ -1419,6 +1455,16 @@ class Group: Whether the group should only be usable in guild contexts. Due to a Discord limitation, this does not work on subcommands. + allowed_contexts: Optional[:class:`~discord.app_commands.AppCommandContext`] + The contexts that this group is allowed to be used in. Overrides + guild_only if set. + + .. versionadded:: 2.4 + allowed_installs: Optional[:class:`~discord.app_commands.AppInstallationType`] + The installation contexts that the command is allowed to be installed + on. + + .. versionadded:: 2.4 nsfw: :class:`bool` Whether the command is NSFW and should only work in NSFW channels. @@ -1438,6 +1484,8 @@ class Group: __discord_app_commands_group_locale_description__: Optional[locale_str] = None __discord_app_commands_group_nsfw__: bool = False __discord_app_commands_guild_only__: bool = MISSING + __discord_app_commands_contexts__: Optional[AppCommandContext] = MISSING + __discord_app_commands_installation_types__: Optional[AppInstallationType] = MISSING __discord_app_commands_default_permissions__: Optional[Permissions] = MISSING __discord_app_commands_has_module__: bool = False __discord_app_commands_error_handler__: Optional[ @@ -1506,6 +1554,8 @@ class Group: parent: Optional[Group] = None, guild_ids: Optional[List[int]] = None, guild_only: bool = MISSING, + allowed_contexts: Optional[AppCommandContext] = MISSING, + allowed_installs: Optional[AppInstallationType] = MISSING, nsfw: bool = MISSING, auto_locale_strings: bool = True, default_permissions: Optional[Permissions] = MISSING, @@ -1554,6 +1604,22 @@ class Group: self.guild_only: bool = guild_only + if allowed_contexts is MISSING: + if cls.__discord_app_commands_contexts__ is MISSING: + allowed_contexts = None + else: + allowed_contexts = cls.__discord_app_commands_contexts__ + + self.allowed_contexts: Optional[AppCommandContext] = allowed_contexts + + if allowed_installs is MISSING: + if cls.__discord_app_commands_installation_types__ is MISSING: + allowed_installs = None + else: + allowed_installs = cls.__discord_app_commands_installation_types__ + + self.allowed_installs: Optional[AppInstallationType] = allowed_installs + if nsfw is MISSING: nsfw = cls.__discord_app_commands_group_nsfw__ @@ -1562,6 +1628,9 @@ class Group: if not self.description: raise TypeError('groups must have a description') + if not self.name: + raise TypeError('groups must have a name') + self.parent: Optional[Group] = parent self.module: Optional[str] if cls.__discord_app_commands_has_module__: @@ -1622,22 +1691,9 @@ class Group: ) -> Group: bindings = {} if bindings is MISSING else bindings - cls = self.__class__ - copy = cls.__new__(cls) - copy.name = self.name - copy._locale_name = self._locale_name - copy._guild_ids = self._guild_ids - copy.description = self.description - copy._locale_description = self._locale_description + copy = shallow_copy(self) copy.parent = parent - copy.module = self.module - copy.default_permissions = self.default_permissions - copy.guild_only = self.guild_only - copy.nsfw = self.nsfw - copy._attr = self._attr - copy._owner_cls = self._owner_cls copy._children = {} - copy.extras = self.extras bindings[self] = copy @@ -1657,8 +1713,8 @@ class Group: return copy - async def get_translated_payload(self, translator: Translator) -> Dict[str, Any]: - base = self.to_dict() + async def get_translated_payload(self, tree: CommandTree[ClientT], translator: Translator) -> Dict[str, Any]: + base = self.to_dict(tree) name_localizations: Dict[str, str] = {} description_localizations: Dict[str, str] = {} @@ -1678,10 +1734,10 @@ class Group: base['name_localizations'] = name_localizations base['description_localizations'] = description_localizations - base['options'] = [await child.get_translated_payload(translator) for child in self._children.values()] + base['options'] = [await child.get_translated_payload(tree, translator) for child in self._children.values()] return base - def to_dict(self) -> Dict[str, Any]: + def to_dict(self, tree: CommandTree[ClientT]) -> Dict[str, Any]: # If this has a parent command then it's part of a subcommand group # Otherwise, it's just a regular command option_type = 1 if self.parent is None else AppCommandOptionType.subcommand_group.value @@ -1689,13 +1745,15 @@ class Group: 'name': self.name, 'description': self.description, 'type': option_type, - 'options': [child.to_dict() for child in self._children.values()], + 'options': [child.to_dict(tree) for child in self._children.values()], } if self.parent is None: base['nsfw'] = self.nsfw base['dm_permission'] = not self.guild_only base['default_member_permissions'] = None if self.default_permissions is None else self.default_permissions.value + base['contexts'] = tree.allowed_contexts._merge_to_array(self.allowed_contexts) + base['integration_types'] = tree.allowed_installs._merge_to_array(self.allowed_installs) return base @@ -1784,7 +1842,7 @@ class Group: if len(params) != 2: raise TypeError('The error handler must have 2 parameters.') - self.on_error = coro + self.on_error = coro # type: ignore return coro async def interaction_check(self, interaction: Interaction, /) -> bool: @@ -2314,6 +2372,12 @@ def guilds(*guild_ids: Union[Snowflake, int]) -> Callable[[T], T]: with the :meth:`CommandTree.command` or :meth:`CommandTree.context_menu` decorator then this must go below that decorator. + .. note :: + + Due to a Discord limitation, this decorator cannot be used in conjunction with + contexts (e.g. :func:`.app_commands.allowed_contexts`) or installation types + (e.g. :func:`.app_commands.allowed_installs`). + Example: .. code-block:: python3 @@ -2445,8 +2509,70 @@ def guild_only(func: Optional[T] = None) -> Union[T, Callable[[T], T]]: def inner(f: T) -> T: if isinstance(f, (Command, Group, ContextMenu)): f.guild_only = True + allowed_contexts = f.allowed_contexts or AppCommandContext() + f.allowed_contexts = allowed_contexts else: f.__discord_app_commands_guild_only__ = True # type: ignore # Runtime attribute assignment + + allowed_contexts = getattr(f, '__discord_app_commands_contexts__', None) or AppCommandContext() + f.__discord_app_commands_contexts__ = allowed_contexts # type: ignore # Runtime attribute assignment + + allowed_contexts.guild = True + + return f + + # Check if called with parentheses or not + if func is None: + # Called with parentheses + return inner + else: + return inner(func) + + +@overload +def private_channel_only(func: None = ...) -> Callable[[T], T]: + ... + + +@overload +def private_channel_only(func: T) -> T: + ... + + +def private_channel_only(func: Optional[T] = None) -> Union[T, Callable[[T], T]]: + """A decorator that indicates this command can only be used in the context of DMs and group DMs. + + This is **not** implemented as a :func:`check`, and is instead verified by Discord server side. + Therefore, there is no error handler called when a command is used within a guild. + + This decorator can be called with or without parentheses. + + Due to a Discord limitation, this decorator does nothing in subcommands and is ignored. + + .. versionadded:: 2.4 + + Examples + --------- + + .. code-block:: python3 + + @app_commands.command() + @app_commands.private_channel_only() + async def my_private_channel_only_command(interaction: discord.Interaction) -> None: + await interaction.response.send_message('I am only available in DMs and GDMs!') + """ + + def inner(f: T) -> T: + if isinstance(f, (Command, Group, ContextMenu)): + f.guild_only = False + allowed_contexts = f.allowed_contexts or AppCommandContext() + f.allowed_contexts = allowed_contexts + else: + allowed_contexts = getattr(f, '__discord_app_commands_contexts__', None) or AppCommandContext() + f.__discord_app_commands_contexts__ = allowed_contexts # type: ignore # Runtime attribute assignment + + allowed_contexts.private_channel = True + return f # Check if called with parentheses or not @@ -2457,7 +2583,245 @@ def guild_only(func: Optional[T] = None) -> Union[T, Callable[[T], T]]: return inner(func) -def default_permissions(**perms: bool) -> Callable[[T], T]: +@overload +def dm_only(func: None = ...) -> Callable[[T], T]: + ... + + +@overload +def dm_only(func: T) -> T: + ... + + +def dm_only(func: Optional[T] = None) -> Union[T, Callable[[T], T]]: + """A decorator that indicates this command can only be used in the context of bot DMs. + + This is **not** implemented as a :func:`check`, and is instead verified by Discord server side. + Therefore, there is no error handler called when a command is used within a guild or group DM. + + This decorator can be called with or without parentheses. + + Due to a Discord limitation, this decorator does nothing in subcommands and is ignored. + + Examples + --------- + + .. code-block:: python3 + + @app_commands.command() + @app_commands.dm_only() + async def my_dm_only_command(interaction: discord.Interaction) -> None: + await interaction.response.send_message('I am only available in DMs!') + """ + + def inner(f: T) -> T: + if isinstance(f, (Command, Group, ContextMenu)): + f.guild_only = False + allowed_contexts = f.allowed_contexts or AppCommandContext() + f.allowed_contexts = allowed_contexts + else: + allowed_contexts = getattr(f, '__discord_app_commands_contexts__', None) or AppCommandContext() + f.__discord_app_commands_contexts__ = allowed_contexts # type: ignore # Runtime attribute assignment + + allowed_contexts.dm_channel = True + return f + + # Check if called with parentheses or not + if func is None: + # Called with parentheses + return inner + else: + return inner(func) + + +def allowed_contexts(guilds: bool = MISSING, dms: bool = MISSING, private_channels: bool = MISSING) -> Callable[[T], T]: + """A decorator that indicates this command can only be used in certain contexts. + Valid contexts are guilds, DMs and private channels. + + This is **not** implemented as a :func:`check`, and is instead verified by Discord server side. + + Due to a Discord limitation, this decorator does nothing in subcommands and is ignored. + + .. versionadded:: 2.4 + + Examples + --------- + + .. code-block:: python3 + + @app_commands.command() + @app_commands.allowed_contexts(guilds=True, dms=False, private_channels=True) + async def my_command(interaction: discord.Interaction) -> None: + await interaction.response.send_message('I am only available in guilds and private channels!') + """ + + def inner(f: T) -> T: + if isinstance(f, (Command, Group, ContextMenu)): + f.guild_only = False + allowed_contexts = f.allowed_contexts or AppCommandContext() + f.allowed_contexts = allowed_contexts + else: + allowed_contexts = getattr(f, '__discord_app_commands_contexts__', None) or AppCommandContext() + f.__discord_app_commands_contexts__ = allowed_contexts # type: ignore # Runtime attribute assignment + + if guilds is not MISSING: + allowed_contexts.guild = guilds + + if dms is not MISSING: + allowed_contexts.dm_channel = dms + + if private_channels is not MISSING: + allowed_contexts.private_channel = private_channels + + return f + + return inner + + +@overload +def guild_install(func: None = ...) -> Callable[[T], T]: + ... + + +@overload +def guild_install(func: T) -> T: + ... + + +def guild_install(func: Optional[T] = None) -> Union[T, Callable[[T], T]]: + """A decorator that indicates this command should be installed in guilds. + + This is **not** implemented as a :func:`check`, and is instead verified by Discord server side. + + Due to a Discord limitation, this decorator does nothing in subcommands and is ignored. + + .. versionadded:: 2.4 + + Examples + --------- + + .. code-block:: python3 + + @app_commands.command() + @app_commands.guild_install() + async def my_guild_install_command(interaction: discord.Interaction) -> None: + await interaction.response.send_message('I am installed in guilds by default!') + """ + + def inner(f: T) -> T: + if isinstance(f, (Command, Group, ContextMenu)): + allowed_installs = f.allowed_installs or AppInstallationType() + f.allowed_installs = allowed_installs + else: + allowed_installs = getattr(f, '__discord_app_commands_installation_types__', None) or AppInstallationType() + f.__discord_app_commands_installation_types__ = allowed_installs # type: ignore # Runtime attribute assignment + + allowed_installs.guild = True + + return f + + # Check if called with parentheses or not + if func is None: + # Called with parentheses + return inner + else: + return inner(func) + + +@overload +def user_install(func: None = ...) -> Callable[[T], T]: + ... + + +@overload +def user_install(func: T) -> T: + ... + + +def user_install(func: Optional[T] = None) -> Union[T, Callable[[T], T]]: + """A decorator that indicates this command should be installed for users. + + This is **not** implemented as a :func:`check`, and is instead verified by Discord server side. + + Due to a Discord limitation, this decorator does nothing in subcommands and is ignored. + + .. versionadded:: 2.4 + + Examples + --------- + + .. code-block:: python3 + + @app_commands.command() + @app_commands.user_install() + async def my_user_install_command(interaction: discord.Interaction) -> None: + await interaction.response.send_message('I am installed in users by default!') + """ + + def inner(f: T) -> T: + if isinstance(f, (Command, Group, ContextMenu)): + allowed_installs = f.allowed_installs or AppInstallationType() + f.allowed_installs = allowed_installs + else: + allowed_installs = getattr(f, '__discord_app_commands_installation_types__', None) or AppInstallationType() + f.__discord_app_commands_installation_types__ = allowed_installs # type: ignore # Runtime attribute assignment + + allowed_installs.user = True + + return f + + # Check if called with parentheses or not + if func is None: + # Called with parentheses + return inner + else: + return inner(func) + + +def allowed_installs( + guilds: bool = MISSING, + users: bool = MISSING, +) -> Callable[[T], T]: + """A decorator that indicates this command should be installed in certain contexts. + Valid contexts are guilds and users. + + This is **not** implemented as a :func:`check`, and is instead verified by Discord server side. + + Due to a Discord limitation, this decorator does nothing in subcommands and is ignored. + + .. versionadded:: 2.4 + + Examples + --------- + + .. code-block:: python3 + + @app_commands.command() + @app_commands.allowed_installs(guilds=False, users=True) + async def my_command(interaction: discord.Interaction) -> None: + await interaction.response.send_message('I am installed in users by default!') + """ + + def inner(f: T) -> T: + if isinstance(f, (Command, Group, ContextMenu)): + allowed_installs = f.allowed_installs or AppInstallationType() + f.allowed_installs = allowed_installs + else: + allowed_installs = getattr(f, '__discord_app_commands_installation_types__', None) or AppInstallationType() + f.__discord_app_commands_installation_types__ = allowed_installs # type: ignore # Runtime attribute assignment + + if guilds is not MISSING: + allowed_installs.guild = guilds + + if users is not MISSING: + allowed_installs.user = users + + return f + + return inner + + +def default_permissions(perms_obj: Optional[Permissions] = None, /, **perms: bool) -> Callable[[T], T]: r"""A decorator that sets the default permissions needed to execute this command. When this decorator is used, by default users must have these permissions to execute the command. @@ -2481,8 +2845,12 @@ def default_permissions(**perms: bool) -> Callable[[T], T]: ----------- \*\*perms: :class:`bool` Keyword arguments denoting the permissions to set as the default. + perms_obj: :class:`~discord.Permissions` + A permissions object as positional argument. This can be used in combination with ``**perms``. - Example + .. versionadded:: 2.5 + + Examples --------- .. code-block:: python3 @@ -2491,9 +2859,21 @@ def default_permissions(**perms: bool) -> Callable[[T], T]: @app_commands.default_permissions(manage_messages=True) async def test(interaction: discord.Interaction): await interaction.response.send_message('You may or may not have manage messages.') + + .. code-block:: python3 + + ADMIN_PERMS = discord.Permissions(administrator=True) + + @app_commands.command() + @app_commands.default_permissions(ADMIN_PERMS, manage_messages=True) + async def test(interaction: discord.Interaction): + await interaction.response.send_message('You may or may not have manage messages.') """ - permissions = Permissions(**perms) + if perms_obj is not None: + permissions = perms_obj | Permissions(**perms) + else: + permissions = Permissions(**perms) def decorator(func: T) -> T: if isinstance(func, (Command, Group, ContextMenu)): diff --git a/discord/app_commands/errors.py b/discord/app_commands/errors.py index 3cc12c72d..2efb4e5b0 100644 --- a/discord/app_commands/errors.py +++ b/discord/app_commands/errors.py @@ -27,7 +27,8 @@ from __future__ import annotations from typing import Any, TYPE_CHECKING, List, Optional, Sequence, Union from ..enums import AppCommandOptionType, AppCommandType, Locale -from ..errors import DiscordException, HTTPException, _flatten_error_dict +from ..errors import DiscordException, HTTPException, _flatten_error_dict, MissingApplicationID as MissingApplicationID +from ..utils import _human_join __all__ = ( 'AppCommandError', @@ -58,11 +59,6 @@ if TYPE_CHECKING: CommandTypes = Union[Command[Any, ..., Any], Group, ContextMenu] -APP_ID_NOT_FOUND = ( - 'Client does not have an application_id set. Either the function was called before on_ready ' - 'was called or application_id was not passed to the Client constructor.' -) - class AppCommandError(DiscordException): """The base exception type for all application command related errors. @@ -242,13 +238,7 @@ class MissingAnyRole(CheckFailure): def __init__(self, missing_roles: SnowflakeList) -> None: self.missing_roles: SnowflakeList = missing_roles - missing = [f"'{role}'" for role in missing_roles] - - if len(missing) > 2: - fmt = '{}, or {}'.format(', '.join(missing[:-1]), missing[-1]) - else: - fmt = ' or '.join(missing) - + fmt = _human_join([f"'{role}'" for role in missing_roles]) message = f'You are missing at least one of the required roles: {fmt}' super().__init__(message) @@ -271,11 +261,7 @@ class MissingPermissions(CheckFailure): self.missing_permissions: List[str] = missing_permissions missing = [perm.replace('_', ' ').replace('guild', 'server').title() for perm in missing_permissions] - - if len(missing) > 2: - fmt = '{}, and {}'.format(", ".join(missing[:-1]), missing[-1]) - else: - fmt = ' and '.join(missing) + fmt = _human_join(missing, final='and') message = f'You are missing {fmt} permission(s) to run this command.' super().__init__(message, *args) @@ -298,11 +284,7 @@ class BotMissingPermissions(CheckFailure): self.missing_permissions: List[str] = missing_permissions missing = [perm.replace('_', ' ').replace('guild', 'server').title() for perm in missing_permissions] - - if len(missing) > 2: - fmt = '{}, and {}'.format(", ".join(missing[:-1]), missing[-1]) - else: - fmt = ' and '.join(missing) + fmt = _human_join(missing, final='and') message = f'Bot requires {fmt} permission(s) to run this command.' super().__init__(message, *args) @@ -435,19 +417,6 @@ class CommandSignatureMismatch(AppCommandError): super().__init__(msg) -class MissingApplicationID(AppCommandError): - """An exception raised when the client does not have an application ID set. - An application ID is required for syncing application commands. - - This inherits from :exc:`~discord.app_commands.AppCommandError`. - - .. versionadded:: 2.0 - """ - - def __init__(self, message: Optional[str] = None): - super().__init__(message or APP_ID_NOT_FOUND) - - def _get_command_error( index: str, inner: Any, @@ -498,6 +467,10 @@ def _get_command_error( if key == 'options': for index, d in remaining.items(): _get_command_error(index, d, children, messages, indent=indent + 2) + elif key == '_errors': + errors = [x.get('message', '') for x in remaining] + + messages.extend(f'{indentation} {message}' for message in errors) else: if isinstance(remaining, dict): try: @@ -506,10 +479,9 @@ def _get_command_error( errors = _flatten_error_dict(remaining, key=key) else: errors = {key: ' '.join(x.get('message', '') for x in inner_errors)} - else: - errors = _flatten_error_dict(remaining, key=key) - messages.extend(f'{indentation} {k}: {v}' for k, v in errors.items()) + if isinstance(errors, dict): + messages.extend(f'{indentation} {k}: {v}' for k, v in errors.items()) class CommandSyncFailure(AppCommandError, HTTPException): @@ -530,8 +502,18 @@ class CommandSyncFailure(AppCommandError, HTTPException): messages = [f'Failed to upload commands to Discord (HTTP status {self.status}, error code {self.code})'] if self._errors: - for index, inner in self._errors.items(): - _get_command_error(index, inner, commands, messages) + # Handle case where the errors dict has no actual chain such as APPLICATION_COMMAND_TOO_LARGE + if len(self._errors) == 1 and '_errors' in self._errors: + errors = self._errors['_errors'] + if len(errors) == 1: + extra = errors[0].get('message') + if extra: + messages[0] += f': {extra}' + else: + messages.extend(f'Error {e.get("code", "")}: {e.get("message", "")}' for e in errors) + else: + for index, inner in self._errors.items(): + _get_command_error(index, inner, commands, messages) # Equivalent to super().__init__(...) but skips other constructors self.args = ('\n'.join(messages),) diff --git a/discord/app_commands/installs.py b/discord/app_commands/installs.py new file mode 100644 index 000000000..e00d13724 --- /dev/null +++ b/discord/app_commands/installs.py @@ -0,0 +1,213 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-present Rapptz + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +""" + +from __future__ import annotations +from typing import TYPE_CHECKING, ClassVar, List, Optional, Sequence + +__all__ = ( + 'AppInstallationType', + 'AppCommandContext', +) + +if TYPE_CHECKING: + from typing_extensions import Self + from ..types.interactions import InteractionContextType, InteractionInstallationType + + +class AppInstallationType: + r"""Represents the installation location of an application command. + + .. versionadded:: 2.4 + + Parameters + ----------- + guild: Optional[:class:`bool`] + Whether the integration is a guild install. + user: Optional[:class:`bool`] + Whether the integration is a user install. + """ + + __slots__ = ('_guild', '_user') + + GUILD: ClassVar[int] = 0 + USER: ClassVar[int] = 1 + + def __init__(self, *, guild: Optional[bool] = None, user: Optional[bool] = None): + self._guild: Optional[bool] = guild + self._user: Optional[bool] = user + + def __repr__(self): + return f'' + + @property + def guild(self) -> bool: + """:class:`bool`: Whether the integration is a guild install.""" + return bool(self._guild) + + @guild.setter + def guild(self, value: bool) -> None: + self._guild = bool(value) + + @property + def user(self) -> bool: + """:class:`bool`: Whether the integration is a user install.""" + return bool(self._user) + + @user.setter + def user(self, value: bool) -> None: + self._user = bool(value) + + def merge(self, other: AppInstallationType) -> AppInstallationType: + # Merging is similar to AllowedMentions where `self` is the base + # and the `other` is the override preference + guild = self._guild if other._guild is None else other._guild + user = self._user if other._user is None else other._user + return AppInstallationType(guild=guild, user=user) + + def _is_unset(self) -> bool: + return all(x is None for x in (self._guild, self._user)) + + def _merge_to_array(self, other: Optional[AppInstallationType]) -> Optional[List[InteractionInstallationType]]: + result = self.merge(other) if other is not None else self + if result._is_unset(): + return None + return result.to_array() + + @classmethod + def _from_value(cls, value: Sequence[InteractionInstallationType]) -> Self: + self = cls() + for x in value: + if x == cls.GUILD: + self._guild = True + elif x == cls.USER: + self._user = True + return self + + def to_array(self) -> List[InteractionInstallationType]: + values = [] + if self._guild: + values.append(self.GUILD) + if self._user: + values.append(self.USER) + return values + + +class AppCommandContext: + r"""Wraps up the Discord :class:`~discord.app_commands.Command` execution context. + + .. versionadded:: 2.4 + + Parameters + ----------- + guild: Optional[:class:`bool`] + Whether the context allows usage in a guild. + dm_channel: Optional[:class:`bool`] + Whether the context allows usage in a DM channel. + private_channel: Optional[:class:`bool`] + Whether the context allows usage in a DM or a GDM channel. + """ + + GUILD: ClassVar[int] = 0 + DM_CHANNEL: ClassVar[int] = 1 + PRIVATE_CHANNEL: ClassVar[int] = 2 + + __slots__ = ('_guild', '_dm_channel', '_private_channel') + + def __init__( + self, + *, + guild: Optional[bool] = None, + dm_channel: Optional[bool] = None, + private_channel: Optional[bool] = None, + ): + self._guild: Optional[bool] = guild + self._dm_channel: Optional[bool] = dm_channel + self._private_channel: Optional[bool] = private_channel + + def __repr__(self) -> str: + return f'' + + @property + def guild(self) -> bool: + """:class:`bool`: Whether the context allows usage in a guild.""" + return bool(self._guild) + + @guild.setter + def guild(self, value: bool) -> None: + self._guild = bool(value) + + @property + def dm_channel(self) -> bool: + """:class:`bool`: Whether the context allows usage in a DM channel.""" + return bool(self._dm_channel) + + @dm_channel.setter + def dm_channel(self, value: bool) -> None: + self._dm_channel = bool(value) + + @property + def private_channel(self) -> bool: + """:class:`bool`: Whether the context allows usage in a DM or a GDM channel.""" + return bool(self._private_channel) + + @private_channel.setter + def private_channel(self, value: bool) -> None: + self._private_channel = bool(value) + + def merge(self, other: AppCommandContext) -> AppCommandContext: + guild = self._guild if other._guild is None else other._guild + dm_channel = self._dm_channel if other._dm_channel is None else other._dm_channel + private_channel = self._private_channel if other._private_channel is None else other._private_channel + return AppCommandContext(guild=guild, dm_channel=dm_channel, private_channel=private_channel) + + def _is_unset(self) -> bool: + return all(x is None for x in (self._guild, self._dm_channel, self._private_channel)) + + def _merge_to_array(self, other: Optional[AppCommandContext]) -> Optional[List[InteractionContextType]]: + result = self.merge(other) if other is not None else self + if result._is_unset(): + return None + return result.to_array() + + @classmethod + def _from_value(cls, value: Sequence[InteractionContextType]) -> Self: + self = cls() + for x in value: + if x == cls.GUILD: + self._guild = True + elif x == cls.DM_CHANNEL: + self._dm_channel = True + elif x == cls.PRIVATE_CHANNEL: + self._private_channel = True + return self + + def to_array(self) -> List[InteractionContextType]: + values = [] + if self._guild: + values.append(self.GUILD) + if self._dm_channel: + values.append(self.DM_CHANNEL) + if self._private_channel: + values.append(self.PRIVATE_CHANNEL) + return values diff --git a/discord/app_commands/models.py b/discord/app_commands/models.py index ef2e1849d..5851e7d8c 100644 --- a/discord/app_commands/models.py +++ b/discord/app_commands/models.py @@ -26,9 +26,17 @@ from __future__ import annotations from datetime import datetime from .errors import MissingApplicationID +from ..flags import AppCommandContext, AppInstallationType, ChannelFlags from .translator import TranslationContextLocation, TranslationContext, locale_str, Translator from ..permissions import Permissions -from ..enums import AppCommandOptionType, AppCommandType, AppCommandPermissionType, ChannelType, Locale, try_enum +from ..enums import ( + AppCommandOptionType, + AppCommandType, + AppCommandPermissionType, + ChannelType, + Locale, + try_enum, +) from ..mixins import Hashable from ..utils import _get_as_snowflake, parse_time, snowflake_time, MISSING from ..object import Object @@ -160,6 +168,14 @@ class AppCommand(Hashable): The default member permissions that can run this command. dm_permission: :class:`bool` A boolean that indicates whether this command can be run in direct messages. + allowed_contexts: Optional[:class:`~discord.app_commands.AppCommandContext`] + The contexts that this command is allowed to be used in. Overrides the ``dm_permission`` attribute. + + .. versionadded:: 2.4 + allowed_installs: Optional[:class:`~discord.app_commands.AppInstallationType`] + The installation contexts that this command is allowed to be installed in. + + .. versionadded:: 2.4 guild_id: Optional[:class:`int`] The ID of the guild this command is registered in. A value of ``None`` denotes that it is a global command. @@ -179,6 +195,8 @@ class AppCommand(Hashable): 'options', 'default_member_permissions', 'dm_permission', + 'allowed_contexts', + 'allowed_installs', 'nsfw', '_state', ) @@ -210,6 +228,19 @@ class AppCommand(Hashable): dm_permission = True self.dm_permission: bool = dm_permission + + allowed_contexts = data.get('contexts') + if allowed_contexts is None: + self.allowed_contexts: Optional[AppCommandContext] = None + else: + self.allowed_contexts = AppCommandContext._from_value(allowed_contexts) + + allowed_installs = data.get('integration_types') + if allowed_installs is None: + self.allowed_installs: Optional[AppInstallationType] = None + else: + self.allowed_installs = AppInstallationType._from_value(allowed_installs) + self.nsfw: bool = data.get('nsfw', False) self.name_localizations: Dict[Locale, str] = _to_locale_dict(data.get('name_localizations') or {}) self.description_localizations: Dict[Locale, str] = _to_locale_dict(data.get('description_localizations') or {}) @@ -223,6 +254,8 @@ class AppCommand(Hashable): 'description': self.description, 'name_localizations': {str(k): v for k, v in self.name_localizations.items()}, 'description_localizations': {str(k): v for k, v in self.description_localizations.items()}, + 'contexts': self.allowed_contexts.to_array() if self.allowed_contexts is not None else None, + 'integration_types': self.allowed_installs.to_array() if self.allowed_installs is not None else None, 'options': [opt.to_dict() for opt in self.options], } # type: ignore # Type checker does not understand this literal. @@ -542,6 +575,35 @@ class AppCommandChannel(Hashable): the application command in that channel. guild_id: :class:`int` The guild ID this channel belongs to. + category_id: Optional[:class:`int`] + The category channel ID this channel belongs to, if applicable. + + .. versionadded:: 2.6 + topic: Optional[:class:`str`] + The channel's topic. ``None`` if it doesn't exist. + + .. versionadded:: 2.6 + position: :class:`int` + The position in the channel list. This is a number that starts at 0. e.g. the + top channel is position 0. + + .. versionadded:: 2.6 + last_message_id: Optional[:class:`int`] + The last message ID of the message sent to this channel. It may + *not* point to an existing or valid message. + + .. versionadded:: 2.6 + slowmode_delay: :class:`int` + The number of seconds a member must wait between sending messages + in this channel. A value of ``0`` denotes that it is disabled. + Bots and users with :attr:`~discord.Permissions.manage_channels` or + :attr:`~discord.Permissions.manage_messages` bypass slowmode. + + .. versionadded:: 2.6 + nsfw: :class:`bool` + If the channel is marked as "not safe for work" or "age restricted". + + .. versionadded:: 2.6 """ __slots__ = ( @@ -550,6 +612,14 @@ class AppCommandChannel(Hashable): 'name', 'permissions', 'guild_id', + 'topic', + 'nsfw', + 'position', + 'category_id', + 'slowmode_delay', + 'last_message_id', + '_last_pin', + '_flags', '_state', ) @@ -566,6 +636,14 @@ class AppCommandChannel(Hashable): self.type: ChannelType = try_enum(ChannelType, data['type']) self.name: str = data['name'] self.permissions: Permissions = Permissions(int(data['permissions'])) + self.topic: Optional[str] = data.get('topic') + self.position: int = data.get('position') or 0 + self.nsfw: bool = data.get('nsfw') or False + self.category_id: Optional[int] = _get_as_snowflake(data, 'parent_id') + self.slowmode_delay: int = data.get('rate_limit_per_user') or 0 + self.last_message_id: Optional[int] = _get_as_snowflake(data, 'last_message_id') + self._last_pin: Optional[datetime] = parse_time(data.get('last_pin_timestamp')) + self._flags: int = data.get('flags', 0) def __str__(self) -> str: return self.name @@ -578,6 +656,28 @@ class AppCommandChannel(Hashable): """Optional[:class:`~discord.Guild`]: The channel's guild, from cache, if found.""" return self._state._get_guild(self.guild_id) + @property + def flags(self) -> ChannelFlags: + """:class:`~discord.ChannelFlags`: The flags associated with this channel object. + + .. versionadded:: 2.6 + """ + return ChannelFlags._from_value(self._flags) + + def is_nsfw(self) -> bool: + """:class:`bool`: Checks if the channel is NSFW. + + .. versionadded:: 2.6 + """ + return self.nsfw + + def is_news(self) -> bool: + """:class:`bool`: Checks if the channel is a news channel. + + .. versionadded:: 2.6 + """ + return self.type == ChannelType.news + def resolve(self) -> Optional[GuildChannel]: """Resolves the application command channel to the appropriate channel from cache if found. @@ -673,7 +773,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. @@ -1030,6 +1130,9 @@ class AppCommandPermissions: self.target: Union[Object, User, Member, Role, AllChannels, GuildChannel] = _object + def __repr__(self) -> str: + return f'' + def to_dict(self) -> ApplicationCommandPermissions: return { 'id': self.target.id, @@ -1073,6 +1176,9 @@ class GuildAppCommandPermissions: AppCommandPermissions(data=value, guild=guild, state=self._state) for value in data['permissions'] ] + def __repr__(self) -> str: + return f'' + def to_dict(self) -> Dict[str, Any]: return {'permissions': [p.to_dict() for p in self.permissions]} diff --git a/discord/app_commands/namespace.py b/discord/app_commands/namespace.py index 7fad617c6..3fa81712c 100644 --- a/discord/app_commands/namespace.py +++ b/discord/app_commands/namespace.py @@ -179,7 +179,7 @@ class Namespace: state = interaction._state members = resolved.get('members', {}) guild_id = interaction.guild_id - guild = state._get_or_create_unavailable_guild(guild_id) if guild_id is not None else None + guild = interaction.guild type = AppCommandOptionType.user.value for (user_id, user_data) in resolved.get('users', {}).items(): try: @@ -220,7 +220,6 @@ class Namespace: } ) - guild = state._get_guild(guild_id) for (message_id, message_data) in resolved.get('messages', {}).items(): channel_id = int(message_data['channel_id']) if guild is None: @@ -232,6 +231,7 @@ class Namespace: # Type checker doesn't understand this due to failure to narrow message = Message(state=state, channel=channel, data=message_data) # type: ignore + message.guild = guild key = ResolveKey(id=message_id, type=-1) completed[key] = message diff --git a/discord/app_commands/transformers.py b/discord/app_commands/transformers.py index 0816c5b87..212991cbe 100644 --- a/discord/app_commands/transformers.py +++ b/discord/app_commands/transformers.py @@ -34,6 +34,7 @@ from typing import ( ClassVar, Coroutine, Dict, + Generic, List, Literal, Optional, @@ -51,11 +52,12 @@ from ..channel import StageChannel, VoiceChannel, TextChannel, CategoryChannel, from ..abc import GuildChannel from ..threads import Thread from ..enums import Enum as InternalEnum, AppCommandOptionType, ChannelType, Locale -from ..utils import MISSING, maybe_coroutine +from ..utils import MISSING, maybe_coroutine, _human_join from ..user import User from ..role import Role from ..member import Member from ..message import Attachment +from .._types import ClientT __all__ = ( 'Transformer', @@ -177,8 +179,7 @@ class CommandParameter: return choice try: - # ParamSpec doesn't understand that transform is a callable since it's unbound - return await maybe_coroutine(self._annotation.transform, interaction, value) # type: ignore + return await maybe_coroutine(self._annotation.transform, interaction, value) except AppCommandError: raise except Exception as e: @@ -192,7 +193,7 @@ class CommandParameter: return self.name if self._rename is MISSING else str(self._rename) -class Transformer: +class Transformer(Generic[ClientT]): """The base class that allows a type annotation in an application command parameter to map into a :class:`~discord.AppCommandOptionType` and transform the raw value into one from this type. @@ -234,7 +235,7 @@ class Transformer: pass def __or__(self, rhs: Any) -> Any: - return Union[self, rhs] # type: ignore + return Union[self, rhs] @property def type(self) -> AppCommandOptionType: @@ -305,7 +306,7 @@ class Transformer: else: return name - async def transform(self, interaction: Interaction, value: Any, /) -> Any: + async def transform(self, interaction: Interaction[ClientT], value: Any, /) -> Any: """|maybecoro| Transforms the converted option value into another value. @@ -325,7 +326,7 @@ class Transformer: raise NotImplementedError('Derived classes need to implement this.') async def autocomplete( - self, interaction: Interaction, value: Union[int, float, str], / + self, interaction: Interaction[ClientT], value: Union[int, float, str], / ) -> List[Choice[Union[int, float, str]]]: """|coro| @@ -353,7 +354,7 @@ class Transformer: raise NotImplementedError('Derived classes can implement this.') -class IdentityTransformer(Transformer): +class IdentityTransformer(Transformer[ClientT]): def __init__(self, type: AppCommandOptionType) -> None: self._type = type @@ -361,7 +362,7 @@ class IdentityTransformer(Transformer): def type(self) -> AppCommandOptionType: return self._type - async def transform(self, interaction: Interaction, value: Any, /) -> Any: + async def transform(self, interaction: Interaction[ClientT], value: Any, /) -> Any: return value @@ -490,7 +491,7 @@ class EnumNameTransformer(Transformer): return self._enum[value] -class InlineTransformer(Transformer): +class InlineTransformer(Transformer[ClientT]): def __init__(self, annotation: Any) -> None: super().__init__() self.annotation: Any = annotation @@ -503,7 +504,7 @@ class InlineTransformer(Transformer): def type(self) -> AppCommandOptionType: return AppCommandOptionType.string - async def transform(self, interaction: Interaction, value: Any, /) -> Any: + async def transform(self, interaction: Interaction[ClientT], value: Any, /) -> Any: return await self.annotation.transform(interaction, value) @@ -526,7 +527,7 @@ else: .. versionadded:: 2.0 """ - def __class_getitem__(cls, items) -> _TransformMetadata: + def __class_getitem__(cls, items) -> Transformer: if not isinstance(items, tuple): raise TypeError(f'expected tuple for arguments, received {items.__class__.__name__} instead') @@ -571,7 +572,7 @@ else: await interaction.response.send_message(f'Your value is {value}', ephemeral=True) """ - def __class_getitem__(cls, obj) -> _TransformMetadata: + def __class_getitem__(cls, obj) -> RangeTransformer: if not isinstance(obj, tuple): raise TypeError(f'expected tuple for arguments, received {obj.__class__.__name__} instead') @@ -612,25 +613,25 @@ else: return transformer -class MemberTransformer(Transformer): +class MemberTransformer(Transformer[ClientT]): @property def type(self) -> AppCommandOptionType: return AppCommandOptionType.user - async def transform(self, interaction: Interaction, value: Any, /) -> Member: + async def transform(self, interaction: Interaction[ClientT], value: Any, /) -> Member: if not isinstance(value, Member): raise TransformerError(value, self.type, self) return value -class BaseChannelTransformer(Transformer): +class BaseChannelTransformer(Transformer[ClientT]): def __init__(self, *channel_types: Type[Any]) -> None: super().__init__() if len(channel_types) == 1: display_name = channel_types[0].__name__ types = CHANNEL_TO_TYPES[channel_types[0]] else: - display_name = '{}, and {}'.format(', '.join(t.__name__ for t in channel_types[:-1]), channel_types[-1].__name__) + display_name = _human_join([t.__name__ for t in channel_types]) types = [] for t in channel_types: @@ -639,7 +640,7 @@ class BaseChannelTransformer(Transformer): except KeyError: raise TypeError('Union type of channels must be entirely made up of channels') from None - self._types: Tuple[Type[Any]] = channel_types + self._types: Tuple[Type[Any], ...] = channel_types self._channel_types: List[ChannelType] = types self._display_name = display_name @@ -655,22 +656,22 @@ class BaseChannelTransformer(Transformer): def channel_types(self) -> List[ChannelType]: return self._channel_types - async def transform(self, interaction: Interaction, value: Any, /): + async def transform(self, interaction: Interaction[ClientT], value: Any, /): resolved = value.resolve() if resolved is None or not isinstance(resolved, self._types): raise TransformerError(value, AppCommandOptionType.channel, self) return resolved -class RawChannelTransformer(BaseChannelTransformer): - async def transform(self, interaction: Interaction, value: Any, /): +class RawChannelTransformer(BaseChannelTransformer[ClientT]): + async def transform(self, interaction: Interaction[ClientT], value: Any, /): if not isinstance(value, self._types): raise TransformerError(value, AppCommandOptionType.channel, self) return value -class UnionChannelTransformer(BaseChannelTransformer): - async def transform(self, interaction: Interaction, value: Any, /): +class UnionChannelTransformer(BaseChannelTransformer[ClientT]): + async def transform(self, interaction: Interaction[ClientT], value: Any, /): if isinstance(value, self._types): return value @@ -688,6 +689,7 @@ CHANNEL_TO_TYPES: Dict[Any, List[ChannelType]] = { ChannelType.news, ChannelType.category, ChannelType.forum, + ChannelType.media, ], GuildChannel: [ ChannelType.stage_voice, @@ -696,6 +698,7 @@ CHANNEL_TO_TYPES: Dict[Any, List[ChannelType]] = { ChannelType.news, ChannelType.category, ChannelType.forum, + ChannelType.media, ], AppCommandThread: [ChannelType.news_thread, ChannelType.private_thread, ChannelType.public_thread], Thread: [ChannelType.news_thread, ChannelType.private_thread, ChannelType.public_thread], @@ -703,7 +706,7 @@ CHANNEL_TO_TYPES: Dict[Any, List[ChannelType]] = { VoiceChannel: [ChannelType.voice], TextChannel: [ChannelType.text, ChannelType.news], CategoryChannel: [ChannelType.category], - ForumChannel: [ChannelType.forum], + ForumChannel: [ChannelType.forum, ChannelType.media], } BUILT_IN_TRANSFORMERS: Dict[Any, Transformer] = { @@ -750,7 +753,7 @@ def get_supported_annotation( try: return (_mapping[annotation], MISSING, True) - except KeyError: + except (KeyError, TypeError): pass if isinstance(annotation, Transformer): @@ -781,11 +784,11 @@ def get_supported_annotation( # Check if there's an origin origin = getattr(annotation, '__origin__', None) if origin is Literal: - args = annotation.__args__ # type: ignore + args = annotation.__args__ return (LiteralTransformer(args), MISSING, True) if origin is Choice: - arg = annotation.__args__[0] # type: ignore + arg = annotation.__args__[0] return (ChoiceTransformer(arg), MISSING, True) if origin is not Union: @@ -793,7 +796,7 @@ def get_supported_annotation( raise TypeError(f'unsupported type annotation {annotation!r}') default = MISSING - args = annotation.__args__ # type: ignore + args = annotation.__args__ if args[-1] is _none: if len(args) == 2: underlying = args[0] diff --git a/discord/app_commands/translator.py b/discord/app_commands/translator.py index 1741054e3..4b6e01d4b 100644 --- a/discord/app_commands/translator.py +++ b/discord/app_commands/translator.py @@ -109,7 +109,7 @@ class TranslationContext(Generic[_L, _D]): def __init__(self, location: Literal[TranslationContextLocation.other], data: Any) -> None: ... - def __init__(self, location: _L, data: _D) -> None: + def __init__(self, location: _L, data: _D) -> None: # type: ignore # pyright doesn't like the overloads self.location: _L = location self.data: _D = data diff --git a/discord/app_commands/tree.py b/discord/app_commands/tree.py index 5bdfbec58..3099071c0 100644 --- a/discord/app_commands/tree.py +++ b/discord/app_commands/tree.py @@ -58,6 +58,7 @@ from .errors import ( CommandSyncFailure, MissingApplicationID, ) +from .installs import AppCommandContext, AppInstallationType from .translator import Translator, locale_str from ..errors import ClientException, HTTPException from ..enums import AppCommandType, InteractionType @@ -72,7 +73,7 @@ if TYPE_CHECKING: from .commands import ContextMenuCallback, CommandCallback, P, T ErrorFunc = Callable[ - [Interaction, AppCommandError], + [Interaction[ClientT], AppCommandError], Coroutine[Any, Any, Any], ] @@ -121,9 +122,26 @@ class CommandTree(Generic[ClientT]): to find the guild-specific ``/ping`` command it will fall back to the global ``/ping`` command. This has the potential to raise more :exc:`~discord.app_commands.CommandSignatureMismatch` errors than usual. Defaults to ``True``. + allowed_contexts: :class:`~discord.app_commands.AppCommandContext` + The default allowed contexts that applies to all commands in this tree. + Note that you can override this on a per command basis. + + .. versionadded:: 2.4 + allowed_installs: :class:`~discord.app_commands.AppInstallationType` + The default allowed install locations that apply to all commands in this tree. + Note that you can override this on a per command basis. + + .. versionadded:: 2.4 """ - def __init__(self, client: ClientT, *, fallback_to_global: bool = True): + def __init__( + self, + client: ClientT, + *, + fallback_to_global: bool = True, + allowed_contexts: AppCommandContext = MISSING, + allowed_installs: AppInstallationType = MISSING, + ): self.client: ClientT = client self._http = client.http self._state = client._connection @@ -133,6 +151,8 @@ class CommandTree(Generic[ClientT]): self._state._command_tree = self self.fallback_to_global: bool = fallback_to_global + self.allowed_contexts = AppCommandContext() if allowed_contexts is MISSING else allowed_contexts + self.allowed_installs = AppInstallationType() if allowed_installs is MISSING else allowed_installs self._guild_commands: Dict[int, Dict[str, Union[Command, Group]]] = {} self._global_commands: Dict[str, Union[Command, Group]] = {} # (name, guild_id, command_type): Command @@ -287,10 +307,24 @@ class CommandTree(Generic[ClientT]): guild: Optional[:class:`~discord.abc.Snowflake`] The guild to add the command to. If not given or ``None`` then it becomes a global command instead. + + .. note :: + + Due to a Discord limitation, this keyword argument cannot be used in conjunction with + contexts (e.g. :func:`.app_commands.allowed_contexts`) or installation types + (e.g. :func:`.app_commands.allowed_installs`). + guilds: List[:class:`~discord.abc.Snowflake`] The list of guilds to add the command to. This cannot be mixed with the ``guild`` parameter. If no guilds are given at all then it becomes a global command instead. + + .. note :: + + Due to a Discord limitation, this keyword argument cannot be used in conjunction with + contexts (e.g. :func:`.app_commands.allowed_contexts`) or installation types + (e.g. :func:`.app_commands.allowed_installs`). + override: :class:`bool` Whether to override a command with the same name. If ``False`` an exception is raised. Default is ``False``. @@ -722,7 +756,7 @@ class CommandTree(Generic[ClientT]): else: guild_id = None if guild is None else guild.id value = type.value - for ((_, g, t), command) in self._context_menus.items(): + for (_, g, t), command in self._context_menus.items(): if g == guild_id and t == value: yield command @@ -799,7 +833,7 @@ class CommandTree(Generic[ClientT]): else: _log.error('Ignoring exception in command tree', exc_info=error) - def error(self, coro: ErrorFunc) -> ErrorFunc: + def error(self, coro: ErrorFunc[ClientT]) -> ErrorFunc[ClientT]: """A decorator that registers a coroutine as a local error handler. This must match the signature of the :meth:`on_error` callback. @@ -825,7 +859,7 @@ class CommandTree(Generic[ClientT]): if len(params) != 2: raise TypeError('error handler must have 2 parameters') - self.on_error = coro + self.on_error = coro # type: ignore return coro def command( @@ -857,10 +891,24 @@ class CommandTree(Generic[ClientT]): guild: Optional[:class:`~discord.abc.Snowflake`] The guild to add the command to. If not given or ``None`` then it becomes a global command instead. + + .. note :: + + Due to a Discord limitation, this keyword argument cannot be used in conjunction with + contexts (e.g. :func:`.app_commands.allowed_contexts`) or installation types + (e.g. :func:`.app_commands.allowed_installs`). + guilds: List[:class:`~discord.abc.Snowflake`] The list of guilds to add the command to. This cannot be mixed with the ``guild`` parameter. If no guilds are given at all then it becomes a global command instead. + + .. note :: + + Due to a Discord limitation, this keyword argument cannot be used in conjunction with + contexts (e.g. :func:`.app_commands.allowed_contexts`) or installation types + (e.g. :func:`.app_commands.allowed_installs`). + auto_locale_strings: :class:`bool` If this is set to ``True``, then all translatable strings will implicitly be wrapped into :class:`locale_str` rather than :class:`str`. This could @@ -940,10 +988,24 @@ class CommandTree(Generic[ClientT]): guild: Optional[:class:`~discord.abc.Snowflake`] The guild to add the command to. If not given or ``None`` then it becomes a global command instead. + + .. note :: + + Due to a Discord limitation, this keyword argument cannot be used in conjunction with + contexts (e.g. :func:`.app_commands.allowed_contexts`) or installation types + (e.g. :func:`.app_commands.allowed_installs`). + guilds: List[:class:`~discord.abc.Snowflake`] The list of guilds to add the command to. This cannot be mixed with the ``guild`` parameter. If no guilds are given at all then it becomes a global command instead. + + .. note :: + + Due to a Discord limitation, this keyword argument cannot be used in conjunction with + contexts (e.g. :func:`.app_commands.allowed_contexts`) or installation types + (e.g. :func:`.app_commands.allowed_installs`). + auto_locale_strings: :class:`bool` If this is set to ``True``, then all translatable strings will implicitly be wrapped into :class:`locale_str` rather than :class:`str`. This could @@ -1058,9 +1120,9 @@ class CommandTree(Generic[ClientT]): translator = self.translator if translator: - payload = [await command.get_translated_payload(translator) for command in commands] + payload = [await command.get_translated_payload(self, translator) for command in commands] else: - payload = [command.to_dict() for command in commands] + payload = [command.to_dict(self) for command in commands] try: if guild is None: @@ -1240,7 +1302,7 @@ class CommandTree(Generic[ClientT]): await command._invoke_autocomplete(interaction, focused, namespace) except Exception: # Suppress exception since it can't be handled anyway. - pass + _log.exception('Ignoring exception in autocomplete for %r', command.qualified_name) return diff --git a/discord/appinfo.py b/discord/appinfo.py index 18c97228b..990c7c2fe 100644 --- a/discord/appinfo.py +++ b/discord/appinfo.py @@ -24,20 +24,24 @@ DEALINGS IN THE SOFTWARE. from __future__ import annotations -from typing import List, TYPE_CHECKING, Optional +from typing import List, TYPE_CHECKING, Literal, Optional from . import utils from .asset import Asset from .flags import ApplicationFlags from .permissions import Permissions +from .utils import MISSING if TYPE_CHECKING: + from typing import Dict, Any + from .guild import Guild from .types.appinfo import ( AppInfo as AppInfoPayload, PartialAppInfo as PartialAppInfoPayload, Team as TeamPayload, InstallParams as InstallParamsPayload, + AppIntegrationTypeConfig as AppIntegrationTypeConfigPayload, ) from .user import User from .state import ConnectionState @@ -46,6 +50,7 @@ __all__ = ( 'AppInfo', 'PartialAppInfo', 'AppInstallParams', + 'IntegrationTypeConfig', ) @@ -131,6 +136,23 @@ class AppInfo: a verification method in the guild's role verification configuration. .. versionadded:: 2.2 + interactions_endpoint_url: Optional[:class:`str`] + The interactions endpoint url of the application to receive interactions over this endpoint rather than + over the gateway, if configured. + + .. versionadded:: 2.4 + redirect_uris: List[:class:`str`] + A list of authentication redirect URIs. + + .. versionadded:: 2.4 + approximate_guild_count: :class:`int` + The approximate count of the guilds the bot was added to. + + .. versionadded:: 2.4 + approximate_user_install_count: Optional[:class:`int`] + The approximate count of the user-level installations the bot has. + + .. versionadded:: 2.5 """ __slots__ = ( @@ -156,6 +178,11 @@ class AppInfo: 'custom_install_url', 'install_params', 'role_connections_verification_url', + 'interactions_endpoint_url', + 'redirect_uris', + 'approximate_guild_count', + 'approximate_user_install_count', + '_integration_types_config', ) def __init__(self, state: ConnectionState, data: AppInfoPayload): @@ -166,7 +193,7 @@ class AppInfo: self.name: str = data['name'] self.description: str = data['description'] self._icon: Optional[str] = data['icon'] - self.rpc_origins: List[str] = data['rpc_origins'] + self.rpc_origins: Optional[List[str]] = data.get('rpc_origins') self.bot_public: bool = data['bot_public'] self.bot_require_code_grant: bool = data['bot_require_code_grant'] self.owner: User = state.create_user(data['owner']) @@ -190,6 +217,13 @@ class AppInfo: params = data.get('install_params') self.install_params: Optional[AppInstallParams] = AppInstallParams(params) if params else None + self.interactions_endpoint_url: Optional[str] = data.get('interactions_endpoint_url') + self.redirect_uris: List[str] = data.get('redirect_uris', []) + self.approximate_guild_count: int = data.get('approximate_guild_count', 0) + self.approximate_user_install_count: Optional[int] = data.get('approximate_user_install_count') + self._integration_types_config: Dict[Literal['0', '1'], AppIntegrationTypeConfigPayload] = data.get( + 'integration_types_config', {} + ) def __repr__(self) -> str: return ( @@ -232,6 +266,236 @@ class AppInfo: """ return ApplicationFlags._from_value(self._flags) + @property + def guild_integration_config(self) -> Optional[IntegrationTypeConfig]: + """Optional[:class:`IntegrationTypeConfig`]: The default settings for the + application's installation context in a guild. + + .. versionadded:: 2.5 + """ + if not self._integration_types_config: + return None + + try: + return IntegrationTypeConfig(self._integration_types_config['0']) + except KeyError: + return None + + @property + def user_integration_config(self) -> Optional[IntegrationTypeConfig]: + """Optional[:class:`IntegrationTypeConfig`]: The default settings for the + application's installation context as a user. + + .. versionadded:: 2.5 + """ + if not self._integration_types_config: + return None + + try: + return IntegrationTypeConfig(self._integration_types_config['1']) + except KeyError: + return None + + async def edit( + self, + *, + reason: Optional[str] = MISSING, + custom_install_url: Optional[str] = MISSING, + description: Optional[str] = MISSING, + role_connections_verification_url: Optional[str] = MISSING, + install_params_scopes: Optional[List[str]] = MISSING, + install_params_permissions: Optional[Permissions] = MISSING, + flags: Optional[ApplicationFlags] = MISSING, + icon: Optional[bytes] = MISSING, + cover_image: Optional[bytes] = MISSING, + interactions_endpoint_url: Optional[str] = MISSING, + tags: Optional[List[str]] = MISSING, + guild_install_scopes: Optional[List[str]] = MISSING, + guild_install_permissions: Optional[Permissions] = MISSING, + user_install_scopes: Optional[List[str]] = MISSING, + user_install_permissions: Optional[Permissions] = MISSING, + ) -> AppInfo: + r"""|coro| + + Edits the application info. + + .. versionadded:: 2.4 + + Parameters + ---------- + custom_install_url: Optional[:class:`str`] + The new custom authorization URL for the application. Can be ``None`` to remove the URL. + description: Optional[:class:`str`] + The new application description. Can be ``None`` to remove the description. + role_connections_verification_url: Optional[:class:`str`] + The new application’s connection verification URL which will render the application + as a verification method in the guild’s role verification configuration. Can be ``None`` to remove the URL. + install_params_scopes: Optional[List[:class:`str`]] + The new list of :ddocs:`OAuth2 scopes ` of + the :attr:`~install_params`. Can be ``None`` to remove the scopes. + install_params_permissions: Optional[:class:`Permissions`] + The new permissions of the :attr:`~install_params`. Can be ``None`` to remove the permissions. + flags: Optional[:class:`ApplicationFlags`] + The new application’s flags. Only limited intent flags (:attr:`~ApplicationFlags.gateway_presence_limited`, + :attr:`~ApplicationFlags.gateway_guild_members_limited`, :attr:`~ApplicationFlags.gateway_message_content_limited`) + can be edited. Can be ``None`` to remove the flags. + + .. warning:: + + Editing the limited intent flags leads to the termination of the bot. + + icon: Optional[:class:`bytes`] + The new application’s icon as a :term:`py:bytes-like object`. Can be ``None`` to remove the icon. + cover_image: Optional[:class:`bytes`] + The new application’s cover image as a :term:`py:bytes-like object` on a store embed. + The cover image is only available if the application is a game sold on Discord. + Can be ``None`` to remove the image. + interactions_endpoint_url: Optional[:class:`str`] + The new interactions endpoint url of the application to receive interactions over this endpoint rather than + over the gateway. Can be ``None`` to remove the URL. + tags: Optional[List[:class:`str`]] + The new list of tags describing the functionality of the application. Can be ``None`` to remove the tags. + guild_install_scopes: Optional[List[:class:`str`]] + The new list of :ddocs:`OAuth2 scopes ` of + the default guild installation context. Can be ``None`` to remove the scopes. + + .. versionadded: 2.5 + guild_install_permissions: Optional[:class:`Permissions`] + The new permissions of the default guild installation context. Can be ``None`` to remove the permissions. + + .. versionadded: 2.5 + user_install_scopes: Optional[List[:class:`str`]] + The new list of :ddocs:`OAuth2 scopes ` of + the default user installation context. Can be ``None`` to remove the scopes. + + .. versionadded: 2.5 + user_install_permissions: Optional[:class:`Permissions`] + The new permissions of the default user installation context. Can be ``None`` to remove the permissions. + + .. versionadded: 2.5 + reason: Optional[:class:`str`] + The reason for editing the application. Shows up on the audit log. + + Raises + ------- + HTTPException + Editing the application failed + ValueError + The image format passed in to ``icon`` or ``cover_image`` is invalid. This is also raised + when ``install_params_scopes`` and ``install_params_permissions`` are incompatible with each other, + or when ``guild_install_scopes`` and ``guild_install_permissions`` are incompatible with each other. + + Returns + ------- + :class:`AppInfo` + The newly updated application info. + """ + payload: Dict[str, Any] = {} + + if custom_install_url is not MISSING: + payload['custom_install_url'] = custom_install_url + + if description is not MISSING: + payload['description'] = description + + if role_connections_verification_url is not MISSING: + payload['role_connections_verification_url'] = role_connections_verification_url + + if install_params_scopes is not MISSING: + install_params: Optional[Dict[str, Any]] = {} + if install_params_scopes is None: + install_params = None + else: + if "bot" not in install_params_scopes and install_params_permissions is not MISSING: + raise ValueError("'bot' must be in install_params_scopes if install_params_permissions is set") + + install_params['scopes'] = install_params_scopes + + if install_params_permissions is MISSING: + install_params['permissions'] = 0 + else: + if install_params_permissions is None: + install_params['permissions'] = 0 + else: + install_params['permissions'] = install_params_permissions.value + + payload['install_params'] = install_params + + else: + if install_params_permissions is not MISSING: + raise ValueError('install_params_scopes must be set if install_params_permissions is set') + + if flags is not MISSING: + if flags is None: + payload['flags'] = flags + else: + payload['flags'] = flags.value + + if icon is not MISSING: + if icon is None: + payload['icon'] = icon + else: + payload['icon'] = utils._bytes_to_base64_data(icon) + + if cover_image is not MISSING: + if cover_image is None: + payload['cover_image'] = cover_image + else: + payload['cover_image'] = utils._bytes_to_base64_data(cover_image) + + if interactions_endpoint_url is not MISSING: + payload['interactions_endpoint_url'] = interactions_endpoint_url + + if tags is not MISSING: + payload['tags'] = tags + + integration_types_config: Dict[str, Any] = {} + if guild_install_scopes is not MISSING or guild_install_permissions is not MISSING: + guild_install_params: Optional[Dict[str, Any]] = {} + if guild_install_scopes in (None, MISSING): + guild_install_scopes = [] + + if 'bot' not in guild_install_scopes and guild_install_permissions is not MISSING: + raise ValueError("'bot' must be in guild_install_scopes if guild_install_permissions is set") + + if guild_install_permissions in (None, MISSING): + guild_install_params['permissions'] = 0 + else: + guild_install_params['permissions'] = guild_install_permissions.value + + guild_install_params['scopes'] = guild_install_scopes + + integration_types_config['0'] = {'oauth2_install_params': guild_install_params or None} + else: + if guild_install_permissions is not MISSING: + raise ValueError('guild_install_scopes must be set if guild_install_permissions is set') + + if user_install_scopes is not MISSING or user_install_permissions is not MISSING: + user_install_params: Optional[Dict[str, Any]] = {} + if user_install_scopes in (None, MISSING): + user_install_scopes = [] + + if 'bot' not in user_install_scopes and user_install_permissions is not MISSING: + raise ValueError("'bot' must be in user_install_scopes if user_install_permissions is set") + + if user_install_permissions in (None, MISSING): + user_install_params['permissions'] = 0 + else: + user_install_params['permissions'] = user_install_permissions.value + + user_install_params['scopes'] = user_install_scopes + + integration_types_config['1'] = {'oauth2_install_params': user_install_params or None} + else: + if user_install_permissions is not MISSING: + raise ValueError('user_install_scopes must be set if user_install_permissions is set') + + if integration_types_config: + payload['integration_types_config'] = integration_types_config + + data = await self._state.http.edit_application_info(reason=reason, payload=payload) + return AppInfo(data=data, state=self._state) + class PartialAppInfo: """Represents a partial AppInfo given by :func:`~discord.abc.GuildChannel.create_invite` @@ -255,6 +519,24 @@ class PartialAppInfo: The application's terms of service URL, if set. privacy_policy_url: Optional[:class:`str`] The application's privacy policy URL, if set. + approximate_guild_count: :class:`int` + The approximate count of the guilds the bot was added to. + + .. versionadded:: 2.3 + redirect_uris: List[:class:`str`] + A list of authentication redirect URIs. + + .. versionadded:: 2.3 + interactions_endpoint_url: Optional[:class:`str`] + The interactions endpoint url of the application to receive interactions over this endpoint rather than + over the gateway, if configured. + + .. versionadded:: 2.3 + role_connections_verification_url: Optional[:class:`str`] + The application's connection verification URL which will render the application as + a verification method in the guild's role verification configuration. + + .. versionadded:: 2.3 """ __slots__ = ( @@ -268,6 +550,11 @@ class PartialAppInfo: 'privacy_policy_url', '_icon', '_flags', + '_cover_image', + 'approximate_guild_count', + 'redirect_uris', + 'interactions_endpoint_url', + 'role_connections_verification_url', ) def __init__(self, *, state: ConnectionState, data: PartialAppInfoPayload): @@ -276,11 +563,16 @@ class PartialAppInfo: self.name: str = data['name'] self._icon: Optional[str] = data.get('icon') self._flags: int = data.get('flags', 0) + self._cover_image: Optional[str] = data.get('cover_image') self.description: str = data['description'] self.rpc_origins: Optional[List[str]] = data.get('rpc_origins') self.verify_key: str = data['verify_key'] self.terms_of_service_url: Optional[str] = data.get('terms_of_service_url') self.privacy_policy_url: Optional[str] = data.get('privacy_policy_url') + self.approximate_guild_count: int = data.get('approximate_guild_count', 0) + self.redirect_uris: List[str] = data.get('redirect_uris', []) + self.interactions_endpoint_url: Optional[str] = data.get('interactions_endpoint_url') + self.role_connections_verification_url: Optional[str] = data.get('role_connections_verification_url') def __repr__(self) -> str: return f'<{self.__class__.__name__} id={self.id} name={self.name!r} description={self.description!r}>' @@ -292,6 +584,18 @@ class PartialAppInfo: return None return Asset._from_icon(self._state, self.id, self._icon, path='app') + @property + def cover_image(self) -> Optional[Asset]: + """Optional[:class:`.Asset`]: Retrieves the cover image of the application's default rich presence. + + This is only available if the application is a game sold on Discord. + + .. versionadded:: 2.3 + """ + if self._cover_image is None: + return None + return Asset._from_cover_image(self._state, self.id, self._cover_image) + @property def flags(self) -> ApplicationFlags: """:class:`ApplicationFlags`: The application's flags. @@ -320,3 +624,22 @@ class AppInstallParams: def __init__(self, data: InstallParamsPayload) -> None: self.scopes: List[str] = data.get('scopes', []) self.permissions: Permissions = Permissions(int(data['permissions'])) + + +class IntegrationTypeConfig: + """Represents the default settings for the application's installation context. + + .. versionadded:: 2.5 + + Attributes + ---------- + oauth2_install_params: Optional[:class:`AppInstallParams`] + The install params for this installation context's default in-app authorization link. + """ + + def __init__(self, data: AppIntegrationTypeConfigPayload) -> None: + self.oauth2_install_params: Optional[AppInstallParams] = None + try: + self.oauth2_install_params = AppInstallParams(data['oauth2_install_params']) # type: ignore # EAFP + except KeyError: + pass diff --git a/discord/asset.py b/discord/asset.py index d88ebb945..e3422f311 100644 --- a/discord/asset.py +++ b/discord/asset.py @@ -246,6 +246,26 @@ class Asset(AssetMixin): animated=animated, ) + @classmethod + def _from_guild_banner(cls, state: _State, 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_avatar_decoration(cls, state: _State, avatar_decoration: str) -> Self: + return cls( + state, + url=f'{cls.BASE}/avatar-decoration-presets/{avatar_decoration}.png?size=96', + key=avatar_decoration, + animated=True, + ) + @classmethod def _from_icon(cls, state: _State, object_id: int, icon_hash: str, path: str) -> Self: return cls( @@ -420,7 +440,7 @@ class Asset(AssetMixin): url = url.with_query(url.raw_query_string) url = str(url) - return Asset(state=self._state, url=url, key=self._key, animated=self._animated) + return self.__class__(state=self._state, url=url, key=self._key, animated=self._animated) def with_size(self, size: int, /) -> Self: """Returns a new asset with the specified size. @@ -448,7 +468,7 @@ class Asset(AssetMixin): raise ValueError('size must be a power of 2 between 16 and 4096') url = str(yarl.URL(self._url).with_query(size=size)) - return Asset(state=self._state, url=url, key=self._key, animated=self._animated) + return self.__class__(state=self._state, url=url, key=self._key, animated=self._animated) def with_format(self, format: ValidAssetFormatTypes, /) -> Self: """Returns a new asset with the specified format. @@ -483,7 +503,7 @@ class Asset(AssetMixin): url = yarl.URL(self._url) path, _ = os.path.splitext(url.path) url = str(url.with_path(f'{path}.{format}').with_query(url.raw_query_string)) - return Asset(state=self._state, url=url, key=self._key, animated=self._animated) + return self.__class__(state=self._state, url=url, key=self._key, animated=self._animated) def with_static_format(self, format: ValidStaticFormatTypes, /) -> Self: """Returns a new asset with the specified static format. diff --git a/discord/audit_logs.py b/discord/audit_logs.py index 72cf8f854..89577769f 100644 --- a/discord/audit_logs.py +++ b/discord/audit_logs.py @@ -33,7 +33,7 @@ from .invite import Invite from .mixins import Hashable from .object import Object from .permissions import PermissionOverwrite, Permissions -from .automod import AutoModTrigger, AutoModRuleAction, AutoModPresets, AutoModRule +from .automod import AutoModTrigger, AutoModRuleAction, AutoModRule from .role import Role from .emoji import Emoji from .partial_emoji import PartialEmoji @@ -61,6 +61,7 @@ if TYPE_CHECKING: from .types.audit_log import ( AuditLogChange as AuditLogChangePayload, AuditLogEntry as AuditLogEntryPayload, + _AuditLogChange_TriggerMetadata as AuditLogChangeTriggerMetadataPayload, ) from .types.channel import ( PermissionOverwrite as PermissionOverwritePayload, @@ -71,9 +72,10 @@ if TYPE_CHECKING: from .types.role import Role as RolePayload from .types.snowflake import Snowflake from .types.command import ApplicationCommandPermissions - from .types.automod import AutoModerationTriggerMetadata, AutoModerationAction + from .types.automod import AutoModerationAction from .user import User from .app_commands import AppCommand + from .webhook import Webhook TargetType = Union[ Guild, @@ -89,6 +91,9 @@ if TYPE_CHECKING: Object, PartialIntegration, AutoModRule, + ScheduledEvent, + Webhook, + AppCommand, None, ] @@ -140,8 +145,8 @@ def _transform_applied_forum_tags(entry: AuditLogEntry, data: List[Snowflake]) - return [Object(id=tag_id, type=ForumTag) for tag_id in data] -def _transform_overloaded_flags(entry: AuditLogEntry, data: int) -> Union[int, flags.ChannelFlags]: - # The `flags` key is definitely overloaded. Right now it's for channels and threads but +def _transform_overloaded_flags(entry: AuditLogEntry, data: int) -> Union[int, flags.ChannelFlags, flags.InviteFlags]: + # The `flags` key is definitely overloaded. Right now it's for channels, threads and invites but # I am aware of `member.flags` and `user.flags` existing. However, this does not impact audit logs # at the moment but better safe than sorry. channel_audit_log_types = ( @@ -152,9 +157,16 @@ def _transform_overloaded_flags(entry: AuditLogEntry, data: int) -> Union[int, f enums.AuditLogAction.thread_update, enums.AuditLogAction.thread_delete, ) + invite_audit_log_types = ( + enums.AuditLogAction.invite_create, + enums.AuditLogAction.invite_update, + enums.AuditLogAction.invite_delete, + ) if entry.action in channel_audit_log_types: return flags.ChannelFlags._from_value(data) + elif entry.action in invite_audit_log_types: + return flags.InviteFlags._from_value(data) return data @@ -226,43 +238,14 @@ def _guild_hash_transformer(path: str) -> Callable[[AuditLogEntry, Optional[str] return _transform -def _transform_automod_trigger_metadata( - entry: AuditLogEntry, data: AutoModerationTriggerMetadata -) -> Optional[AutoModTrigger]: - - if isinstance(entry.target, AutoModRule): - # Trigger type cannot be changed, so type should be the same before and after updates. - # Avoids checking which keys are in data to guess trigger type - # or returning None if data is empty. - try: - return AutoModTrigger.from_data(type=entry.target.trigger.type.value, data=data) - except Exception: - pass - - # Try to infer trigger type from available keys in data - if 'presets' in data: - return AutoModTrigger( - type=enums.AutoModRuleTriggerType.keyword_preset, - presets=AutoModPresets._from_value(data['presets']), # type: ignore - allow_list=data.get('allow_list'), - ) - elif 'keyword_filter' in data: - return AutoModTrigger( - type=enums.AutoModRuleTriggerType.keyword, - keyword_filter=data['keyword_filter'], # type: ignore - allow_list=data.get('allow_list'), - regex_patterns=data.get('regex_patterns'), - ) - elif 'mention_total_limit' in data: - return AutoModTrigger(type=enums.AutoModRuleTriggerType.mention_spam, mention_limit=data['mention_total_limit']) # type: ignore - else: - return AutoModTrigger(type=enums.AutoModRuleTriggerType.spam) - - def _transform_automod_actions(entry: AuditLogEntry, data: List[AutoModerationAction]) -> List[AutoModRuleAction]: return [AutoModRuleAction.from_data(action) for action in data] +def _transform_default_emoji(entry: AuditLogEntry, data: str) -> PartialEmoji: + return PartialEmoji(name=data) + + E = TypeVar('E', bound=enums.Enum) @@ -362,7 +345,6 @@ class AuditLogChanges: 'image_hash': ('cover_image', _transform_cover_image), 'trigger_type': (None, _enum_transformer(enums.AutoModRuleTriggerType)), 'event_type': (None, _enum_transformer(enums.AutoModRuleEventType)), - 'trigger_metadata': ('trigger', _transform_automod_trigger_metadata), 'actions': (None, _transform_automod_actions), 'exempt_channels': (None, _transform_channels_or_threads), 'exempt_roles': (None, _transform_roles), @@ -370,6 +352,8 @@ class AuditLogChanges: 'available_tags': (None, _transform_forum_tags), 'flags': (None, _transform_overloaded_flags), 'default_reaction_emoji': (None, _transform_default_reaction), + 'emoji_name': ('emoji', _transform_default_emoji), + 'user_id': ('user', _transform_member_id) } # fmt: on @@ -408,6 +392,21 @@ class AuditLogChanges: self._handle_role(self.after, self.before, entry, elem['new_value']) # type: ignore # new_value is a list of roles in this case continue + # special case for automod trigger + if attr == 'trigger_metadata': + # given full metadata dict + self._handle_trigger_metadata(entry, elem, data) # type: ignore # should be trigger metadata + continue + elif entry.action is enums.AuditLogAction.automod_rule_update and attr.startswith('$'): + # on update, some trigger attributes are keys and formatted as $(add/remove)_{attribute} + action, _, trigger_attr = attr.partition('_') + # new_value should be a list of added/removed strings for keyword_filter, regex_patterns, or allow_list + if action == '$add': + self._handle_trigger_attr_update(self.before, self.after, entry, trigger_attr, elem['new_value']) # type: ignore + elif action == '$remove': + self._handle_trigger_attr_update(self.after, self.before, entry, trigger_attr, elem['new_value']) # type: ignore + continue + try: key, transformer = self.TRANSFORMERS[attr] except (ValueError, KeyError): @@ -484,6 +483,76 @@ class AuditLogChanges: guild = entry.guild diff.app_command_permissions.append(AppCommandPermissions(data=data, guild=guild, state=state)) + def _handle_trigger_metadata( + self, + entry: AuditLogEntry, + data: AuditLogChangeTriggerMetadataPayload, + full_data: List[AuditLogChangePayload], + ): + trigger_value: Optional[int] = None + trigger_type: Optional[enums.AutoModRuleTriggerType] = None + + # try to get trigger type from before or after + trigger_type = getattr(self.before, 'trigger_type', getattr(self.after, 'trigger_type', None)) + + if trigger_type is None: + if isinstance(entry.target, AutoModRule): + # Trigger type cannot be changed, so it should be the same before and after updates. + # Avoids checking which keys are in data to guess trigger type + trigger_value = entry.target.trigger.type.value + else: + # found a trigger type from before or after + trigger_value = trigger_type.value + + if trigger_value is None: + # try to find trigger type in the full list of changes + _elem = utils.find(lambda elem: elem['key'] == 'trigger_type', full_data) + if _elem is not None: + trigger_value = _elem.get('old_value', _elem.get('new_value')) # type: ignore # trigger type values should be int + + if trigger_value is None: + # try to infer trigger_type from the keys in old or new value + combined = (data.get('old_value') or {}).keys() | (data.get('new_value') or {}).keys() + if not combined: + trigger_value = enums.AutoModRuleTriggerType.spam.value + elif 'presets' in combined: + trigger_value = enums.AutoModRuleTriggerType.keyword_preset.value + elif 'keyword_filter' in combined or 'regex_patterns' in combined: + trigger_value = enums.AutoModRuleTriggerType.keyword.value + elif 'mention_total_limit' in combined or 'mention_raid_protection_enabled' in combined: + trigger_value = enums.AutoModRuleTriggerType.mention_spam.value + else: + # some unknown type + trigger_value = -1 + + self.before.trigger = AutoModTrigger.from_data(trigger_value, data.get('old_value')) + self.after.trigger = AutoModTrigger.from_data(trigger_value, data.get('new_value')) + + def _handle_trigger_attr_update( + self, first: AuditLogDiff, second: AuditLogDiff, entry: AuditLogEntry, attr: str, data: List[str] + ): + self._create_trigger(first, entry) + trigger = self._create_trigger(second, entry) + try: + # guard unexpecte non list attributes or non iterable data + getattr(trigger, attr).extend(data) + except (AttributeError, TypeError): + pass + + def _create_trigger(self, diff: AuditLogDiff, entry: AuditLogEntry) -> AutoModTrigger: + # check if trigger has already been created + if not hasattr(diff, 'trigger'): + # create a trigger + if isinstance(entry.target, AutoModRule): + # get trigger type from the automod rule + trigger_type = entry.target.trigger.type + else: + # unknown trigger type + trigger_type = enums.try_enum(enums.AutoModRuleTriggerType, -1) + + diff.trigger = AutoModTrigger(type=trigger_type) + return diff.trigger + class _AuditLogProxy: def __init__(self, **kwargs: Any) -> None: @@ -521,7 +590,11 @@ class _AuditLogProxyMessageBulkDelete(_AuditLogProxy): class _AuditLogProxyAutoModAction(_AuditLogProxy): automod_rule_name: str automod_rule_trigger_type: str - channel: Union[abc.GuildChannel, Thread] + channel: Optional[Union[abc.GuildChannel, Thread]] + + +class _AuditLogProxyMemberKickOrMemberRoleUpdate(_AuditLogProxy): + integration_type: Optional[str] class AuditLogEntry(Hashable): @@ -580,6 +653,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, ): @@ -589,6 +663,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: @@ -608,6 +683,7 @@ class AuditLogEntry(Hashable): _AuditLogProxyStageInstanceAction, _AuditLogProxyMessageBulkDelete, _AuditLogProxyAutoModAction, + _AuditLogProxyMemberKickOrMemberRoleUpdate, Member, User, None, PartialIntegration, Role, Object ] = None @@ -632,6 +708,10 @@ class AuditLogEntry(Hashable): elif self.action is enums.AuditLogAction.message_bulk_delete: # The bulk message delete action has the number of messages deleted self.extra = _AuditLogProxyMessageBulkDelete(count=int(extra['count'])) + elif self.action in (enums.AuditLogAction.kick, enums.AuditLogAction.member_role_update): + # The member kick action has a dict with some information + integration_type = extra.get('integration_type') + self.extra = _AuditLogProxyMemberKickOrMemberRoleUpdate(integration_type=integration_type) elif self.action.name.endswith('pin'): # the pin actions have a dict with some information channel_id = int(extra['channel_id']) @@ -644,13 +724,19 @@ class AuditLogEntry(Hashable): or self.action is enums.AuditLogAction.automod_flag_message or self.action is enums.AuditLogAction.automod_timeout_member ): - channel_id = int(extra['channel_id']) + channel_id = utils._get_as_snowflake(extra, 'channel_id') + channel = None + + # May be an empty string instead of None due to a Discord issue + if channel_id: + channel = self.guild.get_channel_or_thread(channel_id) or Object(id=channel_id) + self.extra = _AuditLogProxyAutoModAction( automod_rule_name=extra['auto_moderation_rule_name'], automod_rule_trigger_type=enums.try_enum( enums.AutoModRuleTriggerType, extra['auto_moderation_rule_trigger_type'] ), - channel=self.guild.get_channel_or_thread(channel_id) or Object(id=channel_id), + channel=channel, ) elif self.action.name.startswith('overwrite_'): @@ -760,7 +846,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]: @@ -790,7 +881,13 @@ class AuditLogEntry(Hashable): def _convert_target_emoji(self, target_id: int) -> Union[Emoji, Object]: return self._state.get_emoji(target_id) or Object(id=target_id, type=Emoji) - def _convert_target_message(self, target_id: int) -> Union[Member, User, Object]: + def _convert_target_message(self, target_id: Optional[int]) -> Optional[Union[Member, User, Object]]: + # The message_pin and message_unpin 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_stage_instance(self, target_id: int) -> Union[StageInstance, Object]: @@ -840,3 +937,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/automod.py b/discord/automod.py index c90e80389..61683c269 100644 --- a/discord/automod.py +++ b/discord/automod.py @@ -25,8 +25,7 @@ DEALINGS IN THE SOFTWARE. from __future__ import annotations import datetime -from typing import TYPE_CHECKING, Any, Dict, Optional, List, Sequence, Set, Union, Sequence - +from typing import TYPE_CHECKING, Any, Dict, Optional, List, Set, Union, Sequence, overload, Literal from .enums import AutoModRuleTriggerType, AutoModRuleActionType, AutoModRuleEventType, try_enum from .flags import AutoModPresets @@ -59,6 +58,9 @@ __all__ = ( class AutoModRuleAction: """Represents an auto moderation's rule action. + .. note:: + Only one of ``channel_id``, ``duration``, or ``custom_message`` can be used. + .. versionadded:: 2.0 Attributes @@ -73,40 +75,114 @@ class AutoModRuleAction: The duration of the timeout to apply, if any. Has a maximum of 28 days. Passing this sets :attr:`type` to :attr:`~AutoModRuleActionType.timeout`. + custom_message: Optional[:class:`str`] + A custom message which will be shown to a user when their message is blocked. + Passing this sets :attr:`type` to :attr:`~AutoModRuleActionType.block_message`. + + .. versionadded:: 2.2 """ - __slots__ = ('type', 'channel_id', 'duration') + __slots__ = ('type', 'channel_id', 'duration', 'custom_message') + + @overload + def __init__(self, *, channel_id: int = ...) -> None: + ... + + @overload + def __init__(self, *, type: Literal[AutoModRuleActionType.send_alert_message], channel_id: int = ...) -> None: + ... + + @overload + def __init__(self, *, duration: datetime.timedelta = ...) -> None: + ... + + @overload + def __init__(self, *, type: Literal[AutoModRuleActionType.timeout], duration: datetime.timedelta = ...) -> None: + ... + + @overload + def __init__(self, *, custom_message: str = ...) -> None: + ... + + @overload + def __init__(self, *, type: Literal[AutoModRuleActionType.block_message]) -> None: + ... + + @overload + def __init__(self, *, type: Literal[AutoModRuleActionType.block_message], custom_message: Optional[str] = ...) -> None: + ... + + @overload + def __init__( + self, + *, + type: Optional[AutoModRuleActionType] = ..., + channel_id: Optional[int] = ..., + duration: Optional[datetime.timedelta] = ..., + custom_message: Optional[str] = ..., + ) -> None: + ... + + def __init__( + self, + *, + type: Optional[AutoModRuleActionType] = None, + channel_id: Optional[int] = None, + duration: Optional[datetime.timedelta] = None, + custom_message: Optional[str] = None, + ) -> None: + if sum(v is None for v in (channel_id, duration, custom_message)) < 2: + raise ValueError('Only one of channel_id, duration, or custom_message can be passed.') - def __init__(self, *, channel_id: Optional[int] = None, duration: Optional[datetime.timedelta] = None) -> None: - self.channel_id: Optional[int] = channel_id - self.duration: Optional[datetime.timedelta] = duration - if channel_id and duration: - raise ValueError('Please provide only one of ``channel`` or ``duration``') + self.type: AutoModRuleActionType + self.channel_id: Optional[int] = None + self.duration: Optional[datetime.timedelta] = None + self.custom_message: Optional[str] = None - if channel_id: + if type is not None: + self.type = type + elif channel_id is not None: self.type = AutoModRuleActionType.send_alert_message - elif duration: + elif duration is not None: self.type = AutoModRuleActionType.timeout else: self.type = AutoModRuleActionType.block_message + if self.type is AutoModRuleActionType.send_alert_message: + if channel_id is None: + raise ValueError('channel_id cannot be None if type is send_alert_message') + self.channel_id = channel_id + + if self.type is AutoModRuleActionType.timeout: + if duration is None: + raise ValueError('duration cannot be None set if type is timeout') + self.duration = duration + + if self.type is AutoModRuleActionType.block_message: + self.custom_message = custom_message + def __repr__(self) -> str: return f'' @classmethod def from_data(cls, data: AutoModerationActionPayload) -> Self: - type_ = try_enum(AutoModRuleActionType, data['type']) if data['type'] == AutoModRuleActionType.timeout.value: duration_seconds = data['metadata']['duration_seconds'] return cls(duration=datetime.timedelta(seconds=duration_seconds)) elif data['type'] == AutoModRuleActionType.send_alert_message.value: channel_id = int(data['metadata']['channel_id']) return cls(channel_id=channel_id) - return cls() + elif data['type'] == AutoModRuleActionType.block_message.value: + custom_message = data.get('metadata', {}).get('custom_message') + return cls(type=AutoModRuleActionType.block_message, custom_message=custom_message) + + return cls(type=AutoModRuleActionType.block_member_interactions) def to_dict(self) -> Dict[str, Any]: ret = {'type': self.type.value, 'metadata': {}} - if self.type is AutoModRuleActionType.timeout: + if self.type is AutoModRuleActionType.block_message and self.custom_message is not None: + ret['metadata'] = {'custom_message': self.custom_message} + elif self.type is AutoModRuleActionType.timeout: ret['metadata'] = {'duration_seconds': int(self.duration.total_seconds())} # type: ignore # duration cannot be None here elif self.type is AutoModRuleActionType.send_alert_message: ret['metadata'] = {'channel_id': str(self.channel_id)} @@ -128,7 +204,11 @@ class AutoModTrigger: +-----------------------------------------------+------------------------------------------------+ | :attr:`AutoModRuleTriggerType.keyword_preset` | :attr:`presets`\, :attr:`allow_list` | +-----------------------------------------------+------------------------------------------------+ - | :attr:`AutoModRuleTriggerType.mention_spam` | :attr:`mention_limit` | + | :attr:`AutoModRuleTriggerType.mention_spam` | :attr:`mention_limit`, | + | | :attr:`mention_raid_protection` | + +-----------------------------------------------+------------------------------------------------+ + | :attr:`AutoModRuleTriggerType.member_profile` | :attr:`keyword_filter`, :attr:`regex_patterns`,| + | | :attr:`allow_list` | +-----------------------------------------------+------------------------------------------------+ .. versionadded:: 2.0 @@ -138,14 +218,14 @@ class AutoModTrigger: type: :class:`AutoModRuleTriggerType` The type of trigger. keyword_filter: List[:class:`str`] - The list of strings that will trigger the keyword filter. Maximum of 1000. - Keywords can only be up to 30 characters in length. + The list of strings that will trigger the filter. + Maximum of 1000. Keywords can only be up to 60 characters in length. This could be combined with :attr:`regex_patterns`. regex_patterns: List[:class:`str`] The regex pattern that will trigger the filter. The syntax is based off of `Rust's regex syntax `_. - Maximum of 10. Regex strings can only be up to 250 characters in length. + Maximum of 10. Regex strings can only be up to 260 characters in length. This could be combined with :attr:`keyword_filter` and/or :attr:`allow_list` @@ -153,10 +233,15 @@ class AutoModTrigger: presets: :class:`AutoModPresets` The presets used with the preset keyword filter. allow_list: List[:class:`str`] - The list of words that are exempt from the commonly flagged words. + The list of words that are exempt from the commonly flagged words. Maximum of 100. + Keywords can only be up to 60 characters in length. mention_limit: :class:`int` The total number of user and role mentions a message can contain. Has a maximum of 50. + mention_raid_protection: :class:`bool` + Whether mention raid protection is enabled or not. + + .. versionadded:: 2.4 """ __slots__ = ( @@ -166,6 +251,7 @@ class AutoModTrigger: 'allow_list', 'mention_limit', 'regex_patterns', + 'mention_raid_protection', ) def __init__( @@ -177,9 +263,13 @@ class AutoModTrigger: allow_list: Optional[List[str]] = None, mention_limit: Optional[int] = None, regex_patterns: Optional[List[str]] = None, + mention_raid_protection: Optional[bool] = None, ) -> None: - if type is None and sum(arg is not None for arg in (keyword_filter or regex_patterns, presets, mention_limit)) > 1: - raise ValueError('Please pass only one of keyword_filter, regex_patterns, presets, or mention_limit.') + unique_args = (keyword_filter or regex_patterns, presets, mention_limit or mention_raid_protection) + if type is None and sum(arg is not None for arg in unique_args) > 1: + raise ValueError( + 'Please pass only one of keyword_filter/regex_patterns, presets, or mention_limit/mention_raid_protection.' + ) if type is not None: self.type = type @@ -187,17 +277,18 @@ class AutoModTrigger: self.type = AutoModRuleTriggerType.keyword elif presets is not None: self.type = AutoModRuleTriggerType.keyword_preset - elif mention_limit is not None: + elif mention_limit is not None or mention_raid_protection is not None: self.type = AutoModRuleTriggerType.mention_spam else: raise ValueError( - 'Please pass the trigger type explicitly if not using keyword_filter, presets, or mention_limit.' + 'Please pass the trigger type explicitly if not using keyword_filter, regex_patterns, presets, mention_limit, or mention_raid_protection.' ) self.keyword_filter: List[str] = keyword_filter if keyword_filter is not None else [] self.presets: AutoModPresets = presets if presets is not None else AutoModPresets() self.allow_list: List[str] = allow_list if allow_list is not None else [] self.mention_limit: int = mention_limit if mention_limit is not None else 0 + self.mention_raid_protection: bool = mention_raid_protection if mention_raid_protection is not None else False self.regex_patterns: List[str] = regex_patterns if regex_patterns is not None else [] def __repr__(self) -> str: @@ -213,7 +304,7 @@ class AutoModTrigger: type_ = try_enum(AutoModRuleTriggerType, type) if data is None: return cls(type=type_) - elif type_ is AutoModRuleTriggerType.keyword: + elif type_ in (AutoModRuleTriggerType.keyword, AutoModRuleTriggerType.member_profile): return cls( type=type_, keyword_filter=data.get('keyword_filter'), @@ -225,12 +316,16 @@ class AutoModTrigger: type=type_, presets=AutoModPresets._from_value(data.get('presets', [])), allow_list=data.get('allow_list') ) elif type_ is AutoModRuleTriggerType.mention_spam: - return cls(type=type_, mention_limit=data.get('mention_total_limit')) + return cls( + type=type_, + mention_limit=data.get('mention_total_limit'), + mention_raid_protection=data.get('mention_raid_protection_enabled'), + ) else: return cls(type=type_) def to_metadata_dict(self) -> Optional[Dict[str, Any]]: - if self.type is AutoModRuleTriggerType.keyword: + if self.type in (AutoModRuleTriggerType.keyword, AutoModRuleTriggerType.member_profile): return { 'keyword_filter': self.keyword_filter, 'regex_patterns': self.regex_patterns, @@ -239,7 +334,10 @@ class AutoModTrigger: elif self.type is AutoModRuleTriggerType.keyword_preset: return {'presets': self.presets.to_array(), 'allow_list': self.allow_list} elif self.type is AutoModRuleTriggerType.mention_spam: - return {'mention_total_limit': self.mention_limit} + return { + 'mention_total_limit': self.mention_limit, + 'mention_raid_protection_enabled': self.mention_raid_protection, + } class AutoModRule: @@ -265,6 +363,8 @@ class AutoModRule: The IDs of the roles that are exempt from the rule. exempt_channel_ids: Set[:class:`int`] The IDs of the channels that are exempt from the rule. + event_type: :class:`AutoModRuleEventType` + The type of event that will trigger the the rule. """ __slots__ = ( @@ -418,7 +518,7 @@ class AutoModRule: payload['name'] = name if event_type is not MISSING: - payload['event_type'] = event_type + payload['event_type'] = event_type.value if trigger is not MISSING: trigger_metadata = trigger.to_metadata_dict() @@ -441,7 +541,7 @@ class AutoModRule: **payload, ) - return AutoModRule(data=data, guild=self.guild, state=self._state) + return self.__class__(data=data, guild=self.guild, state=self._state) async def delete(self, *, reason: str = MISSING) -> None: """|coro| diff --git a/discord/channel.py b/discord/channel.py index 8aecf7838..a306707d6 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -47,7 +47,16 @@ import datetime import discord.abc from .scheduled_event import ScheduledEvent from .permissions import PermissionOverwrite, Permissions -from .enums import ChannelType, ForumLayoutType, PrivacyLevel, try_enum, VideoQualityMode, EntityType +from .enums import ( + ChannelType, + ForumLayoutType, + ForumOrderType, + PrivacyLevel, + try_enum, + VideoQualityMode, + EntityType, + VoiceChannelEffectAnimationType, +) from .mixins import Hashable from . import utils from .utils import MISSING @@ -56,8 +65,10 @@ from .errors import ClientException from .stage_instance import StageInstance from .threads import Thread from .partial_emoji import _EmojiTag, PartialEmoji -from .flags import ChannelFlags +from .flags import ChannelFlags, MessageFlags from .http import handle_message_parameters +from .object import Object +from .soundboard import BaseSoundboardSound, SoundboardDefaultSound __all__ = ( 'TextChannel', @@ -69,6 +80,8 @@ __all__ = ( 'ForumChannel', 'GroupChannel', 'PartialMessageable', + 'VoiceChannelEffect', + 'VoiceChannelSoundEffect', ) if TYPE_CHECKING: @@ -76,7 +89,6 @@ if TYPE_CHECKING: from .types.threads import ThreadArchiveDuration from .role import Role - from .object import Object from .member import Member, VoiceState from .abc import Snowflake, SnowflakeTime from .embeds import Embed @@ -98,9 +110,13 @@ if TYPE_CHECKING: CategoryChannel as CategoryChannelPayload, GroupDMChannel as GroupChannelPayload, ForumChannel as ForumChannelPayload, + MediaChannel as MediaChannelPayload, ForumTag as ForumTagPayload, + VoiceChannelEffect as VoiceChannelEffectPayload, ) from .types.snowflake import SnowflakeList + from .types.soundboard import BaseSoundboardSound as BaseSoundboardSoundPayload + from .soundboard import SoundboardSound OverwriteKeyT = TypeVar('OverwriteKeyT', Role, BaseUser, Object, Union[Role, Member, Object]) @@ -110,6 +126,121 @@ class ThreadWithMessage(NamedTuple): message: Message +class VoiceChannelEffectAnimation(NamedTuple): + id: int + type: VoiceChannelEffectAnimationType + + +class VoiceChannelSoundEffect(BaseSoundboardSound): + """Represents a Discord voice channel sound effect. + + .. versionadded:: 2.5 + + .. container:: operations + + .. describe:: x == y + + Checks if two sound effects are equal. + + .. describe:: x != y + + Checks if two sound effects are not equal. + + .. describe:: hash(x) + + Returns the sound effect's hash. + + Attributes + ------------ + id: :class:`int` + The ID of the sound. + volume: :class:`float` + The volume of the sound as floating point percentage (e.g. ``1.0`` for 100%). + """ + + __slots__ = ('_state',) + + def __init__(self, *, state: ConnectionState, id: int, volume: float): + data: BaseSoundboardSoundPayload = { + 'sound_id': id, + 'volume': volume, + } + super().__init__(state=state, data=data) + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} id={self.id} volume={self.volume}>" + + @property + def created_at(self) -> Optional[datetime.datetime]: + """Optional[:class:`datetime.datetime`]: Returns the snowflake's creation time in UTC. + Returns ``None`` if it's a default sound.""" + if self.is_default(): + return None + else: + return utils.snowflake_time(self.id) + + def is_default(self) -> bool: + """:class:`bool`: Whether it's a default sound or not.""" + # if it's smaller than the Discord Epoch it cannot be a snowflake + return self.id < utils.DISCORD_EPOCH + + +class VoiceChannelEffect: + """Represents a Discord voice channel effect. + + .. versionadded:: 2.5 + + Attributes + ------------ + channel: :class:`VoiceChannel` + The channel in which the effect is sent. + user: Optional[:class:`Member`] + The user who sent the effect. ``None`` if not found in cache. + animation: Optional[:class:`VoiceChannelEffectAnimation`] + The animation the effect has. Returns ``None`` if the effect has no animation. + emoji: Optional[:class:`PartialEmoji`] + The emoji of the effect. + sound: Optional[:class:`VoiceChannelSoundEffect`] + The sound of the effect. Returns ``None`` if it's an emoji effect. + """ + + __slots__ = ('channel', 'user', 'animation', 'emoji', 'sound') + + def __init__(self, *, state: ConnectionState, data: VoiceChannelEffectPayload, guild: Guild): + self.channel: VoiceChannel = guild.get_channel(int(data['channel_id'])) # type: ignore # will always be a VoiceChannel + self.user: Optional[Member] = guild.get_member(int(data['user_id'])) + self.animation: Optional[VoiceChannelEffectAnimation] = None + + animation_id = data.get('animation_id') + if animation_id is not None: + animation_type = try_enum(VoiceChannelEffectAnimationType, data['animation_type']) # type: ignore # cannot be None here + self.animation = VoiceChannelEffectAnimation(id=animation_id, type=animation_type) + + emoji = data.get('emoji') + self.emoji: Optional[PartialEmoji] = PartialEmoji.from_dict(emoji) if emoji is not None else None + self.sound: Optional[VoiceChannelSoundEffect] = None + + sound_id: Optional[int] = utils._get_as_snowflake(data, 'sound_id') + if sound_id is not None: + sound_volume = data.get('sound_volume') or 0.0 + self.sound = VoiceChannelSoundEffect(state=state, id=sound_id, volume=sound_volume) + + def __repr__(self) -> str: + attrs = [ + ('channel', self.channel), + ('user', self.user), + ('animation', self.animation), + ('emoji', self.emoji), + ('sound', self.sound), + ] + inner = ' '.join('%s=%r' % t for t in attrs) + return f"<{self.__class__.__name__} {inner}>" + + def is_sound(self) -> bool: + """:class:`bool`: Whether the effect is a sound or not.""" + return self.sound is not None + + class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable): """Represents a Discord guild text channel. @@ -160,6 +291,10 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable): The default auto archive duration in minutes for threads created in this channel. .. versionadded:: 2.0 + default_thread_slowmode_delay: :class:`int` + The default slowmode delay in seconds for threads created in this channel. + + .. versionadded:: 2.3 """ __slots__ = ( @@ -176,6 +311,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable): '_type', 'last_message_id', 'default_auto_archive_duration', + 'default_thread_slowmode_delay', ) def __init__(self, *, state: ConnectionState, guild: Guild, data: Union[TextChannelPayload, NewsChannelPayload]): @@ -206,6 +342,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable): # Does this need coercion into `int`? No idea yet. self.slowmode_delay: int = data.get('rate_limit_per_user', 0) self.default_auto_archive_duration: ThreadArchiveDuration = data.get('default_auto_archive_duration', 1440) + self.default_thread_slowmode_delay: int = data.get('default_thread_rate_limit_per_user', 0) self._type: Literal[0, 5] = data.get('type', self._type) self.last_message_id: Optional[int] = utils._get_as_snowflake(data, 'last_message_id') self._fill_overwrites(data) @@ -301,6 +438,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable): category: Optional[CategoryChannel] = ..., slowmode_delay: int = ..., default_auto_archive_duration: ThreadArchiveDuration = ..., + default_thread_slowmode_delay: int = ..., type: ChannelType = ..., overwrites: Mapping[OverwriteKeyT, PermissionOverwrite] = ..., ) -> TextChannel: @@ -359,7 +497,10 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable): Must be one of ``60``, ``1440``, ``4320``, or ``10080``. .. versionadded:: 2.0 + default_thread_slowmode_delay: :class:`int` + The new default slowmode delay in seconds for threads created in this channel. + .. versionadded:: 2.3 Raises ------ ValueError @@ -384,9 +525,26 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable): return self.__class__(state=self._state, guild=self.guild, data=payload) # type: ignore @utils.copy_doc(discord.abc.GuildChannel.clone) - async def clone(self, *, name: Optional[str] = None, reason: Optional[str] = None) -> TextChannel: + async def clone( + self, + *, + name: Optional[str] = None, + category: Optional[CategoryChannel] = None, + reason: Optional[str] = None, + ) -> TextChannel: + base: Dict[Any, Any] = { + 'topic': self.topic, + 'nsfw': self.nsfw, + 'default_auto_archive_duration': self.default_auto_archive_duration, + 'default_thread_rate_limit_per_user': self.default_thread_slowmode_delay, + } + if not self.is_news(): + base['rate_limit_per_user'] = self.slowmode_delay return await self._clone_impl( - {'topic': self.topic, 'nsfw': self.nsfw, 'rate_limit_per_user': self.slowmode_delay}, name=name, reason=reason + base, + name=name, + category=category, + reason=reason, ) async def delete_messages(self, messages: Iterable[Snowflake], *, reason: Optional[str] = None) -> None: @@ -727,7 +885,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. @@ -766,7 +924,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable): self.id, name=name, auto_archive_duration=auto_archive_duration or self.default_auto_archive_duration, - type=type.value, + type=type.value, # type: ignore # we're assuming that the user is passing a valid variant reason=reason, invitable=invitable, rate_limit_per_user=slowmode_delay, @@ -878,7 +1036,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable): before_timestamp = update_before(threads[-1]) -class VocalGuildChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hashable): +class VocalGuildChannel(discord.abc.Messageable, discord.abc.Connectable, discord.abc.GuildChannel, Hashable): __slots__ = ( 'name', 'id', @@ -901,6 +1059,9 @@ class VocalGuildChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hasha self.id: int = int(data['id']) self._update(guild, data) + async def _get_channel(self) -> Self: + return self + def _get_voice_client_key(self) -> Tuple[int, str]: return self.guild.id, 'guild_id' @@ -988,103 +1149,6 @@ class VocalGuildChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hasha base.value &= ~denied.value return base - -class VoiceChannel(discord.abc.Messageable, VocalGuildChannel): - """Represents a Discord guild voice channel. - - .. container:: operations - - .. describe:: x == y - - Checks if two channels are equal. - - .. describe:: x != y - - Checks if two channels are not equal. - - .. describe:: hash(x) - - Returns the channel's hash. - - .. describe:: str(x) - - Returns the channel's name. - - Attributes - ----------- - name: :class:`str` - The channel name. - guild: :class:`Guild` - The guild the channel belongs to. - id: :class:`int` - The channel ID. - nsfw: :class:`bool` - If the channel is marked as "not safe for work" or "age restricted". - - .. versionadded:: 2.0 - category_id: Optional[:class:`int`] - The category channel ID this channel belongs to, if applicable. - position: :class:`int` - The position in the channel list. This is a number that starts at 0. e.g. the - top channel is position 0. - bitrate: :class:`int` - The channel's preferred audio bitrate in bits per second. - user_limit: :class:`int` - The channel's limit for number of members that can be in a voice channel. - rtc_region: Optional[:class:`str`] - The region for the voice channel's voice communication. - A value of ``None`` indicates automatic voice region detection. - - .. versionadded:: 1.7 - - .. versionchanged:: 2.0 - The type of this attribute has changed to :class:`str`. - video_quality_mode: :class:`VideoQualityMode` - The camera video quality for the voice channel's participants. - - .. versionadded:: 2.0 - last_message_id: Optional[:class:`int`] - The last message ID of the message sent to this channel. It may - *not* point to an existing or valid message. - - .. versionadded:: 2.0 - slowmode_delay: :class:`int` - The number of seconds a member must wait between sending messages - in this channel. A value of ``0`` denotes that it is disabled. - Bots and users with :attr:`~Permissions.manage_channels` or - :attr:`~Permissions.manage_messages` bypass slowmode. - - .. versionadded:: 2.2 - """ - - __slots__ = () - - def __repr__(self) -> str: - attrs = [ - ('id', self.id), - ('name', self.name), - ('rtc_region', self.rtc_region), - ('position', self.position), - ('bitrate', self.bitrate), - ('video_quality_mode', self.video_quality_mode), - ('user_limit', self.user_limit), - ('category_id', self.category_id), - ] - joined = ' '.join('%s=%r' % t for t in attrs) - return f'<{self.__class__.__name__} {joined}>' - - async def _get_channel(self) -> Self: - return self - - @property - def _scheduled_event_entity_type(self) -> Optional[EntityType]: - return EntityType.voice - - @property - def type(self) -> Literal[ChannelType.voice]: - """:class:`ChannelType`: The channel's Discord type.""" - return ChannelType.voice - @property def last_message(self) -> Optional[Message]: """Retrieves the last message from this channel in cache. @@ -1129,7 +1193,7 @@ class VoiceChannel(discord.abc.Messageable, VocalGuildChannel): from .message import PartialMessage - return PartialMessage(channel=self, id=message_id) + return PartialMessage(channel=self, id=message_id) # type: ignore # VocalGuildChannel is an impl detail async def delete_messages(self, messages: Iterable[Snowflake], *, reason: Optional[str] = None) -> None: """|coro| @@ -1333,8 +1397,119 @@ class VoiceChannel(discord.abc.Messageable, VocalGuildChannel): return Webhook.from_state(data, state=self._state) @utils.copy_doc(discord.abc.GuildChannel.clone) - async def clone(self, *, name: Optional[str] = None, reason: Optional[str] = None) -> VoiceChannel: - return await self._clone_impl({'bitrate': self.bitrate, 'user_limit': self.user_limit}, name=name, reason=reason) + async def clone( + self, *, name: Optional[str] = None, category: Optional[CategoryChannel] = None, reason: Optional[str] = None + ) -> Self: + base = { + 'bitrate': self.bitrate, + 'user_limit': self.user_limit, + 'rate_limit_per_user': self.slowmode_delay, + 'nsfw': self.nsfw, + 'video_quality_mode': self.video_quality_mode.value, + } + if self.rtc_region: + base['rtc_region'] = self.rtc_region + + return await self._clone_impl( + base, + name=name, + category=category, + reason=reason, + ) + + +class VoiceChannel(VocalGuildChannel): + """Represents a Discord guild voice channel. + + .. container:: operations + + .. describe:: x == y + + Checks if two channels are equal. + + .. describe:: x != y + + Checks if two channels are not equal. + + .. describe:: hash(x) + + Returns the channel's hash. + + .. describe:: str(x) + + Returns the channel's name. + + Attributes + ----------- + name: :class:`str` + The channel name. + guild: :class:`Guild` + The guild the channel belongs to. + id: :class:`int` + The channel ID. + nsfw: :class:`bool` + If the channel is marked as "not safe for work" or "age restricted". + + .. versionadded:: 2.0 + category_id: Optional[:class:`int`] + The category channel ID this channel belongs to, if applicable. + position: :class:`int` + The position in the channel list. This is a number that starts at 0. e.g. the + top channel is position 0. + bitrate: :class:`int` + The channel's preferred audio bitrate in bits per second. + user_limit: :class:`int` + The channel's limit for number of members that can be in a voice channel. + rtc_region: Optional[:class:`str`] + The region for the voice channel's voice communication. + A value of ``None`` indicates automatic voice region detection. + + .. versionadded:: 1.7 + + .. versionchanged:: 2.0 + The type of this attribute has changed to :class:`str`. + video_quality_mode: :class:`VideoQualityMode` + The camera video quality for the voice channel's participants. + + .. versionadded:: 2.0 + last_message_id: Optional[:class:`int`] + The last message ID of the message sent to this channel. It may + *not* point to an existing or valid message. + + .. versionadded:: 2.0 + slowmode_delay: :class:`int` + The number of seconds a member must wait between sending messages + in this channel. A value of ``0`` denotes that it is disabled. + Bots and users with :attr:`~Permissions.manage_channels` or + :attr:`~Permissions.manage_messages` bypass slowmode. + + .. versionadded:: 2.2 + """ + + __slots__ = () + + def __repr__(self) -> str: + attrs = [ + ('id', self.id), + ('name', self.name), + ('rtc_region', self.rtc_region), + ('position', self.position), + ('bitrate', self.bitrate), + ('video_quality_mode', self.video_quality_mode), + ('user_limit', self.user_limit), + ('category_id', self.category_id), + ] + joined = ' '.join('%s=%r' % t for t in attrs) + return f'<{self.__class__.__name__} {joined}>' + + @property + def _scheduled_event_entity_type(self) -> Optional[EntityType]: + return EntityType.voice + + @property + def type(self) -> Literal[ChannelType.voice]: + """:class:`ChannelType`: The channel's Discord type.""" + return ChannelType.voice @overload async def edit(self) -> None: @@ -1358,6 +1533,8 @@ class VoiceChannel(discord.abc.Messageable, VocalGuildChannel): overwrites: Mapping[OverwriteKeyT, PermissionOverwrite] = ..., rtc_region: Optional[str] = ..., video_quality_mode: VideoQualityMode = ..., + slowmode_delay: int = ..., + status: Optional[str] = ..., reason: Optional[str] = ..., ) -> VoiceChannel: ... @@ -1417,6 +1594,11 @@ class VoiceChannel(discord.abc.Messageable, VocalGuildChannel): The camera video quality for the voice channel's participants. .. versionadded:: 2.0 + status: Optional[:class:`str`] + The new voice channel status. It can be up to 500 characters. + Can be ``None`` to remove the status. + + .. versionadded:: 2.4 Raises ------ @@ -1438,6 +1620,35 @@ class VoiceChannel(discord.abc.Messageable, VocalGuildChannel): # the payload will always be the proper channel payload return self.__class__(state=self._state, guild=self.guild, data=payload) # type: ignore + async def send_sound(self, sound: Union[SoundboardSound, SoundboardDefaultSound], /) -> None: + """|coro| + + Sends a soundboard sound for this channel. + + You must have :attr:`~Permissions.speak` and :attr:`~Permissions.use_soundboard` to do this. + Additionally, you must have :attr:`~Permissions.use_external_sounds` if the sound is from + a different guild. + + .. versionadded:: 2.5 + + Parameters + ----------- + sound: Union[:class:`SoundboardSound`, :class:`SoundboardDefaultSound`] + The sound to send for this channel. + + Raises + ------- + Forbidden + You do not have permissions to send a sound for this channel. + HTTPException + Sending the sound failed. + """ + payload = {'sound_id': sound.id} + if not isinstance(sound, SoundboardDefaultSound) and self.guild.id != sound.guild.id: + payload['source_guild_id'] = sound.guild.id + + await self._state.http.send_soundboard_sound(self.id, **payload) + class StageChannel(VocalGuildChannel): """Represents a Discord guild stage channel. @@ -1492,6 +1703,11 @@ class StageChannel(VocalGuildChannel): The camera video quality for the stage channel's participants. .. versionadded:: 2.0 + last_message_id: Optional[:class:`int`] + The last message ID of the message sent to this channel. It may + *not* point to an existing or valid message. + + .. versionadded:: 2.2 slowmode_delay: :class:`int` The number of seconds a member must wait between sending messages in this channel. A value of ``0`` denotes that it is disabled. @@ -1565,10 +1781,6 @@ class StageChannel(VocalGuildChannel): """:class:`ChannelType`: The channel's Discord type.""" return ChannelType.stage_voice - @utils.copy_doc(discord.abc.GuildChannel.clone) - async def clone(self, *, name: Optional[str] = None, reason: Optional[str] = None) -> StageChannel: - return await self._clone_impl({}, name=name, reason=reason) - @property def instance(self) -> Optional[StageInstance]: """Optional[:class:`StageInstance`]: The running stage instance of the stage channel. @@ -1578,7 +1790,13 @@ class StageChannel(VocalGuildChannel): return utils.get(self.guild.stage_instances, channel_id=self.id) async def create_instance( - self, *, topic: str, privacy_level: PrivacyLevel = MISSING, reason: Optional[str] = None + self, + *, + topic: str, + privacy_level: PrivacyLevel = MISSING, + send_start_notification: bool = False, + scheduled_event: Snowflake = MISSING, + reason: Optional[str] = None, ) -> StageInstance: """|coro| @@ -1594,6 +1812,15 @@ class StageChannel(VocalGuildChannel): The stage instance's topic. privacy_level: :class:`PrivacyLevel` The stage instance's privacy level. Defaults to :attr:`PrivacyLevel.guild_only`. + send_start_notification: :class:`bool` + Whether to send a start notification. This sends a push notification to @everyone if ``True``. Defaults to ``False``. + You must have :attr:`~Permissions.mention_everyone` to do this. + + .. versionadded:: 2.3 + scheduled_event: :class:`~discord.abc.Snowflake` + The guild scheduled event associated with the stage instance. + + .. versionadded:: 2.4 reason: :class:`str` The reason the stage instance was created. Shows up on the audit log. @@ -1620,6 +1847,11 @@ class StageChannel(VocalGuildChannel): payload['privacy_level'] = privacy_level.value + if scheduled_event is not MISSING: + payload['guild_scheduled_event_id'] = scheduled_event.id + + payload['send_start_notification'] = send_start_notification + data = await self._state.http.create_stage_instance(**payload, reason=reason) return StageInstance(guild=self.guild, state=self._state, data=data) @@ -1659,12 +1891,15 @@ class StageChannel(VocalGuildChannel): *, name: str = ..., nsfw: bool = ..., + bitrate: int = ..., + user_limit: int = ..., position: int = ..., sync_permissions: int = ..., category: Optional[CategoryChannel] = ..., overwrites: Mapping[OverwriteKeyT, PermissionOverwrite] = ..., rtc_region: Optional[str] = ..., video_quality_mode: VideoQualityMode = ..., + slowmode_delay: int = ..., reason: Optional[str] = ..., ) -> StageChannel: ... @@ -1693,10 +1928,14 @@ class StageChannel(VocalGuildChannel): ---------- name: :class:`str` The new channel's name. + bitrate: :class:`int` + The new channel's bitrate. position: :class:`int` The new channel's position. nsfw: :class:`bool` To mark the channel as NSFW or not. + user_limit: :class:`int` + The new channel's user limit. sync_permissions: :class:`bool` Whether to sync permissions with the channel's new or pre-existing category. Defaults to ``False``. @@ -1819,7 +2058,13 @@ class CategoryChannel(discord.abc.GuildChannel, Hashable): return self.nsfw @utils.copy_doc(discord.abc.GuildChannel.clone) - async def clone(self, *, name: Optional[str] = None, reason: Optional[str] = None) -> CategoryChannel: + async def clone( + self, + *, + name: Optional[str] = None, + category: Optional[CategoryChannel] = None, + reason: Optional[str] = None, + ) -> CategoryChannel: return await self._clone_impl({'nsfw': self.nsfw}, name=name, reason=reason) @overload @@ -1939,6 +2184,16 @@ class CategoryChannel(discord.abc.GuildChannel, Hashable): ret.sort(key=lambda c: (c.position, c.id)) return ret + @property + def forums(self) -> List[ForumChannel]: + """List[:class:`ForumChannel`]: Returns the forum channels that are under this category. + + .. versionadded:: 2.4 + """ + r = [c for c in self.guild.channels if c.category_id == self.id and isinstance(c, ForumChannel)] + r.sort(key=lambda c: (c.position, c.id)) + return r + async def create_text_channel(self, name: str, **options: Any) -> TextChannel: """|coro| @@ -2147,6 +2402,10 @@ class ForumChannel(discord.abc.GuildChannel, Hashable): Defaults to :attr:`ForumLayoutType.not_set`. .. versionadded:: 2.2 + default_sort_order: Optional[:class:`ForumOrderType`] + The default sort order for posts in this forum channel. + + .. versionadded:: 2.3 """ __slots__ = ( @@ -2156,6 +2415,7 @@ class ForumChannel(discord.abc.GuildChannel, Hashable): 'topic', '_state', '_flags', + '_type', 'nsfw', 'category_id', 'position', @@ -2166,13 +2426,15 @@ class ForumChannel(discord.abc.GuildChannel, Hashable): 'default_thread_slowmode_delay', 'default_reaction_emoji', 'default_layout', + 'default_sort_order', '_available_tags', '_flags', ) - def __init__(self, *, state: ConnectionState, guild: Guild, data: ForumChannelPayload): + def __init__(self, *, state: ConnectionState, guild: Guild, data: Union[ForumChannelPayload, MediaChannelPayload]): self._state: ConnectionState = state self.id: int = int(data['id']) + self._type: Literal[15, 16] = data['type'] self._update(guild, data) def __repr__(self) -> str: @@ -2186,7 +2448,7 @@ class ForumChannel(discord.abc.GuildChannel, Hashable): joined = ' '.join('%s=%r' % t for t in attrs) return f'<{self.__class__.__name__} {joined}>' - def _update(self, guild: Guild, data: ForumChannelPayload) -> None: + def _update(self, guild: Guild, data: Union[ForumChannelPayload, MediaChannelPayload]) -> None: self.guild: Guild = guild self.name: str = data['name'] self.category_id: Optional[int] = utils._get_as_snowflake(data, 'parent_id') @@ -2211,18 +2473,33 @@ class ForumChannel(discord.abc.GuildChannel, Hashable): name=default_reaction_emoji.get('emoji_name') or '', ) + self.default_sort_order: Optional[ForumOrderType] = None + default_sort_order = data.get('default_sort_order') + if default_sort_order is not None: + self.default_sort_order = try_enum(ForumOrderType, default_sort_order) + self._flags: int = data.get('flags', 0) self._fill_overwrites(data) @property - def type(self) -> Literal[ChannelType.forum]: + def type(self) -> Literal[ChannelType.forum, ChannelType.media]: """:class:`ChannelType`: The channel's Discord type.""" + if self._type == 16: + return ChannelType.media return ChannelType.forum @property def _sorting_bucket(self) -> int: return ChannelType.text.value + @property + def members(self) -> List[Member]: + """List[:class:`Member`]: Returns all members that can see this channel. + + .. versionadded:: 2.5 + """ + return [m for m in self.guild.members if self.permissions_for(m).read_messages] + @property def _scheduled_event_entity_type(self) -> Optional[EntityType]: return None @@ -2304,10 +2581,41 @@ class ForumChannel(discord.abc.GuildChannel, Hashable): """:class:`bool`: Checks if the forum is NSFW.""" return self.nsfw + def is_media(self) -> bool: + """:class:`bool`: Checks if the channel is a media channel. + + .. versionadded:: 2.4 + """ + return self._type == ChannelType.media.value + @utils.copy_doc(discord.abc.GuildChannel.clone) - async def clone(self, *, name: Optional[str] = None, reason: Optional[str] = None) -> ForumChannel: + async def clone( + self, + *, + name: Optional[str] = None, + category: Optional[CategoryChannel], + reason: Optional[str] = None, + ) -> ForumChannel: + base = { + 'topic': self.topic, + 'rate_limit_per_user': self.slowmode_delay, + 'nsfw': self.nsfw, + 'default_auto_archive_duration': self.default_auto_archive_duration, + 'available_tags': [tag.to_dict() for tag in self.available_tags], + 'default_thread_rate_limit_per_user': self.default_thread_slowmode_delay, + } + if self.default_sort_order: + base['default_sort_order'] = self.default_sort_order.value + if self.default_reaction_emoji: + base['default_reaction_emoji'] = self.default_reaction_emoji._to_forum_tag_payload() + if not self.is_media() and self.default_layout: + base['default_forum_layout'] = self.default_layout.value + return await self._clone_impl( - {'topic': self.topic, 'nsfw': self.nsfw, 'rate_limit_per_user': self.slowmode_delay}, name=name, reason=reason + base, + name=name, + category=category, + reason=reason, ) @overload @@ -2337,6 +2645,7 @@ class ForumChannel(discord.abc.GuildChannel, Hashable): default_thread_slowmode_delay: int = ..., default_reaction_emoji: Optional[EmojiInputType] = ..., default_layout: ForumLayoutType = ..., + default_sort_order: ForumOrderType = ..., require_tag: bool = ..., ) -> ForumChannel: ... @@ -2395,6 +2704,10 @@ class ForumChannel(discord.abc.GuildChannel, Hashable): The new default layout for posts in this forum. .. versionadded:: 2.2 + default_sort_order: Optional[:class:`ForumOrderType`] + The new default sort order for posts in this forum. + + .. versionadded:: 2.3 require_tag: :class:`bool` Whether to require a tag for threads in this channel or not. @@ -2457,6 +2770,21 @@ class ForumChannel(discord.abc.GuildChannel, Hashable): options['default_forum_layout'] = layout.value + try: + sort_order = options.pop('default_sort_order') + except KeyError: + pass + else: + if sort_order is None: + options['default_sort_order'] = None + else: + if not isinstance(sort_order, ForumOrderType): + raise TypeError( + f'default_sort_order parameter must be a ForumOrderType not {sort_order.__class__.__name__}' + ) + + options['default_sort_order'] = sort_order.value + payload = await self._edit(options, reason=reason) if payload is not None: # the payload will always be the proper channel payload @@ -2548,7 +2876,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. @@ -2618,8 +2946,6 @@ class ForumChannel(discord.abc.GuildChannel, Hashable): raise TypeError(f'view parameter must be View not {view.__class__.__name__}') if suppress_embeds: - from .message import MessageFlags # circular import - flags = MessageFlags._from_value(4) else: flags = MISSING @@ -2818,17 +3144,21 @@ class DMChannel(discord.abc.Messageable, discord.abc.PrivateChannel, Hashable): The user you are participating with in the direct message channel. If this channel is received through the gateway, the recipient information may not be always available. + recipients: List[:class:`User`] + The users you are participating with in the DM channel. + + .. versionadded:: 2.4 me: :class:`ClientUser` The user presenting yourself. id: :class:`int` The direct message channel ID. """ - __slots__ = ('id', 'recipient', 'me', '_state') + __slots__ = ('id', 'recipients', 'me', '_state') def __init__(self, *, me: ClientUser, state: ConnectionState, data: DMChannelPayload): self._state: ConnectionState = state - self.recipient: Optional[User] = state.store_user(data['recipients'][0]) + self.recipients: List[User] = [state.store_user(u) for u in data.get('recipients', [])] self.me: ClientUser = me self.id: int = int(data['id']) @@ -2848,11 +3178,17 @@ class DMChannel(discord.abc.Messageable, discord.abc.PrivateChannel, Hashable): self = cls.__new__(cls) self._state = state self.id = channel_id - self.recipient = None + self.recipients = [] # state.user won't be None here self.me = state.user # type: ignore return self + @property + def recipient(self) -> Optional[User]: + if self.recipients: + return self.recipients[0] + return None + @property def type(self) -> Literal[ChannelType.private]: """:class:`ChannelType`: The channel's Discord type.""" @@ -3201,6 +3537,14 @@ class PartialMessageable(discord.abc.Messageable, Hashable): return Permissions.none() + @property + def mention(self) -> str: + """:class:`str`: Returns a string that allows you to mention the channel. + + .. versionadded:: 2.5 + """ + return f'<#{self.id}>' + def get_partial_message(self, message_id: int, /) -> PartialMessage: """Creates a :class:`PartialMessage` from the message ID. @@ -3237,6 +3581,8 @@ def _guild_channel_factory(channel_type: int): return StageChannel, value elif value is ChannelType.forum: return ForumChannel, value + elif value is ChannelType.media: + return ForumChannel, value else: return None, value diff --git a/discord/client.py b/discord/client.py index c53b0f3f3..68422435b 100644 --- a/discord/client.py +++ b/discord/client.py @@ -48,14 +48,15 @@ from typing import ( import aiohttp +from .sku import SKU, Entitlement from .user import User, ClientUser from .invite import Invite from .template import Template from .widget import Widget -from .guild import Guild +from .guild import Guild, GuildPreview from .emoji import Emoji from .channel import _threaded_channel_factory, PartialMessageable -from .enums import ChannelType +from .enums import ChannelType, EntitlementOwnerType from .mentions import AllowedMentions from .errors import * from .enums import Status @@ -66,15 +67,17 @@ from .voice_client import VoiceClient from .http import HTTPClient from .state import ConnectionState from . import utils -from .utils import MISSING, time_snowflake +from .utils import MISSING, time_snowflake, deprecated from .object import Object from .backoff import ExponentialBackoff from .webhook import Webhook from .appinfo import AppInfo from .ui.view import View +from .ui.dynamic import DynamicItem from .stage_instance import StageInstance from .threads import Thread from .sticker import GuildSticker, StandardSticker, StickerPack, _sticker_factory +from .soundboard import SoundboardDefaultSound, SoundboardSound if TYPE_CHECKING: from types import TracebackType @@ -105,14 +108,18 @@ if TYPE_CHECKING: RawThreadMembersUpdate, RawThreadUpdateEvent, RawTypingEvent, + RawPollVoteActionEvent, ) from .reaction import Reaction from .role import Role from .scheduled_event import ScheduledEvent from .threads import ThreadMember from .types.guild import Guild as GuildPayload + from .ui.item import Item from .voice_client import VoiceProtocol from .audit_logs import AuditLogEntry + from .poll import PollAnswer + from .subscription import Subscription # fmt: off @@ -230,6 +237,15 @@ class Client: To enable these events, this must be set to ``True``. Defaults to ``False``. .. versionadded:: 2.0 + enable_raw_presences: :class:`bool` + Whether to manually enable or disable the :func:`on_raw_presence_update` event. + + Setting this flag to ``True`` requires :attr:`Intents.presences` to be enabled. + + By default, this flag is set to ``True`` only when :attr:`Intents.presences` is enabled and :attr:`Intents.members` + is disabled, otherwise it's set to ``False``. + + .. versionadded:: 2.5 http_trace: :class:`aiohttp.TraceConfig` The trace configuration to use for tracking HTTP requests the library does using ``aiohttp``. This allows you to check requests the library is using. For more information, check the @@ -244,6 +260,11 @@ class Client: set to is ``30.0`` seconds. .. versionadded:: 2.0 + connector: Optional[:class:`aiohttp.BaseConnector`] + The aiohttp connector to use for this client. This can be used to control underlying aiohttp + behavior, such as setting a dns resolver or sslcontext. + + .. versionadded:: 2.5 Attributes ----------- @@ -259,6 +280,7 @@ class Client: self.shard_id: Optional[int] = options.get('shard_id') self.shard_count: Optional[int] = options.get('shard_count') + connector: Optional[aiohttp.BaseConnector] = options.get('connector', None) proxy: Optional[str] = options.pop('proxy', None) proxy_auth: Optional[aiohttp.BasicAuth] = options.pop('proxy_auth', None) unsync_clock: bool = options.pop('assume_unsync_clock', True) @@ -266,6 +288,7 @@ class Client: max_ratelimit_timeout: Optional[float] = options.pop('max_ratelimit_timeout', None) self.http: HTTPClient = HTTPClient( self.loop, + connector, proxy=proxy, proxy_auth=proxy_auth, unsync_clock=unsync_clock, @@ -284,7 +307,7 @@ class Client: self._enable_debug_events: bool = options.pop('enable_debug_events', False) self._connection: ConnectionState[Self] = self._get_state(intents=intents, **options) self._connection.shard_count = self.shard_count - self._closed: bool = False + self._closing_task: Optional[asyncio.Task[None]] = None self._ready: asyncio.Event = MISSING self._application: Optional[AppInfo] = None self._connection._get_websocket = self._get_websocket @@ -304,7 +327,10 @@ class Client: exc_value: Optional[BaseException], traceback: Optional[TracebackType], ) -> None: - if not self.is_closed(): + # This avoids double-calling a user-provided .close() + if self._closing_task: + await self._closing_task + else: await self.close() # internals @@ -312,7 +338,7 @@ class Client: def _get_websocket(self, guild_id: Optional[int] = None, *, shard_id: Optional[int] = None) -> DiscordWebSocket: return self.ws - def _get_state(self, **options: Any) -> ConnectionState: + def _get_state(self, **options: Any) -> ConnectionState[Self]: return ConnectionState(dispatch=self.dispatch, handlers=self._handlers, hooks=self._hooks, http=self.http, **options) def _handle_ready(self) -> None: @@ -351,7 +377,13 @@ class Client: @property def emojis(self) -> Sequence[Emoji]: - """Sequence[:class:`.Emoji`]: The emojis that the connected client has.""" + """Sequence[:class:`.Emoji`]: The emojis that the connected client has. + + .. note:: + + This not include the emojis that are owned by the application. + Use :meth:`.fetch_application_emoji` to get those. + """ return self._connection.emojis @property @@ -362,6 +394,14 @@ class Client: """ return self._connection.stickers + @property + def soundboard_sounds(self) -> List[SoundboardSound]: + """List[:class:`.SoundboardSound`]: The soundboard sounds that the connected client has. + + .. versionadded:: 2.5 + """ + return self._connection.soundboard_sounds + @property def cached_messages(self) -> Sequence[Message]: """Sequence[:class:`.Message`]: Read-only list of messages the connected client has cached. @@ -615,6 +655,11 @@ class Client: if self._connection.application_id is None: self._connection.application_id = self._application.id + if self._application.interactions_endpoint_url is not None: + _log.warning( + 'Application has an interaction endpoint URL set, this means registered components and app commands will not be received by the library.' + ) + if not self._connection.application_flags: self._connection.application_flags = self._application.flags @@ -672,7 +717,6 @@ class Client: aiohttp.ClientError, asyncio.TimeoutError, ) as exc: - self.dispatch('disconnect') if not reconnect: await self.close() @@ -724,22 +768,24 @@ class Client: Closes the connection to Discord. """ - if self._closed: - return + if self._closing_task: + return await self._closing_task - self._closed = True + async def _close(): + await self._connection.close() - await self._connection.close() + if self.ws is not None and self.ws.open: + await self.ws.close(code=1000) - if self.ws is not None and self.ws.open: - await self.ws.close(code=1000) + await self.http.close() - await self.http.close() + if self._ready is not MISSING: + self._ready.clear() - if self._ready is not MISSING: - self._ready.clear() + self.loop = MISSING - self.loop = MISSING + self._closing_task = asyncio.create_task(_close()) + await self._closing_task def clear(self) -> None: """Clears the internal state of the bot. @@ -748,7 +794,7 @@ class Client: and :meth:`is_ready` both return ``False`` along with the bot's internal cache cleared. """ - self._closed = False + self._closing_task = None self._ready.clear() self._connection.clear() self.http.clear() @@ -868,7 +914,7 @@ class Client: def is_closed(self) -> bool: """:class:`bool`: Indicates if the websocket connection is closed.""" - return self._closed + return self._closing_task is not None @property def activity(self) -> Optional[ActivityTypes]: @@ -1082,6 +1128,23 @@ class Client: """ return self._connection.get_sticker(id) + def get_soundboard_sound(self, id: int, /) -> Optional[SoundboardSound]: + """Returns a soundboard sound with the given ID. + + .. versionadded:: 2.5 + + Parameters + ---------- + id: :class:`int` + The ID to search for. + + Returns + -------- + Optional[:class:`.SoundboardSound`] + The soundboard sound or ``None`` if not found. + """ + return self._connection.get_soundboard_sound(id) + def get_all_channels(self) -> Generator[GuildChannel, None, None]: """A generator that retrieves every :class:`.abc.GuildChannel` the client can 'access'. @@ -1150,8 +1213,8 @@ class Client: event: Literal['raw_app_command_permissions_update'], /, *, - check: Optional[Callable[[RawAppCommandPermissionsUpdateEvent], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[RawAppCommandPermissionsUpdateEvent], bool]] = ..., + timeout: Optional[float] = ..., ) -> RawAppCommandPermissionsUpdateEvent: ... @@ -1161,9 +1224,9 @@ class Client: event: Literal['app_command_completion'], /, *, - check: Optional[Callable[[Interaction[Self], Union[Command, ContextMenu]], bool]], - timeout: Optional[float] = None, - ) -> Tuple[Interaction[Self], Union[Command, ContextMenu]]: + check: Optional[Callable[[Interaction[Self], Union[Command[Any, ..., Any], ContextMenu]], bool]] = ..., + timeout: Optional[float] = ..., + ) -> Tuple[Interaction[Self], Union[Command[Any, ..., Any], ContextMenu]]: ... # AutoMod @@ -1174,8 +1237,8 @@ class Client: event: Literal['automod_rule_create', 'automod_rule_update', 'automod_rule_delete'], /, *, - check: Optional[Callable[[AutoModRule], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[AutoModRule], bool]] = ..., + timeout: Optional[float] = ..., ) -> AutoModRule: ... @@ -1185,8 +1248,8 @@ class Client: event: Literal['automod_action'], /, *, - check: Optional[Callable[[AutoModAction], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[AutoModAction], bool]] = ..., + timeout: Optional[float] = ..., ) -> AutoModAction: ... @@ -1198,8 +1261,8 @@ class Client: event: Literal['private_channel_update'], /, *, - check: Optional[Callable[[GroupChannel, GroupChannel], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[GroupChannel, GroupChannel], bool]] = ..., + timeout: Optional[float] = ..., ) -> Tuple[GroupChannel, GroupChannel]: ... @@ -1209,8 +1272,8 @@ class Client: event: Literal['private_channel_pins_update'], /, *, - check: Optional[Callable[[PrivateChannel, datetime.datetime], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[PrivateChannel, datetime.datetime], bool]] = ..., + timeout: Optional[float] = ..., ) -> Tuple[PrivateChannel, datetime.datetime]: ... @@ -1220,8 +1283,8 @@ class Client: event: Literal['guild_channel_delete', 'guild_channel_create'], /, *, - check: Optional[Callable[[GuildChannel], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[GuildChannel], bool]] = ..., + timeout: Optional[float] = ..., ) -> GuildChannel: ... @@ -1231,8 +1294,8 @@ class Client: event: Literal['guild_channel_update'], /, *, - check: Optional[Callable[[GuildChannel, GuildChannel], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[GuildChannel, GuildChannel], bool]] = ..., + timeout: Optional[float] = ..., ) -> Tuple[GuildChannel, GuildChannel]: ... @@ -1248,7 +1311,7 @@ class Client: bool, ] ], - timeout: Optional[float] = None, + timeout: Optional[float] = ..., ) -> Tuple[Union[GuildChannel, Thread], Optional[datetime.datetime]]: ... @@ -1258,8 +1321,8 @@ class Client: event: Literal['typing'], /, *, - check: Optional[Callable[[Messageable, Union[User, Member], datetime.datetime], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[Messageable, Union[User, Member], datetime.datetime], bool]] = ..., + timeout: Optional[float] = ..., ) -> Tuple[Messageable, Union[User, Member], datetime.datetime]: ... @@ -1269,8 +1332,8 @@ class Client: event: Literal['raw_typing'], /, *, - check: Optional[Callable[[RawTypingEvent], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[RawTypingEvent], bool]] = ..., + timeout: Optional[float] = ..., ) -> RawTypingEvent: ... @@ -1282,8 +1345,8 @@ class Client: event: Literal['connect', 'disconnect', 'ready', 'resumed'], /, *, - check: Optional[Callable[[], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[], bool]] = ..., + timeout: Optional[float] = ..., ) -> None: ... @@ -1293,8 +1356,8 @@ class Client: event: Literal['shard_connect', 'shard_disconnect', 'shard_ready', 'shard_resumed'], /, *, - check: Optional[Callable[[int], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[int], bool]] = ..., + timeout: Optional[float] = ..., ) -> int: ... @@ -1304,8 +1367,8 @@ class Client: event: Literal['socket_event_type', 'socket_raw_receive'], /, *, - check: Optional[Callable[[str], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[str], bool]] = ..., + timeout: Optional[float] = ..., ) -> str: ... @@ -1315,11 +1378,23 @@ class Client: event: Literal['socket_raw_send'], /, *, - check: Optional[Callable[[Union[str, bytes]], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[Union[str, bytes]], bool]] = ..., + timeout: Optional[float] = ..., ) -> Union[str, bytes]: ... + # Entitlements + @overload + async def wait_for( + self, + event: Literal['entitlement_create', 'entitlement_update', 'entitlement_delete'], + /, + *, + check: Optional[Callable[[Entitlement], bool]] = ..., + timeout: Optional[float] = ..., + ) -> Entitlement: + ... + # Guilds @overload @@ -1333,8 +1408,8 @@ class Client: ], /, *, - check: Optional[Callable[[Guild], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[Guild], bool]] = ..., + timeout: Optional[float] = ..., ) -> Guild: ... @@ -1344,8 +1419,8 @@ class Client: event: Literal['guild_update'], /, *, - check: Optional[Callable[[Guild, Guild], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[Guild, Guild], bool]] = ..., + timeout: Optional[float] = ..., ) -> Tuple[Guild, Guild]: ... @@ -1355,8 +1430,8 @@ class Client: event: Literal['guild_emojis_update'], /, *, - check: Optional[Callable[[Guild, Sequence[Emoji], Sequence[Emoji]], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[Guild, Sequence[Emoji], Sequence[Emoji]], bool]] = ..., + timeout: Optional[float] = ..., ) -> Tuple[Guild, Sequence[Emoji], Sequence[Emoji]]: ... @@ -1366,8 +1441,8 @@ class Client: event: Literal['guild_stickers_update'], /, *, - check: Optional[Callable[[Guild, Sequence[GuildSticker], Sequence[GuildSticker]], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[Guild, Sequence[GuildSticker], Sequence[GuildSticker]], bool]] = ..., + timeout: Optional[float] = ..., ) -> Tuple[Guild, Sequence[GuildSticker], Sequence[GuildSticker]]: ... @@ -1377,8 +1452,8 @@ class Client: event: Literal['invite_create', 'invite_delete'], /, *, - check: Optional[Callable[[Invite], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[Invite], bool]] = ..., + timeout: Optional[float] = ..., ) -> Invite: ... @@ -1388,8 +1463,8 @@ class Client: event: Literal['audit_log_entry_create'], /, *, - check: Optional[Callable[[AuditLogEntry], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[AuditLogEntry], bool]] = ..., + timeout: Optional[float] = ..., ) -> AuditLogEntry: ... @@ -1401,8 +1476,8 @@ class Client: event: Literal['integration_create', 'integration_update'], /, *, - check: Optional[Callable[[Integration], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[Integration], bool]] = ..., + timeout: Optional[float] = ..., ) -> Integration: ... @@ -1412,8 +1487,8 @@ class Client: event: Literal['guild_integrations_update'], /, *, - check: Optional[Callable[[Guild], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[Guild], bool]] = ..., + timeout: Optional[float] = ..., ) -> Guild: ... @@ -1423,8 +1498,8 @@ class Client: event: Literal['webhooks_update'], /, *, - check: Optional[Callable[[GuildChannel], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[GuildChannel], bool]] = ..., + timeout: Optional[float] = ..., ) -> GuildChannel: ... @@ -1434,8 +1509,8 @@ class Client: event: Literal['raw_integration_delete'], /, *, - check: Optional[Callable[[RawIntegrationDeleteEvent], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[RawIntegrationDeleteEvent], bool]] = ..., + timeout: Optional[float] = ..., ) -> RawIntegrationDeleteEvent: ... @@ -1447,8 +1522,8 @@ class Client: event: Literal['interaction'], /, *, - check: Optional[Callable[[Interaction[Self]], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[Interaction[Self]], bool]] = ..., + timeout: Optional[float] = ..., ) -> Interaction[Self]: ... @@ -1460,8 +1535,8 @@ class Client: event: Literal['member_join', 'member_remove'], /, *, - check: Optional[Callable[[Member], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[Member], bool]] = ..., + timeout: Optional[float] = ..., ) -> Member: ... @@ -1471,8 +1546,8 @@ class Client: event: Literal['raw_member_remove'], /, *, - check: Optional[Callable[[RawMemberRemoveEvent], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[RawMemberRemoveEvent], bool]] = ..., + timeout: Optional[float] = ..., ) -> RawMemberRemoveEvent: ... @@ -1482,8 +1557,8 @@ class Client: event: Literal['member_update', 'presence_update'], /, *, - check: Optional[Callable[[Member, Member], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[Member, Member], bool]] = ..., + timeout: Optional[float] = ..., ) -> Tuple[Member, Member]: ... @@ -1493,8 +1568,8 @@ class Client: event: Literal['user_update'], /, *, - check: Optional[Callable[[User, User], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[User, User], bool]] = ..., + timeout: Optional[float] = ..., ) -> Tuple[User, User]: ... @@ -1504,8 +1579,8 @@ class Client: event: Literal['member_ban'], /, *, - check: Optional[Callable[[Guild, Union[User, Member]], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[Guild, Union[User, Member]], bool]] = ..., + timeout: Optional[float] = ..., ) -> Tuple[Guild, Union[User, Member]]: ... @@ -1515,8 +1590,8 @@ class Client: event: Literal['member_unban'], /, *, - check: Optional[Callable[[Guild, User], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[Guild, User], bool]] = ..., + timeout: Optional[float] = ..., ) -> Tuple[Guild, User]: ... @@ -1528,8 +1603,8 @@ class Client: event: Literal['message', 'message_delete'], /, *, - check: Optional[Callable[[Message], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[Message], bool]] = ..., + timeout: Optional[float] = ..., ) -> Message: ... @@ -1539,8 +1614,8 @@ class Client: event: Literal['message_edit'], /, *, - check: Optional[Callable[[Message, Message], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[Message, Message], bool]] = ..., + timeout: Optional[float] = ..., ) -> Tuple[Message, Message]: ... @@ -1550,8 +1625,8 @@ class Client: event: Literal['bulk_message_delete'], /, *, - check: Optional[Callable[[List[Message]], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[List[Message]], bool]] = ..., + timeout: Optional[float] = ..., ) -> List[Message]: ... @@ -1561,8 +1636,8 @@ class Client: event: Literal['raw_message_edit'], /, *, - check: Optional[Callable[[RawMessageUpdateEvent], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[RawMessageUpdateEvent], bool]] = ..., + timeout: Optional[float] = ..., ) -> RawMessageUpdateEvent: ... @@ -1572,8 +1647,8 @@ class Client: event: Literal['raw_message_delete'], /, *, - check: Optional[Callable[[RawMessageDeleteEvent], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[RawMessageDeleteEvent], bool]] = ..., + timeout: Optional[float] = ..., ) -> RawMessageDeleteEvent: ... @@ -1583,8 +1658,8 @@ class Client: event: Literal['raw_bulk_message_delete'], /, *, - check: Optional[Callable[[RawBulkMessageDeleteEvent], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[RawBulkMessageDeleteEvent], bool]] = ..., + timeout: Optional[float] = ..., ) -> RawBulkMessageDeleteEvent: ... @@ -1596,8 +1671,8 @@ class Client: event: Literal['reaction_add', 'reaction_remove'], /, *, - check: Optional[Callable[[Reaction, Union[Member, User]], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[Reaction, Union[Member, User]], bool]] = ..., + timeout: Optional[float] = ..., ) -> Tuple[Reaction, Union[Member, User]]: ... @@ -1607,8 +1682,8 @@ class Client: event: Literal['reaction_clear'], /, *, - check: Optional[Callable[[Message, List[Reaction]], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[Message, List[Reaction]], bool]] = ..., + timeout: Optional[float] = ..., ) -> Tuple[Message, List[Reaction]]: ... @@ -1618,8 +1693,8 @@ class Client: event: Literal['reaction_clear_emoji'], /, *, - check: Optional[Callable[[Reaction], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[Reaction], bool]] = ..., + timeout: Optional[float] = ..., ) -> Reaction: ... @@ -1629,8 +1704,8 @@ class Client: event: Literal['raw_reaction_add', 'raw_reaction_remove'], /, *, - check: Optional[Callable[[RawReactionActionEvent], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[RawReactionActionEvent], bool]] = ..., + timeout: Optional[float] = ..., ) -> RawReactionActionEvent: ... @@ -1640,8 +1715,8 @@ class Client: event: Literal['raw_reaction_clear'], /, *, - check: Optional[Callable[[RawReactionClearEvent], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[RawReactionClearEvent], bool]] = ..., + timeout: Optional[float] = ..., ) -> RawReactionClearEvent: ... @@ -1651,8 +1726,8 @@ class Client: event: Literal['raw_reaction_clear_emoji'], /, *, - check: Optional[Callable[[RawReactionClearEmojiEvent], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[RawReactionClearEmojiEvent], bool]] = ..., + timeout: Optional[float] = ..., ) -> RawReactionClearEmojiEvent: ... @@ -1664,8 +1739,8 @@ class Client: event: Literal['guild_role_create', 'guild_role_delete'], /, *, - check: Optional[Callable[[Role], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[Role], bool]] = ..., + timeout: Optional[float] = ..., ) -> Role: ... @@ -1675,8 +1750,8 @@ class Client: event: Literal['guild_role_update'], /, *, - check: Optional[Callable[[Role, Role], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[Role, Role], bool]] = ..., + timeout: Optional[float] = ..., ) -> Tuple[Role, Role]: ... @@ -1688,8 +1763,8 @@ class Client: event: Literal['scheduled_event_create', 'scheduled_event_delete'], /, *, - check: Optional[Callable[[ScheduledEvent], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[ScheduledEvent], bool]] = ..., + timeout: Optional[float] = ..., ) -> ScheduledEvent: ... @@ -1699,8 +1774,8 @@ class Client: event: Literal['scheduled_event_user_add', 'scheduled_event_user_remove'], /, *, - check: Optional[Callable[[ScheduledEvent, User], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[ScheduledEvent, User], bool]] = ..., + timeout: Optional[float] = ..., ) -> Tuple[ScheduledEvent, User]: ... @@ -1712,8 +1787,8 @@ class Client: event: Literal['stage_instance_create', 'stage_instance_delete'], /, *, - check: Optional[Callable[[StageInstance], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[StageInstance], bool]] = ..., + timeout: Optional[float] = ..., ) -> StageInstance: ... @@ -1723,11 +1798,23 @@ class Client: event: Literal['stage_instance_update'], /, *, - check: Optional[Callable[[StageInstance, StageInstance], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[StageInstance, StageInstance], bool]] = ..., + timeout: Optional[float] = ..., ) -> Coroutine[Any, Any, Tuple[StageInstance, StageInstance]]: ... + # Subscriptions + @overload + async def wait_for( + self, + event: Literal['subscription_create', 'subscription_update', 'subscription_delete'], + /, + *, + check: Optional[Callable[[Subscription], bool]] = ..., + timeout: Optional[float] = ..., + ) -> Subscription: + ... + # Threads @overload async def wait_for( @@ -1735,8 +1822,8 @@ class Client: event: Literal['thread_create', 'thread_join', 'thread_remove', 'thread_delete'], /, *, - check: Optional[Callable[[Thread], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[Thread], bool]] = ..., + timeout: Optional[float] = ..., ) -> Thread: ... @@ -1746,8 +1833,8 @@ class Client: event: Literal['thread_update'], /, *, - check: Optional[Callable[[Thread, Thread], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[Thread, Thread], bool]] = ..., + timeout: Optional[float] = ..., ) -> Tuple[Thread, Thread]: ... @@ -1757,8 +1844,8 @@ class Client: event: Literal['raw_thread_update'], /, *, - check: Optional[Callable[[RawThreadUpdateEvent], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[RawThreadUpdateEvent], bool]] = ..., + timeout: Optional[float] = ..., ) -> RawThreadUpdateEvent: ... @@ -1768,8 +1855,8 @@ class Client: event: Literal['raw_thread_delete'], /, *, - check: Optional[Callable[[RawThreadDeleteEvent], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[RawThreadDeleteEvent], bool]] = ..., + timeout: Optional[float] = ..., ) -> RawThreadDeleteEvent: ... @@ -1779,8 +1866,8 @@ class Client: event: Literal['thread_member_join', 'thread_member_remove'], /, *, - check: Optional[Callable[[ThreadMember], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[ThreadMember], bool]] = ..., + timeout: Optional[float] = ..., ) -> ThreadMember: ... @@ -1790,8 +1877,8 @@ class Client: event: Literal['raw_thread_member_remove'], /, *, - check: Optional[Callable[[RawThreadMembersUpdate], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[RawThreadMembersUpdate], bool]] = ..., + timeout: Optional[float] = ..., ) -> RawThreadMembersUpdate: ... @@ -1803,11 +1890,35 @@ class Client: event: Literal['voice_state_update'], /, *, - check: Optional[Callable[[Member, VoiceState, VoiceState], bool]], - timeout: Optional[float] = None, + check: Optional[Callable[[Member, VoiceState, VoiceState], bool]] = ..., + timeout: Optional[float] = ..., ) -> Tuple[Member, VoiceState, VoiceState]: ... + # Polls + + @overload + async def wait_for( + self, + event: Literal['poll_vote_add', 'poll_vote_remove'], + /, + *, + check: Optional[Callable[[Union[User, Member], PollAnswer], bool]] = ..., + timeout: Optional[float] = ..., + ) -> Tuple[Union[User, Member], PollAnswer]: + ... + + @overload + async def wait_for( + self, + event: Literal['raw_poll_vote_add', 'raw_poll_vote_remove'], + /, + *, + check: Optional[Callable[[RawPollVoteActionEvent], bool]] = ..., + timeout: Optional[float] = ..., + ) -> RawPollVoteActionEvent: + ... + # Commands @overload @@ -1816,9 +1927,9 @@ class Client: event: Literal["command", "command_completion"], /, *, - check: Optional[Callable[[Context], bool]] = None, - timeout: Optional[float] = None, - ) -> Context: + check: Optional[Callable[[Context[Any]], bool]] = ..., + timeout: Optional[float] = ..., + ) -> Context[Any]: ... @overload @@ -1827,9 +1938,9 @@ class Client: event: Literal["command_error"], /, *, - check: Optional[Callable[[Context, CommandError], bool]] = None, - timeout: Optional[float] = None, - ) -> Tuple[Context, CommandError]: + check: Optional[Callable[[Context[Any], CommandError], bool]] = ..., + timeout: Optional[float] = ..., + ) -> Tuple[Context[Any], CommandError]: ... @overload @@ -1838,8 +1949,8 @@ class Client: event: str, /, *, - check: Optional[Callable[..., bool]] = None, - timeout: Optional[float] = None, + check: Optional[Callable[..., bool]] = ..., + timeout: Optional[float] = ..., ) -> Any: ... @@ -2059,13 +2170,15 @@ class Client: limit: Optional[int] = 200, before: Optional[SnowflakeTime] = None, after: Optional[SnowflakeTime] = None, + with_counts: bool = True, ) -> AsyncIterator[Guild]: """Retrieves an :term:`asynchronous iterator` that enables receiving your guilds. .. note:: Using this, you will only receive :attr:`.Guild.owner`, :attr:`.Guild.icon`, - :attr:`.Guild.id`, and :attr:`.Guild.name` per :class:`.Guild`. + :attr:`.Guild.id`, :attr:`.Guild.name`, :attr:`.Guild.approximate_member_count`, + and :attr:`.Guild.approximate_presence_count` per :class:`.Guild`. .. note:: @@ -2106,6 +2219,12 @@ class Client: Retrieve guilds after this date or object. If a datetime is provided, it is recommended to use a UTC aware datetime. If the datetime is naive, it is assumed to be local time. + with_counts: :class:`bool` + Whether to include count information in the guilds. This fills the + :attr:`.Guild.approximate_member_count` and :attr:`.Guild.approximate_presence_count` + attributes without needing any privileged intents. Defaults to ``True``. + + .. versionadded:: 2.3 Raises ------ @@ -2120,7 +2239,7 @@ class Client: async def _before_strategy(retrieve: int, before: Optional[Snowflake], limit: Optional[int]): before_id = before.id if before else None - data = await self.http.get_guilds(retrieve, before=before_id) + data = await self.http.get_guilds(retrieve, before=before_id, with_counts=with_counts) if data: if limit is not None: @@ -2132,7 +2251,7 @@ class Client: async def _after_strategy(retrieve: int, after: Optional[Snowflake], limit: Optional[int]): after_id = after.id if after else None - data = await self.http.get_guilds(retrieve, after=after_id) + data = await self.http.get_guilds(retrieve, after=after_id, with_counts=with_counts) if data: if limit is not None: @@ -2233,8 +2352,8 @@ class Client: Raises ------ - Forbidden - You do not have access to the guild. + NotFound + The guild doesn't exist or you got no access to it. HTTPException Getting the guild failed. @@ -2246,6 +2365,30 @@ class Client: data = await self.http.get_guild(guild_id, with_counts=with_counts) return Guild(data=data, state=self._connection) + async def fetch_guild_preview(self, guild_id: int) -> GuildPreview: + """|coro| + + Retrieves a preview of a :class:`.Guild` from an ID. If the guild is discoverable, + you don't have to be a member of it. + + .. versionadded:: 2.5 + + Raises + ------ + NotFound + The guild doesn't exist, or is not discoverable and you are not in it. + HTTPException + Getting the guild failed. + + Returns + -------- + :class:`.GuildPreview` + The guild preview from the ID. + """ + data = await self.http.get_guild_preview(guild_id) + return GuildPreview(data=data, state=self._connection) + + @deprecated() async def create_guild( self, *, @@ -2266,6 +2409,9 @@ class Client: This function will now raise :exc:`ValueError` instead of ``InvalidArgument``. + .. deprecated:: 2.6 + This function is deprecated and will be removed in a future version. + Parameters ---------- name: :class:`str` @@ -2404,7 +2550,7 @@ class Client: ) return Invite.from_incomplete(state=self._connection, data=data) - async def delete_invite(self, invite: Union[Invite, str], /) -> None: + async def delete_invite(self, invite: Union[Invite, str], /) -> Invite: """|coro| Revokes an :class:`.Invite`, URL, or ID to an invite. @@ -2432,7 +2578,8 @@ class Client: """ resolved = utils.resolve_invite(invite) - await self.http.delete_invite(resolved.code) + data = await self.http.delete_invite(resolved.code) + return Invite.from_incomplete(state=self._connection, data=data) # Miscellaneous stuff @@ -2486,8 +2633,6 @@ class Client: The bot's application information. """ data = await self.http.application_info() - if 'rpc_origins' not in data: - data['rpc_origins'] = None return AppInfo(self._connection, data) async def fetch_user(self, user_id: int, /) -> User: @@ -2624,6 +2769,248 @@ class Client: # The type checker is not smart enough to figure out the constructor is correct return cls(state=self._connection, data=data) # type: ignore + async def fetch_skus(self) -> List[SKU]: + """|coro| + + Retrieves the bot's available SKUs. + + .. versionadded:: 2.4 + + Raises + ------- + MissingApplicationID + The application ID could not be found. + HTTPException + Retrieving the SKUs failed. + + Returns + -------- + List[:class:`.SKU`] + The bot's available SKUs. + """ + + if self.application_id is None: + raise MissingApplicationID + + data = await self.http.get_skus(self.application_id) + return [SKU(state=self._connection, data=sku) for sku in data] + + async def fetch_entitlement(self, entitlement_id: int, /) -> Entitlement: + """|coro| + + Retrieves a :class:`.Entitlement` with the specified ID. + + .. versionadded:: 2.4 + + Parameters + ----------- + entitlement_id: :class:`int` + The entitlement's ID to fetch from. + + Raises + ------- + NotFound + An entitlement with this ID does not exist. + MissingApplicationID + The application ID could not be found. + HTTPException + Fetching the entitlement failed. + + Returns + -------- + :class:`.Entitlement` + The entitlement you requested. + """ + + if self.application_id is None: + raise MissingApplicationID + + data = await self.http.get_entitlement(self.application_id, entitlement_id) + return Entitlement(state=self._connection, data=data) + + async def entitlements( + self, + *, + limit: Optional[int] = 100, + before: Optional[SnowflakeTime] = None, + after: Optional[SnowflakeTime] = None, + skus: Optional[Sequence[Snowflake]] = None, + user: Optional[Snowflake] = None, + guild: Optional[Snowflake] = None, + exclude_ended: bool = False, + exclude_deleted: bool = True, + ) -> AsyncIterator[Entitlement]: + """Retrieves an :term:`asynchronous iterator` of the :class:`.Entitlement` that applications has. + + .. versionadded:: 2.4 + + Examples + --------- + + Usage :: + + async for entitlement in client.entitlements(limit=100): + print(entitlement.user_id, entitlement.ends_at) + + Flattening into a list :: + + entitlements = [entitlement async for entitlement in client.entitlements(limit=100)] + # entitlements is now a list of Entitlement... + + All parameters are optional. + + Parameters + ----------- + limit: Optional[:class:`int`] + The number of entitlements to retrieve. If ``None``, it retrieves every entitlement for this application. + Note, however, that this would make it a slow operation. Defaults to ``100``. + before: Optional[Union[:class:`~discord.abc.Snowflake`, :class:`datetime.datetime`]] + Retrieve entitlements before this date or entitlement. + If a datetime is provided, it is recommended to use a UTC aware datetime. + If the datetime is naive, it is assumed to be local time. + after: Optional[Union[:class:`~discord.abc.Snowflake`, :class:`datetime.datetime`]] + Retrieve entitlements after this date or entitlement. + If a datetime is provided, it is recommended to use a UTC aware datetime. + If the datetime is naive, it is assumed to be local time. + skus: Optional[Sequence[:class:`~discord.abc.Snowflake`]] + A list of SKUs to filter by. + user: Optional[:class:`~discord.abc.Snowflake`] + The user to filter by. + guild: Optional[:class:`~discord.abc.Snowflake`] + The guild to filter by. + exclude_ended: :class:`bool` + Whether to exclude ended entitlements. Defaults to ``False``. + exclude_deleted: :class:`bool` + Whether to exclude deleted entitlements. Defaults to ``True``. + + .. versionadded:: 2.5 + + Raises + ------- + MissingApplicationID + The application ID could not be found. + HTTPException + Fetching the entitlements failed. + TypeError + Both ``after`` and ``before`` were provided, as Discord does not + support this type of pagination. + + Yields + -------- + :class:`.Entitlement` + The entitlement with the application. + """ + + if self.application_id is None: + raise MissingApplicationID + + if before is not None and after is not None: + raise TypeError('entitlements pagination does not support both before and after') + + # This endpoint paginates in ascending order. + endpoint = self.http.get_entitlements + + async def _before_strategy(retrieve: int, before: Optional[Snowflake], limit: Optional[int]): + before_id = before.id if before else None + data = await endpoint( + self.application_id, # type: ignore # We already check for None above + limit=retrieve, + before=before_id, + sku_ids=[sku.id for sku in skus] if skus else None, + user_id=user.id if user else None, + guild_id=guild.id if guild else None, + exclude_ended=exclude_ended, + exclude_deleted=exclude_deleted, + ) + + if data: + if limit is not None: + limit -= len(data) + + before = Object(id=int(data[0]['id'])) + + return data, before, limit + + async def _after_strategy(retrieve: int, after: Optional[Snowflake], limit: Optional[int]): + after_id = after.id if after else None + data = await endpoint( + self.application_id, # type: ignore # We already check for None above + limit=retrieve, + after=after_id, + sku_ids=[sku.id for sku in skus] if skus else None, + user_id=user.id if user else None, + guild_id=guild.id if guild else None, + exclude_ended=exclude_ended, + ) + + if data: + if limit is not None: + limit -= len(data) + + after = Object(id=int(data[-1]['id'])) + + return data, after, limit + + if isinstance(before, datetime.datetime): + before = Object(id=utils.time_snowflake(before, high=False)) + if isinstance(after, datetime.datetime): + after = Object(id=utils.time_snowflake(after, high=True)) + + if before: + strategy, state = _before_strategy, before + else: + strategy, state = _after_strategy, after + + while True: + retrieve = 100 if limit is None else min(limit, 100) + if retrieve < 1: + return + + data, state, limit = await strategy(retrieve, state, limit) + + # Terminate loop on next iteration; there's no data left after this + if len(data) < 100: + limit = 0 + + for e in data: + yield Entitlement(self._connection, e) + + async def create_entitlement( + self, + sku: Snowflake, + owner: Snowflake, + owner_type: EntitlementOwnerType, + ) -> None: + """|coro| + + Creates a test :class:`.Entitlement` for the application. + + .. versionadded:: 2.4 + + Parameters + ----------- + sku: :class:`~discord.abc.Snowflake` + The SKU to create the entitlement for. + owner: :class:`~discord.abc.Snowflake` + The ID of the owner. + owner_type: :class:`.EntitlementOwnerType` + The type of the owner. + + Raises + ------- + MissingApplicationID + The application ID could not be found. + NotFound + The SKU or owner could not be found. + HTTPException + Creating the entitlement failed. + """ + + if self.application_id is None: + raise MissingApplicationID + + await self.http.create_entitlement(self.application_id, sku.id, owner.id, owner_type.value) + async def fetch_premium_sticker_packs(self) -> List[StickerPack]: """|coro| @@ -2644,6 +3031,53 @@ class Client: data = await self.http.list_premium_sticker_packs() return [StickerPack(state=self._connection, data=pack) for pack in data['sticker_packs']] + async def fetch_premium_sticker_pack(self, sticker_pack_id: int, /) -> StickerPack: + """|coro| + + Retrieves a premium sticker pack with the specified ID. + + .. versionadded:: 2.5 + + Parameters + ---------- + sticker_pack_id: :class:`int` + The sticker pack's ID to fetch from. + + Raises + ------- + NotFound + A sticker pack with this ID does not exist. + HTTPException + Retrieving the sticker pack failed. + + Returns + ------- + :class:`.StickerPack` + The retrieved premium sticker pack. + """ + data = await self.http.get_sticker_pack(sticker_pack_id) + return StickerPack(state=self._connection, data=data) + + async def fetch_soundboard_default_sounds(self) -> List[SoundboardDefaultSound]: + """|coro| + + Retrieves all default soundboard sounds. + + .. versionadded:: 2.5 + + Raises + ------- + HTTPException + Retrieving the default soundboard sounds failed. + + Returns + --------- + List[:class:`.SoundboardDefaultSound`] + All default soundboard sounds. + """ + data = await self.http.get_soundboard_default_sounds() + return [SoundboardDefaultSound(state=self._connection, data=sound) for sound in data] + async def create_dm(self, user: Snowflake) -> DMChannel: """|coro| @@ -2672,6 +3106,54 @@ class Client: data = await state.http.start_private_message(user.id) return state.add_dm_channel(data) + def add_dynamic_items(self, *items: Type[DynamicItem[Item[Any]]]) -> None: + r"""Registers :class:`~discord.ui.DynamicItem` classes for persistent listening. + + This method accepts *class types* rather than instances. + + .. versionadded:: 2.4 + + Parameters + ----------- + \*items: Type[:class:`~discord.ui.DynamicItem`] + The classes of dynamic items to add. + + Raises + ------- + TypeError + A class is not a subclass of :class:`~discord.ui.DynamicItem`. + """ + + for item in items: + if not issubclass(item, DynamicItem): + raise TypeError(f'expected subclass of DynamicItem not {item.__name__}') + + self._connection.store_dynamic_items(*items) + + def remove_dynamic_items(self, *items: Type[DynamicItem[Item[Any]]]) -> None: + r"""Removes :class:`~discord.ui.DynamicItem` classes from persistent listening. + + This method accepts *class types* rather than instances. + + .. versionadded:: 2.4 + + Parameters + ----------- + \*items: Type[:class:`~discord.ui.DynamicItem`] + The classes of dynamic items to remove. + + Raises + ------- + TypeError + A class is not a subclass of :class:`~discord.ui.DynamicItem`. + """ + + for item in items: + if not issubclass(item, DynamicItem): + raise TypeError(f'expected subclass of DynamicItem not {item.__name__}') + + self._connection.remove_dynamic_items(*items) + def add_view(self, view: View, *, message_id: Optional[int] = None) -> None: """Registers a :class:`~discord.ui.View` for persistent listening. @@ -2716,3 +3198,97 @@ class Client: .. versionadded:: 2.0 """ return self._connection.persistent_views + + async def create_application_emoji( + self, + *, + name: str, + image: bytes, + ) -> Emoji: + """|coro| + + Create an emoji for the current application. + + .. versionadded:: 2.5 + + Parameters + ---------- + name: :class:`str` + The emoji name. Must be between 2 and 32 characters long. + image: :class:`bytes` + The :term:`py:bytes-like object` representing the image data to use. + Only JPG, PNG and GIF images are supported. + + Raises + ------ + MissingApplicationID + The application ID could not be found. + HTTPException + Creating the emoji failed. + + Returns + ------- + :class:`.Emoji` + The emoji that was created. + """ + if self.application_id is None: + raise MissingApplicationID + + img = utils._bytes_to_base64_data(image) + data = await self.http.create_application_emoji(self.application_id, name, img) + return Emoji(guild=Object(0), state=self._connection, data=data) + + async def fetch_application_emoji(self, emoji_id: int, /) -> Emoji: + """|coro| + + Retrieves an emoji for the current application. + + .. versionadded:: 2.5 + + Parameters + ---------- + emoji_id: :class:`int` + The emoji ID to retrieve. + + Raises + ------ + MissingApplicationID + The application ID could not be found. + HTTPException + Retrieving the emoji failed. + + Returns + ------- + :class:`.Emoji` + The emoji requested. + """ + if self.application_id is None: + raise MissingApplicationID + + data = await self.http.get_application_emoji(self.application_id, emoji_id) + return Emoji(guild=Object(0), state=self._connection, data=data) + + async def fetch_application_emojis(self) -> List[Emoji]: + """|coro| + + Retrieves all emojis for the current application. + + .. versionadded:: 2.5 + + Raises + ------- + MissingApplicationID + The application ID could not be found. + HTTPException + Retrieving the emojis failed. + + Returns + ------- + List[:class:`.Emoji`] + The list of emojis for the current application. + """ + if self.application_id is None: + raise MissingApplicationID + + data = await self.http.get_application_emojis(self.application_id) + return [Emoji(guild=Object(0), state=self._connection, data=emoji) for emoji in data['items']] diff --git a/discord/colour.py b/discord/colour.py index 6a9f7b8d5..8c40dac35 100644 --- a/discord/colour.py +++ b/discord/colour.py @@ -21,6 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ + from __future__ import annotations import colorsys @@ -104,6 +105,11 @@ class Colour: Returns the raw colour value. + .. note:: + + The colour values in the classmethods are mostly provided as-is and can change between + versions should the Discord client's representation of that colour also change. + Attributes ------------ value: :class:`int` @@ -170,7 +176,7 @@ class Colour: return cls.from_rgb(*(int(x * 255) for x in rgb)) @classmethod - def from_str(cls, value: str) -> Self: + def from_str(cls, value: str) -> Colour: """Constructs a :class:`Colour` from a string. The following formats are accepted: @@ -191,6 +197,9 @@ class Colour: The string could not be converted into a colour. """ + if not value: + raise ValueError('unknown colour format given') + if value[0] == '#': return parse_hex_number(value[1:]) @@ -449,20 +458,59 @@ class Colour: """ return cls(0x99AAB5) + @classmethod + def ash_theme(cls) -> Self: + """A factory method that returns a :class:`Colour` with a value of ``0x2E2E34``. + + This will appear transparent on Discord's ash theme. + + .. colour:: #2E2E34 + + .. versionadded:: 2.6 + """ + return cls(0x2E2E34) + @classmethod def dark_theme(cls) -> Self: - """A factory method that returns a :class:`Colour` with a value of ``0x313338``. + """A factory method that returns a :class:`Colour` with a value of ``0x1A1A1E``. This will appear transparent on Discord's dark theme. - .. colour:: #313338 + .. colour:: #1A1A1E .. versionadded:: 1.5 .. versionchanged:: 2.2 Updated colour from previous ``0x36393F`` to reflect discord theme changes. + + .. versionchanged:: 2.6 + Updated colour from previous ``0x313338`` to reflect discord theme changes. """ - return cls(0x313338) + return cls(0x1A1A1E) + + @classmethod + def onyx_theme(cls) -> Self: + """A factory method that returns a :class:`Colour` with a value of ``0x070709``. + + This will appear transparent on Discord's onyx theme. + + .. colour:: #070709 + + .. versionadded:: 2.6 + """ + return cls(0x070709) + + @classmethod + def light_theme(cls) -> Self: + """A factory method that returns a :class:`Colour` with a value of ``0xFBFBFB``. + + This will appear transparent on Discord's light theme. + + .. colour:: #FBFBFB + + .. versionadded:: 2.6 + """ + return cls(0xFBFBFB) @classmethod def fuchsia(cls) -> Self: @@ -484,25 +532,62 @@ class Colour: """ return cls(0xFEE75C) + @classmethod + def ash_embed(cls) -> Self: + """A factory method that returns a :class:`Colour` with a value of ``0x37373E``. + + .. colour:: #37373E + + .. versionadded:: 2.6 + + """ + return cls(0x37373E) + @classmethod def dark_embed(cls) -> Self: - """A factory method that returns a :class:`Colour` with a value of ``0x2B2D31``. + """A factory method that returns a :class:`Colour` with a value of ``0x242429``. - .. colour:: #2B2D31 + .. colour:: #242429 .. versionadded:: 2.2 + + .. versionchanged:: 2.6 + Updated colour from previous ``0x2B2D31`` to reflect discord theme changes. + """ + return cls(0x242429) + + @classmethod + def onyx_embed(cls) -> Self: + """A factory method that returns a :class:`Colour` with a value of ``0x131416``. + + .. colour:: #131416 + + .. versionadded:: 2.6 """ - return cls(0x2B2D31) + return cls(0x131416) @classmethod def light_embed(cls) -> Self: - """A factory method that returns a :class:`Colour` with a value of ``0xEEEFF1``. + """A factory method that returns a :class:`Colour` with a value of ``0xFFFFFF``. .. colour:: #EEEFF1 .. versionadded:: 2.2 + + .. versionchanged:: 2.6 + Updated colour from previous ``0xEEEFF1`` to reflect discord theme changes. + """ + return cls(0xFFFFFF) + + @classmethod + def pink(cls) -> Self: + """A factory method that returns a :class:`Colour` with a value of ``0xEB459F``. + + .. colour:: #EB459F + + .. versionadded:: 2.3 """ - return cls(0xEEEFF1) + return cls(0xEB459F) Color = Colour diff --git a/discord/components.py b/discord/components.py index c0a213efa..b62ab6bf9 100644 --- a/discord/components.py +++ b/discord/components.py @@ -25,7 +25,7 @@ DEALINGS IN THE SOFTWARE. from __future__ import annotations from typing import ClassVar, List, Literal, Optional, TYPE_CHECKING, Tuple, Union, overload -from .enums import try_enum, ComponentType, ButtonStyle, TextStyle, ChannelType +from .enums import try_enum, ComponentType, ButtonStyle, TextStyle, ChannelType, SelectDefaultValueType from .utils import get_slots, MISSING from .partial_emoji import PartialEmoji, _EmojiTag @@ -40,8 +40,10 @@ if TYPE_CHECKING: ActionRow as ActionRowPayload, TextInput as TextInputPayload, ActionRowChildComponent as ActionRowChildComponentPayload, + SelectDefaultValues as SelectDefaultValuesPayload, ) from .emoji import Emoji + from .abc import Snowflake ActionRowChildComponentType = Union['Button', 'SelectMenu', 'TextInput'] @@ -53,6 +55,7 @@ __all__ = ( 'SelectMenu', 'SelectOption', 'TextInput', + 'SelectDefaultValue', ) @@ -167,6 +170,10 @@ class Button(Component): The label of the button, if any. emoji: Optional[:class:`PartialEmoji`] The emoji of the button, if available. + sku_id: Optional[:class:`int`] + The SKU ID this button sends you to, if available. + + .. versionadded:: 2.4 """ __slots__: Tuple[str, ...] = ( @@ -176,6 +183,7 @@ class Button(Component): 'disabled', 'label', 'emoji', + 'sku_id', ) __repr_info__: ClassVar[Tuple[str, ...]] = __slots__ @@ -188,10 +196,15 @@ class Button(Component): self.label: Optional[str] = data.get('label') self.emoji: Optional[PartialEmoji] try: - self.emoji = PartialEmoji.from_dict(data['emoji']) + self.emoji = PartialEmoji.from_dict(data['emoji']) # pyright: ignore[reportTypedDictNotRequiredAccess] except KeyError: self.emoji = None + try: + self.sku_id: Optional[int] = int(data['sku_id']) # pyright: ignore[reportTypedDictNotRequiredAccess] + except KeyError: + self.sku_id = None + @property def type(self) -> Literal[ComponentType.button]: """:class:`ComponentType`: The type of component.""" @@ -204,6 +217,9 @@ class Button(Component): 'disabled': self.disabled, } + if self.sku_id: + payload['sku_id'] = str(self.sku_id) + if self.label: payload['label'] = self.label @@ -263,6 +279,7 @@ class SelectMenu(Component): 'options', 'disabled', 'channel_types', + 'default_values', ) __repr_info__: ClassVar[Tuple[str, ...]] = __slots__ @@ -276,10 +293,13 @@ class SelectMenu(Component): self.options: List[SelectOption] = [SelectOption.from_dict(option) for option in data.get('options', [])] self.disabled: bool = data.get('disabled', False) self.channel_types: List[ChannelType] = [try_enum(ChannelType, t) for t in data.get('channel_types', [])] + self.default_values: List[SelectDefaultValue] = [ + SelectDefaultValue.from_dict(d) for d in data.get('default_values', []) + ] def to_dict(self) -> SelectMenuPayload: payload: SelectMenuPayload = { - 'type': self.type.value, + 'type': self.type.value, # type: ignore # we know this is a select menu. 'custom_id': self.custom_id, 'min_values': self.min_values, 'max_values': self.max_values, @@ -291,6 +311,8 @@ class SelectMenu(Component): payload['options'] = [op.to_dict() for op in self.options] if self.channel_types: payload['channel_types'] = [t.value for t in self.channel_types] + if self.default_values: + payload["default_values"] = [v.to_dict() for v in self.default_values] return payload @@ -309,8 +331,8 @@ class SelectOption: Can only be up to 100 characters. value: :class:`str` The value of the option. This is not displayed to users. - If not provided when constructed then it defaults to the - label. Can only be up to 100 characters. + If not provided when constructed then it defaults to the label. + Can only be up to 100 characters. description: Optional[:class:`str`] An additional description of the option, if any. Can only be up to 100 characters. @@ -323,14 +345,12 @@ class SelectOption: ----------- label: :class:`str` The label of the option. This is displayed to users. - Can only be up to 100 characters. value: :class:`str` The value of the option. This is not displayed to users. If not provided when constructed then it defaults to the - label. Can only be up to 100 characters. + label. description: Optional[:class:`str`] An additional description of the option, if any. - Can only be up to 100 characters. default: :class:`bool` Whether this option is selected by default. """ @@ -395,7 +415,7 @@ class SelectOption: @classmethod def from_dict(cls, data: SelectOptionPayload) -> SelectOption: try: - emoji = PartialEmoji.from_dict(data['emoji']) + emoji = PartialEmoji.from_dict(data['emoji']) # pyright: ignore[reportTypedDictNotRequiredAccess] except KeyError: emoji = None @@ -422,6 +442,9 @@ class SelectOption: return payload + def copy(self) -> SelectOption: + return self.__class__.from_dict(self.to_dict()) + class TextInput(Component): """Represents a text input from the Discord Bot UI Kit. @@ -512,6 +535,116 @@ class TextInput(Component): return self.value +class SelectDefaultValue: + """Represents a select menu's default value. + + These can be created by users. + + .. versionadded:: 2.4 + + Parameters + ----------- + id: :class:`int` + The id of a role, user, or channel. + type: :class:`SelectDefaultValueType` + The type of value that ``id`` represents. + """ + + def __init__( + self, + *, + id: int, + type: SelectDefaultValueType, + ) -> None: + self.id: int = id + self._type: SelectDefaultValueType = type + + @property + def type(self) -> SelectDefaultValueType: + """:class:`SelectDefaultValueType`: The type of value that ``id`` represents.""" + return self._type + + @type.setter + def type(self, value: SelectDefaultValueType) -> None: + if not isinstance(value, SelectDefaultValueType): + raise TypeError(f'expected SelectDefaultValueType, received {value.__class__.__name__} instead') + + self._type = value + + def __repr__(self) -> str: + return f'' + + @classmethod + def from_dict(cls, data: SelectDefaultValuesPayload) -> SelectDefaultValue: + return cls( + id=data['id'], + type=try_enum(SelectDefaultValueType, data['type']), + ) + + def to_dict(self) -> SelectDefaultValuesPayload: + return { + 'id': self.id, + 'type': self._type.value, + } + + @classmethod + def from_channel(cls, channel: Snowflake, /) -> Self: + """Creates a :class:`SelectDefaultValue` with the type set to :attr:`~SelectDefaultValueType.channel`. + + Parameters + ----------- + channel: :class:`~discord.abc.Snowflake` + The channel to create the default value for. + + Returns + -------- + :class:`SelectDefaultValue` + The default value created with the channel. + """ + return cls( + id=channel.id, + type=SelectDefaultValueType.channel, + ) + + @classmethod + def from_role(cls, role: Snowflake, /) -> Self: + """Creates a :class:`SelectDefaultValue` with the type set to :attr:`~SelectDefaultValueType.role`. + + Parameters + ----------- + role: :class:`~discord.abc.Snowflake` + The role to create the default value for. + + Returns + -------- + :class:`SelectDefaultValue` + The default value created with the role. + """ + return cls( + id=role.id, + type=SelectDefaultValueType.role, + ) + + @classmethod + def from_user(cls, user: Snowflake, /) -> Self: + """Creates a :class:`SelectDefaultValue` with the type set to :attr:`~SelectDefaultValueType.user`. + + Parameters + ----------- + user: :class:`~discord.abc.Snowflake` + The user to create the default value for. + + Returns + -------- + :class:`SelectDefaultValue` + The default value created with the user. + """ + return cls( + id=user.id, + type=SelectDefaultValueType.user, + ) + + @overload def _component_factory(data: ActionRowChildComponentPayload) -> Optional[ActionRowChildComponentType]: ... @@ -527,7 +660,7 @@ def _component_factory(data: ComponentPayload) -> Optional[Union[ActionRow, Acti return ActionRow(data) elif data['type'] == 2: return Button(data) - elif data['type'] == 3: - return SelectMenu(data) elif data['type'] == 4: return TextInput(data) + elif data['type'] in (3, 5, 6, 7, 8): + return SelectMenu(data) diff --git a/discord/embeds.py b/discord/embeds.py index 6a79fef71..f55c7cac1 100644 --- a/discord/embeds.py +++ b/discord/embeds.py @@ -29,6 +29,7 @@ from typing import Any, Dict, List, Mapping, Optional, Protocol, TYPE_CHECKING, from . import utils from .colour import Colour +from .flags import AttachmentFlags, EmbedFlags # fmt: off __all__ = ( @@ -45,7 +46,7 @@ class EmbedProxy: return len(self.__dict__) def __repr__(self) -> str: - inner = ', '.join((f'{k}={v!r}' for k, v in self.__dict__.items() if not k.startswith('_'))) + inner = ', '.join((f'{k}={getattr(self, k)!r}' for k in dir(self) if not k.startswith('_'))) return f'EmbedProxy({inner})' def __getattr__(self, attr: str) -> None: @@ -55,6 +56,22 @@ class EmbedProxy: return isinstance(other, EmbedProxy) and self.__dict__ == other.__dict__ +class EmbedMediaProxy(EmbedProxy): + def __init__(self, layer: Dict[str, Any]): + super().__init__(layer) + self._flags = self.__dict__.pop('flags', 0) + + def __bool__(self) -> bool: + # This is a nasty check to see if we only have the `_flags` attribute which is created regardless in init. + # Had we had any of the other items, like image/video data this would be >1 and therefor + # would not be "empty". + return len(self.__dict__) > 1 + + @property + def flags(self) -> AttachmentFlags: + return AttachmentFlags._from_value(self._flags or 0) + + if TYPE_CHECKING: from typing_extensions import Self @@ -76,11 +93,7 @@ if TYPE_CHECKING: proxy_url: Optional[str] height: Optional[int] width: Optional[int] - - class _EmbedVideoProxy(Protocol): - url: Optional[str] - height: Optional[int] - width: Optional[int] + flags: AttachmentFlags class _EmbedProviderProxy(Protocol): name: Optional[str] @@ -131,7 +144,7 @@ class Embed: The type of embed. Usually "rich". This can be set during initialisation. Possible strings for embed types can be found on discord's - :ddocs:`api docs ` + :ddocs:`api docs ` description: Optional[:class:`str`] The description of the embed. This can be set during initialisation. @@ -162,6 +175,7 @@ class Embed: '_author', '_fields', 'description', + '_flags', ) def __init__( @@ -181,6 +195,7 @@ class Embed: self.type: EmbedType = type self.url: Optional[str] = url self.description: Optional[str] = description + self._flags: int = 0 if self.title is not None: self.title = str(self.title) @@ -199,7 +214,7 @@ class Embed: """Converts a :class:`dict` to a :class:`Embed` provided it is in the format that Discord expects it to be in. - You can find out about this format in the :ddocs:`official Discord documentation `. + You can find out about this format in the :ddocs:`official Discord documentation `. Parameters ----------- @@ -215,6 +230,7 @@ class Embed: self.type = data.get('type', None) self.description = data.get('description', None) self.url = data.get('url', None) + self._flags = data.get('flags', 0) if self.title is not None: self.title = str(self.title) @@ -305,8 +321,17 @@ class Embed: and self.image == other.image and self.provider == other.provider and self.video == other.video + and self._flags == other._flags ) + @property + def flags(self) -> EmbedFlags: + """:class:`EmbedFlags`: The flags of this embed. + + .. versionadded:: 2.5 + """ + return EmbedFlags._from_value(self._flags or 0) + @property def colour(self) -> Optional[Colour]: return getattr(self, '_colour', None) @@ -395,15 +420,16 @@ class Embed: Possible attributes you can access are: - - ``url`` - - ``proxy_url`` - - ``width`` - - ``height`` + - ``url`` for the image URL. + - ``proxy_url`` for the proxied image URL. + - ``width`` for the image width. + - ``height`` for the image height. + - ``flags`` for the image's attachment flags. If the attribute has no value then ``None`` is returned. """ # Lying to the type checker for better developer UX. - return EmbedProxy(getattr(self, '_image', {})) # type: ignore + return EmbedMediaProxy(getattr(self, '_image', {})) # type: ignore def set_image(self, *, url: Optional[Any]) -> Self: """Sets the image for the embed content. @@ -413,8 +439,9 @@ class Embed: Parameters ----------- - url: :class:`str` + url: Optional[:class:`str`] The source URL for the image. Only HTTP(S) is supported. + If ``None`` is passed, any existing image is removed. Inline attachment URLs are also supported, see :ref:`local_image`. """ @@ -436,15 +463,16 @@ class Embed: Possible attributes you can access are: - - ``url`` - - ``proxy_url`` - - ``width`` - - ``height`` + - ``url`` for the thumbnail URL. + - ``proxy_url`` for the proxied thumbnail URL. + - ``width`` for the thumbnail width. + - ``height`` for the thumbnail height. + - ``flags`` for the thumbnail's attachment flags. If the attribute has no value then ``None`` is returned. """ # Lying to the type checker for better developer UX. - return EmbedProxy(getattr(self, '_thumbnail', {})) # type: ignore + return EmbedMediaProxy(getattr(self, '_thumbnail', {})) # type: ignore def set_thumbnail(self, *, url: Optional[Any]) -> Self: """Sets the thumbnail for the embed content. @@ -452,13 +480,11 @@ class Embed: This function returns the class instance to allow for fluent-style chaining. - .. versionchanged:: 1.4 - Passing ``None`` removes the thumbnail. - Parameters ----------- - url: :class:`str` + url: Optional[:class:`str`] The source URL for the thumbnail. Only HTTP(S) is supported. + If ``None`` is passed, any existing thumbnail is removed. Inline attachment URLs are also supported, see :ref:`local_image`. """ @@ -475,19 +501,21 @@ class Embed: return self @property - def video(self) -> _EmbedVideoProxy: + def video(self) -> _EmbedMediaProxy: """Returns an ``EmbedProxy`` denoting the video contents. Possible attributes include: - ``url`` for the video URL. + - ``proxy_url`` for the proxied video URL. - ``height`` for the video height. - ``width`` for the video width. + - ``flags`` for the video's attachment flags. If the attribute has no value then ``None`` is returned. """ # Lying to the type checker for better developer UX. - return EmbedProxy(getattr(self, '_video', {})) # type: ignore + return EmbedMediaProxy(getattr(self, '_video', {})) # type: ignore @property def provider(self) -> _EmbedProviderProxy: @@ -715,7 +743,7 @@ class Embed: # fmt: off result = { key[1:]: getattr(self, key) - for key in self.__slots__ + for key in Embed.__slots__ if key[0] == '_' and hasattr(self, key) } # fmt: on diff --git a/discord/emoji.py b/discord/emoji.py index 045486d5a..74f344acc 100644 --- a/discord/emoji.py +++ b/discord/emoji.py @@ -29,6 +29,8 @@ from .asset import Asset, AssetMixin from .utils import SnowflakeList, snowflake_time, MISSING from .partial_emoji import _EmojiTag, PartialEmoji from .user import User +from .errors import MissingApplicationID +from .object import Object # fmt: off __all__ = ( @@ -93,6 +95,10 @@ class Emoji(_EmojiTag, AssetMixin): user: Optional[:class:`User`] The user that created the emoji. This can only be retrieved using :meth:`Guild.fetch_emoji` and having :attr:`~Permissions.manage_emojis`. + + Or if :meth:`.is_application_owned` is ``True``, this is the team member that uploaded + the emoji, or the bot user if it was uploaded using the API and this can + only be retrieved using :meth:`~discord.Client.fetch_application_emoji` or :meth:`~discord.Client.fetch_application_emojis`. """ __slots__: Tuple[str, ...] = ( @@ -108,7 +114,7 @@ class Emoji(_EmojiTag, AssetMixin): 'available', ) - def __init__(self, *, guild: Guild, state: ConnectionState, data: EmojiPayload) -> None: + def __init__(self, *, guild: Snowflake, state: ConnectionState, data: EmojiPayload) -> None: self.guild_id: int = guild.id self._state: ConnectionState = state self._from_data(data) @@ -196,20 +202,32 @@ class Emoji(_EmojiTag, AssetMixin): Deletes the custom emoji. - You must have :attr:`~Permissions.manage_emojis` to do this. + You must have :attr:`~Permissions.manage_emojis` to do this if + :meth:`.is_application_owned` is ``False``. Parameters ----------- reason: Optional[:class:`str`] The reason for deleting this emoji. Shows up on the audit log. + This does not apply if :meth:`.is_application_owned` is ``True``. + Raises ------- Forbidden You are not allowed to delete emojis. HTTPException An error occurred deleting the emoji. + MissingApplicationID + The emoji is owned by an application but the application ID is missing. """ + if self.is_application_owned(): + application_id = self._state.application_id + if application_id is None: + raise MissingApplicationID + + await self._state.http.delete_application_emoji(application_id, self.id) + return await self._state.http.delete_custom_emoji(self.guild_id, self.id, reason=reason) @@ -231,15 +249,22 @@ class Emoji(_EmojiTag, AssetMixin): The new emoji name. roles: List[:class:`~discord.abc.Snowflake`] A list of roles that can use this emoji. An empty list can be passed to make it available to everyone. + + This does not apply if :meth:`.is_application_owned` is ``True``. + reason: Optional[:class:`str`] The reason for editing this emoji. Shows up on the audit log. + This does not apply if :meth:`.is_application_owned` is ``True``. + Raises ------- Forbidden You are not allowed to edit emojis. HTTPException An error occurred editing the emoji. + MissingApplicationID + The emoji is owned by an application but the application ID is missing Returns -------- @@ -253,5 +278,25 @@ class Emoji(_EmojiTag, AssetMixin): if roles is not MISSING: payload['roles'] = [role.id for role in roles] + if self.is_application_owned(): + application_id = self._state.application_id + if application_id is None: + raise MissingApplicationID + + payload.pop('roles', None) + data = await self._state.http.edit_application_emoji( + application_id, + self.id, + payload=payload, + ) + return Emoji(guild=Object(0), data=data, state=self._state) + data = await self._state.http.edit_custom_emoji(self.guild_id, self.id, payload=payload, reason=reason) return Emoji(guild=self.guild, data=data, state=self._state) # type: ignore # if guild is None, the http request would have failed + + def is_application_owned(self) -> bool: + """:class:`bool`: Whether the emoji is owned by an application. + + .. versionadded:: 2.5 + """ + return self.guild_id == 0 diff --git a/discord/enums.py b/discord/enums.py index 96518f42e..71f755c12 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -42,6 +42,7 @@ __all__ = ( 'ActivityType', 'NotificationLevel', 'TeamMembershipState', + 'TeamMemberRole', 'WebhookType', 'ExpireBehaviour', 'ExpireBehavior', @@ -67,23 +68,29 @@ __all__ = ( 'AutoModRuleEventType', 'AutoModRuleActionType', 'ForumLayoutType', + 'ForumOrderType', + 'SelectDefaultValueType', + 'SKUType', + 'EntitlementType', + 'EntitlementOwnerType', + 'PollLayoutType', + 'VoiceChannelEffectAnimationType', + 'SubscriptionStatus', + 'MessageReferenceType', ) -if TYPE_CHECKING: - from typing_extensions import Self - def _create_value_cls(name: str, comparable: bool): # All the type ignores here are due to the type checker being unable to recognise # Runtime type creation without exploding. cls = namedtuple('_EnumValue_' + name, 'name value') - cls.__repr__ = lambda self: f'<{name}.{self.name}: {self.value!r}>' # type: ignore - cls.__str__ = lambda self: f'{name}.{self.name}' # type: ignore + cls.__repr__ = lambda self: f'<{name}.{self.name}: {self.value!r}>' + cls.__str__ = lambda self: f'{name}.{self.name}' if comparable: - cls.__le__ = lambda self, other: isinstance(other, self.__class__) and self.value <= other.value # type: ignore - cls.__ge__ = lambda self, other: isinstance(other, self.__class__) and self.value >= other.value # type: ignore - cls.__lt__ = lambda self, other: isinstance(other, self.__class__) and self.value < other.value # type: ignore - cls.__gt__ = lambda self, other: isinstance(other, self.__class__) and self.value > other.value # type: ignore + cls.__le__ = lambda self, other: isinstance(other, self.__class__) and self.value <= other.value + cls.__ge__ = lambda self, other: isinstance(other, self.__class__) and self.value >= other.value + cls.__lt__ = lambda self, other: isinstance(other, self.__class__) and self.value < other.value + cls.__gt__ = lambda self, other: isinstance(other, self.__class__) and self.value > other.value return cls @@ -98,7 +105,14 @@ class EnumMeta(type): _enum_member_map_: ClassVar[Dict[str, Any]] _enum_value_map_: ClassVar[Dict[Any, Any]] - def __new__(cls, name: str, bases: Tuple[type, ...], attrs: Dict[str, Any], *, comparable: bool = False) -> Self: + def __new__( + cls, + name: str, + bases: Tuple[type, ...], + attrs: Dict[str, Any], + *, + comparable: bool = False, + ) -> EnumMeta: value_mapping = {} member_mapping = {} member_names = [] @@ -201,11 +215,18 @@ class ChannelType(Enum): private_thread = 12 stage_voice = 13 forum = 15 + media = 16 def __str__(self) -> str: return self.name +class MessageReferenceType(Enum): + default = 0 + reply = 0 + forward = 1 + + class MessageType(Enum): default = 0 recipient_add = 1 @@ -234,12 +255,18 @@ class MessageType(Enum): auto_moderation_action = 24 role_subscription_purchase = 25 interaction_premium_upsell = 26 - # stage_start = 27 - # stage_end = 28 - # stage_speaker = 29 - # stage_raise_hand = 30 - # stage_topic = 31 + stage_start = 27 + stage_end = 28 + stage_speaker = 29 + stage_raise_hand = 30 + stage_topic = 31 guild_application_premium_subscription = 32 + guild_incident_alert_mode_enabled = 36 + guild_incident_alert_mode_disabled = 37 + guild_incident_report_raid = 38 + guild_incident_report_false_alarm = 39 + purchase_notification = 44 + poll_result = 46 class SpeakingState(Enum): @@ -294,6 +321,7 @@ class DefaultAvatar(Enum): green = 2 orange = 3 red = 4 + pink = 5 def __str__(self) -> str: return self.name @@ -312,120 +340,130 @@ class AuditLogActionCategory(Enum): class AuditLogAction(Enum): # fmt: off - guild_update = 1 - channel_create = 10 - channel_update = 11 - channel_delete = 12 - overwrite_create = 13 - overwrite_update = 14 - overwrite_delete = 15 - kick = 20 - member_prune = 21 - ban = 22 - unban = 23 - member_update = 24 - member_role_update = 25 - member_move = 26 - member_disconnect = 27 - bot_add = 28 - role_create = 30 - role_update = 31 - role_delete = 32 - invite_create = 40 - invite_update = 41 - invite_delete = 42 - webhook_create = 50 - webhook_update = 51 - webhook_delete = 52 - emoji_create = 60 - emoji_update = 61 - emoji_delete = 62 - message_delete = 72 - message_bulk_delete = 73 - message_pin = 74 - message_unpin = 75 - integration_create = 80 - integration_update = 81 - integration_delete = 82 - stage_instance_create = 83 - stage_instance_update = 84 - stage_instance_delete = 85 - sticker_create = 90 - sticker_update = 91 - sticker_delete = 92 - scheduled_event_create = 100 - scheduled_event_update = 101 - scheduled_event_delete = 102 - thread_create = 110 - thread_update = 111 - thread_delete = 112 - app_command_permission_update = 121 - automod_rule_create = 140 - automod_rule_update = 141 - automod_rule_delete = 142 - automod_block_message = 143 - automod_flag_message = 144 - automod_timeout_member = 145 + guild_update = 1 + channel_create = 10 + channel_update = 11 + channel_delete = 12 + overwrite_create = 13 + overwrite_update = 14 + overwrite_delete = 15 + kick = 20 + member_prune = 21 + ban = 22 + unban = 23 + member_update = 24 + member_role_update = 25 + member_move = 26 + member_disconnect = 27 + bot_add = 28 + role_create = 30 + role_update = 31 + role_delete = 32 + invite_create = 40 + invite_update = 41 + invite_delete = 42 + webhook_create = 50 + webhook_update = 51 + webhook_delete = 52 + emoji_create = 60 + emoji_update = 61 + emoji_delete = 62 + message_delete = 72 + message_bulk_delete = 73 + message_pin = 74 + message_unpin = 75 + integration_create = 80 + integration_update = 81 + integration_delete = 82 + stage_instance_create = 83 + stage_instance_update = 84 + stage_instance_delete = 85 + sticker_create = 90 + sticker_update = 91 + sticker_delete = 92 + scheduled_event_create = 100 + scheduled_event_update = 101 + scheduled_event_delete = 102 + thread_create = 110 + thread_update = 111 + thread_delete = 112 + app_command_permission_update = 121 + soundboard_sound_create = 130 + soundboard_sound_update = 131 + soundboard_sound_delete = 132 + automod_rule_create = 140 + automod_rule_update = 141 + automod_rule_delete = 142 + automod_block_message = 143 + automod_flag_message = 144 + automod_timeout_member = 145 + creator_monetization_request_created = 150 + creator_monetization_terms_accepted = 151 # fmt: on @property def category(self) -> Optional[AuditLogActionCategory]: # fmt: off lookup: Dict[AuditLogAction, Optional[AuditLogActionCategory]] = { - AuditLogAction.guild_update: AuditLogActionCategory.update, - AuditLogAction.channel_create: AuditLogActionCategory.create, - AuditLogAction.channel_update: AuditLogActionCategory.update, - AuditLogAction.channel_delete: AuditLogActionCategory.delete, - AuditLogAction.overwrite_create: AuditLogActionCategory.create, - AuditLogAction.overwrite_update: AuditLogActionCategory.update, - AuditLogAction.overwrite_delete: AuditLogActionCategory.delete, - AuditLogAction.kick: None, - AuditLogAction.member_prune: None, - AuditLogAction.ban: None, - AuditLogAction.unban: None, - AuditLogAction.member_update: AuditLogActionCategory.update, - AuditLogAction.member_role_update: AuditLogActionCategory.update, - AuditLogAction.member_move: None, - AuditLogAction.member_disconnect: None, - AuditLogAction.bot_add: None, - AuditLogAction.role_create: AuditLogActionCategory.create, - AuditLogAction.role_update: AuditLogActionCategory.update, - AuditLogAction.role_delete: AuditLogActionCategory.delete, - AuditLogAction.invite_create: AuditLogActionCategory.create, - AuditLogAction.invite_update: AuditLogActionCategory.update, - AuditLogAction.invite_delete: AuditLogActionCategory.delete, - AuditLogAction.webhook_create: AuditLogActionCategory.create, - AuditLogAction.webhook_update: AuditLogActionCategory.update, - AuditLogAction.webhook_delete: AuditLogActionCategory.delete, - AuditLogAction.emoji_create: AuditLogActionCategory.create, - AuditLogAction.emoji_update: AuditLogActionCategory.update, - AuditLogAction.emoji_delete: AuditLogActionCategory.delete, - AuditLogAction.message_delete: AuditLogActionCategory.delete, - AuditLogAction.message_bulk_delete: AuditLogActionCategory.delete, - AuditLogAction.message_pin: None, - AuditLogAction.message_unpin: None, - AuditLogAction.integration_create: AuditLogActionCategory.create, - AuditLogAction.integration_update: AuditLogActionCategory.update, - AuditLogAction.integration_delete: AuditLogActionCategory.delete, - AuditLogAction.stage_instance_create: AuditLogActionCategory.create, - AuditLogAction.stage_instance_update: AuditLogActionCategory.update, - AuditLogAction.stage_instance_delete: AuditLogActionCategory.delete, - AuditLogAction.sticker_create: AuditLogActionCategory.create, - AuditLogAction.sticker_update: AuditLogActionCategory.update, - AuditLogAction.sticker_delete: AuditLogActionCategory.delete, - AuditLogAction.scheduled_event_create: AuditLogActionCategory.create, - AuditLogAction.scheduled_event_update: AuditLogActionCategory.update, - AuditLogAction.scheduled_event_delete: AuditLogActionCategory.delete, - AuditLogAction.thread_create: AuditLogActionCategory.create, - AuditLogAction.thread_delete: AuditLogActionCategory.delete, - AuditLogAction.thread_update: AuditLogActionCategory.update, - AuditLogAction.app_command_permission_update: AuditLogActionCategory.update, - AuditLogAction.automod_rule_create: AuditLogActionCategory.create, - AuditLogAction.automod_rule_update: AuditLogActionCategory.update, - AuditLogAction.automod_rule_delete: AuditLogActionCategory.delete, - AuditLogAction.automod_block_message: None, - AuditLogAction.automod_flag_message: None, - AuditLogAction.automod_timeout_member: None, + AuditLogAction.guild_update: AuditLogActionCategory.update, + AuditLogAction.channel_create: AuditLogActionCategory.create, + AuditLogAction.channel_update: AuditLogActionCategory.update, + AuditLogAction.channel_delete: AuditLogActionCategory.delete, + AuditLogAction.overwrite_create: AuditLogActionCategory.create, + AuditLogAction.overwrite_update: AuditLogActionCategory.update, + AuditLogAction.overwrite_delete: AuditLogActionCategory.delete, + AuditLogAction.kick: None, + AuditLogAction.member_prune: None, + AuditLogAction.ban: None, + AuditLogAction.unban: None, + AuditLogAction.member_update: AuditLogActionCategory.update, + AuditLogAction.member_role_update: AuditLogActionCategory.update, + AuditLogAction.member_move: None, + AuditLogAction.member_disconnect: None, + AuditLogAction.bot_add: None, + AuditLogAction.role_create: AuditLogActionCategory.create, + AuditLogAction.role_update: AuditLogActionCategory.update, + AuditLogAction.role_delete: AuditLogActionCategory.delete, + AuditLogAction.invite_create: AuditLogActionCategory.create, + AuditLogAction.invite_update: AuditLogActionCategory.update, + AuditLogAction.invite_delete: AuditLogActionCategory.delete, + AuditLogAction.webhook_create: AuditLogActionCategory.create, + AuditLogAction.webhook_update: AuditLogActionCategory.update, + AuditLogAction.webhook_delete: AuditLogActionCategory.delete, + AuditLogAction.emoji_create: AuditLogActionCategory.create, + AuditLogAction.emoji_update: AuditLogActionCategory.update, + AuditLogAction.emoji_delete: AuditLogActionCategory.delete, + AuditLogAction.message_delete: AuditLogActionCategory.delete, + AuditLogAction.message_bulk_delete: AuditLogActionCategory.delete, + AuditLogAction.message_pin: None, + AuditLogAction.message_unpin: None, + AuditLogAction.integration_create: AuditLogActionCategory.create, + AuditLogAction.integration_update: AuditLogActionCategory.update, + AuditLogAction.integration_delete: AuditLogActionCategory.delete, + AuditLogAction.stage_instance_create: AuditLogActionCategory.create, + AuditLogAction.stage_instance_update: AuditLogActionCategory.update, + AuditLogAction.stage_instance_delete: AuditLogActionCategory.delete, + AuditLogAction.sticker_create: AuditLogActionCategory.create, + AuditLogAction.sticker_update: AuditLogActionCategory.update, + AuditLogAction.sticker_delete: AuditLogActionCategory.delete, + AuditLogAction.scheduled_event_create: AuditLogActionCategory.create, + AuditLogAction.scheduled_event_update: AuditLogActionCategory.update, + AuditLogAction.scheduled_event_delete: AuditLogActionCategory.delete, + AuditLogAction.thread_create: AuditLogActionCategory.create, + AuditLogAction.thread_delete: AuditLogActionCategory.delete, + AuditLogAction.thread_update: AuditLogActionCategory.update, + AuditLogAction.app_command_permission_update: AuditLogActionCategory.update, + AuditLogAction.automod_rule_create: AuditLogActionCategory.create, + AuditLogAction.automod_rule_update: AuditLogActionCategory.update, + AuditLogAction.automod_rule_delete: AuditLogActionCategory.delete, + AuditLogAction.automod_block_message: None, + AuditLogAction.automod_flag_message: None, + AuditLogAction.automod_timeout_member: None, + AuditLogAction.creator_monetization_request_created: None, + AuditLogAction.creator_monetization_terms_accepted: None, + AuditLogAction.soundboard_sound_create: AuditLogActionCategory.create, + AuditLogAction.soundboard_sound_update: AuditLogActionCategory.update, + AuditLogAction.soundboard_sound_delete: AuditLogActionCategory.delete, } # fmt: on return lookup[self] @@ -465,10 +503,12 @@ class AuditLogAction(Enum): return 'thread' elif v < 122: return 'integration_or_app_command' - elif v < 143: + elif 139 < v < 143: return 'auto_moderation' elif v < 146: return 'user' + elif v < 152: + return 'creator_monetization' class UserFlags(Enum): @@ -512,6 +552,12 @@ class TeamMembershipState(Enum): accepted = 2 +class TeamMemberRole(Enum): + admin = 'admin' + developer = 'developer' + read_only = 'read_only' + + class WebhookType(Enum): incoming = 1 channel_follower = 2 @@ -574,6 +620,8 @@ class InteractionResponseType(Enum): message_update = 7 # for components autocomplete_result = 8 modal = 9 # for modals + # premium_required = 10 (deprecated) + launch_activity = 12 class VideoQualityMode(Enum): @@ -605,6 +653,7 @@ class ButtonStyle(Enum): success = 3 danger = 4 link = 5 + premium = 6 # Aliases blurple = 1 @@ -665,6 +714,7 @@ class Locale(Enum): italian = 'it' japanese = 'ja' korean = 'ko' + latin_american_spanish = 'es-419' lithuanian = 'lt' norwegian = 'no' polish = 'pl' @@ -733,16 +783,19 @@ class AutoModRuleTriggerType(Enum): spam = 3 keyword_preset = 4 mention_spam = 5 + member_profile = 6 class AutoModRuleEventType(Enum): message_send = 1 + member_update = 2 class AutoModRuleActionType(Enum): block_message = 1 send_alert_message = 2 timeout = 3 + block_member_interactions = 4 class ForumLayoutType(Enum): @@ -751,9 +804,64 @@ class ForumLayoutType(Enum): gallery_view = 2 -class OnboardingPromptType(Enum): - multiple_choice = 0 - dropdown = 1 +class ForumOrderType(Enum): + latest_activity = 0 + creation_date = 1 + + +class SelectDefaultValueType(Enum): + user = 'user' + role = 'role' + channel = 'channel' + + +class SKUType(Enum): + durable = 2 + consumable = 3 + subscription = 5 + subscription_group = 6 + + +class EntitlementType(Enum): + purchase = 1 + premium_subscription = 2 + developer_gift = 3 + test_mode_purchase = 4 + free_purchase = 5 + user_gift = 6 + premium_purchase = 7 + application_subscription = 8 + + +class EntitlementOwnerType(Enum): + guild = 1 + user = 2 + + +class PollLayoutType(Enum): + default = 1 + + +class InviteType(Enum): + guild = 0 + group_dm = 1 + friend = 2 + + +class ReactionType(Enum): + normal = 0 + burst = 1 + + +class VoiceChannelEffectAnimationType(Enum): + premium = 0 + basic = 1 + + +class SubscriptionStatus(Enum): + active = 0 + ending = 1 + inactive = 2 def create_unknown_value(cls: Type[E], val: Any) -> E: diff --git a/discord/errors.py b/discord/errors.py index 6035ace7c..a40842578 100644 --- a/discord/errors.py +++ b/discord/errors.py @@ -47,6 +47,12 @@ __all__ = ( 'ConnectionClosed', 'PrivilegedIntentsRequired', 'InteractionResponded', + 'MissingApplicationID', +) + +APP_ID_NOT_FOUND = ( + 'Client does not have an application_id set. Either the function was called before on_ready ' + 'was called or application_id was not passed to the Client constructor.' ) @@ -278,3 +284,22 @@ class InteractionResponded(ClientException): def __init__(self, interaction: Interaction): self.interaction: Interaction = interaction super().__init__('This interaction has already been responded to before') + + +class MissingApplicationID(ClientException): + """An exception raised when the client does not have an application ID set. + + An application ID is required for syncing application commands and various + other application tasks such as SKUs or application emojis. + + This inherits from :exc:`~discord.app_commands.AppCommandError` + and :class:`~discord.ClientException`. + + .. versionadded:: 2.0 + + .. versionchanged:: 2.5 + This is now exported to the ``discord`` namespace and now inherits from :class:`~discord.ClientException`. + """ + + def __init__(self, message: Optional[str] = None): + super().__init__(message or APP_ID_NOT_FOUND) diff --git a/discord/ext/commands/bot.py b/discord/ext/commands/bot.py index 363b6656c..8ce872f1a 100644 --- a/discord/ext/commands/bot.py +++ b/discord/ext/commands/bot.py @@ -166,14 +166,21 @@ class BotBase(GroupMixin[None]): help_command: Optional[HelpCommand] = _default, tree_cls: Type[app_commands.CommandTree[Any]] = app_commands.CommandTree, description: Optional[str] = None, + allowed_contexts: app_commands.AppCommandContext = MISSING, + allowed_installs: app_commands.AppInstallationType = MISSING, intents: discord.Intents, **options: Any, ) -> None: super().__init__(intents=intents, **options) - self.command_prefix: PrefixType[BotT] = command_prefix + self.command_prefix: PrefixType[BotT] = command_prefix # type: ignore self.extra_events: Dict[str, List[CoroFunc]] = {} # Self doesn't have the ClientT bound, but since this is a mixin it technically does self.__tree: app_commands.CommandTree[Self] = tree_cls(self) # type: ignore + if allowed_contexts is not MISSING: + self.__tree.allowed_contexts = allowed_contexts + if allowed_installs is not MISSING: + self.__tree.allowed_installs = allowed_installs + self.__cogs: Dict[str, Cog] = {} self.__extensions: Dict[str, types.ModuleType] = {} self._checks: List[UserCheck] = [] @@ -480,7 +487,7 @@ class BotBase(GroupMixin[None]): if len(data) == 0: return True - return await discord.utils.async_all(f(ctx) for f in data) + return await discord.utils.async_all(f(ctx) for f in data) # type: ignore async def is_owner(self, user: User, /) -> bool: """|coro| @@ -499,6 +506,12 @@ class BotBase(GroupMixin[None]): ``user`` parameter is now positional-only. + .. versionchanged:: 2.4 + + This function now respects the team member roles if the bot is team-owned. + In order to be considered an owner, they must be either an admin or + a developer. + Parameters ----------- user: :class:`.abc.User` @@ -515,10 +528,13 @@ class BotBase(GroupMixin[None]): elif self.owner_ids: return user.id in self.owner_ids else: - - app = await self.application_info() # type: ignore + app: discord.AppInfo = await self.application_info() # type: ignore if app.team: - self.owner_ids = ids = {m.id for m in app.team.members} + self.owner_ids = ids = { + m.id + for m in app.team.members + if m.role in (discord.TeamMemberRole.admin, discord.TeamMemberRole.developer) + } return user.id in ids else: self.owner_id = owner_id = app.owner.id @@ -1479,6 +1495,20 @@ class Bot(BotBase, discord.Client): The type of application command tree to use. Defaults to :class:`~discord.app_commands.CommandTree`. .. versionadded:: 2.0 + allowed_contexts: :class:`~discord.app_commands.AppCommandContext` + The default allowed contexts that applies to all application commands + in the application command tree. + + Note that you can override this on a per command basis. + + .. versionadded:: 2.4 + allowed_installs: :class:`~discord.app_commands.AppInstallationType` + The default allowed install locations that apply to all application commands + in the application command tree. + + Note that you can override this on a per command basis. + + .. versionadded:: 2.4 """ pass diff --git a/discord/ext/commands/cog.py b/discord/ext/commands/cog.py index 11f6a0393..659d69ebb 100644 --- a/discord/ext/commands/cog.py +++ b/discord/ext/commands/cog.py @@ -25,6 +25,7 @@ from __future__ import annotations import inspect import discord +import logging from discord import app_commands from discord.utils import maybe_coroutine, _to_kebab_case @@ -50,6 +51,7 @@ from ._types import _BaseCommand, BotT if TYPE_CHECKING: from typing_extensions import Self from discord.abc import Snowflake + from discord._types import ClientT from .bot import BotBase from .context import Context @@ -64,6 +66,7 @@ __all__ = ( FuncT = TypeVar('FuncT', bound=Callable[..., Any]) MISSING: Any = discord.utils.MISSING +_log = logging.getLogger(__name__) class CogMeta(type): @@ -166,7 +169,7 @@ class CogMeta(type): __cog_app_commands__: List[Union[app_commands.Group, app_commands.Command[Any, ..., Any]]] __cog_listeners__: List[Tuple[str, str]] - def __new__(cls, *args: Any, **kwargs: Any) -> Self: + def __new__(cls, *args: Any, **kwargs: Any) -> CogMeta: name, bases, attrs = args if any(issubclass(base, app_commands.Group) for base in bases): raise TypeError( @@ -304,6 +307,7 @@ class Cog(metaclass=CogMeta): # Register the application commands children: List[Union[app_commands.Group, app_commands.Command[Self, ..., Any]]] = [] + app_command_refs: Dict[str, Union[app_commands.Group, app_commands.Command[Self, ..., Any]]] = {} if cls.__cog_is_app_commands_group__: group = app_commands.Group( @@ -314,6 +318,8 @@ class Cog(metaclass=CogMeta): parent=None, guild_ids=getattr(cls, '__discord_app_commands_default_guilds__', None), guild_only=getattr(cls, '__discord_app_commands_guild_only__', False), + allowed_contexts=getattr(cls, '__discord_app_commands_contexts__', None), + allowed_installs=getattr(cls, '__discord_app_commands_installation_types__', None), default_permissions=getattr(cls, '__discord_app_commands_default_permissions__', None), extras=cls.__cog_group_extras__, ) @@ -330,6 +336,16 @@ class Cog(metaclass=CogMeta): # Get the latest parent reference parent = lookup[parent.qualified_name] # type: ignore + # Hybrid commands already deal with updating the reference + # Due to the copy below, so we need to handle them specially + if hasattr(parent, '__commands_is_hybrid__') and hasattr(command, '__commands_is_hybrid__'): + current: Optional[Union[app_commands.Group, app_commands.Command[Self, ..., Any]]] = getattr( + command, 'app_command', None + ) + updated = app_command_refs.get(command.qualified_name) + if current and updated: + command.app_command = updated # type: ignore # Safe attribute access + # Update our parent's reference to our self parent.remove_command(command.name) # type: ignore parent.add_command(command) # type: ignore @@ -344,8 +360,15 @@ class Cog(metaclass=CogMeta): # The type checker does not see the app_command attribute even though it exists command.app_command = app_command # type: ignore + # Update all the references to point to the new copy + if isinstance(app_command, app_commands.Group): + for child in app_command.walk_commands(): + app_command_refs[child.qualified_name] = child + if hasattr(child, '__commands_is_hybrid_app_command__') and child.qualified_name in lookup: + child.wrapped = lookup[child.qualified_name] # type: ignore + if self.__cog_app_commands_group__: - children.append(app_command) # type: ignore # Somehow it thinks it can be None here + children.append(app_command) if Cog._get_overridden_method(self.cog_app_command_error) is not None: error_handler = self.cog_app_command_error @@ -376,7 +399,7 @@ class Cog(metaclass=CogMeta): if len(mapping) > 25: raise TypeError('maximum number of application command children exceeded') - self.__cog_app_commands_group__._children = mapping # type: ignore # Variance issue + self.__cog_app_commands_group__._children = mapping return self @@ -549,6 +572,8 @@ class Cog(metaclass=CogMeta): Subclasses must replace this if they want special unloading behaviour. + Exceptions raised in this method are ignored during extension unloading. + .. versionchanged:: 2.0 This method can now be a :term:`coroutine`. @@ -585,6 +610,18 @@ class Cog(metaclass=CogMeta): """ return True + @_cog_special_method + def interaction_check(self, interaction: discord.Interaction[ClientT], /) -> bool: + """A special method that registers as a :func:`discord.app_commands.check` + for every app command and subcommand in this cog. + + This function **can** be a coroutine and must take a sole parameter, + ``interaction``, to represent the :class:`~discord.Interaction`. + + .. versionadded:: 2.0 + """ + return True + @_cog_special_method async def cog_command_error(self, ctx: Context[BotT], error: Exception) -> None: """|coro| @@ -738,7 +775,7 @@ class Cog(metaclass=CogMeta): try: await maybe_coroutine(self.cog_unload) except Exception: - pass + _log.exception('Ignoring exception in cog unload for Cog %r (%r)', cls, self.qualified_name) class GroupCog(Cog): diff --git a/discord/ext/commands/context.py b/discord/ext/commands/context.py index 8c1f7212d..b5b96c15f 100644 --- a/discord/ext/commands/context.py +++ b/discord/ext/commands/context.py @@ -24,7 +24,7 @@ DEALINGS IN THE SOFTWARE. from __future__ import annotations import re -from typing import TYPE_CHECKING, Any, Dict, Generator, Generic, List, Optional, TypeVar, Union, Sequence, Type +from typing import TYPE_CHECKING, Any, Dict, Generator, Generic, List, Optional, TypeVar, Union, Sequence, Type, overload import discord.abc import discord.utils @@ -50,6 +50,7 @@ if TYPE_CHECKING: from discord.message import MessageReference, PartialMessage from discord.ui import View from discord.types.interactions import ApplicationCommandInteractionData + from discord.poll import Poll from .cog import Cog from .core import Command @@ -81,16 +82,20 @@ def is_cog(obj: Any) -> TypeGuard[Cog]: return hasattr(obj, '__cog_commands__') -class DeferTyping: +class DeferTyping(Generic[BotT]): def __init__(self, ctx: Context[BotT], *, ephemeral: bool): self.ctx: Context[BotT] = ctx self.ephemeral: bool = ephemeral + async def do_defer(self) -> None: + if self.ctx.interaction and not self.ctx.interaction.response.is_done(): + await self.ctx.interaction.response.defer(ephemeral=self.ephemeral) + def __await__(self) -> Generator[Any, None, None]: - return self.ctx.defer(ephemeral=self.ephemeral).__await__() + return self.do_defer().__await__() async def __aenter__(self) -> None: - await self.ctx.defer(ephemeral=self.ephemeral) + await self.do_defer() async def __aexit__( self, @@ -251,7 +256,7 @@ class Context(discord.abc.Messageable, Generic[BotT]): if command is None: raise ValueError('interaction does not have command data') - bot: BotT = interaction.client # type: ignore + bot: BotT = interaction.client data: ApplicationCommandInteractionData = interaction.data # type: ignore if interaction.message is None: synthetic_payload = { @@ -430,6 +435,14 @@ class Context(discord.abc.Messageable, Generic[BotT]): return None return self.command.cog + @property + def filesize_limit(self) -> int: + """:class:`int`: Returns the maximum number of bytes files can have when uploaded to this guild or DM channel associated with this context. + + .. versionadded:: 2.3 + """ + return self.guild.filesize_limit if self.guild is not None else discord.utils.DEFAULT_FILE_SIZE_LIMIT_BYTES + @discord.utils.cached_property def guild(self) -> Optional[Guild]: """Optional[:class:`.Guild`]: Returns the guild associated with this context's command. None if not available.""" @@ -464,7 +477,7 @@ class Context(discord.abc.Messageable, Generic[BotT]): .. versionadded:: 2.0 """ - if self.channel.type is ChannelType.private: + if self.interaction is None and self.channel.type is ChannelType.private: return Permissions._dm_permissions() if not self.interaction: # channel and author will always match relevant types here @@ -498,7 +511,7 @@ class Context(discord.abc.Messageable, Generic[BotT]): .. versionadded:: 2.0 """ channel = self.channel - if channel.type == ChannelType.private: + if self.interaction is None and channel.type == ChannelType.private: return Permissions._dm_permissions() if not self.interaction: # channel and me will always match relevant types here @@ -615,6 +628,94 @@ class Context(discord.abc.Messageable, Generic[BotT]): except CommandError as e: await cmd.on_help_command_error(self, e) + @overload + async def reply( + self, + content: Optional[str] = ..., + *, + tts: bool = ..., + embed: Embed = ..., + file: File = ..., + stickers: Sequence[Union[GuildSticker, StickerItem]] = ..., + delete_after: float = ..., + nonce: Union[str, int] = ..., + allowed_mentions: AllowedMentions = ..., + reference: Union[Message, MessageReference, PartialMessage] = ..., + mention_author: bool = ..., + view: View = ..., + suppress_embeds: bool = ..., + ephemeral: bool = ..., + silent: bool = ..., + poll: Poll = ..., + ) -> Message: + ... + + @overload + async def reply( + self, + content: Optional[str] = ..., + *, + tts: bool = ..., + embed: Embed = ..., + files: Sequence[File] = ..., + stickers: Sequence[Union[GuildSticker, StickerItem]] = ..., + delete_after: float = ..., + nonce: Union[str, int] = ..., + allowed_mentions: AllowedMentions = ..., + reference: Union[Message, MessageReference, PartialMessage] = ..., + mention_author: bool = ..., + view: View = ..., + suppress_embeds: bool = ..., + ephemeral: bool = ..., + silent: bool = ..., + poll: Poll = ..., + ) -> Message: + ... + + @overload + async def reply( + self, + content: Optional[str] = ..., + *, + tts: bool = ..., + embeds: Sequence[Embed] = ..., + file: File = ..., + stickers: Sequence[Union[GuildSticker, StickerItem]] = ..., + delete_after: float = ..., + nonce: Union[str, int] = ..., + allowed_mentions: AllowedMentions = ..., + reference: Union[Message, MessageReference, PartialMessage] = ..., + mention_author: bool = ..., + view: View = ..., + suppress_embeds: bool = ..., + ephemeral: bool = ..., + silent: bool = ..., + poll: Poll = ..., + ) -> Message: + ... + + @overload + async def reply( + self, + content: Optional[str] = ..., + *, + tts: bool = ..., + embeds: Sequence[Embed] = ..., + files: Sequence[File] = ..., + stickers: Sequence[Union[GuildSticker, StickerItem]] = ..., + delete_after: float = ..., + nonce: Union[str, int] = ..., + allowed_mentions: AllowedMentions = ..., + reference: Union[Message, MessageReference, PartialMessage] = ..., + mention_author: bool = ..., + view: View = ..., + suppress_embeds: bool = ..., + ephemeral: bool = ..., + silent: bool = ..., + poll: Poll = ..., + ) -> Message: + ... + async def reply(self, content: Optional[str] = None, **kwargs: Any) -> Message: """|coro| @@ -650,7 +751,7 @@ class Context(discord.abc.Messageable, Generic[BotT]): else: return await self.send(content, **kwargs) - def typing(self, *, ephemeral: bool = False) -> Union[Typing, DeferTyping]: + def typing(self, *, ephemeral: bool = False) -> Union[Typing, DeferTyping[BotT]]: """Returns an asynchronous context manager that allows you to send a typing indicator to the destination for an indefinite period of time, or 10 seconds if the context manager is called using ``await``. @@ -716,6 +817,94 @@ class Context(discord.abc.Messageable, Generic[BotT]): if self.interaction: await self.interaction.response.defer(ephemeral=ephemeral) + @overload + async def send( + self, + content: Optional[str] = ..., + *, + tts: bool = ..., + embed: Embed = ..., + file: File = ..., + stickers: Sequence[Union[GuildSticker, StickerItem]] = ..., + delete_after: float = ..., + nonce: Union[str, int] = ..., + allowed_mentions: AllowedMentions = ..., + reference: Union[Message, MessageReference, PartialMessage] = ..., + mention_author: bool = ..., + view: View = ..., + suppress_embeds: bool = ..., + ephemeral: bool = ..., + silent: bool = ..., + poll: Poll = ..., + ) -> Message: + ... + + @overload + async def send( + self, + content: Optional[str] = ..., + *, + tts: bool = ..., + embed: Embed = ..., + files: Sequence[File] = ..., + stickers: Sequence[Union[GuildSticker, StickerItem]] = ..., + delete_after: float = ..., + nonce: Union[str, int] = ..., + allowed_mentions: AllowedMentions = ..., + reference: Union[Message, MessageReference, PartialMessage] = ..., + mention_author: bool = ..., + view: View = ..., + suppress_embeds: bool = ..., + ephemeral: bool = ..., + silent: bool = ..., + poll: Poll = ..., + ) -> Message: + ... + + @overload + async def send( + self, + content: Optional[str] = ..., + *, + tts: bool = ..., + embeds: Sequence[Embed] = ..., + file: File = ..., + stickers: Sequence[Union[GuildSticker, StickerItem]] = ..., + delete_after: float = ..., + nonce: Union[str, int] = ..., + allowed_mentions: AllowedMentions = ..., + reference: Union[Message, MessageReference, PartialMessage] = ..., + mention_author: bool = ..., + view: View = ..., + suppress_embeds: bool = ..., + ephemeral: bool = ..., + silent: bool = ..., + poll: Poll = ..., + ) -> Message: + ... + + @overload + async def send( + self, + content: Optional[str] = ..., + *, + tts: bool = ..., + embeds: Sequence[Embed] = ..., + files: Sequence[File] = ..., + stickers: Sequence[Union[GuildSticker, StickerItem]] = ..., + delete_after: float = ..., + nonce: Union[str, int] = ..., + allowed_mentions: AllowedMentions = ..., + reference: Union[Message, MessageReference, PartialMessage] = ..., + mention_author: bool = ..., + view: View = ..., + suppress_embeds: bool = ..., + ephemeral: bool = ..., + silent: bool = ..., + poll: Poll = ..., + ) -> Message: + ... + async def send( self, content: Optional[str] = None, @@ -735,6 +924,7 @@ class Context(discord.abc.Messageable, Generic[BotT]): suppress_embeds: bool = False, ephemeral: bool = False, silent: bool = False, + poll: Optional[Poll] = None, ) -> Message: """|coro| @@ -824,6 +1014,13 @@ class Context(discord.abc.Messageable, Generic[BotT]): .. versionadded:: 2.2 + poll: Optional[:class:`~discord.Poll`] + The poll to send with this message. + + .. versionadded:: 2.4 + .. versionchanged:: 2.6 + This can now be ``None`` and defaults to ``None`` instead of ``MISSING``. + Raises -------- ~discord.HTTPException @@ -861,6 +1058,7 @@ class Context(discord.abc.Messageable, Generic[BotT]): view=view, suppress_embeds=suppress_embeds, silent=silent, + poll=poll, ) # type: ignore # The overloads don't support Optional but the implementation does # Convert the kwargs from None to MISSING to appease the remaining implementations @@ -876,13 +1074,17 @@ class Context(discord.abc.Messageable, Generic[BotT]): 'suppress_embeds': suppress_embeds, 'ephemeral': ephemeral, 'silent': silent, + 'poll': MISSING if poll is None else poll, } if self.interaction.response.is_done(): msg = await self.interaction.followup.send(**kwargs, wait=True) else: - await self.interaction.response.send_message(**kwargs) - msg = await self.interaction.original_response() + response = await self.interaction.response.send_message(**kwargs) + if not isinstance(response.resource, discord.InteractionMessage): + msg = await self.interaction.original_response() + else: + msg = response.resource if delete_after is not None: await msg.delete(delay=delete_after) diff --git a/discord/ext/commands/converter.py b/discord/ext/commands/converter.py index cc7a3eb9b..d316f6ccc 100644 --- a/discord/ext/commands/converter.py +++ b/discord/ext/commands/converter.py @@ -82,6 +82,7 @@ __all__ = ( 'GuildChannelConverter', 'GuildStickerConverter', 'ScheduledEventConverter', + 'SoundboardSoundConverter', 'clean_content', 'Greedy', 'Range', @@ -126,6 +127,10 @@ class Converter(Protocol[T_co]): raise a :exc:`.CommandError` derived exception as it will properly propagate to the error handlers. + Note that if this method is called manually, :exc:`Exception` + should be caught to handle the cases where a subclass does + not explicitly inherit from :exc:`.CommandError`. + Parameters ----------- ctx: :class:`.Context` @@ -186,9 +191,11 @@ class MemberConverter(IDConverter[discord.Member]): 1. Lookup by ID. 2. Lookup by mention. - 3. Lookup by name#discrim - 4. Lookup by name - 5. Lookup by nickname + 3. Lookup by username#discriminator (deprecated). + 4. Lookup by username#0 (deprecated, only gets users that migrated from their discriminator). + 5. Lookup by user name. + 6. Lookup by global name. + 7. Lookup by guild nickname. .. versionchanged:: 1.5 Raise :exc:`.MemberNotFound` instead of generic :exc:`.BadArgument` @@ -196,17 +203,29 @@ class MemberConverter(IDConverter[discord.Member]): .. versionchanged:: 1.5.1 This converter now lazily fetches members from the gateway and HTTP APIs, optionally caching the result if :attr:`.MemberCacheFlags.joined` is enabled. + + .. deprecated:: 2.3 + Looking up users by discriminator will be removed in a future version due to + the removal of discriminators in an API change. """ async def query_member_named(self, guild: discord.Guild, argument: str) -> Optional[discord.Member]: cache = guild._state.member_cache_flags.joined - if len(argument) > 5 and argument[-5] == '#': - username, _, discriminator = argument.rpartition('#') - members = await guild.query_members(username, limit=100, cache=cache) - return discord.utils.get(members, name=username, discriminator=discriminator) + username, _, discriminator = argument.rpartition('#') + + # If # isn't found then "discriminator" actually has the username + if not username: + discriminator, username = username, discriminator + + if discriminator == '0' or (len(discriminator) == 4 and discriminator.isdigit()): + lookup = username + predicate = lambda m: m.name == username and m.discriminator == discriminator else: - members = await guild.query_members(argument, limit=100, cache=cache) - return discord.utils.find(lambda m: m.name == argument or m.nick == argument, members) + lookup = argument + predicate = lambda m: m.name == argument or m.global_name == argument or m.nick == argument + + members = await guild.query_members(lookup, limit=100, cache=cache) + return discord.utils.find(predicate, members) async def query_member_by_id(self, bot: _Bot, guild: discord.Guild, user_id: int) -> Optional[discord.Member]: ws = bot._get_websocket(shard_id=guild.shard_id) @@ -273,8 +292,10 @@ class UserConverter(IDConverter[discord.User]): 1. Lookup by ID. 2. Lookup by mention. - 3. Lookup by name#discrim - 4. Lookup by name + 3. Lookup by username#discriminator (deprecated). + 4. Lookup by username#0 (deprecated, only gets users that migrated from their discriminator). + 5. Lookup by user name. + 6. Lookup by global name. .. versionchanged:: 1.5 Raise :exc:`.UserNotFound` instead of generic :exc:`.BadArgument` @@ -282,6 +303,10 @@ class UserConverter(IDConverter[discord.User]): .. versionchanged:: 1.6 This converter now lazily fetches users from the HTTP APIs if an ID is passed and it's not available in cache. + + .. deprecated:: 2.3 + Looking up users by discriminator will be removed in a future version due to + the removal of discriminators in an API change. """ async def convert(self, ctx: Context[BotT], argument: str) -> discord.User: @@ -300,25 +325,18 @@ class UserConverter(IDConverter[discord.User]): return result # type: ignore - arg = argument + username, _, discriminator = argument.rpartition('#') - # Remove the '@' character if this is the first character from the argument - if arg[0] == '@': - # Remove first character - arg = arg[1:] + # If # isn't found then "discriminator" actually has the username + if not username: + discriminator, username = username, discriminator - # check for discriminator if it exists, - if len(arg) > 5 and arg[-5] == '#': - discrim = arg[-4:] - name = arg[:-5] - predicate = lambda u: u.name == name and u.discriminator == discrim - result = discord.utils.find(predicate, state._users.values()) - if result is not None: - return result + if discriminator == '0' or (len(discriminator) == 4 and discriminator.isdigit()): + predicate = lambda u: u.name == username and u.discriminator == discriminator + else: + predicate = lambda u: u.name == argument or u.global_name == argument - predicate = lambda u: u.name == arg result = discord.utils.find(predicate, state._users.values()) - if result is None: raise UserNotFound(argument) @@ -425,19 +443,36 @@ class GuildChannelConverter(IDConverter[discord.abc.GuildChannel]): 1. Lookup by ID. 2. Lookup by mention. - 3. Lookup by name. + 3. Lookup by channel URL. + 4. Lookup by name. .. versionadded:: 2.0 + + .. versionchanged:: 2.4 + Add lookup by channel URL, accessed via "Copy Link" in the Discord client within channels. """ async def convert(self, ctx: Context[BotT], argument: str) -> discord.abc.GuildChannel: return self._resolve_channel(ctx, argument, 'channels', discord.abc.GuildChannel) + @staticmethod + def _parse_from_url(argument: str) -> Optional[re.Match[str]]: + link_regex = re.compile( + r'https?://(?:(?:ptb|canary|www)\.)?discord(?:app)?\.com/channels/' + r'(?:[0-9]{15,20}|@me)' + r'/([0-9]{15,20})(?:/(?:[0-9]{15,20})/?)?$' + ) + return link_regex.match(argument) + @staticmethod def _resolve_channel(ctx: Context[BotT], argument: str, attribute: str, type: Type[CT]) -> CT: bot = ctx.bot - match = IDConverter._get_id_match(argument) or re.match(r'<#([0-9]{15,20})>$', argument) + match = ( + IDConverter._get_id_match(argument) + or re.match(r'<#([0-9]{15,20})>$', argument) + or GuildChannelConverter._parse_from_url(argument) + ) result = None guild = ctx.guild @@ -467,7 +502,11 @@ class GuildChannelConverter(IDConverter[discord.abc.GuildChannel]): @staticmethod def _resolve_thread(ctx: Context[BotT], argument: str, attribute: str, type: Type[TT]) -> TT: - match = IDConverter._get_id_match(argument) or re.match(r'<#([0-9]{15,20})>$', argument) + match = ( + IDConverter._get_id_match(argument) + or re.match(r'<#([0-9]{15,20})>$', argument) + or GuildChannelConverter._parse_from_url(argument) + ) result = None guild = ctx.guild @@ -497,10 +536,14 @@ class TextChannelConverter(IDConverter[discord.TextChannel]): 1. Lookup by ID. 2. Lookup by mention. - 3. Lookup by name + 3. Lookup by channel URL. + 4. Lookup by name .. versionchanged:: 1.5 Raise :exc:`.ChannelNotFound` instead of generic :exc:`.BadArgument` + + .. versionchanged:: 2.4 + Add lookup by channel URL, accessed via "Copy Link" in the Discord client within channels. """ async def convert(self, ctx: Context[BotT], argument: str) -> discord.TextChannel: @@ -517,10 +560,14 @@ class VoiceChannelConverter(IDConverter[discord.VoiceChannel]): 1. Lookup by ID. 2. Lookup by mention. - 3. Lookup by name + 3. Lookup by channel URL. + 4. Lookup by name .. versionchanged:: 1.5 Raise :exc:`.ChannelNotFound` instead of generic :exc:`.BadArgument` + + .. versionchanged:: 2.4 + Add lookup by channel URL, accessed via "Copy Link" in the Discord client within channels. """ async def convert(self, ctx: Context[BotT], argument: str) -> discord.VoiceChannel: @@ -539,7 +586,11 @@ class StageChannelConverter(IDConverter[discord.StageChannel]): 1. Lookup by ID. 2. Lookup by mention. - 3. Lookup by name + 3. Lookup by channel URL. + 4. Lookup by name + + .. versionchanged:: 2.4 + Add lookup by channel URL, accessed via "Copy Link" in the Discord client within channels. """ async def convert(self, ctx: Context[BotT], argument: str) -> discord.StageChannel: @@ -556,7 +607,11 @@ class CategoryChannelConverter(IDConverter[discord.CategoryChannel]): 1. Lookup by ID. 2. Lookup by mention. - 3. Lookup by name + 3. Lookup by channel URL. + 4. Lookup by name + + .. versionchanged:: 2.4 + Add lookup by channel URL, accessed via "Copy Link" in the Discord client within channels. .. versionchanged:: 1.5 Raise :exc:`.ChannelNotFound` instead of generic :exc:`.BadArgument` @@ -575,9 +630,13 @@ class ThreadConverter(IDConverter[discord.Thread]): 1. Lookup by ID. 2. Lookup by mention. - 3. Lookup by name. + 3. Lookup by channel URL. + 4. Lookup by name. .. versionadded: 2.0 + + .. versionchanged:: 2.4 + Add lookup by channel URL, accessed via "Copy Link" in the Discord client within channels. """ async def convert(self, ctx: Context[BotT], argument: str) -> discord.Thread: @@ -594,9 +653,13 @@ class ForumChannelConverter(IDConverter[discord.ForumChannel]): 1. Lookup by ID. 2. Lookup by mention. - 3. Lookup by name + 3. Lookup by channel URL. + 4. Lookup by name .. versionadded:: 2.0 + + .. versionchanged:: 2.4 + Add lookup by channel URL, accessed via "Copy Link" in the Discord client within channels. """ async def convert(self, ctx: Context[BotT], argument: str) -> discord.ForumChannel: @@ -889,6 +952,44 @@ class ScheduledEventConverter(IDConverter[discord.ScheduledEvent]): return result +class SoundboardSoundConverter(IDConverter[discord.SoundboardSound]): + """Converts to a :class:`~discord.SoundboardSound`. + + Lookups are done for the local guild if available. Otherwise, for a DM context, + lookup is done by the global cache. + + The lookup strategy is as follows (in order): + + 1. Lookup by ID. + 2. Lookup by name. + + .. versionadded:: 2.5 + """ + + async def convert(self, ctx: Context[BotT], argument: str) -> discord.SoundboardSound: + guild = ctx.guild + match = self._get_id_match(argument) + result = None + + if match: + # ID match + sound_id = int(match.group(1)) + if guild: + result = guild.get_soundboard_sound(sound_id) + else: + result = ctx.bot.get_soundboard_sound(sound_id) + else: + # lookup by name + if guild: + result = discord.utils.get(guild.soundboard_sounds, name=argument) + else: + result = discord.utils.get(ctx.bot.soundboard_sounds, name=argument) + if result is None: + raise SoundboardSoundNotFound(argument) + + return result + + class clean_content(Converter[str]): """Converts the argument to mention scrubbed version of said content. @@ -1024,7 +1125,7 @@ class Greedy(List[T]): args = getattr(converter, '__args__', ()) if discord.utils.PY_310 and converter.__class__ is types.UnionType: # type: ignore - converter = Union[args] # type: ignore + converter = Union[args] origin = getattr(converter, '__origin__', None) @@ -1037,7 +1138,7 @@ class Greedy(List[T]): if origin is Union and type(None) in args: raise TypeError(f'Greedy[{converter!r}] is invalid.') - return cls(converter=converter) + return cls(converter=converter) # type: ignore @property def constructed_converter(self) -> Any: @@ -1172,7 +1273,7 @@ def _convert_to_bool(argument: str) -> bool: raise BadBoolArgument(lowered) -_GenericAlias = type(List[T]) +_GenericAlias = type(List[T]) # type: ignore def is_generic_type(tp: Any, *, _GenericAlias: type = _GenericAlias) -> bool: @@ -1201,6 +1302,7 @@ CONVERTER_MAPPING: Dict[type, Any] = { discord.GuildSticker: GuildStickerConverter, discord.ScheduledEvent: ScheduledEventConverter, discord.ForumChannel: ForumChannelConverter, + discord.SoundboardSound: SoundboardSoundConverter, } @@ -1223,7 +1325,7 @@ async def _actual_conversion(ctx: Context[BotT], converter: Any, argument: str, else: return await converter().convert(ctx, argument) elif isinstance(converter, Converter): - return await converter.convert(ctx, argument) # type: ignore + return await converter.convert(ctx, argument) except CommandError: raise except Exception as exc: @@ -1330,7 +1432,7 @@ async def run_converters(ctx: Context[BotT], converter: Any, argument: str, para return value # if we're here, then we failed to match all the literals - raise BadLiteralArgument(param, literal_args, errors) + raise BadLiteralArgument(param, literal_args, errors, argument) # This must be the last if-clause in the chain of origin checking # Nearly every type is a generic type within the typing library diff --git a/discord/ext/commands/cooldowns.py b/discord/ext/commands/cooldowns.py index 2af7cb017..cf328d9b3 100644 --- a/discord/ext/commands/cooldowns.py +++ b/discord/ext/commands/cooldowns.py @@ -27,11 +27,11 @@ from __future__ import annotations from typing import Any, Callable, Deque, Dict, Optional, Union, Generic, TypeVar, TYPE_CHECKING from discord.enums import Enum +from discord.abc import PrivateChannel import time import asyncio from collections import deque -from ...abc import PrivateChannel from .errors import MaxConcurrencyReached from .context import Context from discord.app_commands import Cooldown as Cooldown @@ -71,7 +71,7 @@ class BucketType(Enum): elif self is BucketType.member: return ((msg.guild and msg.guild.id), msg.author.id) elif self is BucketType.category: - return (msg.channel.category or msg.channel).id # type: ignore + return (getattr(msg.channel, 'category', None) or msg.channel).id elif self is BucketType.role: # we return the channel id of a private-channel as there are only roles in guilds # and that yields the same result as for a guild with only the @everyone role diff --git a/discord/ext/commands/core.py b/discord/ext/commands/core.py index f3850a224..372fcbedf 100644 --- a/discord/ext/commands/core.py +++ b/discord/ext/commands/core.py @@ -151,6 +151,7 @@ def get_signature_parameters( parameter._default = default.default parameter._description = default._description parameter._displayed_default = default._displayed_default + parameter._displayed_name = default._displayed_name annotation = parameter.annotation @@ -194,8 +195,13 @@ def extract_descriptions_from_docstring(function: Callable[..., Any], params: Di description, param_docstring = divide for match in NUMPY_DOCSTRING_ARG_REGEX.finditer(param_docstring): name = match.group('name') + if name not in params: - continue + is_display_name = discord.utils.get(params.values(), displayed_name=name) + if is_display_name: + name = is_display_name.name + else: + continue param = params[name] if param.description is None: @@ -455,7 +461,7 @@ class Command(_BaseCommand, Generic[CogT, P, T]): # bandaid for the fact that sometimes parent can be the bot instance parent: Optional[GroupMixin[Any]] = kwargs.get('parent') - self.parent: Optional[GroupMixin[Any]] = parent if isinstance(parent, _BaseCommand) else None # type: ignore # Does not recognise mixin usage + self.parent: Optional[GroupMixin[Any]] = parent if isinstance(parent, _BaseCommand) else None self._before_invoke: Optional[Hook] = None try: @@ -1169,7 +1175,9 @@ class Command(_BaseCommand, Generic[CogT, P, T]): return '' result = [] - for name, param in params.items(): + for param in params.values(): + name = param.displayed_name or param.name + greedy = isinstance(param.converter, Greedy) optional = False # postpone evaluation of if it's an optional argument @@ -1277,7 +1285,7 @@ class Command(_BaseCommand, Generic[CogT, P, T]): # since we have no checks, then we just return True. return True - return await discord.utils.async_all(predicate(ctx) for predicate in predicates) + return await discord.utils.async_all(predicate(ctx) for predicate in predicates) # type: ignore finally: ctx.command = original @@ -1996,7 +2004,7 @@ def check_any(*checks: Check[ContextT]) -> Check[ContextT]: # if we're here, all checks failed raise CheckAnyFailure(unwrapped, errors) - return check(predicate) # type: ignore + return check(predicate) def has_role(item: Union[int, str], /) -> Check[Any]: @@ -2036,7 +2044,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 +2091,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 +2125,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 +2155,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/ext/commands/errors.py b/discord/ext/commands/errors.py index 17dd49830..feb4aee27 100644 --- a/discord/ext/commands/errors.py +++ b/discord/ext/commands/errors.py @@ -24,9 +24,12 @@ DEALINGS IN THE SOFTWARE. from __future__ import annotations -from typing import TYPE_CHECKING, Any, Callable, List, Optional, Tuple, Union +from typing import TYPE_CHECKING, Any, Callable, List, Optional, Tuple, Union, Generic from discord.errors import ClientException, DiscordException +from discord.utils import _human_join + +from ._types import BotT if TYPE_CHECKING: from discord.abc import GuildChannel @@ -34,7 +37,6 @@ if TYPE_CHECKING: from discord.types.snowflake import Snowflake, SnowflakeList from discord.app_commands import AppCommandError - from ._types import BotT from .context import Context from .converter import Converter from .cooldowns import BucketType, Cooldown @@ -74,6 +76,7 @@ __all__ = ( 'EmojiNotFound', 'GuildStickerNotFound', 'ScheduledEventNotFound', + 'SoundboardSoundNotFound', 'PartialEmojiConversionFailure', 'BadBoolArgument', 'MissingRole', @@ -182,7 +185,7 @@ class MissingRequiredArgument(UserInputError): def __init__(self, param: Parameter) -> None: self.param: Parameter = param - super().__init__(f'{param.name} is a required argument that is missing.') + super().__init__(f'{param.displayed_name or param.name} is a required argument that is missing.') class MissingRequiredAttachment(UserInputError): @@ -201,7 +204,7 @@ class MissingRequiredAttachment(UserInputError): def __init__(self, param: Parameter) -> None: self.param: Parameter = param - super().__init__(f'{param.name} is a required argument that is missing an attachment.') + super().__init__(f'{param.displayed_name or param.name} is a required argument that is missing an attachment.') class TooManyArguments(UserInputError): @@ -233,7 +236,7 @@ class CheckFailure(CommandError): pass -class CheckAnyFailure(CheckFailure): +class CheckAnyFailure(Generic[BotT], CheckFailure): """Exception raised when all predicates in :func:`check_any` fail. This inherits from :exc:`CheckFailure`. @@ -563,6 +566,24 @@ class ScheduledEventNotFound(BadArgument): super().__init__(f'ScheduledEvent "{argument}" not found.') +class SoundboardSoundNotFound(BadArgument): + """Exception raised when the bot can not find the soundboard sound. + + This inherits from :exc:`BadArgument` + + .. versionadded:: 2.5 + + Attributes + ----------- + argument: :class:`str` + The sound supplied by the caller that was not found + """ + + def __init__(self, argument: str) -> None: + self.argument: str = argument + super().__init__(f'SoundboardSound "{argument}" not found.') + + class BadBoolArgument(BadArgument): """Exception raised when a boolean argument was not convertable. @@ -758,12 +779,7 @@ class MissingAnyRole(CheckFailure): self.missing_roles: SnowflakeList = missing_roles missing = [f"'{role}'" for role in missing_roles] - - if len(missing) > 2: - fmt = '{}, or {}'.format(', '.join(missing[:-1]), missing[-1]) - else: - fmt = ' or '.join(missing) - + fmt = _human_join(missing) message = f'You are missing at least one of the required roles: {fmt}' super().__init__(message) @@ -788,12 +804,7 @@ class BotMissingAnyRole(CheckFailure): self.missing_roles: SnowflakeList = missing_roles missing = [f"'{role}'" for role in missing_roles] - - if len(missing) > 2: - fmt = '{}, or {}'.format(', '.join(missing[:-1]), missing[-1]) - else: - fmt = ' or '.join(missing) - + fmt = _human_join(missing) message = f'Bot is missing at least one of the required roles: {fmt}' super().__init__(message) @@ -832,11 +843,7 @@ class MissingPermissions(CheckFailure): self.missing_permissions: List[str] = missing_permissions missing = [perm.replace('_', ' ').replace('guild', 'server').title() for perm in missing_permissions] - - if len(missing) > 2: - fmt = '{}, and {}'.format(', '.join(missing[:-1]), missing[-1]) - else: - fmt = ' and '.join(missing) + fmt = _human_join(missing, final='and') message = f'You are missing {fmt} permission(s) to run this command.' super().__init__(message, *args) @@ -857,11 +864,7 @@ class BotMissingPermissions(CheckFailure): self.missing_permissions: List[str] = missing_permissions missing = [perm.replace('_', ' ').replace('guild', 'server').title() for perm in missing_permissions] - - if len(missing) > 2: - fmt = '{}, and {}'.format(', '.join(missing[:-1]), missing[-1]) - else: - fmt = ' and '.join(missing) + fmt = _human_join(missing, final='and') message = f'Bot requires {fmt} permission(s) to run this command.' super().__init__(message, *args) @@ -896,12 +899,8 @@ class BadUnionArgument(UserInputError): return x.__class__.__name__ to_string = [_get_name(x) for x in converters] - if len(to_string) > 2: - fmt = '{}, or {}'.format(', '.join(to_string[:-1]), to_string[-1]) - else: - fmt = ' or '.join(to_string) - - super().__init__(f'Could not convert "{param.name}" into {fmt}.') + fmt = _human_join(to_string) + super().__init__(f'Could not convert "{param.displayed_name or param.name}" into {fmt}.') class BadLiteralArgument(UserInputError): @@ -920,20 +919,21 @@ class BadLiteralArgument(UserInputError): A tuple of values compared against in conversion, in order of failure. errors: List[:class:`CommandError`] A list of errors that were caught from failing the conversion. + argument: :class:`str` + The argument's value that failed to be converted. Defaults to an empty string. + + .. versionadded:: 2.3 """ - def __init__(self, param: Parameter, literals: Tuple[Any, ...], errors: List[CommandError]) -> None: + def __init__(self, param: Parameter, literals: Tuple[Any, ...], errors: List[CommandError], argument: str = "") -> None: self.param: Parameter = param self.literals: Tuple[Any, ...] = literals self.errors: List[CommandError] = errors + self.argument: str = argument to_string = [repr(l) for l in literals] - if len(to_string) > 2: - fmt = '{}, or {}'.format(', '.join(to_string[:-1]), to_string[-1]) - else: - fmt = ' or '.join(to_string) - - super().__init__(f'Could not convert "{param.name}" into the literal {fmt}.') + fmt = _human_join(to_string) + super().__init__(f'Could not convert "{param.displayed_name or param.name}" into the literal {fmt}.') class ArgumentParsingError(UserInputError): @@ -1081,7 +1081,7 @@ class ExtensionNotFound(ExtensionError): """ def __init__(self, name: str) -> None: - msg = f'Extension {name!r} could not be loaded.' + msg = f'Extension {name!r} could not be loaded or found.' super().__init__(msg, name=name) diff --git a/discord/ext/commands/flags.py b/discord/ext/commands/flags.py index 423101852..0766ecae3 100644 --- a/discord/ext/commands/flags.py +++ b/discord/ext/commands/flags.py @@ -79,6 +79,10 @@ class Flag: description: :class:`str` The description of the flag. Shown for hybrid commands when they're used as application commands. + positional: :class:`bool` + Whether the flag is positional or not. There can only be one positional flag. + + .. versionadded:: 2.4 """ name: str = MISSING @@ -89,6 +93,7 @@ class Flag: max_args: int = MISSING override: bool = MISSING description: str = MISSING + positional: bool = MISSING cast_to_dict: bool = False @property @@ -109,6 +114,7 @@ def flag( override: bool = MISSING, converter: Any = MISSING, description: str = MISSING, + positional: bool = MISSING, ) -> Any: """Override default functionality and parameters of the underlying :class:`FlagConverter` class attributes. @@ -136,6 +142,10 @@ def flag( description: :class:`str` The description of the flag. Shown for hybrid commands when they're used as application commands. + positional: :class:`bool` + Whether the flag is positional or not. There can only be one positional flag. + + .. versionadded:: 2.4 """ return Flag( name=name, @@ -145,6 +155,7 @@ def flag( override=override, annotation=converter, description=description, + positional=positional, ) @@ -171,6 +182,7 @@ def get_flags(namespace: Dict[str, Any], globals: Dict[str, Any], locals: Dict[s flags: Dict[str, Flag] = {} cache: Dict[str, Any] = {} names: Set[str] = set() + positional: Optional[Flag] = None for name, annotation in annotations.items(): flag = namespace.pop(name, MISSING) if isinstance(flag, Flag): @@ -183,6 +195,11 @@ def get_flags(namespace: Dict[str, Any], globals: Dict[str, Any], locals: Dict[s if flag.name is MISSING: flag.name = name + if flag.positional: + if positional is not None: + raise TypeError(f"{flag.name!r} positional flag conflicts with {positional.name!r} flag.") + positional = flag + annotation = flag.annotation = resolve_annotation(flag.annotation, globals, locals, cache) if flag.default is MISSING and hasattr(annotation, '__commands_is_flag__') and annotation._can_be_constructible(): @@ -270,6 +287,7 @@ class FlagsMeta(type): __commands_flag_case_insensitive__: bool __commands_flag_delimiter__: str __commands_flag_prefix__: str + __commands_flag_positional__: Optional[Flag] def __new__( cls, @@ -280,7 +298,7 @@ class FlagsMeta(type): case_insensitive: bool = MISSING, delimiter: str = MISSING, prefix: str = MISSING, - ) -> Self: + ) -> FlagsMeta: attrs['__commands_is_flag__'] = True try: @@ -324,9 +342,13 @@ class FlagsMeta(type): delimiter = attrs.setdefault('__commands_flag_delimiter__', ':') prefix = attrs.setdefault('__commands_flag_prefix__', '') + positional: Optional[Flag] = None for flag_name, flag in get_flags(attrs, global_ns, local_ns).items(): flags[flag_name] = flag aliases.update({alias_name: flag_name for alias_name in flag.aliases}) + if flag.positional: + positional = flag + attrs['__commands_flag_positional__'] = positional forbidden = set(delimiter).union(prefix) for flag_name in flags: @@ -421,7 +443,7 @@ async def convert_flag(ctx: Context[BotT], argument: str, flag: Flag, annotation return await convert_flag(ctx, argument, flag, annotation) elif origin is Union and type(None) in annotation.__args__: # typing.Optional[x] - annotation = Union[tuple(arg for arg in annotation.__args__ if arg is not type(None))] # type: ignore + annotation = Union[tuple(arg for arg in annotation.__args__ if arg is not type(None))] return await run_converters(ctx, annotation, argument, param) elif origin is dict: # typing.Dict[K, V] -> typing.Tuple[K, V] @@ -485,7 +507,7 @@ class FlagConverter(metaclass=FlagsMeta): for flag in flags.values(): if callable(flag.default): # Type checker does not understand that flag.default is a Callable - default = await maybe_coroutine(flag.default, ctx) # type: ignore + default = await maybe_coroutine(flag.default, ctx) setattr(self, flag.attribute, default) else: setattr(self, flag.attribute, flag.default) @@ -500,10 +522,25 @@ class FlagConverter(metaclass=FlagsMeta): result: Dict[str, List[str]] = {} flags = cls.__commands_flags__ aliases = cls.__commands_flag_aliases__ + positional_flag = cls.__commands_flag_positional__ last_position = 0 last_flag: Optional[Flag] = None case_insensitive = cls.__commands_flag_case_insensitive__ + + if positional_flag is not None: + match = cls.__commands_flag_regex__.search(argument) + if match is not None: + begin, end = match.span(0) + value = argument[:begin].strip() + else: + value = argument.strip() + last_position = len(argument) + + if value: + name = positional_flag.name.casefold() if case_insensitive else positional_flag.name + result[name] = [value] + for match in cls.__commands_flag_regex__.finditer(argument): begin, end = match.span(0) key = match.group('flag') @@ -600,7 +637,7 @@ class FlagConverter(metaclass=FlagsMeta): else: if callable(flag.default): # Type checker does not understand flag.default is a Callable - default = await maybe_coroutine(flag.default, ctx) # type: ignore + default = await maybe_coroutine(flag.default, ctx) setattr(self, flag.attribute, default) else: setattr(self, flag.attribute, flag.default) diff --git a/discord/ext/commands/help.py b/discord/ext/commands/help.py index d8f341474..d06fbd8bf 100644 --- a/discord/ext/commands/help.py +++ b/discord/ext/commands/help.py @@ -294,6 +294,14 @@ class _HelpCommandImpl(Command): cog.walk_commands = cog.walk_commands.__wrapped__ self.cog = None + # Revert `on_error` to use the original one in case of race conditions + self.on_error = self._injected.on_help_command_error + + def update(self, **kwargs: Any) -> None: + cog = self.cog + self.__init__(self._original, **dict(self.__original_kwargs__, **kwargs)) + self.cog = cog + class HelpCommand: r"""The base implementation for help command formatting. @@ -374,9 +382,8 @@ class HelpCommand: return obj def _add_to_bot(self, bot: BotBase) -> None: - command = _HelpCommandImpl(self, **self.command_attrs) - bot.add_command(command) - self._command_impl = command + self._command_impl.update(**self.command_attrs) + bot.add_command(self._command_impl) def _remove_from_bot(self, bot: BotBase) -> None: bot.remove_command(self._command_impl.name) @@ -1166,7 +1173,7 @@ class DefaultHelpCommand(HelpCommand): get_width = discord.utils._string_width for argument in arguments: - name = argument.name + name = argument.displayed_name or argument.name width = max_size - (get_width(name) - len(name)) entry = f'{self.indent * " "}{name:<{width}} {argument.description or self.default_argument_description}' # we do not want to shorten the default value, if any. diff --git a/discord/ext/commands/hybrid.py b/discord/ext/commands/hybrid.py index fb6d39b31..0857003fa 100644 --- a/discord/ext/commands/hybrid.py +++ b/discord/ext/commands/hybrid.py @@ -43,7 +43,7 @@ import inspect from discord import app_commands from discord.utils import MISSING, maybe_coroutine, async_all from .core import Command, Group -from .errors import BadArgument, CommandRegistrationError, CommandError, HybridCommandError, ConversionError +from .errors import BadArgument, CommandRegistrationError, CommandError, HybridCommandError, ConversionError, DisabledCommand from .converter import Converter, Range, Greedy, run_converters, CONVERTER_MAPPING from .parameters import Parameter from .flags import is_flag, FlagConverter @@ -72,9 +72,9 @@ __all__ = ( T = TypeVar('T') U = TypeVar('U') CogT = TypeVar('CogT', bound='Cog') -CommandT = TypeVar('CommandT', bound='Command') +CommandT = TypeVar('CommandT', bound='Command[Any, ..., Any]') # CHT = TypeVar('CHT', bound='Check') -GroupT = TypeVar('GroupT', bound='Group') +GroupT = TypeVar('GroupT', bound='Group[Any, ..., Any]') _NoneType = type(None) if TYPE_CHECKING: @@ -203,9 +203,9 @@ def replace_parameter( # Fallback to see if the behaviour needs changing origin = getattr(converter, '__origin__', None) args = getattr(converter, '__args__', []) - if isinstance(converter, Range): + if isinstance(converter, Range): # type: ignore # Range is not an Annotation at runtime r = converter - param = param.replace(annotation=app_commands.Range[r.annotation, r.min, r.max]) + param = param.replace(annotation=app_commands.Range[r.annotation, r.min, r.max]) # type: ignore elif isinstance(converter, Greedy): # Greedy is "optional" in ext.commands # However, in here, it probably makes sense to make it required. @@ -234,6 +234,12 @@ def replace_parameter( descriptions[name] = flag.description if flag.name != flag.attribute: renames[name] = flag.name + if pseudo.default is not pseudo.empty: + # This ensures the default is wrapped around _CallableDefault if callable + # else leaves it as-is. + pseudo = pseudo.replace( + default=_CallableDefault(flag.default) if callable(flag.default) else flag.default + ) mapping[name] = pseudo @@ -251,7 +257,7 @@ def replace_parameter( inner = args[0] is_inner_transformer = is_transformer(inner) if is_converter(inner) and not is_inner_transformer: - param = param.replace(annotation=Optional[ConverterTransformer(inner, original)]) # type: ignore + param = param.replace(annotation=Optional[ConverterTransformer(inner, original)]) else: raise elif origin: @@ -283,7 +289,7 @@ def replace_parameters( param = param.replace(default=default) if isinstance(param.default, Parameter): - # If we're here, then then it hasn't been handled yet so it should be removed completely + # If we're here, then it hasn't been handled yet so it should be removed completely param = param.replace(default=parameter.empty) # Flags are flattened out and thus don't get their parameter in the actual mapping @@ -297,14 +303,20 @@ def replace_parameters( class HybridAppCommand(discord.app_commands.Command[CogT, P, T]): - def __init__(self, wrapped: Union[HybridCommand[CogT, Any, T], HybridGroup[CogT, Any, T]]) -> None: + __commands_is_hybrid_app_command__: ClassVar[bool] = True + + def __init__( + self, + wrapped: Union[HybridCommand[CogT, ..., T], HybridGroup[CogT, ..., T]], + name: Optional[Union[str, app_commands.locale_str]] = None, + ) -> None: signature = inspect.signature(wrapped.callback) params = replace_parameters(wrapped.params, wrapped.callback, signature) wrapped.callback.__signature__ = signature.replace(parameters=params) nsfw = getattr(wrapped.callback, '__discord_app_commands_is_nsfw__', False) try: super().__init__( - name=wrapped._locale_name or wrapped.name, + name=name or wrapped._locale_name or wrapped.name, callback=wrapped.callback, # type: ignore # Signature doesn't match but we're overriding the invoke description=wrapped._locale_description or wrapped.description or wrapped.short_doc or '…', nsfw=nsfw, @@ -312,7 +324,7 @@ class HybridAppCommand(discord.app_commands.Command[CogT, P, T]): finally: del wrapped.callback.__signature__ - self.wrapped: Union[HybridCommand[CogT, Any, T], HybridGroup[CogT, Any, T]] = wrapped + self.wrapped: Union[HybridCommand[CogT, ..., T], HybridGroup[CogT, ..., T]] = wrapped self.binding: Optional[CogT] = wrapped.cog # This technically means only one flag converter is supported self.flag_converter: Optional[Tuple[str, Type[FlagConverter]]] = getattr( @@ -398,7 +410,7 @@ class HybridAppCommand(discord.app_commands.Command[CogT, P, T]): if self.binding is not None: try: # Type checker does not like runtime attribute retrieval - check: AppCommandCheck = self.binding.interaction_check # type: ignore + check: AppCommandCheck = self.binding.interaction_check except AttributeError: pass else: @@ -412,10 +424,10 @@ class HybridAppCommand(discord.app_commands.Command[CogT, P, T]): if not ret: return False - if self.checks and not await async_all(f(interaction) for f in self.checks): + if self.checks and not await async_all(f(interaction) for f in self.checks): # type: ignore return False - if self.wrapped.checks and not await async_all(f(ctx) for f in self.wrapped.checks): + if self.wrapped.checks and not await async_all(f(ctx) for f in self.wrapped.checks): # type: ignore return False return True @@ -520,6 +532,9 @@ class HybridCommand(Command[CogT, P, T]): self.app_command.binding = value async def can_run(self, ctx: Context[BotT], /) -> bool: + if not self.enabled: + raise DisabledCommand(f'{self.name} command is disabled') + if ctx.interaction is not None and self.app_command: return await self.app_command._check_can_run(ctx.interaction) else: @@ -594,6 +609,8 @@ class HybridGroup(Group[CogT, P, T]): application command groups cannot be invoked, this creates a subcommand within the group that can be invoked with the given group callback. If ``None`` then no fallback command is given. Defaults to ``None``. + fallback_locale: Optional[:class:`~discord.app_commands.locale_str`] + The fallback command name's locale string, if available. """ __commands_is_hybrid__: ClassVar[bool] = True @@ -603,7 +620,7 @@ class HybridGroup(Group[CogT, P, T]): *args: Any, name: Union[str, app_commands.locale_str] = MISSING, description: Union[str, app_commands.locale_str] = MISSING, - fallback: Optional[str] = None, + fallback: Optional[Union[str, app_commands.locale_str]] = None, **attrs: Any, ) -> None: name, name_locale = (name.message, name) if isinstance(name, app_commands.locale_str) else (name, None) @@ -631,7 +648,12 @@ class HybridGroup(Group[CogT, P, T]): # However, Python does not have conditional typing so it's very hard to # make this type depend on the with_app_command bool without a lot of needless repetition self.app_command: app_commands.Group = MISSING + + fallback, fallback_locale = ( + (fallback.message, fallback) if isinstance(fallback, app_commands.locale_str) else (fallback, None) + ) self.fallback: Optional[str] = fallback + self.fallback_locale: Optional[app_commands.locale_str] = fallback_locale if self.with_app_command: guild_ids = attrs.pop('guild_ids', None) or getattr( @@ -640,6 +662,8 @@ class HybridGroup(Group[CogT, P, T]): guild_only = getattr(self.callback, '__discord_app_commands_guild_only__', False) default_permissions = getattr(self.callback, '__discord_app_commands_default_permissions__', None) nsfw = getattr(self.callback, '__discord_app_commands_is_nsfw__', False) + contexts = getattr(self.callback, '__discord_app_commands_contexts__', MISSING) + installs = getattr(self.callback, '__discord_app_commands_installation_types__', MISSING) self.app_command = app_commands.Group( name=self._locale_name or self.name, description=self._locale_description or self.description or self.short_doc or '…', @@ -647,6 +671,8 @@ class HybridGroup(Group[CogT, P, T]): guild_only=guild_only, default_permissions=default_permissions, nsfw=nsfw, + allowed_installs=installs, + allowed_contexts=contexts, ) # This prevents the group from re-adding the command at __init__ @@ -654,8 +680,7 @@ class HybridGroup(Group[CogT, P, T]): self.app_command.module = self.module if fallback is not None: - command = HybridAppCommand(self) - command.name = fallback + command = HybridAppCommand(self, name=fallback_locale or fallback) self.app_command.add_command(command) @property @@ -890,7 +915,8 @@ def hybrid_command( def decorator(func: CommandCallback[CogT, ContextT, P, T]) -> HybridCommand[CogT, P, T]: if isinstance(func, Command): raise TypeError('Callback is already a command.') - return HybridCommand(func, name=name, with_app_command=with_app_command, **attrs) # type: ignore # ??? + # Pyright does not allow Command[Any] to be assigned to Command[CogT] despite it being okay here + return HybridCommand(func, name=name, with_app_command=with_app_command, **attrs) # type: ignore return decorator @@ -908,6 +934,9 @@ def hybrid_group( Parameters ----------- + name: Union[:class:`str`, :class:`~discord.app_commands.locale_str`] + The name to create the group with. By default this uses the + function name unchanged. with_app_command: :class:`bool` Whether to register the command also as an application command. @@ -917,9 +946,9 @@ def hybrid_group( If the function is not a coroutine or is already a command. """ - def decorator(func: CommandCallback[CogT, ContextT, P, T]): + def decorator(func: CommandCallback[CogT, ContextT, P, T]) -> HybridGroup[CogT, P, T]: if isinstance(func, Command): raise TypeError('Callback is already a command.') return HybridGroup(func, name=name, with_app_command=with_app_command, **attrs) - return decorator # type: ignore + return decorator diff --git a/discord/ext/commands/parameters.py b/discord/ext/commands/parameters.py index 5039a16aa..2640902a3 100644 --- a/discord/ext/commands/parameters.py +++ b/discord/ext/commands/parameters.py @@ -87,7 +87,7 @@ class Parameter(inspect.Parameter): .. versionadded:: 2.0 """ - __slots__ = ('_displayed_default', '_description', '_fallback') + __slots__ = ('_displayed_default', '_description', '_fallback', '_displayed_name') def __init__( self, @@ -97,6 +97,7 @@ class Parameter(inspect.Parameter): annotation: Any = empty, description: str = empty, displayed_default: str = empty, + displayed_name: str = empty, ) -> None: super().__init__(name=name, kind=kind, default=default, annotation=annotation) self._name = name @@ -106,6 +107,10 @@ class Parameter(inspect.Parameter): self._annotation = annotation self._displayed_default = displayed_default self._fallback = False + self._displayed_name = displayed_name + + def __repr__(self) -> str: + return f'<{self.__class__.__name__} name={self._name!r} required={self.required}>' def replace( self, @@ -116,6 +121,7 @@ class Parameter(inspect.Parameter): annotation: Any = MISSING, description: str = MISSING, displayed_default: Any = MISSING, + displayed_name: Any = MISSING, ) -> Self: if name is MISSING: name = self._name @@ -129,15 +135,20 @@ class Parameter(inspect.Parameter): description = self._description if displayed_default is MISSING: displayed_default = self._displayed_default + if displayed_name is MISSING: + displayed_name = self._displayed_name - return self.__class__( + ret = self.__class__( name=name, kind=kind, default=default, annotation=annotation, description=description, displayed_default=displayed_default, + displayed_name=displayed_name, ) + ret._fallback = self._fallback + return ret if not TYPE_CHECKING: # this is to prevent anything breaking if inspect internals change name = _gen_property('name') @@ -169,7 +180,21 @@ class Parameter(inspect.Parameter): if self._displayed_default is not empty: return self._displayed_default - return None if self.required else str(self.default) + if self.required: + return None + + if callable(self.default) or self.default is None: + return None + + return str(self.default) + + @property + def displayed_name(self) -> Optional[str]: + """Optional[:class:`str`]: The name that is displayed to the user. + + .. versionadded:: 2.3 + """ + return self._displayed_name if self._displayed_name is not empty else None async def get_default(self, ctx: Context[Any]) -> Any: """|coro| @@ -183,7 +208,7 @@ class Parameter(inspect.Parameter): """ # pre-condition: required is False if callable(self.default): - return await maybe_coroutine(self.default, ctx) # type: ignore + return await maybe_coroutine(self.default, ctx) return self.default @@ -193,8 +218,9 @@ def parameter( default: Any = empty, description: str = empty, displayed_default: str = empty, + displayed_name: str = empty, ) -> Any: - r"""parameter(\*, converter=..., default=..., description=..., displayed_default=...) + r"""parameter(\*, converter=..., default=..., description=..., displayed_default=..., displayed_name=...) A way to assign custom metadata for a :class:`Command`\'s parameter. @@ -221,7 +247,17 @@ def parameter( The description of this parameter. displayed_default: :class:`str` The displayed default in :attr:`Command.signature`. + displayed_name: :class:`str` + The name that is displayed to the user. + + .. versionadded:: 2.3 """ + if isinstance(default, Parameter): + if displayed_default is empty: + displayed_default = default._displayed_default + + default = default._default + return Parameter( name='empty', kind=inspect.Parameter.POSITIONAL_OR_KEYWORD, @@ -229,6 +265,7 @@ def parameter( default=default, description=description, displayed_default=displayed_default, + displayed_name=displayed_name, ) @@ -240,12 +277,13 @@ class ParameterAlias(Protocol): default: Any = empty, description: str = empty, displayed_default: str = empty, + displayed_name: str = empty, ) -> Any: ... param: ParameterAlias = parameter -r"""param(\*, converter=..., default=..., description=..., displayed_default=...) +r"""param(\*, converter=..., default=..., description=..., displayed_default=..., displayed_name=...) An alias for :func:`parameter`. @@ -279,6 +317,7 @@ CurrentGuild = parameter( displayed_default='', converter=GuildConverter, ) +CurrentGuild._fallback = True class Signature(inspect.Signature): diff --git a/discord/ext/tasks/__init__.py b/discord/ext/tasks/__init__.py index cf175b7a8..57f9e741b 100644 --- a/discord/ext/tasks/__init__.py +++ b/discord/ext/tasks/__init__.py @@ -111,12 +111,17 @@ class SleepHandle: self.loop: asyncio.AbstractEventLoop = loop self.future: asyncio.Future[None] = loop.create_future() relative_delta = discord.utils.compute_timedelta(dt) - self.handle = loop.call_later(relative_delta, self.future.set_result, True) + self.handle = loop.call_later(relative_delta, self._wrapped_set_result, self.future) + + @staticmethod + def _wrapped_set_result(future: asyncio.Future) -> None: + if not future.done(): + future.set_result(None) def recalculate(self, dt: datetime.datetime) -> None: self.handle.cancel() relative_delta = discord.utils.compute_timedelta(dt) - self.handle: asyncio.TimerHandle = self.loop.call_later(relative_delta, self.future.set_result, True) + self.handle: asyncio.TimerHandle = self.loop.call_later(relative_delta, self._wrapped_set_result, self.future) def wait(self) -> asyncio.Future[Any]: return self.future @@ -144,6 +149,7 @@ class Loop(Generic[LF]): time: Union[datetime.time, Sequence[datetime.time]], count: Optional[int], reconnect: bool, + name: Optional[str], ) -> None: self.coro: LF = coro self.reconnect: bool = reconnect @@ -165,6 +171,7 @@ class Loop(Generic[LF]): self._is_being_cancelled = False self._has_failed = False self._stop_next_iteration = False + self._name: str = f'discord-ext-tasks: {coro.__qualname__}' if name is None else name if self.count is not None and self.count <= 0: raise ValueError('count must be greater than 0 or None.') @@ -282,6 +289,7 @@ class Loop(Generic[LF]): time=self._time, count=self.count, reconnect=self.reconnect, + name=self._name, ) copy._injected = obj copy._before_loop = self._before_loop @@ -395,7 +403,7 @@ class Loop(Generic[LF]): args = (self._injected, *args) self._has_failed = False - self._task = asyncio.create_task(self._loop(*args, **kwargs)) + self._task = asyncio.create_task(self._loop(*args, **kwargs), name=self._name) return self._task def stop(self) -> None: @@ -770,6 +778,7 @@ def loop( time: Union[datetime.time, Sequence[datetime.time]] = MISSING, count: Optional[int] = None, reconnect: bool = True, + name: Optional[str] = None, ) -> Callable[[LF], Loop[LF]]: """A decorator that schedules a task in the background for you with optional reconnect logic. The decorator returns a :class:`Loop`. @@ -802,6 +811,12 @@ def loop( Whether to handle errors and restart the task using an exponential back-off algorithm similar to the one used in :meth:`discord.Client.connect`. + name: Optional[:class:`str`] + The name to assign to the internal task. By default + it is assigned a name based off of the callable name + such as ``discord-ext-tasks: function_name``. + + .. versionadded:: 2.4 Raises -------- @@ -821,6 +836,7 @@ def loop( count=count, time=time, reconnect=reconnect, + name=name, ) return decorator diff --git a/discord/file.py b/discord/file.py index 504d86b73..7e4df415b 100644 --- a/discord/file.py +++ b/discord/file.py @@ -111,7 +111,7 @@ class File: else: filename = getattr(fp, 'name', 'untitled') - self._filename, filename_spoiler = _strip_spoiler(filename) + self._filename, filename_spoiler = _strip_spoiler(filename) # type: ignore # pyright doesn't understand the above getattr if spoiler is MISSING: spoiler = filename_spoiler diff --git a/discord/flags.py b/discord/flags.py index 1dbb7c3c3..59a4909b8 100644 --- a/discord/flags.py +++ b/discord/flags.py @@ -25,7 +25,22 @@ DEALINGS IN THE SOFTWARE. from __future__ import annotations from functools import reduce -from typing import TYPE_CHECKING, Any, Callable, ClassVar, Dict, Iterator, List, Optional, Tuple, Type, TypeVar, overload +from operator import or_ +from typing import ( + TYPE_CHECKING, + Any, + Callable, + ClassVar, + Dict, + Iterator, + List, + Optional, + Sequence, + Tuple, + Type, + TypeVar, + overload, +) from .enums import UserFlags @@ -43,6 +58,13 @@ __all__ = ( 'ChannelFlags', 'AutoModPresets', 'MemberFlags', + 'AppCommandContext', + 'AttachmentFlags', + 'RoleFlags', + 'AppInstallationType', + 'SKUFlags', + 'EmbedFlags', + 'InviteFlags', ) BF = TypeVar('BF', bound='BaseFlags') @@ -115,7 +137,7 @@ class BaseFlags: setattr(self, key, value) @classmethod - def _from_value(cls, value): + def _from_value(cls, value: int) -> Self: self = cls.__new__(cls) self.value = value return self @@ -238,6 +260,12 @@ class SystemChannelFlags(BaseFlags): Returns an iterator of ``(name, value)`` pairs. This allows it to be, for example, constructed as a dict or a list of pairs. + .. describe:: bool(b) + + Returns whether any flag is set to ``True``. + + .. versionadded:: 2.0 + Attributes ----------- value: :class:`int` @@ -360,6 +388,12 @@ class MessageFlags(BaseFlags): Returns an iterator of ``(name, value)`` pairs. This allows it to be, for example, constructed as a dict or a list of pairs. + .. describe:: bool(b) + + Returns whether any flag is set to ``True``. + + .. versionadded:: 2.0 + .. versionadded:: 1.3 Attributes @@ -450,6 +484,22 @@ class MessageFlags(BaseFlags): """ return 4096 + @flag_value + def voice(self): + """:class:`bool`: Returns ``True`` if the message is a voice message. + + .. versionadded:: 2.3 + """ + return 8192 + + @flag_value + def forwarded(self): + """:class:`bool`: Returns ``True`` if the message is a forwarded message. + + .. versionadded:: 2.5 + """ + return 16384 + @fill_with_flags() class PublicUserFlags(BaseFlags): @@ -500,6 +550,12 @@ class PublicUserFlags(BaseFlags): to be, for example, constructed as a dict or a list of pairs. Note that aliases are not shown. + .. describe:: bool(b) + + Returns whether any flag is set to ``True``. + + .. versionadded:: 2.0 + .. versionadded:: 1.4 Attributes @@ -684,6 +740,12 @@ class Intents(BaseFlags): Returns an iterator of ``(name, value)`` pairs. This allows it to be, for example, constructed as a dict or a list of pairs. + .. describe:: bool(b) + + Returns whether any intent is enabled. + + .. versionadded:: 2.0 + Attributes ----------- value: :class:`int` @@ -783,6 +845,7 @@ class Intents(BaseFlags): - :attr:`User.name` - :attr:`User.avatar` - :attr:`User.discriminator` + - :attr:`User.global_name` For more information go to the :ref:`member intent documentation `. @@ -816,9 +879,9 @@ class Intents(BaseFlags): """ return 1 << 2 - @flag_value + @alias_flag_value def emojis(self): - """:class:`bool`: Alias of :attr:`.emojis_and_stickers`. + """:class:`bool`: Alias of :attr:`.expressions`. .. versionchanged:: 2.0 Changed to an alias. @@ -827,25 +890,43 @@ class Intents(BaseFlags): @alias_flag_value def emojis_and_stickers(self): - """:class:`bool`: Whether guild emoji and sticker related events are enabled. + """:class:`bool`: Alias of :attr:`.expressions`. .. versionadded:: 2.0 + .. versionchanged:: 2.5 + Changed to an alias. + """ + return 1 << 3 + + @flag_value + def expressions(self): + """:class:`bool`: Whether guild emoji, sticker, and soundboard sound related events are enabled. + + .. versionadded:: 2.5 + This corresponds to the following events: - :func:`on_guild_emojis_update` - :func:`on_guild_stickers_update` + - :func:`on_soundboard_sound_create` + - :func:`on_soundboard_sound_update` + - :func:`on_soundboard_sound_delete` This also corresponds to the following attributes and classes in terms of cache: - :class:`Emoji` - :class:`GuildSticker` + - :class:`SoundboardSound` - :meth:`Client.get_emoji` - :meth:`Client.get_sticker` + - :meth:`Client.get_soundboard_sound` - :meth:`Client.emojis` - :meth:`Client.stickers` + - :meth:`Client.soundboard_sounds` - :attr:`Guild.emojis` - :attr:`Guild.stickers` + - :attr:`Guild.soundboard_sounds` """ return 1 << 3 @@ -1161,7 +1242,7 @@ class Intents(BaseFlags): """ return 1 << 16 - @flag_value + @alias_flag_value def auto_moderation(self): """:class:`bool`: Whether auto moderation related events are enabled. @@ -1204,6 +1285,57 @@ class Intents(BaseFlags): """ return 1 << 21 + @alias_flag_value + def polls(self): + """:class:`bool`: Whether guild and direct messages poll related events are enabled. + + This is a shortcut to set or get both :attr:`guild_polls` and :attr:`dm_polls`. + + This corresponds to the following events: + + - :func:`on_poll_vote_add` (both guilds and DMs) + - :func:`on_poll_vote_remove` (both guilds and DMs) + - :func:`on_raw_poll_vote_add` (both guilds and DMs) + - :func:`on_raw_poll_vote_remove` (both guilds and DMs) + + .. versionadded:: 2.4 + """ + return (1 << 24) | (1 << 25) + + @flag_value + def guild_polls(self): + """:class:`bool`: Whether guild poll related events are enabled. + + See also :attr:`dm_polls` and :attr:`polls`. + + This corresponds to the following events: + + - :func:`on_poll_vote_add` (only for guilds) + - :func:`on_poll_vote_remove` (only for guilds) + - :func:`on_raw_poll_vote_add` (only for guilds) + - :func:`on_raw_poll_vote_remove` (only for guilds) + + .. versionadded:: 2.4 + """ + return 1 << 24 + + @flag_value + def dm_polls(self): + """:class:`bool`: Whether direct messages poll related events are enabled. + + See also :attr:`guild_polls` and :attr:`polls`. + + This corresponds to the following events: + + - :func:`on_poll_vote_add` (only for DMs) + - :func:`on_poll_vote_remove` (only for DMs) + - :func:`on_raw_poll_vote_add` (only for DMs) + - :func:`on_raw_poll_vote_remove` (only for DMs) + + .. versionadded:: 2.4 + """ + return 1 << 25 + @fill_with_flags() class MemberCacheFlags(BaseFlags): @@ -1269,6 +1401,12 @@ class MemberCacheFlags(BaseFlags): Returns an iterator of ``(name, value)`` pairs. This allows it to be, for example, constructed as a dict or a list of pairs. + .. describe:: bool(b) + + Returns whether any flag is set to ``True``. + + .. versionadded:: 2.0 + Attributes ----------- value: :class:`int` @@ -1412,6 +1550,10 @@ class ApplicationFlags(BaseFlags): to be, for example, constructed as a dict or a list of pairs. Note that aliases are not shown. + .. describe:: bool(b) + + Returns whether any flag is set to ``True``. + .. versionadded:: 2.0 Attributes @@ -1421,6 +1563,15 @@ class ApplicationFlags(BaseFlags): rather than using this raw value. """ + @flag_value + def auto_mod_badge(self): + """:class:`bool`: Returns ``True`` if the application uses at least 100 automod rules across all guilds. + This shows up as a badge in the official client. + + .. versionadded:: 2.3 + """ + return 1 << 6 + @flag_value def gateway_presence(self): """:class:`bool`: Returns ``True`` if the application is verified and is allowed to @@ -1538,6 +1689,10 @@ class ChannelFlags(BaseFlags): to be, for example, constructed as a dict or a list of pairs. Note that aliases are not shown. + .. describe:: bool(b) + + Returns whether any flag is set to ``True``. + .. versionadded:: 2.0 Attributes @@ -1561,16 +1716,49 @@ class ChannelFlags(BaseFlags): """ return 1 << 4 + @flag_value + def hide_media_download_options(self): + """:class:`bool`: Returns ``True`` if the client hides embedded media download options in a :class:`ForumChannel`. + Only available in media channels. + + .. versionadded:: 2.4 + """ + return 1 << 15 + class ArrayFlags(BaseFlags): @classmethod - def _from_value(cls: Type[Self], value: List[int]) -> Self: + def _from_value(cls: Type[Self], value: Sequence[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]: - return [i + 1 for i in range(self.value.bit_length()) if self.value & (1 << i)] + def to_array(self, *, offset: int = 0) -> List[int]: + return [i + offset for i in range(self.value.bit_length()) if self.value & (1 << i)] + + @classmethod + def all(cls: Type[Self]) -> Self: + """A factory method that creates an instance of ArrayFlags with everything enabled.""" + bits = max(cls.VALID_FLAGS.values()).bit_length() + value = (1 << bits) - 1 + self = cls.__new__(cls) + self.value = value + return self + + @classmethod + def none(cls: Type[Self]) -> Self: + """A factory method that creates an instance of ArrayFlags with everything disabled.""" + self = cls.__new__(cls) + self.value = self.DEFAULT_VALUE + return self @fill_with_flags() @@ -1626,6 +1814,10 @@ class AutoModPresets(ArrayFlags): to be, for example, constructed as a dict or a list of pairs. Note that aliases are not shown. + .. describe:: bool(b) + + Returns whether any flag is set to ``True``. + Attributes ----------- value: :class:`int` @@ -1633,6 +1825,9 @@ class AutoModPresets(ArrayFlags): rather than using this raw value. """ + def to_array(self) -> List[int]: + return super().to_array(offset=1) + @flag_value def profanity(self): """:class:`bool`: Whether to use the preset profanity filter.""" @@ -1648,21 +1843,144 @@ class AutoModPresets(ArrayFlags): """:class:`bool`: Whether to use the preset slurs filter.""" return 1 << 2 - @classmethod - def all(cls: Type[Self]) -> Self: - """A factory method that creates a :class:`AutoModPresets` with everything enabled.""" - bits = max(cls.VALID_FLAGS.values()).bit_length() - value = (1 << bits) - 1 - self = cls.__new__(cls) - self.value = value - return self - @classmethod - def none(cls: Type[Self]) -> Self: - """A factory method that creates a :class:`AutoModPresets` with everything disabled.""" - self = cls.__new__(cls) - self.value = self.DEFAULT_VALUE - return self +@fill_with_flags() +class AppCommandContext(ArrayFlags): + r"""Wraps up the Discord :class:`~discord.app_commands.Command` execution context. + + .. versionadded:: 2.4 + + .. container:: operations + + .. describe:: x == y + + Checks if two AppCommandContext flags are equal. + + .. describe:: x != y + + Checks if two AppCommandContext flags are not equal. + + .. describe:: x | y, x |= y + + Returns an AppCommandContext instance with all enabled flags from + both x and y. + + .. describe:: x & y, x &= y + + Returns an AppCommandContext instance with only flags enabled on + both x and y. + + .. describe:: x ^ y, x ^= y + + Returns an AppCommandContext instance with only flags enabled on + only one of x or y, not on both. + + .. describe:: ~x + + Returns an AppCommandContext instance with all flags inverted from x + + .. describe:: hash(x) + + Return the flag's hash. + .. describe:: iter(x) + + Returns an iterator of ``(name, value)`` pairs. This allows it + to be, for example, constructed as a dict or a list of pairs. + Note that aliases are not shown. + + .. describe:: bool(b) + + Returns whether any flag is set to ``True``. + + Attributes + ----------- + value: :class:`int` + The raw value. You should query flags via the properties + rather than using this raw value. + """ + + DEFAULT_VALUE = 3 + + @flag_value + def guild(self): + """:class:`bool`: Whether the context allows usage in a guild.""" + return 1 << 0 + + @flag_value + def dm_channel(self): + """:class:`bool`: Whether the context allows usage in a DM channel.""" + return 1 << 1 + + @flag_value + def private_channel(self): + """:class:`bool`: Whether the context allows usage in a DM or a GDM channel.""" + return 1 << 2 + + +@fill_with_flags() +class AppInstallationType(ArrayFlags): + r"""Represents the installation location of an application command. + + .. versionadded:: 2.4 + + .. container:: operations + + .. describe:: x == y + + Checks if two AppInstallationType flags are equal. + + .. describe:: x != y + + Checks if two AppInstallationType flags are not equal. + + .. describe:: x | y, x |= y + + Returns an AppInstallationType instance with all enabled flags from + both x and y. + + .. describe:: x & y, x &= y + + Returns an AppInstallationType instance with only flags enabled on + both x and y. + + .. describe:: x ^ y, x ^= y + + Returns an AppInstallationType instance with only flags enabled on + only one of x or y, not on both. + + .. describe:: ~x + + Returns an AppInstallationType instance with all flags inverted from x + + .. describe:: hash(x) + + Return the flag's hash. + .. describe:: iter(x) + + Returns an iterator of ``(name, value)`` pairs. This allows it + to be, for example, constructed as a dict or a list of pairs. + Note that aliases are not shown. + + .. describe:: bool(b) + + Returns whether any flag is set to ``True``. + + Attributes + ----------- + value: :class:`int` + The raw value. You should query flags via the properties + rather than using this raw value. + """ + + @flag_value + def guild(self): + """:class:`bool`: Whether the integration is a guild install.""" + return 1 << 0 + + @flag_value + def user(self): + """:class:`bool`: Whether the integration is a user install.""" + return 1 << 1 @fill_with_flags() @@ -1710,6 +2028,10 @@ class MemberFlags(BaseFlags): to be, for example, constructed as a dict or a list of pairs. Note that aliases are not shown. + .. describe:: bool(b) + + Returns whether any flag is set to ``True``. + Attributes ----------- @@ -1737,3 +2059,398 @@ class MemberFlags(BaseFlags): def started_onboarding(self): """:class:`bool`: Returns ``True`` if the member has started onboarding.""" return 1 << 3 + + @flag_value + def guest(self): + """:class:`bool`: Returns ``True`` if the member is a guest and can only access + the voice channel they were invited to. + + .. versionadded:: 2.5 + """ + return 1 << 4 + + @flag_value + def started_home_actions(self): + """:class:`bool`: Returns ``True`` if the member has started Server Guide new member actions. + + .. versionadded:: 2.5 + """ + return 1 << 5 + + @flag_value + def completed_home_actions(self): + """:class:`bool`: Returns ``True`` if the member has completed Server Guide new member actions. + + .. versionadded:: 2.5 + """ + return 1 << 6 + + @flag_value + def automod_quarantined_username(self): + """:class:`bool`: Returns ``True`` if the member's username, nickname, or global name has been + blocked by AutoMod. + + .. versionadded:: 2.5 + """ + return 1 << 7 + + @flag_value + def dm_settings_upsell_acknowledged(self): + """:class:`bool`: Returns ``True`` if the member has dismissed the DM settings upsell. + + .. versionadded:: 2.5 + """ + return 1 << 9 + + +@fill_with_flags() +class AttachmentFlags(BaseFlags): + r"""Wraps up the Discord Attachment flags + + .. versionadded:: 2.4 + + .. container:: operations + + .. describe:: x == y + + Checks if two AttachmentFlags are equal. + + .. describe:: x != y + + Checks if two AttachmentFlags are not equal. + + .. describe:: x | y, x |= y + + Returns a AttachmentFlags instance with all enabled flags from + both x and y. + + .. describe:: x & y, x &= y + + Returns a AttachmentFlags instance with only flags enabled on + both x and y. + + .. describe:: x ^ y, x ^= y + + Returns a AttachmentFlags instance with only flags enabled on + only one of x or y, not on both. + + .. describe:: ~x + + Returns a AttachmentFlags instance with all flags inverted from x. + + .. describe:: hash(x) + + Return the flag's hash. + + .. describe:: iter(x) + + Returns an iterator of ``(name, value)`` pairs. This allows it + to be, for example, constructed as a dict or a list of pairs. + Note that aliases are not shown. + + .. describe:: bool(b) + + Returns whether any flag is set to ``True``. + + + Attributes + ----------- + value: :class:`int` + The raw value. You should query flags via the properties + rather than using this raw value. + """ + + @flag_value + def clip(self): + """:class:`bool`: Returns ``True`` if the attachment is a clip.""" + return 1 << 0 + + @flag_value + def thumbnail(self): + """:class:`bool`: Returns ``True`` if the attachment is a thumbnail.""" + return 1 << 1 + + @flag_value + def remix(self): + """:class:`bool`: Returns ``True`` if the attachment has been edited using the remix feature.""" + return 1 << 2 + + @flag_value + def spoiler(self): + """:class:`bool`: Returns ``True`` if the attachment was marked as a spoiler. + + .. versionadded:: 2.5 + """ + return 1 << 3 + + @flag_value + def contains_explicit_media(self): + """:class:`bool`: Returns ``True`` if the attachment was flagged as sensitive content. + + .. versionadded:: 2.5 + """ + return 1 << 4 + + @flag_value + def animated(self): + """:class:`bool`: Returns ``True`` if the attachment is an animated image. + + .. versionadded:: 2.5 + """ + return 1 << 5 + + +@fill_with_flags() +class RoleFlags(BaseFlags): + r"""Wraps up the Discord Role flags + + .. versionadded:: 2.4 + + .. container:: operations + + .. describe:: x == y + + Checks if two RoleFlags are equal. + + .. describe:: x != y + + Checks if two RoleFlags are not equal. + + .. describe:: x | y, x |= y + + Returns a RoleFlags instance with all enabled flags from + both x and y. + + .. describe:: x & y, x &= y + + Returns a RoleFlags instance with only flags enabled on + both x and y. + + .. describe:: x ^ y, x ^= y + + Returns a RoleFlags instance with only flags enabled on + only one of x or y, not on both. + + .. describe:: ~x + + Returns a RoleFlags instance with all flags inverted from x. + + .. describe:: hash(x) + + Return the flag's hash. + + .. describe:: iter(x) + + Returns an iterator of ``(name, value)`` pairs. This allows it + to be, for example, constructed as a dict or a list of pairs. + Note that aliases are not shown. + + .. describe:: bool(b) + + Returns whether any flag is set to ``True``. + + + Attributes + ----------- + value: :class:`int` + The raw value. You should query flags via the properties + rather than using this raw value. + """ + + @flag_value + def in_prompt(self): + """:class:`bool`: Returns ``True`` if the role can be selected by members in an onboarding prompt.""" + return 1 << 0 + + +@fill_with_flags() +class SKUFlags(BaseFlags): + r"""Wraps up the Discord SKU flags + + .. versionadded:: 2.4 + + .. container:: operations + + .. describe:: x == y + + Checks if two SKUFlags are equal. + + .. describe:: x != y + + Checks if two SKUFlags are not equal. + + .. describe:: x | y, x |= y + + Returns a SKUFlags instance with all enabled flags from + both x and y. + + .. describe:: x & y, x &= y + + Returns a SKUFlags instance with only flags enabled on + both x and y. + + .. describe:: x ^ y, x ^= y + + Returns a SKUFlags instance with only flags enabled on + only one of x or y, not on both. + + .. describe:: ~x + + Returns a SKUFlags instance with all flags inverted from x. + + .. describe:: hash(x) + + Return the flag's hash. + + .. describe:: iter(x) + + Returns an iterator of ``(name, value)`` pairs. This allows it + to be, for example, constructed as a dict or a list of pairs. + Note that aliases are not shown. + + .. describe:: bool(b) + + Returns whether any flag is set to ``True``. + + + Attributes + ----------- + value: :class:`int` + The raw value. You should query flags via the properties + rather than using this raw value. + """ + + @flag_value + def available(self): + """:class:`bool`: Returns ``True`` if the SKU is available for purchase.""" + return 1 << 2 + + @flag_value + def guild_subscription(self): + """:class:`bool`: Returns ``True`` if the SKU is a guild subscription.""" + return 1 << 7 + + @flag_value + def user_subscription(self): + """:class:`bool`: Returns ``True`` if the SKU is a user subscription.""" + return 1 << 8 + + +@fill_with_flags() +class EmbedFlags(BaseFlags): + r"""Wraps up the Discord Embed flags + + .. versionadded:: 2.5 + + .. container:: operations + + .. describe:: x == y + + Checks if two EmbedFlags are equal. + + .. describe:: x != y + + Checks if two EmbedFlags are not equal. + + .. describe:: x | y, x |= y + + Returns an EmbedFlags instance with all enabled flags from + both x and y. + + .. describe:: x ^ y, x ^= y + + Returns an EmbedFlags instance with only flags enabled on + only one of x or y, not on both. + + .. describe:: ~x + + Returns an EmbedFlags instance with all flags inverted from x. + + .. describe:: hash(x) + + Returns the flag's hash. + + .. describe:: iter(x) + + Returns an iterator of ``(name, value)`` pairs. This allows it + to be, for example, constructed as a dict or a list of pairs. + Note that aliases are not shown. + + .. describe:: bool(b) + + Returns whether any flag is set to ``True``. + + Attributes + ---------- + value: :class:`int` + The raw value. You should query flags via the properties + rather than using this raw value. + """ + + @flag_value + def contains_explicit_media(self): + """:class:`bool`: Returns ``True`` if the embed was flagged as sensitive content.""" + return 1 << 4 + + @flag_value + def content_inventory_entry(self): + """:class:`bool`: Returns ``True`` if the embed is a reply to an activity card, and is no + longer displayed. + """ + return 1 << 5 + + +class InviteFlags(BaseFlags): + r"""Wraps up the Discord Invite flags + + .. versionadded:: 2.6 + + .. container:: operations + + .. describe:: x == y + + Checks if two InviteFlags are equal. + + .. describe:: x != y + + Checks if two InviteFlags are not equal. + + .. describe:: x | y, x |= y + + Returns a InviteFlags instance with all enabled flags from + both x and y. + + .. describe:: x ^ y, x ^= y + + Returns a InviteFlags instance with only flags enabled on + only one of x or y, not on both. + + .. describe:: ~x + + Returns a InviteFlags instance with all flags inverted from x. + + .. describe:: hash(x) + + Returns the flag's hash. + + .. describe:: iter(x) + + Returns an iterator of ``(name, value)`` pairs. This allows it + to be, for example, constructed as a dict or a list of pairs. + Note that aliases are not shown. + + .. describe:: bool(b) + + Returns whether any flag is set to ``True``. + + Attributes + ---------- + value: :class:`int` + The raw value. You should query flags via the properties + rather than using this raw value. + """ + + @flag_value + def guest(self): + """:class:`bool`: Returns ``True`` if this is a guest invite for a voice channel.""" + return 1 << 0 diff --git a/discord/gateway.py b/discord/gateway.py index a06195307..a2c3da3d2 100644 --- a/discord/gateway.py +++ b/discord/gateway.py @@ -21,6 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ + from __future__ import annotations import asyncio @@ -32,9 +33,8 @@ import sys import time import threading import traceback -import zlib -from typing import Any, Callable, Coroutine, Deque, Dict, List, TYPE_CHECKING, NamedTuple, Optional, TypeVar +from typing import Any, Callable, Coroutine, Deque, Dict, List, TYPE_CHECKING, NamedTuple, Optional, TypeVar, Tuple import aiohttp import yarl @@ -59,7 +59,7 @@ if TYPE_CHECKING: from .client import Client from .state import ConnectionState - from .voice_client import VoiceClient + from .voice_state import VoiceConnectionState class ReconnectWebSocket(Exception): @@ -132,11 +132,12 @@ class KeepAliveHandler(threading.Thread): shard_id: Optional[int] = None, **kwargs: Any, ) -> None: - super().__init__(*args, **kwargs) + daemon: bool = kwargs.pop('daemon', True) + name: str = kwargs.pop('name', f'keep-alive-handler:shard-{shard_id}') + super().__init__(*args, daemon=daemon, name=name, **kwargs) self.ws: DiscordWebSocket = ws self._main_thread_id: int = ws.thread_id self.interval: Optional[float] = interval - self.daemon: bool = True self.shard_id: Optional[int] = shard_id self.msg: str = 'Keeping shard ID %s websocket alive with sequence %s.' self.block_msg: str = 'Shard ID %s heartbeat blocked for more than %s seconds.' @@ -211,8 +212,12 @@ class KeepAliveHandler(threading.Thread): class VoiceKeepAliveHandler(KeepAliveHandler): + if TYPE_CHECKING: + ws: DiscordVoiceWebSocket + def __init__(self, *args: Any, **kwargs: Any) -> None: - super().__init__(*args, **kwargs) + name: str = kwargs.pop('name', f'voice-keep-alive-handler:{id(self):#x}') + super().__init__(*args, name=name, **kwargs) self.recent_ack_latencies: Deque[float] = deque(maxlen=20) self.msg: str = 'Keeping shard ID %s voice websocket alive with timestamp %s.' self.block_msg: str = 'Shard ID %s voice heartbeat blocked for more than %s seconds' @@ -221,7 +226,10 @@ class VoiceKeepAliveHandler(KeepAliveHandler): def get_payload(self) -> Dict[str, Any]: return { 'op': self.ws.HEARTBEAT, - 'd': int(time.time() * 1000), + 'd': { + 't': int(time.time() * 1000), + 'seq_ack': self.ws.seq_ack, + }, } def ack(self) -> None: @@ -293,19 +301,19 @@ class DiscordWebSocket: # fmt: off DEFAULT_GATEWAY = yarl.URL('wss://gateway.discord.gg/') - DISPATCH = 0 - HEARTBEAT = 1 - IDENTIFY = 2 - PRESENCE = 3 - VOICE_STATE = 4 - VOICE_PING = 5 - RESUME = 6 - RECONNECT = 7 - REQUEST_MEMBERS = 8 - INVALIDATE_SESSION = 9 - HELLO = 10 - HEARTBEAT_ACK = 11 - GUILD_SYNC = 12 + DISPATCH = 0 + HEARTBEAT = 1 + IDENTIFY = 2 + PRESENCE = 3 + VOICE_STATE = 4 + VOICE_PING = 5 + RESUME = 6 + RECONNECT = 7 + REQUEST_MEMBERS = 8 + INVALIDATE_SESSION = 9 + HELLO = 10 + HEARTBEAT_ACK = 11 + GUILD_SYNC = 12 # fmt: on def __init__(self, socket: aiohttp.ClientWebSocketResponse, *, loop: asyncio.AbstractEventLoop) -> None: @@ -323,8 +331,7 @@ class DiscordWebSocket: # ws related stuff self.session_id: Optional[str] = None self.sequence: Optional[int] = None - self._zlib: zlib._Decompress = zlib.decompressobj() - self._buffer: bytearray = bytearray() + self._decompressor: utils._DecompressionContext = utils._ActiveDecompressionContext() self._close_code: Optional[int] = None self._rate_limiter: GatewayRatelimiter = GatewayRatelimiter() @@ -353,7 +360,7 @@ class DiscordWebSocket: sequence: Optional[int] = None, resume: bool = False, encoding: str = 'json', - zlib: bool = True, + compress: bool = True, ) -> Self: """Creates a main websocket for Discord from a :class:`Client`. @@ -364,10 +371,12 @@ class DiscordWebSocket: gateway = gateway or cls.DEFAULT_GATEWAY - if zlib: - url = gateway.with_query(v=INTERNAL_API_VERSION, encoding=encoding, compress='zlib-stream') - else: + if not compress: url = gateway.with_query(v=INTERNAL_API_VERSION, encoding=encoding) + else: + url = gateway.with_query( + v=INTERNAL_API_VERSION, encoding=encoding, compress=utils._ActiveDecompressionContext.COMPRESSION_TYPE + ) socket = await client.http.ws_connect(str(url)) ws = cls(socket, loop=client.loop) @@ -486,13 +495,11 @@ class DiscordWebSocket: async def received_message(self, msg: Any, /) -> None: if type(msg) is bytes: - self._buffer.extend(msg) + msg = self._decompressor.decompress(msg) - if len(msg) < 4 or msg[-4:] != b'\x00\x00\xff\xff': + # Received a partial gateway message + if msg is None: return - msg = self._zlib.decompress(self._buffer) - msg = msg.decode('utf-8') - self._buffer = bytearray() self.log_receive(msg) msg = utils._from_json(msg) @@ -605,7 +612,10 @@ class DiscordWebSocket: def _can_handle_close(self) -> bool: code = self._close_code or self.socket.close_code - return code not in (1000, 4004, 4010, 4011, 4012, 4013, 4014) + # If the socket is closed remotely with 1000 and it's not our own explicit close + # then it's an improper close that should be handled and reconnected + is_improper_close = self._close_code is None and self.socket.close_code == 1000 + return is_improper_close or code not in (1000, 4004, 4010, 4011, 4012, 4013, 4014) async def poll_event(self) -> None: """Polls for a DISPATCH event and handles the general gateway loop. @@ -622,8 +632,8 @@ class DiscordWebSocket: elif msg.type is aiohttp.WSMsgType.BINARY: await self.received_message(msg.data) elif msg.type is aiohttp.WSMsgType.ERROR: - _log.debug('Received %s', msg) - raise msg.data + _log.debug('Received error %s', msg) + raise WebSocketClosure elif msg.type in (aiohttp.WSMsgType.CLOSED, aiohttp.WSMsgType.CLOSING, aiohttp.WSMsgType.CLOSE): _log.debug('Received %s', msg) raise WebSocketClosure @@ -795,7 +805,7 @@ class DiscordVoiceWebSocket: if TYPE_CHECKING: thread_id: int - _connection: VoiceClient + _connection: VoiceConnectionState gateway: str _max_heartbeat_timeout: float @@ -825,9 +835,11 @@ class DiscordVoiceWebSocket: self.loop: asyncio.AbstractEventLoop = loop self._keep_alive: Optional[VoiceKeepAliveHandler] = None self._close_code: Optional[int] = None - self.secret_key: Optional[str] = None + self.secret_key: Optional[List[int]] = None + # defaulting to -1 + self.seq_ack: int = -1 if hook: - self._hook = hook + self._hook = hook # type: ignore async def _hook(self, *args: Any) -> None: pass @@ -846,6 +858,7 @@ class DiscordVoiceWebSocket: 'token': state.token, 'server_id': str(state.server_id), 'session_id': state.session_id, + 'seq_ack': self.seq_ack, }, } await self.send_as_json(payload) @@ -864,16 +877,23 @@ class DiscordVoiceWebSocket: await self.send_as_json(payload) @classmethod - async def from_client( - cls, client: VoiceClient, *, resume: bool = False, hook: Optional[Callable[..., Coroutine[Any, Any, Any]]] = None + async def from_connection_state( + cls, + state: VoiceConnectionState, + *, + resume: bool = False, + hook: Optional[Callable[..., Coroutine[Any, Any, Any]]] = None, + seq_ack: int = -1, ) -> Self: """Creates a voice websocket for the :class:`VoiceClient`.""" - gateway = 'wss://' + client.endpoint + '/?v=4' + gateway = f'wss://{state.endpoint}/?v=8' + client = state.voice_client http = client._state.http socket = await http.ws_connect(gateway, compress=15) ws = cls(socket, loop=client.loop, hook=hook) ws.gateway = gateway - ws._connection = client + ws.seq_ack = seq_ack + ws._connection = state ws._max_heartbeat_timeout = 60.0 ws.thread_id = threading.get_ident() @@ -884,7 +904,7 @@ class DiscordVoiceWebSocket: return ws - async def select_protocol(self, ip: str, port: int, mode: int) -> None: + async def select_protocol(self, ip: str, port: int, mode: str) -> None: payload = { 'op': self.SELECT_PROTOCOL, 'd': { @@ -915,6 +935,7 @@ class DiscordVoiceWebSocket: 'd': { 'speaking': int(state), 'delay': 0, + 'ssrc': self._connection.ssrc, }, } @@ -924,6 +945,7 @@ class DiscordVoiceWebSocket: _log.debug('Voice websocket frame received: %s', msg) op = msg['op'] data = msg['d'] # According to Discord this key is always given + self.seq_ack = msg.get('seq', self.seq_ack) # this key could not be given if op == self.READY: await self.initial_connection(data) @@ -948,30 +970,50 @@ class DiscordVoiceWebSocket: state.voice_port = data['port'] state.endpoint_ip = data['ip'] - packet = bytearray(70) - struct.pack_into('>H', packet, 0, 1) # 1 = Send - struct.pack_into('>H', packet, 2, 70) # 70 = Length - struct.pack_into('>I', packet, 4, state.ssrc) - state.socket.sendto(packet, (state.endpoint_ip, state.voice_port)) - recv = await self.loop.sock_recv(state.socket, 70) - _log.debug('received packet in initial_connection: %s', recv) - - # the ip is ascii starting at the 4th byte and ending at the first null - ip_start = 4 - ip_end = recv.index(0, ip_start) - state.ip = recv[ip_start:ip_end].decode('ascii') - - state.port = struct.unpack_from('>H', recv, len(recv) - 2)[0] - _log.debug('detected ip: %s port: %s', state.ip, state.port) + _log.debug('Connecting to voice socket') + await self.loop.sock_connect(state.socket, (state.endpoint_ip, state.voice_port)) + state.ip, state.port = await self.discover_ip() # there *should* always be at least one supported mode (xsalsa20_poly1305) modes = [mode for mode in data['modes'] if mode in self._connection.supported_modes] - _log.debug('received supported encryption modes: %s', ", ".join(modes)) + _log.debug('received supported encryption modes: %s', ', '.join(modes)) mode = modes[0] await self.select_protocol(state.ip, state.port, mode) _log.debug('selected the voice protocol for use (%s)', mode) + async def discover_ip(self) -> Tuple[str, int]: + state = self._connection + packet = bytearray(74) + struct.pack_into('>H', packet, 0, 1) # 1 = Send + struct.pack_into('>H', packet, 2, 70) # 70 = Length + struct.pack_into('>I', packet, 4, state.ssrc) + + _log.debug('Sending ip discovery packet') + await self.loop.sock_sendall(state.socket, packet) + + fut: asyncio.Future[bytes] = self.loop.create_future() + + def get_ip_packet(data: bytes): + if data[1] == 0x02 and len(data) == 74: + self.loop.call_soon_threadsafe(fut.set_result, data) + + fut.add_done_callback(lambda f: state.remove_socket_listener(get_ip_packet)) + state.add_socket_listener(get_ip_packet) + recv = await fut + + _log.debug('Received ip discovery packet: %s', recv) + + # the ip is ascii starting at the 8th byte and ending at the first null + ip_start = 8 + ip_end = recv.index(0, ip_start) + ip = recv[ip_start:ip_end].decode('ascii') + + port = struct.unpack_from('>H', recv, len(recv) - 2)[0] + _log.debug('detected ip: %s port: %s', ip, port) + + return ip, port + @property def latency(self) -> float: """:class:`float`: Latency between a HEARTBEAT and its HEARTBEAT_ACK in seconds.""" @@ -990,7 +1032,10 @@ class DiscordVoiceWebSocket: async def load_secret_key(self, data: Dict[str, Any]) -> None: _log.debug('received secret key for voice connection') self.secret_key = self._connection.secret_key = data['secret_key'] - await self.speak() + + # Send a speak command with the "not speaking" state. + # This also tells Discord our SSRC value, which Discord requires before + # sending any voice data (and is the real reason why we call this here). await self.speak(SpeakingState.none) async def poll_event(self) -> None: @@ -999,10 +1044,10 @@ class DiscordVoiceWebSocket: if msg.type is aiohttp.WSMsgType.TEXT: await self.received_message(utils._from_json(msg.data)) elif msg.type is aiohttp.WSMsgType.ERROR: - _log.debug('Received %s', msg) + _log.debug('Received voice %s', msg) raise ConnectionClosed(self.ws, shard_id=None) from msg.data elif msg.type in (aiohttp.WSMsgType.CLOSED, aiohttp.WSMsgType.CLOSE, aiohttp.WSMsgType.CLOSING): - _log.debug('Received %s', msg) + _log.debug('Received voice %s', msg) raise ConnectionClosed(self.ws, shard_id=None, code=self._close_code) async def close(self, code: int = 1000) -> None: diff --git a/discord/guild.py b/discord/guild.py index af7f43134..6b8e8814e 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -34,6 +34,7 @@ from typing import ( Collection, Coroutine, Dict, + Iterable, List, Mapping, NamedTuple, @@ -73,6 +74,8 @@ from .enums import ( MFALevel, Locale, AutoModRuleEventType, + ForumOrderType, + ForumLayoutType, ) from .mixins import Hashable from .user import User @@ -90,11 +93,13 @@ from .audit_logs import AuditLogEntry from .object import OLDEST_OBJECT, Object from .welcome_screen import WelcomeScreen, WelcomeChannel from .automod import AutoModRule, AutoModTrigger, AutoModRuleAction -from .onboarding import Onboarding - +from .partial_emoji import _EmojiTag, PartialEmoji +from .soundboard import SoundboardSound +from .presences import RawPresenceUpdateEvent __all__ = ( 'Guild', + 'GuildPreview', 'BanEntry', ) @@ -105,8 +110,10 @@ if TYPE_CHECKING: from .types.guild import ( Ban as BanPayload, Guild as GuildPayload, + GuildPreview as GuildPreviewPayload, RolePositionUpdate as RolePositionUpdatePayload, GuildFeature, + IncidentData, ) from .types.threads import ( Thread as ThreadPayload, @@ -130,6 +137,8 @@ if TYPE_CHECKING: from .types.integration import IntegrationType from .types.snowflake import SnowflakeList from .types.widget import EditWidgetSettings + from .types.audit_log import AuditLogEvent + from .message import EmojiInputType VocalGuildChannel = Union[VoiceChannel, StageChannel] GuildChannel = Union[VocalGuildChannel, ForumChannel, TextChannel, CategoryChannel] @@ -141,6 +150,11 @@ class BanEntry(NamedTuple): user: User +class BulkBanResult(NamedTuple): + banned: List[Object] + failed: List[Object] + + class _GuildLimit(NamedTuple): emoji: int stickers: int @@ -148,6 +162,121 @@ class _GuildLimit(NamedTuple): filesize: int +class GuildPreview(Hashable): + """Represents a preview of a Discord guild. + + .. versionadded:: 2.5 + + .. container:: operations + + .. describe:: x == y + + Checks if two guild previews are equal. + + .. describe:: x != y + + Checks if two guild previews are not equal. + + .. describe:: hash(x) + + Returns the guild's hash. + + .. describe:: str(x) + + Returns the guild's name. + + Attributes + ---------- + name: :class:`str` + The guild preview's name. + id: :class:`int` + The guild preview's ID. + features: List[:class:`str`] + A list of features the guild has. See :attr:`Guild.features` for more information. + description: Optional[:class:`str`] + The guild preview's description. + emojis: Tuple[:class:`Emoji`, ...] + All emojis that the guild owns. + stickers: Tuple[:class:`GuildSticker`, ...] + All stickers that the guild owns. + approximate_member_count: :class:`int` + The approximate number of members in the guild. + approximate_presence_count: :class:`int` + The approximate number of members currently active in in the guild. Offline members are excluded. + """ + + __slots__ = ( + '_state', + '_icon', + '_splash', + '_discovery_splash', + 'id', + 'name', + 'emojis', + 'stickers', + 'features', + 'description', + "approximate_member_count", + "approximate_presence_count", + ) + + def __init__(self, *, data: GuildPreviewPayload, state: ConnectionState) -> None: + self._state: ConnectionState = state + self.id = int(data['id']) + self.name: str = data['name'] + self._icon: Optional[str] = data.get('icon') + self._splash: Optional[str] = data.get('splash') + self._discovery_splash: Optional[str] = data.get('discovery_splash') + self.emojis: Tuple[Emoji, ...] = tuple( + map( + lambda d: Emoji(guild=state._get_or_create_unavailable_guild(self.id), state=state, data=d), + data.get('emojis', []), + ) + ) + self.stickers: Tuple[GuildSticker, ...] = tuple( + map(lambda d: GuildSticker(state=state, data=d), data.get('stickers', [])) + ) + self.features: List[GuildFeature] = data.get('features', []) + self.description: Optional[str] = data.get('description') + self.approximate_member_count: int = data.get('approximate_member_count') + self.approximate_presence_count: int = data.get('approximate_presence_count') + + def __str__(self) -> str: + return self.name + + def __repr__(self) -> str: + return ( + f'<{self.__class__.__name__} id={self.id} name={self.name!r} description={self.description!r} ' + f'features={self.features}>' + ) + + @property + def created_at(self) -> datetime.datetime: + """:class:`datetime.datetime`: Returns the guild's creation time in UTC.""" + return utils.snowflake_time(self.id) + + @property + def icon(self) -> Optional[Asset]: + """Optional[:class:`Asset`]: Returns the guild's icon asset, if available.""" + if self._icon is None: + return None + return Asset._from_guild_icon(self._state, self.id, self._icon) + + @property + def splash(self) -> Optional[Asset]: + """Optional[:class:`Asset`]: Returns the guild's invite splash asset, if available.""" + if self._splash is None: + return None + return Asset._from_guild_image(self._state, self.id, self._splash, path='splashes') + + @property + def discovery_splash(self) -> Optional[Asset]: + """Optional[:class:`Asset`]: Returns the guild's discovery splash asset, if available.""" + if self._discovery_splash is None: + return None + return Asset._from_guild_image(self._state, self.id, self._discovery_splash, path='discovery-splashes') + + class Guild(Hashable): """Represents a Discord guild. @@ -183,8 +312,6 @@ class Guild(Hashable): .. versionadded:: 2.0 afk_timeout: :class:`int` The number of seconds until someone is moved to the AFK channel. - afk_channel: Optional[:class:`VoiceChannel`] - The channel that denotes the AFK channel. ``None`` if it doesn't exist. id: :class:`int` The guild's ID. owner_id: :class:`int` @@ -247,13 +374,13 @@ class Guild(Hashable): approximate_member_count: Optional[:class:`int`] The approximate number of members in the guild. This is ``None`` unless the guild is obtained - using :meth:`Client.fetch_guild` with ``with_counts=True``. + using :meth:`Client.fetch_guild` or :meth:`Client.fetch_guilds` with ``with_counts=True``. .. versionadded:: 2.0 approximate_presence_count: Optional[:class:`int`] The approximate number of members currently active in the guild. Offline members are excluded. This is ``None`` unless the guild is obtained using - :meth:`Client.fetch_guild` with ``with_counts=True``. + :meth:`Client.fetch_guild` or :meth:`Client.fetch_guilds` with ``with_counts=True``. .. versionchanged:: 2.0 premium_progress_bar_enabled: :class:`bool` @@ -264,11 +391,14 @@ class Guild(Hashable): Indicates if the guild has widget enabled. .. versionadded:: 2.0 + max_stage_video_users: Optional[:class:`int`] + The maximum amount of users in a stage video channel. + + .. versionadded:: 2.3 """ __slots__ = ( 'afk_timeout', - 'afk_channel', 'name', 'id', 'unavailable', @@ -290,6 +420,8 @@ class Guild(Hashable): 'mfa_level', 'vanity_url_code', 'widget_enabled', + '_widget_channel_id', + '_afk_channel_id', '_members', '_channels', '_icon', @@ -311,12 +443,16 @@ class Guild(Hashable): 'approximate_member_count', 'approximate_presence_count', 'premium_progress_bar_enabled', + '_safety_alerts_channel_id', + 'max_stage_video_users', + '_incidents_data', + '_soundboard_sounds', ) _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=utils.DEFAULT_FILE_SIZE_LIMIT_BYTES), + 0: _GuildLimit(emoji=50, stickers=5, bitrate=96e3, filesize=utils.DEFAULT_FILE_SIZE_LIMIT_BYTES), + 1: _GuildLimit(emoji=100, stickers=15, bitrate=128e3, filesize=utils.DEFAULT_FILE_SIZE_LIMIT_BYTES), 2: _GuildLimit(emoji=150, stickers=30, bitrate=256e3, filesize=52428800), 3: _GuildLimit(emoji=250, stickers=60, bitrate=384e3, filesize=104857600), } @@ -328,6 +464,7 @@ class Guild(Hashable): self._threads: Dict[int, Thread] = {} self._stage_instances: Dict[int, StageInstance] = {} self._scheduled_events: Dict[int, ScheduledEvent] = {} + self._soundboard_sounds: Dict[int, SoundboardSound] = {} self._state: ConnectionState = state self._member_count: Optional[int] = None self._from_data(data) @@ -361,10 +498,11 @@ class Guild(Hashable): def _clear_threads(self) -> None: self._threads.clear() - def _remove_threads_by_channel(self, channel_id: int) -> None: - to_remove = [k for k, t in self._threads.items() if t.parent_id == channel_id] - for k in to_remove: - del self._threads[k] + def _remove_threads_by_channel(self, channel_id: int) -> List[Thread]: + to_remove = [t for t in self._threads.values() if t.parent_id == channel_id] + for thread in to_remove: + del self._threads[thread.id] + return to_remove def _filter_threads(self, channel_ids: Set[int]) -> Dict[int, Thread]: to_remove: Dict[int, Thread] = {k: t for k, t in self._threads.items() if t.parent_id in channel_ids} @@ -372,6 +510,12 @@ class Guild(Hashable): del self._threads[k] return to_remove + def _add_soundboard_sound(self, sound: SoundboardSound, /) -> None: + self._soundboard_sounds[sound.id] = sound + + def _remove_soundboard_sound(self, sound: SoundboardSound, /) -> None: + self._soundboard_sounds.pop(sound.id, None) + def __str__(self) -> str: return self.name or '' @@ -407,42 +551,30 @@ class Guild(Hashable): member = self.get_member(user_id) if member is None: try: - member = Member(data=data['member'], state=self._state, guild=self) + member_data = data['member'] # pyright: ignore[reportTypedDictNotRequiredAccess] + member = Member(data=member_data, state=self._state, guild=self) except KeyError: member = None return member, before, after def _add_role(self, role: Role, /) -> None: - # roles get added to the bottom (position 1, pos 0 is @everyone) - # so since self.roles has the @everyone role, we can't increment - # its position because it's stuck at position 0. Luckily x += False - # is equivalent to adding 0. So we cast the position to a bool and - # increment it. - for r in self._roles.values(): - r.position += not r.is_default() - self._roles[role.id] = role def _remove_role(self, role_id: int, /) -> Role: # this raises KeyError if it fails.. - role = self._roles.pop(role_id) - - # since it didn't, we can change the positions now - # basically the same as above except we only decrement - # the position if we're above the role we deleted. - for r in self._roles.values(): - r.position -= r.position > role.position - - return role + return self._roles.pop(role_id) @classmethod - def _create_unavailable(cls, *, state: ConnectionState, guild_id: int) -> Guild: - return cls(state=state, data={'id': guild_id, 'unavailable': True}) # type: ignore + def _create_unavailable(cls, *, state: ConnectionState, guild_id: int, data: Optional[Dict[str, Any]]) -> Guild: + if data is None: + data = {'unavailable': True} + data.update(id=guild_id) + return cls(state=state, data=data) # type: ignore def _from_data(self, guild: GuildPayload) -> None: try: - self._member_count = guild['member_count'] + self._member_count = guild['member_count'] # pyright: ignore[reportTypedDictNotRequiredAccess] except KeyError: pass @@ -463,9 +595,15 @@ class Guild(Hashable): role = Role(guild=self, data=r, state=state) self._roles[role.id] = role - self.emojis: Tuple[Emoji, ...] = tuple(map(lambda d: state.store_emoji(self, d), guild.get('emojis', []))) - self.stickers: Tuple[GuildSticker, ...] = tuple( - map(lambda d: state.store_sticker(self, d), guild.get('stickers', [])) + self.emojis: Tuple[Emoji, ...] = ( + tuple(map(lambda d: state.store_emoji(self, d), guild.get('emojis', []))) + if state.cache_guild_expressions + else () + ) + self.stickers: Tuple[GuildSticker, ...] = ( + tuple(map(lambda d: state.store_sticker(self, d), guild.get('stickers', []))) + if state.cache_guild_expressions + else () ) self.features: List[GuildFeature] = guild.get('features', []) self._splash: Optional[str] = guild.get('splash') @@ -474,74 +612,74 @@ class Guild(Hashable): self.max_presences: Optional[int] = guild.get('max_presences') self.max_members: Optional[int] = guild.get('max_members') self.max_video_channel_users: Optional[int] = guild.get('max_video_channel_users') + self.max_stage_video_users: Optional[int] = guild.get('max_stage_video_channel_users') self.premium_tier: int = guild.get('premium_tier', 0) self.premium_subscription_count: int = guild.get('premium_subscription_count') or 0 self.vanity_url_code: Optional[str] = guild.get('vanity_url_code') self.widget_enabled: bool = guild.get('widget_enabled', False) + self._widget_channel_id: Optional[int] = utils._get_as_snowflake(guild, 'widget_channel_id') self._system_channel_flags: int = guild.get('system_channel_flags', 0) self.preferred_locale: Locale = try_enum(Locale, guild.get('preferred_locale', 'en-US')) self._discovery_splash: Optional[str] = guild.get('discovery_splash') self._rules_channel_id: Optional[int] = utils._get_as_snowflake(guild, 'rules_channel_id') self._public_updates_channel_id: Optional[int] = utils._get_as_snowflake(guild, 'public_updates_channel_id') + self._safety_alerts_channel_id: Optional[int] = utils._get_as_snowflake(guild, 'safety_alerts_channel_id') self.nsfw_level: NSFWLevel = try_enum(NSFWLevel, guild.get('nsfw_level', 0)) self.mfa_level: MFALevel = try_enum(MFALevel, guild.get('mfa_level', 0)) self.approximate_presence_count: Optional[int] = guild.get('approximate_presence_count') self.approximate_member_count: Optional[int] = guild.get('approximate_member_count') self.premium_progress_bar_enabled: bool = guild.get('premium_progress_bar_enabled', False) self.owner_id: Optional[int] = utils._get_as_snowflake(guild, 'owner_id') - - self._sync(guild) self._large: Optional[bool] = None if self._member_count is None else self._member_count >= 250 + self._afk_channel_id: Optional[int] = utils._get_as_snowflake(guild, 'afk_channel_id') + self._incidents_data: Optional[IncidentData] = guild.get('incidents_data') - self.afk_channel: Optional[VocalGuildChannel] = self.get_channel(utils._get_as_snowflake(guild, 'afk_channel_id')) # type: ignore - - # TODO: refactor/remove? - def _sync(self, data: GuildPayload) -> None: - try: - self._large = data['large'] - except KeyError: - pass - - if 'channels' in data: - channels = data['channels'] + if 'channels' in guild: + channels = guild['channels'] for c in channels: factory, ch_type = _guild_channel_factory(c['type']) if factory: self._add_channel(factory(guild=self, data=c, state=self._state)) # type: ignore - for obj in data.get('voice_states', []): + for obj in guild.get('voice_states', []): self._update_voice_state(obj, int(obj['channel_id'])) cache_joined = self._state.member_cache_flags.joined cache_voice = self._state.member_cache_flags.voice self_id = self._state.self_id - for mdata in data.get('members', []): + for mdata in guild.get('members', []): member = Member(data=mdata, guild=self, state=self._state) # type: ignore # Members will have the 'user' key in this scenario if cache_joined or member.id == self_id or (cache_voice and member.id in self._voice_states): self._add_member(member) empty_tuple = () - for presence in data.get('presences', []): - user_id = int(presence['user']['id']) - member = self.get_member(user_id) + for presence in guild.get('presences', []): + raw_presence = RawPresenceUpdateEvent(data=presence, state=self._state) + member = self.get_member(raw_presence.user_id) + if member is not None: - member._presence_update(presence, empty_tuple) # type: ignore + member._presence_update(raw_presence, empty_tuple) # type: ignore - if 'threads' in data: - threads = data['threads'] + if 'threads' in guild: + threads = guild['threads'] for thread in threads: self._add_thread(Thread(guild=self, state=self._state, data=thread)) - if 'stage_instances' in data: - for s in data['stage_instances']: + if 'stage_instances' in guild: + for s in guild['stage_instances']: stage_instance = StageInstance(guild=self, data=s, state=self._state) self._stage_instances[stage_instance.id] = stage_instance - if 'guild_scheduled_events' in data: - for s in data['guild_scheduled_events']: + if 'guild_scheduled_events' in guild: + for s in guild['guild_scheduled_events']: scheduled_event = ScheduledEvent(data=s, state=self._state) self._scheduled_events[scheduled_event.id] = scheduled_event + if 'soundboard_sounds' in guild: + for s in guild['soundboard_sounds']: + soundboard_sound = SoundboardSound(guild=self, data=s, state=self._state) + self._add_soundboard_sound(soundboard_sound) + @property def channels(self) -> Sequence[GuildChannel]: """Sequence[:class:`abc.GuildChannel`]: A list of channels that belongs to this guild.""" @@ -737,6 +875,34 @@ class Guild(Hashable): """ return self._threads.get(thread_id) + def get_emoji(self, emoji_id: int, /) -> Optional[Emoji]: + """Returns an emoji with the given ID. + + .. versionadded:: 2.3 + + Parameters + ---------- + emoji_id: int + The ID to search for. + + Returns + -------- + Optional[:class:`Emoji`] + The returned Emoji or ``None`` if not found. + """ + emoji = self._state.get_emoji(emoji_id) + if emoji and emoji.guild == self: + return emoji + return None + + @property + def afk_channel(self) -> Optional[VocalGuildChannel]: + """Optional[Union[:class:`VoiceChannel`, :class:`StageChannel`]]: The channel that denotes the AFK channel. + + If no channel is set, then this returns ``None``. + """ + return self.get_channel(self._afk_channel_id) # type: ignore + @property def system_channel(self) -> Optional[TextChannel]: """Optional[:class:`TextChannel`]: Returns the guild's channel used for system messages. @@ -776,6 +942,29 @@ class Guild(Hashable): channel_id = self._public_updates_channel_id return channel_id and self._channels.get(channel_id) # type: ignore + @property + def safety_alerts_channel(self) -> Optional[TextChannel]: + """Optional[:class:`TextChannel`]: Return's the guild's channel used for safety alerts, if set. + + For example, this is used for the raid protection setting. The guild must have the ``COMMUNITY`` feature. + + .. versionadded:: 2.3 + """ + channel_id = self._safety_alerts_channel_id + return channel_id and self._channels.get(channel_id) # type: ignore + + @property + def widget_channel(self) -> Optional[Union[TextChannel, ForumChannel, VoiceChannel, StageChannel]]: + """Optional[Union[:class:`TextChannel`, :class:`ForumChannel`, :class:`VoiceChannel`, :class:`StageChannel`]]: Returns + the widget channel of the guild. + + If no channel is set, then this returns ``None``. + + .. versionadded:: 2.3 + """ + channel_id = self._widget_channel_id + return channel_id and self._channels.get(channel_id) # type: ignore + @property def emoji_limit(self) -> int: """:class:`int`: The maximum number of emoji slots this guild has.""" @@ -940,6 +1129,37 @@ class Guild(Hashable): """ return self._scheduled_events.get(scheduled_event_id) + @property + def soundboard_sounds(self) -> Sequence[SoundboardSound]: + """Sequence[:class:`SoundboardSound`]: Returns a sequence of the guild's soundboard sounds. + + .. versionadded:: 2.5 + """ + return utils.SequenceProxy(self._soundboard_sounds.values()) + + def get_soundboard_sound(self, sound_id: int, /) -> Optional[SoundboardSound]: + """Returns a soundboard sound with the given ID. + + .. versionadded:: 2.5 + + Parameters + ----------- + sound_id: :class:`int` + The ID to search for. + + Returns + -------- + Optional[:class:`SoundboardSound`] + The soundboard sound or ``None`` if not found. + """ + return self._soundboard_sounds.get(sound_id) + + def _resolve_soundboard_sound(self, id: Optional[int], /) -> Optional[SoundboardSound]: + if id is None: + return + + return self._soundboard_sounds.get(id) + @property def owner(self) -> Optional[Member]: """Optional[:class:`Member`]: The member that owns the guild.""" @@ -1019,15 +1239,13 @@ class Guild(Hashable): def get_member_named(self, name: str, /) -> Optional[Member]: """Returns the first member found that matches the name provided. - The name can have an optional discriminator argument, e.g. "Jake#0001" - or "Jake" will both do the lookup. However the former will give a more - precise result. Note that the discriminator must have all 4 digits - for this to work. + The name is looked up in the following order: - If a nickname is passed, then it is looked up via the nickname. Note - however, that a nickname + discriminator combo will not lookup the nickname - but rather the username + discriminator combo due to nickname + discriminator - not being unique. + - Username#Discriminator (deprecated) + - Username#0 (deprecated, only gets users that migrated from their discriminator) + - Nickname + - Global name + - Username If no member is found, ``None`` is returned. @@ -1035,10 +1253,14 @@ class Guild(Hashable): ``name`` parameter is now positional-only. + .. deprecated:: 2.3 + + Looking up users via discriminator due to Discord API change. + Parameters ----------- name: :class:`str` - The name of the member to lookup with an optional discriminator. + The name of the member to lookup. Returns -------- @@ -1047,22 +1269,19 @@ class Guild(Hashable): then ``None`` is returned. """ - result = None members = self.members - if len(name) > 5 and name[-5] == '#': - # The 5 length is checking to see if #0000 is in the string, - # as a#0000 has a length of 6, the minimum for a potential - # discriminator lookup. - potential_discriminator = name[-4:] - - # do the actual lookup and return if found - # if it isn't found then we'll do a full name lookup below. - result = utils.get(members, name=name[:-5], discriminator=potential_discriminator) - if result is not None: - return result + + username, _, discriminator = name.rpartition('#') + + # If # isn't found then "discriminator" actually has the username + if not username: + discriminator, username = username, discriminator + + if discriminator == '0' or (len(discriminator) == 4 and discriminator.isdigit()): + return utils.find(lambda m: m.name == username and m.discriminator == discriminator, members) def pred(m: Member) -> bool: - return m.nick == name or m.name == name + return m.nick == name or m.global_name == name or m.name == name return utils.find(pred, members) @@ -1071,7 +1290,7 @@ class Guild(Hashable): self, name: str, channel_type: Literal[ChannelType.text], - overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = ..., + overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite] = ..., category: Optional[Snowflake] = ..., **options: Any, ) -> Coroutine[Any, Any, TextChannelPayload]: @@ -1082,7 +1301,7 @@ class Guild(Hashable): self, name: str, channel_type: Literal[ChannelType.voice], - overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = ..., + overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite] = ..., category: Optional[Snowflake] = ..., **options: Any, ) -> Coroutine[Any, Any, VoiceChannelPayload]: @@ -1093,7 +1312,7 @@ class Guild(Hashable): self, name: str, channel_type: Literal[ChannelType.stage_voice], - overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = ..., + overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite] = ..., category: Optional[Snowflake] = ..., **options: Any, ) -> Coroutine[Any, Any, StageChannelPayload]: @@ -1104,7 +1323,7 @@ class Guild(Hashable): self, name: str, channel_type: Literal[ChannelType.category], - overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = ..., + overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite] = ..., category: Optional[Snowflake] = ..., **options: Any, ) -> Coroutine[Any, Any, CategoryChannelPayload]: @@ -1115,7 +1334,7 @@ class Guild(Hashable): self, name: str, channel_type: Literal[ChannelType.news], - overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = ..., + overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite] = ..., category: Optional[Snowflake] = ..., **options: Any, ) -> Coroutine[Any, Any, NewsChannelPayload]: @@ -1126,7 +1345,7 @@ class Guild(Hashable): self, name: str, channel_type: Literal[ChannelType.news, ChannelType.text], - overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = ..., + overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite] = ..., category: Optional[Snowflake] = ..., **options: Any, ) -> Coroutine[Any, Any, Union[TextChannelPayload, NewsChannelPayload]]: @@ -1137,7 +1356,7 @@ class Guild(Hashable): self, name: str, channel_type: Literal[ChannelType.forum], - overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = ..., + overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite] = ..., category: Optional[Snowflake] = ..., **options: Any, ) -> Coroutine[Any, Any, ForumChannelPayload]: @@ -1148,7 +1367,7 @@ class Guild(Hashable): self, name: str, channel_type: ChannelType, - overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = ..., + overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite] = ..., category: Optional[Snowflake] = ..., **options: Any, ) -> Coroutine[Any, Any, GuildChannelPayload]: @@ -1158,7 +1377,7 @@ class Guild(Hashable): self, name: str, channel_type: ChannelType, - overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = MISSING, + overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite] = MISSING, category: Optional[Snowflake] = None, **options: Any, ) -> Coroutine[Any, Any, GuildChannelPayload]: @@ -1198,8 +1417,9 @@ class Guild(Hashable): topic: str = MISSING, slowmode_delay: int = MISSING, nsfw: bool = MISSING, - overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = MISSING, + overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite] = MISSING, default_auto_archive_duration: int = MISSING, + default_thread_slowmode_delay: int = MISSING, ) -> TextChannel: """|coro| @@ -1265,7 +1485,7 @@ class Guild(Hashable): nsfw: :class:`bool` To mark the channel as NSFW or not. news: :class:`bool` - Whether to create the text channel as a news channel. + Whether to create the text channel as a news channel. .. versionadded:: 2.0 default_auto_archive_duration: :class:`int` @@ -1273,6 +1493,10 @@ class Guild(Hashable): Must be one of ``60``, ``1440``, ``4320``, or ``10080``. .. versionadded:: 2.0 + default_thread_slowmode_delay: :class:`int` + The default slowmode delay in seconds for threads created in the text channel. + + .. versionadded:: 2.3 reason: Optional[:class:`str`] The reason for creating this channel. Shows up on the audit log. @@ -1305,7 +1529,10 @@ class Guild(Hashable): options['nsfw'] = nsfw if default_auto_archive_duration is not MISSING: - options["default_auto_archive_duration"] = default_auto_archive_duration + options['default_auto_archive_duration'] = default_auto_archive_duration + + if default_thread_slowmode_delay is not MISSING: + options['default_thread_rate_limit_per_user'] = default_thread_slowmode_delay data = await self._create_channel( name, @@ -1332,7 +1559,8 @@ class Guild(Hashable): user_limit: int = MISSING, rtc_region: Optional[str] = MISSING, video_quality_mode: VideoQualityMode = MISSING, - overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = MISSING, + overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite] = MISSING, + nsfw: bool = MISSING, ) -> VoiceChannel: """|coro| @@ -1370,6 +1598,10 @@ class Guild(Hashable): The camera video quality for the voice channel's participants. .. versionadded:: 2.0 + nsfw: :class:`bool` + To mark the channel as NSFW or not. + + .. versionadded:: 2.6 reason: Optional[:class:`str`] The reason for creating this channel. Shows up on the audit log. @@ -1405,6 +1637,9 @@ class Guild(Hashable): raise TypeError('video_quality_mode must be of type VideoQualityMode') options['video_quality_mode'] = video_quality_mode.value + if nsfw is not MISSING: + options['nsfw'] = nsfw + data = await self._create_channel( name, overwrites=overwrites, channel_type=ChannelType.voice, category=category, reason=reason, **options ) @@ -1425,7 +1660,8 @@ class Guild(Hashable): user_limit: int = MISSING, rtc_region: Optional[str] = MISSING, video_quality_mode: VideoQualityMode = MISSING, - overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = MISSING, + overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite] = MISSING, + nsfw: bool = MISSING, ) -> StageChannel: """|coro| @@ -1469,6 +1705,10 @@ class Guild(Hashable): The camera video quality for the voice channel's participants. .. versionadded:: 2.2 + nsfw: :class:`bool` + To mark the channel as NSFW or not. + + .. versionadded:: 2.6 reason: Optional[:class:`str`] The reason for creating this channel. Shows up on the audit log. @@ -1505,6 +1745,9 @@ class Guild(Hashable): raise TypeError('video_quality_mode must be of type VideoQualityMode') options['video_quality_mode'] = video_quality_mode.value + if nsfw is not MISSING: + options['nsfw'] = nsfw + data = await self._create_channel( name, overwrites=overwrites, channel_type=ChannelType.stage_voice, category=category, reason=reason, **options ) @@ -1518,7 +1761,7 @@ class Guild(Hashable): self, name: str, *, - overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = MISSING, + overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite] = MISSING, reason: Optional[str] = None, position: int = MISSING, ) -> CategoryChannel: @@ -1573,10 +1816,14 @@ class Guild(Hashable): category: Optional[CategoryChannel] = None, slowmode_delay: int = MISSING, nsfw: bool = MISSING, - overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = MISSING, + media: bool = MISSING, + overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite] = MISSING, reason: Optional[str] = None, default_auto_archive_duration: int = MISSING, default_thread_slowmode_delay: int = MISSING, + default_sort_order: ForumOrderType = MISSING, + default_reaction_emoji: EmojiInputType = MISSING, + default_layout: ForumLayoutType = MISSING, available_tags: Sequence[ForumTag] = MISSING, ) -> ForumChannel: """|coro| @@ -1594,6 +1841,10 @@ class Guild(Hashable): ----------- name: :class:`str` The channel's name. + overwrites: Dict[Union[:class:`Role`, :class:`Member`], :class:`PermissionOverwrite`] + A :class:`dict` of target (either a role or a member) to + :class:`PermissionOverwrite` to apply upon creation of a channel. + Useful for creating secret channels. topic: :class:`str` The channel's topic. category: Optional[:class:`CategoryChannel`] @@ -1617,10 +1868,28 @@ class Guild(Hashable): The default slowmode delay in seconds for threads created in this forum. .. versionadded:: 2.1 + default_sort_order: :class:`ForumOrderType` + The default sort order for posts in this forum channel. + + .. versionadded:: 2.3 + default_reaction_emoji: Union[:class:`Emoji`, :class:`PartialEmoji`, :class:`str`] + The default reaction emoji for threads created in this forum to show in the + add reaction button. + + .. versionadded:: 2.3 + default_layout: :class:`ForumLayoutType` + The default layout for posts in this forum. + This cannot be set if ``media`` is set to ``True``. + + .. versionadded:: 2.3 available_tags: Sequence[:class:`ForumTag`] The available tags for this forum channel. .. versionadded:: 2.1 + media: :class:`bool` + Whether to create a media forum channel. + + .. versionadded:: 2.6 Raises ------- @@ -1656,14 +1925,45 @@ class Guild(Hashable): if default_thread_slowmode_delay is not MISSING: options['default_thread_rate_limit_per_user'] = default_thread_slowmode_delay + if default_sort_order is not MISSING: + if not isinstance(default_sort_order, ForumOrderType): + raise TypeError( + f'default_sort_order parameter must be a ForumOrderType not {default_sort_order.__class__.__name__}' + ) + + options['default_sort_order'] = default_sort_order.value + + if default_reaction_emoji is not MISSING: + if isinstance(default_reaction_emoji, _EmojiTag): + options['default_reaction_emoji'] = default_reaction_emoji._to_partial()._to_forum_tag_payload() + elif isinstance(default_reaction_emoji, str): + options['default_reaction_emoji'] = PartialEmoji.from_str(default_reaction_emoji)._to_forum_tag_payload() + else: + raise ValueError(f'default_reaction_emoji parameter must be either Emoji, PartialEmoji, or str') + + if not media and default_layout is not MISSING: + if not isinstance(default_layout, ForumLayoutType): + raise TypeError( + f'default_layout parameter must be a ForumLayoutType not {default_layout.__class__.__name__}' + ) + + options['default_forum_layout'] = default_layout.value + if available_tags is not MISSING: options['available_tags'] = [t.to_dict() for t in available_tags] data = await self._create_channel( - name=name, overwrites=overwrites, channel_type=ChannelType.forum, category=category, reason=reason, **options + name=name, + overwrites=overwrites, + channel_type=ChannelType.forum if not media else ChannelType.media, + category=category, + reason=reason, + **options, ) - channel = ForumChannel(state=self._state, guild=self, data=data) + channel = ForumChannel( + state=self._state, guild=self, data=data # pyright: ignore[reportArgumentType] # it's the correct data + ) # temporarily add to the cache self._channels[channel.id] = channel @@ -1728,6 +2028,13 @@ class Guild(Hashable): premium_progress_bar_enabled: bool = MISSING, discoverable: bool = MISSING, invites_disabled: bool = MISSING, + widget_enabled: bool = MISSING, + widget_channel: Optional[Snowflake] = MISSING, + mfa_level: MFALevel = MISSING, + raid_alerts_disabled: bool = MISSING, + safety_alerts_channel: TextChannel = MISSING, + invites_disabled_until: datetime.datetime = MISSING, + dms_disabled_until: datetime.datetime = MISSING, ) -> Guild: r"""|coro| @@ -1735,12 +2042,6 @@ class Guild(Hashable): You must have :attr:`~Permissions.manage_guild` to edit the guild. - .. versionchanged:: 1.4 - The ``rules_channel`` and ``public_updates_channel`` keyword parameters were added. - - .. versionchanged:: 2.0 - The ``discovery_splash`` and ``community`` keyword parameters were added. - .. versionchanged:: 2.0 The newly updated guild is returned. @@ -1751,15 +2052,6 @@ class Guild(Hashable): This function will now raise :exc:`TypeError` or :exc:`ValueError` instead of ``InvalidArgument``. - .. versionchanged:: 2.0 - The ``preferred_locale`` keyword parameter now accepts an enum instead of :class:`str`. - - .. versionchanged:: 2.0 - The ``premium_progress_bar_enabled`` keyword parameter was added. - - .. versionchanged:: 2.1 - The ``discoverable`` and ``invites_disabled`` keyword parameters were added. - Parameters ---------- name: :class:`str` @@ -1785,9 +2077,13 @@ class Guild(Hashable): Only PNG/JPEG supported. Could be ``None`` to denote removing the splash. This is only available to guilds that contain ``DISCOVERABLE`` in :attr:`Guild.features`. + + .. versionadded:: 2.0 community: :class:`bool` Whether the guild should be a Community guild. If set to ``True``\, both ``rules_channel`` and ``public_updates_channel`` parameters are required. + + .. versionadded:: 2.0 afk_channel: Optional[:class:`VoiceChannel`] The new channel that is the AFK channel. Could be ``None`` for no AFK channel. afk_timeout: :class:`int` @@ -1809,23 +2105,74 @@ class Guild(Hashable): The new system channel settings to use with the new system channel. preferred_locale: :class:`Locale` The new preferred locale for the guild. Used as the primary language in the guild. + + .. versionchanged:: 2.0 + + Now accepts an enum instead of :class:`str`. rules_channel: Optional[:class:`TextChannel`] The new channel that is used for rules. This is only available to guilds that contain ``COMMUNITY`` in :attr:`Guild.features`. Could be ``None`` for no rules channel. + + .. versionadded:: 1.4 public_updates_channel: Optional[:class:`TextChannel`] The new channel that is used for public updates from Discord. This is only available to guilds that contain ``COMMUNITY`` in :attr:`Guild.features`. Could be ``None`` for no public updates channel. + + .. versionadded:: 1.4 premium_progress_bar_enabled: :class:`bool` Whether the premium AKA server boost level progress bar should be enabled for the guild. + + .. versionadded:: 2.0 discoverable: :class:`bool` Whether server discovery is enabled for this guild. + + .. versionadded:: 2.1 invites_disabled: :class:`bool` Whether joining via invites should be disabled for the guild. + + .. versionadded:: 2.1 + widget_enabled: :class:`bool` + Whether to enable the widget for the guild. + + .. versionadded:: 2.3 + widget_channel: Optional[:class:`abc.Snowflake`] + The new widget channel. ``None`` removes the widget channel. + + .. versionadded:: 2.3 + mfa_level: :class:`MFALevel` + The new guild's Multi-Factor Authentication requirement level. + Note that you must be owner of the guild to do this. + + .. versionadded:: 2.3 reason: Optional[:class:`str`] The reason for editing this guild. Shows up on the audit log. + raid_alerts_disabled: :class:`bool` + Whether the alerts for raid protection should be disabled for the guild. + + .. versionadded:: 2.3 + + safety_alerts_channel: Optional[:class:`TextChannel`] + The new channel that is used for safety alerts. This is only available to + guilds that contain ``COMMUNITY`` in :attr:`Guild.features`. Could be ``None`` for no + safety alerts channel. + + .. versionadded:: 2.3 + + invites_disabled_until: Optional[:class:`datetime.datetime`] + The time when invites should be enabled again, or ``None`` to disable the action. + This must be a timezone-aware datetime object. Consider using :func:`utils.utcnow`. + + .. versionadded:: 2.4 + + dms_disabled_until: Optional[:class:`datetime.datetime`] + The time when direct messages should be allowed again, or ``None`` to disable the action. + This must be a timezone-aware datetime object. Consider using :func:`utils.utcnow`. + + .. versionadded:: 2.4 + Raises ------- Forbidden @@ -1837,9 +2184,9 @@ class Guild(Hashable): PNG or JPG. This is also raised if you are not the owner of the guild and request an ownership transfer. TypeError - The type passed to the ``default_notifications``, ``verification_level``, - ``explicit_content_filter``, or ``system_channel_flags`` parameter was - of the incorrect type. + The type passed to the ``default_notifications``, ``rules_channel``, ``public_updates_channel``, + ``safety_alerts_channel`` ``verification_level``, ``explicit_content_filter``, + ``system_channel_flags``, or ``mfa_level`` parameter was of the incorrect type. Returns -------- @@ -1911,14 +2258,33 @@ class Guild(Hashable): if rules_channel is None: fields['rules_channel_id'] = rules_channel else: + if not isinstance(rules_channel, TextChannel): + raise TypeError(f'rules_channel must be of type TextChannel not {rules_channel.__class__.__name__}') + fields['rules_channel_id'] = rules_channel.id if public_updates_channel is not MISSING: if public_updates_channel is None: fields['public_updates_channel_id'] = public_updates_channel else: + if not isinstance(public_updates_channel, TextChannel): + raise TypeError( + f'public_updates_channel must be of type TextChannel not {public_updates_channel.__class__.__name__}' + ) + fields['public_updates_channel_id'] = public_updates_channel.id + if safety_alerts_channel is not MISSING: + if safety_alerts_channel is None: + fields['safety_alerts_channel_id'] = safety_alerts_channel + else: + if not isinstance(safety_alerts_channel, TextChannel): + raise TypeError( + f'safety_alerts_channel must be of type TextChannel not {safety_alerts_channel.__class__.__name__}' + ) + + fields['safety_alerts_channel_id'] = safety_alerts_channel.id + if owner is not MISSING: if self.owner_id != self._state.self_id: raise ValueError('To transfer ownership you must be the owner of the guild.') @@ -1943,7 +2309,7 @@ class Guild(Hashable): fields['system_channel_flags'] = system_channel_flags.value - if any(feat is not MISSING for feat in (community, discoverable, invites_disabled)): + if any(feat is not MISSING for feat in (community, discoverable, invites_disabled, raid_alerts_disabled)): features = set(self.features) if community is not MISSING: @@ -1969,11 +2335,56 @@ class Guild(Hashable): else: features.discard('INVITES_DISABLED') + if raid_alerts_disabled is not MISSING: + if raid_alerts_disabled: + features.add('RAID_ALERTS_DISABLED') + else: + features.discard('RAID_ALERTS_DISABLED') + fields['features'] = list(features) if premium_progress_bar_enabled is not MISSING: fields['premium_progress_bar_enabled'] = premium_progress_bar_enabled + widget_payload: EditWidgetSettings = {} + if widget_channel is not MISSING: + widget_payload['channel_id'] = None if widget_channel is None else widget_channel.id + if widget_enabled is not MISSING: + widget_payload['enabled'] = widget_enabled + + if widget_payload: + await self._state.http.edit_widget(self.id, payload=widget_payload, reason=reason) + + if mfa_level is not MISSING: + if not isinstance(mfa_level, MFALevel): + raise TypeError(f'mfa_level must be of type MFALevel not {mfa_level.__class__.__name__}') + + await http.edit_guild_mfa_level(self.id, mfa_level=mfa_level.value) + + incident_actions_payload: IncidentData = {} + if invites_disabled_until is not MISSING: + if invites_disabled_until is None: + incident_actions_payload['invites_disabled_until'] = None + else: + if invites_disabled_until.tzinfo is None: + raise TypeError( + 'invites_disabled_until must be an aware datetime. Consider using discord.utils.utcnow() or datetime.datetime.now().astimezone() for local time.' + ) + incident_actions_payload['invites_disabled_until'] = invites_disabled_until.isoformat() + + if dms_disabled_until is not MISSING: + if dms_disabled_until is None: + incident_actions_payload['dms_disabled_until'] = None + else: + if dms_disabled_until.tzinfo is None: + raise TypeError( + 'dms_disabled_until must be an aware datetime. Consider using discord.utils.utcnow() or datetime.datetime.now().astimezone() for local time.' + ) + incident_actions_payload['dms_disabled_until'] = dms_disabled_until.isoformat() + + if incident_actions_payload: + await http.edit_incident_actions(self.id, payload=incident_actions_payload) + data = await http.edit_guild(self.id, reason=reason, **fields) return Guild(data=data, state=self._state) @@ -2341,7 +2752,7 @@ class Guild(Hashable): The inactive members are denoted if they have not logged on in ``days`` number of days and they have no roles. - You must have :attr:`~Permissions.kick_members` to do this. + You must have both :attr:`~Permissions.kick_members` and :attr:`~Permissions.manage_guild` to do this. To check how many members you would prune without actually pruning, see the :meth:`estimate_pruned_members` function. @@ -2539,6 +2950,11 @@ class Guild(Hashable): The name of the template. description: :class:`str` The description of the template. + + Returns + -------- + :class:`Template` + The created template. """ from .template import Template @@ -2721,7 +3137,10 @@ class Guild(Hashable): payload['tags'] = emoji data = await self._state.http.create_guild_sticker(self.id, payload, file, reason) - return self._state.store_sticker(self, data) + if self._state.cache_guild_expressions: + return self._state.store_sticker(self, data) + else: + return GuildSticker(state=self._state, data=data) async def delete_sticker(self, sticker: Snowflake, /, *, reason: Optional[str] = None) -> None: """|coro| @@ -2783,7 +3202,7 @@ class Guild(Hashable): Parameters ------------ - id: :class:`int` + scheduled_event_id: :class:`int` The scheduled event ID. with_counts: :class:`bool` Whether to include the number of users that are subscribed to the event. @@ -2804,6 +3223,68 @@ class Guild(Hashable): data = await self._state.http.get_scheduled_event(self.id, scheduled_event_id, with_counts) return ScheduledEvent(state=self._state, data=data) + @overload + async def create_scheduled_event( + self, + *, + name: str, + start_time: datetime.datetime, + entity_type: Literal[EntityType.external] = ..., + privacy_level: PrivacyLevel = ..., + location: str = ..., + end_time: datetime.datetime = ..., + description: str = ..., + image: bytes = ..., + reason: Optional[str] = ..., + ) -> ScheduledEvent: + ... + + @overload + async def create_scheduled_event( + self, + *, + name: str, + start_time: datetime.datetime, + entity_type: Literal[EntityType.stage_instance, EntityType.voice] = ..., + privacy_level: PrivacyLevel = ..., + channel: Snowflake = ..., + end_time: datetime.datetime = ..., + description: str = ..., + image: bytes = ..., + reason: Optional[str] = ..., + ) -> ScheduledEvent: + ... + + @overload + async def create_scheduled_event( + self, + *, + name: str, + start_time: datetime.datetime, + privacy_level: PrivacyLevel = ..., + location: str = ..., + end_time: datetime.datetime = ..., + description: str = ..., + image: bytes = ..., + reason: Optional[str] = ..., + ) -> ScheduledEvent: + ... + + @overload + async def create_scheduled_event( + self, + *, + name: str, + start_time: datetime.datetime, + privacy_level: PrivacyLevel = ..., + channel: Union[VoiceChannel, StageChannel] = ..., + end_time: datetime.datetime = ..., + description: str = ..., + image: bytes = ..., + reason: Optional[str] = ..., + ) -> ScheduledEvent: + ... + async def create_scheduled_event( self, *, @@ -2847,6 +3328,8 @@ class Guild(Hashable): datetime object. Consider using :func:`utils.utcnow`. Required if the entity type is :attr:`EntityType.external`. + privacy_level: :class:`PrivacyLevel` + The privacy level of the scheduled event. entity_type: :class:`EntityType` The entity type of the scheduled event. If the channel is a :class:`StageInstance` or :class:`VoiceChannel` then this is @@ -2894,27 +3377,32 @@ class Guild(Hashable): ) payload['scheduled_start_time'] = start_time.isoformat() + entity_type = entity_type or getattr(channel, '_scheduled_event_entity_type', MISSING) if entity_type is MISSING: - if channel is MISSING: + if channel and isinstance(channel, Object): + if channel.type is VoiceChannel: + entity_type = EntityType.voice + elif channel.type is StageChannel: + entity_type = EntityType.stage_instance + + elif location not in (MISSING, None): entity_type = EntityType.external - else: - _entity_type = getattr(channel, '_scheduled_event_entity_type', MISSING) - if _entity_type is None: - raise TypeError( - 'invalid GuildChannel type passed, must be VoiceChannel or StageChannel ' - f'not {channel.__class__.__name__}' - ) - if _entity_type is MISSING: - raise TypeError('entity_type must be passed in when passing an ambiguous channel type') + else: + if not isinstance(entity_type, EntityType): + raise TypeError('entity_type must be of type EntityType') - entity_type = _entity_type + payload['entity_type'] = entity_type.value - if not isinstance(entity_type, EntityType): - raise TypeError('entity_type must be of type EntityType') + if entity_type is None: + raise TypeError( + 'invalid GuildChannel type passed, must be VoiceChannel or StageChannel ' f'not {channel.__class__.__name__}' + ) - payload['entity_type'] = entity_type.value + if privacy_level is not MISSING: + if not isinstance(privacy_level, PrivacyLevel): + raise TypeError('privacy_level must be of type PrivacyLevel.') - payload['privacy_level'] = PrivacyLevel.guild_only.value + payload['privacy_level'] = privacy_level.value if description is not MISSING: payload['description'] = description @@ -2924,7 +3412,7 @@ class Guild(Hashable): payload['image'] = image_as_str if entity_type in (EntityType.stage_instance, EntityType.voice): - if channel is MISSING or channel is None: + if channel in (MISSING, None): raise TypeError('channel must be set when entity_type is voice or stage_instance') payload['channel_id'] = channel.id @@ -2940,12 +3428,15 @@ class Guild(Hashable): metadata['location'] = location - if end_time is not MISSING: - if end_time.tzinfo is None: - raise ValueError( - 'end_time must be an aware datetime. Consider using discord.utils.utcnow() or datetime.datetime.now().astimezone() for local time.' - ) - payload['scheduled_end_time'] = end_time.isoformat() + if end_time in (MISSING, None): + raise TypeError('end_time must be set when entity_type is external') + + if end_time not in (MISSING, None): + if end_time.tzinfo is None: + raise ValueError( + 'end_time must be an aware datetime. Consider using discord.utils.utcnow() or datetime.datetime.now().astimezone() for local time.' + ) + payload['scheduled_end_time'] = end_time.isoformat() if metadata: payload['entity_metadata'] = metadata @@ -3058,7 +3549,10 @@ class Guild(Hashable): role_ids = [] data = await self._state.http.create_custom_emoji(self.id, name, img, roles=role_ids, reason=reason) - return self._state.store_emoji(self, data) + if self._state.cache_guild_expressions: + return self._state.store_emoji(self, data) + else: + return Emoji(guild=self, state=self._state, data=data) async def delete_emoji(self, emoji: Snowflake, /, *, reason: Optional[str] = None) -> None: """|coro| @@ -3112,6 +3606,37 @@ class Guild(Hashable): data = await self._state.http.get_roles(self.id) return [Role(guild=self, state=self._state, data=d) for d in data] + async def fetch_role(self, role_id: int, /) -> Role: + """|coro| + + Retrieves a :class:`Role` with the specified ID. + + .. versionadded:: 2.5 + + .. note:: + + This method is an API call. For general usage, consider :attr:`get_role` instead. + + Parameters + ---------- + role_id: :class:`int` + The role's ID. + + Raises + ------- + NotFound + The role requested could not be found. + HTTPException + An error occurred fetching the role. + + Returns + ------- + :class:`Role` + The retrieved role. + """ + data = await self._state.http.get_role(self.id, role_id) + return Role(guild=self, state=self._state, data=data) + @overload async def create_role( self, @@ -3237,7 +3762,6 @@ class Guild(Hashable): data = await self._state.http.create_role(self.id, reason=reason, **fields) role = Role(guild=self, data=data, state=self._state) - # TODO: add to cache return role async def edit_role_positions(self, positions: Mapping[Snowflake, int], *, reason: Optional[str] = None) -> List[Role]: @@ -3347,7 +3871,7 @@ class Guild(Hashable): The guild must have ``COMMUNITY`` in :attr:`~Guild.features`. - You must have :attr:`~Permissions.manage_guild` to do this.as well. + You must have :attr:`~Permissions.manage_guild` to do this as well. .. versionadded:: 2.0 @@ -3387,7 +3911,7 @@ class Guild(Hashable): Parameters ----------- user: :class:`abc.Snowflake` - The user to kick from their guild. + The user to kick from the guild. reason: Optional[:class:`str`] The reason the user got kicked. @@ -3419,7 +3943,7 @@ class Guild(Hashable): Parameters ----------- user: :class:`abc.Snowflake` - The user to ban from their guild. + The user to ban from the guild. delete_message_days: :class:`int` The number of days worth of messages to delete from the user in the guild. The minimum is 0 and the maximum is 7. @@ -3488,6 +4012,58 @@ class Guild(Hashable): """ await self._state.http.unban(user.id, self.id, reason=reason) + async def bulk_ban( + self, + users: Iterable[Snowflake], + *, + reason: Optional[str] = None, + delete_message_seconds: int = 86400, + ) -> BulkBanResult: + """|coro| + + Bans multiple users from the guild. + + The users must meet the :class:`abc.Snowflake` abc. + + You must have :attr:`~Permissions.ban_members` and :attr:`~Permissions.manage_guild` to do this. + + .. versionadded:: 2.4 + + Parameters + ----------- + users: Iterable[:class:`abc.Snowflake`] + The users to ban from the guild, up to 200 users. + delete_message_seconds: :class:`int` + The number of seconds worth of messages to delete from the user + in the guild. The minimum is 0 and the maximum is 604800 (7 days). + Defaults to 1 day. + reason: Optional[:class:`str`] + The reason the users got banned. + + Raises + ------- + Forbidden + You do not have the proper permissions to ban. + HTTPException + Banning failed. + + Returns + -------- + :class:`BulkBanResult` + The result of the bulk ban operation. + """ + + response = await self._state.http.bulk_ban( + self.id, + user_ids=[u.id for u in users], + delete_message_seconds=delete_message_seconds, + reason=reason, + ) + return BulkBanResult( + banned=[Object(id=int(user_id), type=User) for user_id in response.get('banned_users', []) or []], + failed=[Object(id=int(user_id), type=User) for user_id in response.get('failed_users', []) or []], + ) + @property def vanity_url(self) -> Optional[str]: """Optional[:class:`str`]: The Discord vanity invite URL for this guild, if available. @@ -3505,7 +4081,7 @@ class Guild(Hashable): The guild must have ``VANITY_URL`` in :attr:`~Guild.features`. - You must have :attr:`~Permissions.manage_guild` to do this.as well. + You must have :attr:`~Permissions.manage_guild` to do this as well. Raises ------- @@ -3606,7 +4182,7 @@ class Guild(Hashable): async def _before_strategy(retrieve: int, before: Optional[Snowflake], limit: Optional[int]): before_id = before.id if before else None data = await self._state.http.get_audit_logs( - self.id, limit=retrieve, user_id=user_id, action_type=action, before=before_id + self.id, limit=retrieve, user_id=user_id, action_type=action_type, before=before_id ) entries = data.get('audit_log_entries', []) @@ -3622,7 +4198,7 @@ class Guild(Hashable): async def _after_strategy(retrieve: int, after: Optional[Snowflake], limit: Optional[int]): after_id = after.id if after else None data = await self._state.http.get_audit_logs( - self.id, limit=retrieve, user_id=user_id, action_type=action, after=after_id + self.id, limit=retrieve, user_id=user_id, action_type=action_type, after=after_id ) entries = data.get('audit_log_entries', []) @@ -3631,7 +4207,7 @@ class Guild(Hashable): if limit is not None: limit -= len(entries) - after = Object(id=int(entries[0]['id'])) + after = Object(id=int(entries[-1]['id'])) return data, entries, after, limit @@ -3640,32 +4216,34 @@ class Guild(Hashable): else: user_id = None - if action: - action = action.value + if action is not MISSING: + action_type: Optional[AuditLogEvent] = action.value + else: + action_type = None if isinstance(before, datetime.datetime): before = Object(id=utils.time_snowflake(before, high=False)) if isinstance(after, datetime.datetime): after = Object(id=utils.time_snowflake(after, high=True)) - if oldest_first is MISSING: - reverse = after is not MISSING - else: - reverse = oldest_first + if oldest_first: + if after is MISSING: + after = OLDEST_OBJECT predicate = None - if reverse: + if oldest_first: strategy, state = _after_strategy, after if before: predicate = lambda m: int(m['id']) < before.id else: strategy, state = _before_strategy, before - if after and after != OLDEST_OBJECT: + if after: predicate = lambda m: int(m['id']) > after.id # avoid circular import from .app_commands import AppCommand + from .webhook import Webhook while True: retrieve = 100 if limit is None else min(limit, 100) @@ -3674,8 +4252,6 @@ class Guild(Hashable): data, raw_entries, state, limit = await strategy(retrieve, state, limit) - if reverse: - raw_entries = reversed(raw_entries) if predicate: raw_entries = filter(predicate, raw_entries) @@ -3694,6 +4270,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): @@ -3707,6 +4286,7 @@ class Guild(Hashable): integrations=integration_map, app_commands=app_command_map, automod_rules=automod_rule_map, + webhooks=webhook_map, guild=self, ) @@ -3748,7 +4328,7 @@ class Guild(Hashable): ) -> None: """|coro| - Edits the widget of the guild. + Edits the widget of the guild. This can also be done with :attr:`~Guild.edit`. You must have :attr:`~Permissions.manage_guild` to do this. @@ -3776,7 +4356,8 @@ class Guild(Hashable): if enabled is not MISSING: payload['enabled'] = enabled - await self._state.http.edit_widget(self.id, payload=payload, reason=reason) + if payload: + await self._state.http.edit_widget(self.id, payload=payload, reason=reason) async def chunk(self, *, cache: bool = True) -> List[Member]: """|coro| @@ -3925,6 +4506,8 @@ class Guild(Hashable): ------- Forbidden You do not have permission to view the automod rule. + NotFound + The automod rule does not exist within this guild. Returns -------- @@ -3968,7 +4551,7 @@ class Guild(Hashable): event_type: AutoModRuleEventType, trigger: AutoModTrigger, actions: List[AutoModRuleAction], - enabled: bool = MISSING, + enabled: bool = False, exempt_roles: Sequence[Snowflake] = MISSING, exempt_channels: Sequence[Snowflake] = MISSING, reason: str = MISSING, @@ -3993,7 +4576,7 @@ class Guild(Hashable): The actions that will be taken when the automod rule is triggered. enabled: :class:`bool` Whether the automod rule is enabled. - Discord will default to ``False``. + Defaults to ``False``. exempt_roles: Sequence[:class:`abc.Snowflake`] A list of roles that will be exempt from the automod rule. exempt_channels: Sequence[:class:`abc.Snowflake`] @@ -4022,23 +4605,221 @@ class Guild(Hashable): actions=[a.to_dict() for a in actions], enabled=enabled, exempt_roles=[str(r.id) for r in exempt_roles] if exempt_roles else None, - exempt_channel=[str(c.id) for c in exempt_channels] if exempt_channels else None, + exempt_channels=[str(c.id) for c in exempt_channels] if exempt_channels else None, reason=reason, ) return AutoModRule(data=data, guild=self, state=self._state) - async def fetch_onboarding(self) -> Optional[Onboarding]: + @property + def invites_paused_until(self) -> Optional[datetime.datetime]: + """Optional[:class:`datetime.datetime`]: If invites are paused, returns when + invites will get enabled in UTC, otherwise returns None. + + .. versionadded:: 2.4 + """ + if not self._incidents_data: + return None + + return utils.parse_time(self._incidents_data.get('invites_disabled_until')) + + @property + def dms_paused_until(self) -> Optional[datetime.datetime]: + """Optional[:class:`datetime.datetime`]: If DMs are paused, returns when DMs + will get enabled in UTC, otherwise returns None. + + .. versionadded:: 2.4 + """ + if not self._incidents_data: + return None + + return utils.parse_time(self._incidents_data.get('dms_disabled_until')) + + @property + def dm_spam_detected_at(self) -> Optional[datetime.datetime]: + """:class:`datetime.datetime`: Returns the time when DM spam was detected in the guild. + + .. versionadded:: 2.5 + """ + if not self._incidents_data: + return None + + return utils.parse_time(self._incidents_data.get('dm_spam_detected_at')) + + @property + def raid_detected_at(self) -> Optional[datetime.datetime]: + """Optional[:class:`datetime.datetime`]: Returns the time when a raid was detected in the guild. + + .. versionadded:: 2.5 + """ + if not self._incidents_data: + return None + + return utils.parse_time(self._incidents_data.get('raid_detected_at')) + + def invites_paused(self) -> bool: + """:class:`bool`: Whether invites are paused in the guild. + + .. versionadded:: 2.4 + """ + if not self.invites_paused_until: + return 'INVITES_DISABLED' in self.features + + return self.invites_paused_until > utils.utcnow() + + def dms_paused(self) -> bool: + """:class:`bool`: Whether DMs are paused in the guild. + + .. versionadded:: 2.4 + """ + if not self.dms_paused_until: + return False + + return self.dms_paused_until > utils.utcnow() + + def is_dm_spam_detected(self) -> bool: + """:class:`bool`: Whether DM spam was detected in the guild. + + .. versionadded:: 2.5 + """ + if not self.dm_spam_detected_at: + return False + + return self.dm_spam_detected_at > utils.utcnow() + + def is_raid_detected(self) -> bool: + """:class:`bool`: Whether a raid was detected in the guild. + + .. versionadded:: 2.5 + """ + if not self.raid_detected_at: + return False + + return self.raid_detected_at > utils.utcnow() + + async def fetch_soundboard_sound(self, sound_id: int, /) -> SoundboardSound: + """|coro| + + Retrieves a :class:`SoundboardSound` with the specified ID. + + .. versionadded:: 2.5 + + .. note:: + + Using this, in order to receive :attr:`SoundboardSound.user`, you must have :attr:`~Permissions.create_expressions` + or :attr:`~Permissions.manage_expressions`. + + .. note:: + + This method is an API call. For general usage, consider :attr:`get_soundboard_sound` instead. + + Raises + ------- + NotFound + The sound requested could not be found. + HTTPException + Retrieving the sound failed. + + Returns + -------- + :class:`SoundboardSound` + The retrieved sound. + """ + data = await self._state.http.get_soundboard_sound(self.id, sound_id) + return SoundboardSound(guild=self, state=self._state, data=data) + + async def fetch_soundboard_sounds(self) -> List[SoundboardSound]: """|coro| - Fetches the onboarding information for this guild. + Retrieves a list of all soundboard sounds for the guild. - .. versionadded:: 2.2 + .. versionadded:: 2.5 + + .. note:: + + Using this, in order to receive :attr:`SoundboardSound.user`, you must have :attr:`~Permissions.create_expressions` + or :attr:`~Permissions.manage_expressions`. + + .. note:: + + This method is an API call. For general usage, consider :attr:`soundboard_sounds` instead. + + Raises + ------- + HTTPException + Retrieving the sounds failed. Returns -------- - Optional[:class:`Onboarding`] - The onboarding information for this guild. + List[:class:`SoundboardSound`] + The retrieved soundboard sounds. + """ + data = await self._state.http.get_soundboard_sounds(self.id) + return [SoundboardSound(guild=self, state=self._state, data=sound) for sound in data['items']] + + async def create_soundboard_sound( + self, + *, + name: str, + sound: bytes, + volume: float = 1, + emoji: Optional[EmojiInputType] = None, + reason: Optional[str] = None, + ) -> SoundboardSound: + """|coro| + + Creates a :class:`SoundboardSound` for the guild. + You must have :attr:`Permissions.create_expressions` to do this. + + .. versionadded:: 2.5 + + Parameters + ---------- + name: :class:`str` + The name of the sound. Must be between 2 and 32 characters. + sound: :class:`bytes` + The :term:`py:bytes-like object` representing the sound data. + Only MP3 and OGG sound files that don't exceed the duration of 5.2s are supported. + volume: :class:`float` + The volume of the sound. Must be between 0 and 1. Defaults to ``1``. + emoji: Optional[Union[:class:`Emoji`, :class:`PartialEmoji`, :class:`str`]] + The emoji of the sound. + reason: Optional[:class:`str`] + The reason for creating the sound. Shows up on the audit log. + + Raises + ------- + Forbidden + You do not have permissions to create a soundboard sound. + HTTPException + Creating the soundboard sound failed. + + Returns + ------- + :class:`SoundboardSound` + The newly created soundboard sound. """ - data = await self._state.http.get_guild_onboarding(self.id) - return Onboarding(guild=self, data=data) + payload: Dict[str, Any] = { + 'name': name, + 'sound': utils._bytes_to_base64_data(sound, audio=True), + 'volume': volume, + 'emoji_id': None, + 'emoji_name': None, + } + + if emoji is not None: + if isinstance(emoji, _EmojiTag): + partial_emoji = emoji._to_partial() + elif isinstance(emoji, str): + partial_emoji = PartialEmoji.from_str(emoji) + else: + partial_emoji = None + + if partial_emoji is not None: + if partial_emoji.id is None: + payload['emoji_name'] = partial_emoji.name + else: + payload['emoji_id'] = partial_emoji.id + + data = await self._state.http.create_soundboard_sound(self.id, reason=reason, **payload) + return SoundboardSound(guild=self, state=self._state, data=data) diff --git a/discord/http.py b/discord/http.py index d89fed131..71912f71b 100644 --- a/discord/http.py +++ b/discord/http.py @@ -67,7 +67,7 @@ if TYPE_CHECKING: from .embeds import Embed from .message import Attachment from .flags import MessageFlags - from .enums import AuditLogAction + from .poll import Poll from .types import ( appinfo, @@ -90,9 +90,14 @@ if TYPE_CHECKING: scheduled_event, sticker, welcome_screen, - onboarding, + sku, + poll, + voice, + soundboard, + subscription, ) from .types.snowflake import Snowflake, SnowflakeList + from .types.gateway import SessionStartLimit from types import TracebackType @@ -153,6 +158,8 @@ def handle_message_parameters( mention_author: Optional[bool] = None, thread_name: str = MISSING, channel_payload: Dict[str, Any] = MISSING, + applied_tags: Optional[SnowflakeList] = MISSING, + poll: Optional[Poll] = MISSING, ) -> MultipartParameters: if files is not MISSING and file is not MISSING: raise TypeError('Cannot mix file and files keyword arguments.') @@ -191,6 +198,7 @@ def handle_message_parameters( if nonce is not None: payload['nonce'] = str(nonce) + payload['enforce_nonce'] = True if message_reference is not MISSING: payload['message_reference'] = message_reference @@ -243,12 +251,21 @@ def handle_message_parameters( payload['attachments'] = attachments_payload + if applied_tags is not MISSING: + if applied_tags is not None: + payload['applied_tags'] = applied_tags + else: + payload['applied_tags'] = [] + if channel_payload is not MISSING: payload = { 'message': payload, } payload.update(channel_payload) + if poll not in (MISSING, None): + payload['poll'] = poll._to_dict() # type: ignore + multipart = [] if files: multipart.append({'name': 'payload_json', 'value': utils._to_json(payload)}) @@ -293,7 +310,7 @@ class Route: self.metadata: Optional[str] = metadata url = self.BASE + self.path if parameters: - url = url.format_map({k: _uriquote(v) if isinstance(v, str) else v for k, v in parameters.items()}) + url = url.format_map({k: _uriquote(v, safe='') if isinstance(v, str) else v for k, v in parameters.items()}) self.url: str = url # major parameters: @@ -444,7 +461,12 @@ class Ratelimit: future = self._loop.create_future() self._pending_requests.append(future) try: - await future + while not future.done(): + # 30 matches the smallest allowed max_ratelimit_timeout + max_wait_time = self.expires - self._loop.time() if self.expires else 30 + await asyncio.wait([future], timeout=max_wait_time) + if not future.done(): + await self._refresh() except: future.cancel() if self.remaining > 0 and not future.cancelled(): @@ -651,14 +673,13 @@ class HTTPClient: _log.debug(fmt, route_key, bucket_hash, discord_hash) self._bucket_hashes[route_key] = discord_hash - recalculated_key = discord_hash + route.major_parameters - self._buckets[recalculated_key] = ratelimit + self._buckets[f'{discord_hash}:{route.major_parameters}'] = ratelimit self._buckets.pop(key, None) elif route_key not in self._bucket_hashes: fmt = '%s has found its initial rate limit bucket hash (%s).' _log.debug(fmt, route_key, discord_hash) self._bucket_hashes[route_key] = discord_hash - self._buckets[discord_hash + route.major_parameters] = ratelimit + self._buckets[f'{discord_hash}:{route.major_parameters}'] = ratelimit if has_ratelimit_headers: if response.status != 429: @@ -762,7 +783,15 @@ class HTTPClient: raise RuntimeError('Unreachable code in HTTP handling') async def get_from_cdn(self, url: str) -> bytes: - async with self.__session.get(url) as resp: + kwargs = {} + + # Proxy support + if self.proxy is not None: + kwargs['proxy'] = self.proxy + if self.proxy_auth is not None: + kwargs['proxy_auth'] = self.proxy_auth + + async with self.__session.get(url, **kwargs) as resp: if resp.status == 200: return await resp.read() elif resp.status == 404: @@ -791,6 +820,7 @@ class HTTPClient: connector=self.connector, ws_response_class=DiscordClientWebSocketResponse, trace_configs=None if self.http_trace is None else [self.http_trace], + cookie_jar=aiohttp.DummyCookieJar(), ) self._global_over = asyncio.Event() self._global_over.set() @@ -927,6 +957,7 @@ class HTTPClient: emoji: str, limit: int, after: Optional[Snowflake] = None, + type: Optional[message.ReactionType] = None, ) -> Response[List[user.User]]: r = Route( 'GET', @@ -941,6 +972,10 @@ class HTTPClient: } if after: params['after'] = after + + if type is not None: + params['type'] = type + return self.request(r, params=params) def clear_reactions(self, channel_id: Snowflake, message_id: Snowflake) -> Response[None]: @@ -1047,6 +1082,20 @@ class HTTPClient: r = Route('DELETE', '/guilds/{guild_id}/bans/{user_id}', guild_id=guild_id, user_id=user_id) return self.request(r, reason=reason) + def bulk_ban( + self, + guild_id: Snowflake, + user_ids: List[Snowflake], + delete_message_seconds: int = 86400, + reason: Optional[str] = None, + ) -> Response[guild.BulkBanUserResponse]: + r = Route('POST', '/guilds/{guild_id}/bulk-ban', guild_id=guild_id) + payload = { + 'user_ids': user_ids, + 'delete_message_seconds': delete_message_seconds, + } + return self.request(r, json=payload, reason=reason) + def guild_voice_state( self, user_id: Snowflake, @@ -1115,6 +1164,12 @@ class HTTPClient: r = Route('PATCH', '/guilds/{guild_id}/members/{user_id}', guild_id=guild_id, user_id=user_id) return self.request(r, json=fields, reason=reason) + def get_my_voice_state(self, guild_id: Snowflake) -> Response[voice.GuildVoiceState]: + return self.request(Route('GET', '/guilds/{guild_id}/voice-states/@me', guild_id=guild_id)) + + def get_voice_state(self, guild_id: Snowflake, user_id: Snowflake) -> Response[voice.GuildVoiceState]: + return self.request(Route('GET', '/guilds/{guild_id}/voice-states/{user_id}', guild_id=guild_id, user_id=user_id)) + # Channel management def edit_channel( @@ -1149,11 +1204,19 @@ class HTTPClient: 'available_tags', 'applied_tags', 'default_forum_layout', + 'default_sort_order', ) payload = {k: v for k, v in options.items() if k in valid_keys} return self.request(r, reason=reason, json=payload) + def edit_voice_channel_status( + self, status: Optional[str], *, channel_id: int, reason: Optional[str] = None + ) -> Response[None]: + r = Route('PUT', '/channels/{channel_id}/voice-status', channel_id=channel_id) + payload = {'status': status} + return self.request(r, reason=reason, json=payload) + def bulk_channel_update( self, guild_id: Snowflake, @@ -1190,6 +1253,9 @@ class HTTPClient: 'video_quality_mode', 'default_auto_archive_duration', 'default_thread_rate_limit_per_user', + 'default_sort_order', + 'default_reaction_emoji', + 'default_forum_layout', 'available_tags', ) payload.update({k: v for k, v in options.items() if k in valid_keys and v is not None}) @@ -1370,9 +1436,11 @@ class HTTPClient: limit: int, before: Optional[Snowflake] = None, after: Optional[Snowflake] = None, + with_counts: bool = True, ) -> Response[List[guild.Guild]]: params: Dict[str, Any] = { 'limit': limit, + 'with_counts': int(with_counts), } if before: @@ -1389,6 +1457,9 @@ class HTTPClient: params = {'with_counts': int(with_counts)} return self.request(Route('GET', '/guilds/{guild_id}', guild_id=guild_id), params=params) + def get_guild_preview(self, guild_id: Snowflake) -> Response[guild.GuildPreview]: + return self.request(Route('GET', '/guilds/{guild_id}/preview', guild_id=guild_id)) + def delete_guild(self, guild_id: Snowflake) -> Response[None]: return self.request(Route('DELETE', '/guilds/{guild_id}', guild_id=guild_id)) @@ -1423,12 +1494,19 @@ class HTTPClient: 'public_updates_channel_id', 'preferred_locale', 'premium_progress_bar_enabled', + 'safety_alerts_channel_id', ) payload = {k: v for k, v in fields.items() if k in valid_keys} return self.request(Route('PATCH', '/guilds/{guild_id}', guild_id=guild_id), json=payload, reason=reason) + def edit_guild_mfa_level( + self, guild_id: Snowflake, *, mfa_level: int, reason: Optional[str] = None + ) -> Response[guild.GuildMFALevel]: + payload = {'level': mfa_level} + return self.request(Route('POST', '/guilds/{guild_id}/mfa', guild_id=guild_id), json=payload, reason=reason) + def get_template(self, code: str) -> Response[template.Template]: return self.request(Route('GET', '/guilds/templates/{code}', code=code)) @@ -1558,6 +1636,9 @@ class HTTPClient: 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, sticker_pack_id: Snowflake) -> Response[sticker.StickerPack]: + return self.request(Route('GET', '/sticker-packs/{sticker_pack_id}', sticker_pack_id=sticker_pack_id)) + def list_premium_sticker_packs(self) -> Response[sticker.ListPremiumStickerPacks]: return self.request(Route('GET', '/sticker-packs')) @@ -1713,12 +1794,12 @@ class HTTPClient: before: Optional[Snowflake] = None, after: Optional[Snowflake] = None, user_id: Optional[Snowflake] = None, - action_type: Optional[AuditLogAction] = None, + action_type: Optional[audit_log.AuditLogEvent] = None, ) -> Response[audit_log.AuditLog]: params: Dict[str, Any] = {'limit': limit} if before: params['before'] = before - if after: + if after is not None: params['after'] = after if user_id: params['user_id'] = user_id @@ -1736,8 +1817,8 @@ class HTTPClient: ) -> Response[widget.WidgetSettings]: return self.request(Route('PATCH', '/guilds/{guild_id}/widget', guild_id=guild_id), json=payload, reason=reason) - def get_guild_onboarding(self, guild_id: Snowflake) -> Response[onboarding.Onboarding]: - return self.request(Route('GET', '/guilds/{guild_id}/onboarding', guild_id=guild_id)) + def edit_incident_actions(self, guild_id: Snowflake, payload: guild.IncidentData) -> Response[guild.IncidentData]: + return self.request(Route('PUT', '/guilds/{guild_id}/incident-actions', guild_id=guild_id), json=payload) # Invite management @@ -1753,6 +1834,7 @@ class HTTPClient: target_type: Optional[invite.InviteTargetType] = None, target_user_id: Optional[Snowflake] = None, target_application_id: Optional[Snowflake] = None, + flags: Optional[int] = None, ) -> Response[invite.Invite]: r = Route('POST', '/channels/{channel_id}/invites', channel_id=channel_id) payload = { @@ -1771,6 +1853,9 @@ class HTTPClient: if target_application_id: payload['target_application_id'] = str(target_application_id) + if flags: + payload['flags'] = flags + return self.request(r, reason=reason, json=payload) def get_invite( @@ -1797,7 +1882,7 @@ class HTTPClient: def invites_from_channel(self, channel_id: Snowflake) -> Response[List[invite.Invite]]: return self.request(Route('GET', '/channels/{channel_id}/invites', channel_id=channel_id)) - def delete_invite(self, invite_id: str, *, reason: Optional[str] = None) -> Response[None]: + def delete_invite(self, invite_id: str, *, reason: Optional[str] = None) -> Response[invite.Invite]: return self.request(Route('DELETE', '/invites/{invite_id}', invite_id=invite_id), reason=reason) # Role management @@ -1805,6 +1890,9 @@ class HTTPClient: def get_roles(self, guild_id: Snowflake) -> Response[List[role.Role]]: return self.request(Route('GET', '/guilds/{guild_id}/roles', guild_id=guild_id)) + def get_role(self, guild_id: Snowflake, role_id: Snowflake) -> Response[role.Role]: + return self.request(Route('GET', '/guilds/{guild_id}/roles/{role_id}', guild_id=guild_id, role_id=role_id)) + def edit_role( self, guild_id: Snowflake, role_id: Snowflake, *, reason: Optional[str] = None, **fields: Any ) -> Response[role.Role]: @@ -1907,6 +1995,8 @@ class HTTPClient: 'channel_id', 'topic', 'privacy_level', + 'send_start_notification', + 'guild_scheduled_event_id', ) payload = {k: v for k, v in payload.items() if k in valid_keys} @@ -2363,33 +2453,329 @@ class HTTPClient: reason=reason, ) - # Misc + # SKU + + def get_skus(self, application_id: Snowflake) -> Response[List[sku.SKU]]: + return self.request(Route('GET', '/applications/{application_id}/skus', application_id=application_id)) + + def get_entitlements( + self, + application_id: Snowflake, + user_id: Optional[Snowflake] = None, + sku_ids: Optional[SnowflakeList] = None, + before: Optional[Snowflake] = None, + after: Optional[Snowflake] = None, + limit: Optional[int] = None, + guild_id: Optional[Snowflake] = None, + exclude_ended: Optional[bool] = None, + exclude_deleted: Optional[bool] = None, + ) -> Response[List[sku.Entitlement]]: + params: Dict[str, Any] = {} + + if user_id is not None: + params['user_id'] = user_id + if sku_ids is not None: + params['sku_ids'] = ','.join(map(str, sku_ids)) + if before is not None: + params['before'] = before + if after is not None: + params['after'] = after + if limit is not None: + params['limit'] = limit + if guild_id is not None: + params['guild_id'] = guild_id + if exclude_ended is not None: + params['exclude_ended'] = int(exclude_ended) + if exclude_deleted is not None: + params['exclude_deleted'] = int(exclude_deleted) + + return self.request( + Route('GET', '/applications/{application_id}/entitlements', application_id=application_id), params=params + ) + + def get_entitlement(self, application_id: Snowflake, entitlement_id: Snowflake) -> Response[sku.Entitlement]: + return self.request( + Route( + 'GET', + '/applications/{application_id}/entitlements/{entitlement_id}', + application_id=application_id, + entitlement_id=entitlement_id, + ), + ) + + def consume_entitlement(self, application_id: Snowflake, entitlement_id: Snowflake) -> Response[None]: + return self.request( + Route( + 'POST', + '/applications/{application_id}/entitlements/{entitlement_id}/consume', + application_id=application_id, + entitlement_id=entitlement_id, + ), + ) + + def create_entitlement( + self, application_id: Snowflake, sku_id: Snowflake, owner_id: Snowflake, owner_type: sku.EntitlementOwnerType + ) -> Response[sku.Entitlement]: + payload = { + 'sku_id': sku_id, + 'owner_id': owner_id, + 'owner_type': owner_type, + } + + return self.request( + Route( + 'POST', + '/applications/{application_id}/entitlements', + application_id=application_id, + ), + json=payload, + ) + + def delete_entitlement(self, application_id: Snowflake, entitlement_id: Snowflake) -> Response[None]: + return self.request( + Route( + 'DELETE', + '/applications/{application_id}/entitlements/{entitlement_id}', + application_id=application_id, + entitlement_id=entitlement_id, + ), + ) + + # Soundboard + + def get_soundboard_default_sounds(self) -> Response[List[soundboard.SoundboardDefaultSound]]: + return self.request(Route('GET', '/soundboard-default-sounds')) + + def get_soundboard_sound(self, guild_id: Snowflake, sound_id: Snowflake) -> Response[soundboard.SoundboardSound]: + return self.request( + Route('GET', '/guilds/{guild_id}/soundboard-sounds/{sound_id}', guild_id=guild_id, sound_id=sound_id) + ) + + def get_soundboard_sounds(self, guild_id: Snowflake) -> Response[Dict[str, List[soundboard.SoundboardSound]]]: + return self.request(Route('GET', '/guilds/{guild_id}/soundboard-sounds', guild_id=guild_id)) + + def create_soundboard_sound( + self, guild_id: Snowflake, *, reason: Optional[str], **payload: Any + ) -> Response[soundboard.SoundboardSound]: + valid_keys = ( + 'name', + 'sound', + 'volume', + 'emoji_id', + 'emoji_name', + ) + + payload = {k: v for k, v in payload.items() if k in valid_keys and v is not None} + + return self.request( + Route('POST', '/guilds/{guild_id}/soundboard-sounds', guild_id=guild_id), json=payload, reason=reason + ) + + def edit_soundboard_sound( + self, guild_id: Snowflake, sound_id: Snowflake, *, reason: Optional[str], **payload: Any + ) -> Response[soundboard.SoundboardSound]: + valid_keys = ( + 'name', + 'volume', + 'emoji_id', + 'emoji_name', + ) + + payload = {k: v for k, v in payload.items() if k in valid_keys} + + return self.request( + Route( + 'PATCH', + '/guilds/{guild_id}/soundboard-sounds/{sound_id}', + guild_id=guild_id, + sound_id=sound_id, + ), + json=payload, + reason=reason, + ) + + def delete_soundboard_sound(self, guild_id: Snowflake, sound_id: Snowflake, *, reason: Optional[str]) -> Response[None]: + return self.request( + Route( + 'DELETE', + '/guilds/{guild_id}/soundboard-sounds/{sound_id}', + guild_id=guild_id, + sound_id=sound_id, + ), + reason=reason, + ) + + def send_soundboard_sound(self, channel_id: Snowflake, **payload: Any) -> Response[None]: + valid_keys = ('sound_id', 'source_guild_id') + payload = {k: v for k, v in payload.items() if k in valid_keys} + return self.request( + (Route('POST', '/channels/{channel_id}/send-soundboard-sound', channel_id=channel_id)), json=payload + ) + + # Application def application_info(self) -> Response[appinfo.AppInfo]: return self.request(Route('GET', '/oauth2/applications/@me')) - async def get_gateway(self, *, encoding: str = 'json', zlib: bool = True) -> str: - try: - data = await self.request(Route('GET', '/gateway')) - except HTTPException as exc: - raise GatewayNotFound() from exc - if zlib: - value = '{0}?encoding={1}&v={2}&compress=zlib-stream' - else: - value = '{0}?encoding={1}&v={2}' - return value.format(data['url'], encoding, INTERNAL_API_VERSION) + def edit_application_info(self, *, reason: Optional[str], payload: Any) -> Response[appinfo.AppInfo]: + valid_keys = ( + 'custom_install_url', + 'description', + 'role_connections_verification_url', + 'install_params', + 'flags', + 'icon', + 'cover_image', + 'interactions_endpoint_url ', + 'tags', + 'integration_types_config', + ) + + payload = {k: v for k, v in payload.items() if k in valid_keys} + return self.request(Route('PATCH', '/applications/@me'), json=payload, reason=reason) + + def get_application_emojis(self, application_id: Snowflake) -> Response[appinfo.ListAppEmojis]: + return self.request(Route('GET', '/applications/{application_id}/emojis', application_id=application_id)) + + def get_application_emoji(self, application_id: Snowflake, emoji_id: Snowflake) -> Response[emoji.Emoji]: + return self.request( + Route( + 'GET', '/applications/{application_id}/emojis/{emoji_id}', application_id=application_id, emoji_id=emoji_id + ) + ) + + def create_application_emoji( + self, + application_id: Snowflake, + name: str, + image: str, + ) -> Response[emoji.Emoji]: + payload = { + 'name': name, + 'image': image, + } + + return self.request( + Route('POST', '/applications/{application_id}/emojis', application_id=application_id), json=payload + ) + + def edit_application_emoji( + self, + application_id: Snowflake, + emoji_id: Snowflake, + *, + payload: Dict[str, Any], + ) -> Response[emoji.Emoji]: + r = Route( + 'PATCH', '/applications/{application_id}/emojis/{emoji_id}', application_id=application_id, emoji_id=emoji_id + ) + return self.request(r, json=payload) + + def delete_application_emoji( + self, + application_id: Snowflake, + emoji_id: Snowflake, + ) -> Response[None]: + return self.request( + Route( + 'DELETE', + '/applications/{application_id}/emojis/{emoji_id}', + application_id=application_id, + emoji_id=emoji_id, + ) + ) + + # Poll - async def get_bot_gateway(self, *, encoding: str = 'json', zlib: bool = True) -> Tuple[int, str]: + def get_poll_answer_voters( + self, + channel_id: Snowflake, + message_id: Snowflake, + answer_id: Snowflake, + after: Optional[Snowflake] = None, + limit: Optional[int] = None, + ) -> Response[poll.PollAnswerVoters]: + params = {} + + if after: + params['after'] = int(after) + + if limit is not None: + params['limit'] = limit + + return self.request( + Route( + 'GET', + '/channels/{channel_id}/polls/{message_id}/answers/{answer_id}', + channel_id=channel_id, + message_id=message_id, + answer_id=answer_id, + ), + params=params, + ) + + def end_poll(self, channel_id: Snowflake, message_id: Snowflake) -> Response[message.Message]: + return self.request( + Route( + 'POST', + '/channels/{channel_id}/polls/{message_id}/expire', + channel_id=channel_id, + message_id=message_id, + ) + ) + + # Subscriptions + + def list_sku_subscriptions( + self, + sku_id: Snowflake, + before: Optional[Snowflake] = None, + after: Optional[Snowflake] = None, + limit: Optional[int] = None, + user_id: Optional[Snowflake] = None, + ) -> Response[List[subscription.Subscription]]: + params = {} + + if before is not None: + params['before'] = before + + if after is not None: + params['after'] = after + + if limit is not None: + params['limit'] = limit + + if user_id is not None: + params['user_id'] = user_id + + return self.request( + Route( + 'GET', + '/skus/{sku_id}/subscriptions', + sku_id=sku_id, + ), + params=params, + ) + + def get_sku_subscription(self, sku_id: Snowflake, subscription_id: Snowflake) -> Response[subscription.Subscription]: + return self.request( + Route( + 'GET', + '/skus/{sku_id}/subscriptions/{subscription_id}', + sku_id=sku_id, + subscription_id=subscription_id, + ) + ) + + # Misc + + async def get_bot_gateway(self) -> Tuple[int, str, SessionStartLimit]: try: data = await self.request(Route('GET', '/gateway/bot')) except HTTPException as exc: raise GatewayNotFound() from exc - if zlib: - value = '{0}?encoding={1}&v={2}&compress=zlib-stream' - else: - value = '{0}?encoding={1}&v={2}' - return data['shards'], value.format(data['url'], encoding, INTERNAL_API_VERSION) + return data['shards'], data['url'], data['session_start_limit'] def get_user(self, user_id: Snowflake) -> Response[user.User]: return self.request(Route('GET', '/users/{user_id}', user_id=user_id)) diff --git a/discord/interactions.py b/discord/interactions.py index d2b900d3d..82b35e392 100644 --- a/discord/interactions.py +++ b/discord/interactions.py @@ -25,7 +25,9 @@ DEALINGS IN THE SOFTWARE. """ from __future__ import annotations -from typing import Any, Dict, Optional, Generic, TYPE_CHECKING, Sequence, Tuple, Union + +import logging +from typing import Any, Dict, Optional, Generic, TYPE_CHECKING, Sequence, Tuple, Union, List import asyncio import datetime @@ -33,8 +35,9 @@ from . import utils from .enums import try_enum, Locale, InteractionType, InteractionResponseType from .errors import InteractionResponded, HTTPException, ClientException, DiscordException from .flags import MessageFlags -from .channel import PartialMessageable, ChannelType +from .channel import ChannelType from ._types import ClientT +from .sku import Entitlement from .user import User from .member import Member @@ -42,13 +45,17 @@ from .message import Message, Attachment from .permissions import Permissions from .http import handle_message_parameters from .webhook.async_ import async_context, Webhook, interaction_response_params, interaction_message_response_params +from .app_commands.installs import AppCommandContext from .app_commands.namespace import Namespace from .app_commands.translator import locale_str, TranslationContext, TranslationContextLocation +from .channel import _threaded_channel_factory __all__ = ( 'Interaction', 'InteractionMessage', 'InteractionResponse', + 'InteractionCallbackResponse', + 'InteractionCallbackActivityInstance', ) if TYPE_CHECKING: @@ -56,10 +63,13 @@ if TYPE_CHECKING: Interaction as InteractionPayload, InteractionData, ApplicationCommandInteractionData, + InteractionCallback as InteractionCallbackPayload, + InteractionCallbackActivity as InteractionCallbackActivityPayload, ) from .types.webhook import ( Webhook as WebhookPayload, ) + from .types.snowflake import Snowflake from .guild import Guild from .state import ConnectionState from .file import File @@ -69,12 +79,24 @@ if TYPE_CHECKING: from .ui.view import View from .app_commands.models import Choice, ChoiceT from .ui.modal import Modal - from .channel import VoiceChannel, StageChannel, TextChannel, ForumChannel, CategoryChannel + from .channel import VoiceChannel, StageChannel, TextChannel, ForumChannel, CategoryChannel, DMChannel, GroupChannel from .threads import Thread from .app_commands.commands import Command, ContextMenu + from .poll import Poll InteractionChannel = Union[ - VoiceChannel, StageChannel, TextChannel, ForumChannel, CategoryChannel, Thread, PartialMessageable + VoiceChannel, + StageChannel, + TextChannel, + ForumChannel, + CategoryChannel, + Thread, + DMChannel, + GroupChannel, + ] + InteractionCallbackResource = Union[ + "InteractionMessage", + "InteractionCallbackActivityInstance", ] MISSING: Any = utils.MISSING @@ -96,8 +118,14 @@ class Interaction(Generic[ClientT]): The interaction type. guild_id: Optional[:class:`int`] The guild ID the interaction was sent from. - channel_id: Optional[:class:`int`] - The channel ID the interaction was sent from. + channel: Optional[Union[:class:`abc.GuildChannel`, :class:`abc.PrivateChannel`, :class:`Thread`]] + The channel the interaction was sent from. + + Note that due to a Discord limitation, if sent from a DM channel :attr:`~DMChannel.recipient` is ``None``. + entitlement_sku_ids: List[:class:`int`] + The entitlement SKU IDs that the user has. + entitlements: List[:class:`Entitlement`] + The entitlements that the guild or user has. application_id: :class:`int` The application ID that the interaction was for. user: Union[:class:`User`, :class:`Member`] @@ -122,13 +150,20 @@ class Interaction(Generic[ClientT]): command_failed: :class:`bool` Whether the command associated with this interaction failed to execute. This includes checks and execution. + context: :class:`.AppCommandContext` + The context of the interaction. + + .. versionadded:: 2.4 + filesize_limit: int + The maximum number of bytes a file can have when responding to this interaction. + + .. versionadded:: 2.6 """ __slots__: Tuple[str, ...] = ( 'id', 'type', 'guild_id', - 'channel_id', 'data', 'application_id', 'message', @@ -139,6 +174,11 @@ class Interaction(Generic[ClientT]): 'guild_locale', 'extras', 'command_failed', + 'entitlement_sku_ids', + 'entitlements', + 'context', + 'filesize_limit', + '_integration_owners', '_permissions', '_app_permissions', '_state', @@ -148,7 +188,7 @@ class Interaction(Generic[ClientT]): '_original_response', '_cs_response', '_cs_followup', - '_cs_channel', + 'channel', '_cs_namespace', '_cs_command', ) @@ -165,23 +205,61 @@ class Interaction(Generic[ClientT]): self.command_failed: bool = False self._from_data(data) + def __repr__(self) -> str: + return f'<{self.__class__.__name__} id={self.id} type={self.type!r} guild_id={self.guild_id!r} user={self.user!r}>' + def _from_data(self, data: InteractionPayload): self.id: int = int(data['id']) self.type: InteractionType = try_enum(InteractionType, data['type']) self.data: Optional[InteractionData] = data.get('data') self.token: str = data['token'] self.version: int = data['version'] - self.channel_id: Optional[int] = utils._get_as_snowflake(data, 'channel_id') self.guild_id: Optional[int] = utils._get_as_snowflake(data, 'guild_id') + self.channel: Optional[InteractionChannel] = None self.application_id: int = int(data['application_id']) + self.entitlement_sku_ids: List[int] = [int(x) for x in data.get('entitlement_skus', []) or []] + self.entitlements: List[Entitlement] = [Entitlement(self._state, x) for x in data.get('entitlements', [])] + self.filesize_limit: int = data['attachment_size_limit'] + # This is not entirely useful currently, unsure how to expose it in a way that it is. + self._integration_owners: Dict[int, Snowflake] = { + int(k): int(v) for k, v in data.get('authorizing_integration_owners', {}).items() + } + try: + value = data['context'] # pyright: ignore[reportTypedDictNotRequiredAccess] + self.context = AppCommandContext._from_value([value]) + except KeyError: + self.context = AppCommandContext() self.locale: Locale = try_enum(Locale, data.get('locale', 'en-US')) self.guild_locale: Optional[Locale] try: - self.guild_locale = try_enum(Locale, data['guild_locale']) + self.guild_locale = try_enum(Locale, data['guild_locale']) # pyright: ignore[reportTypedDictNotRequiredAccess] except KeyError: self.guild_locale = None + guild = None + if self.guild_id: + # The data type is a TypedDict but it doesn't narrow to Dict[str, Any] properly + guild = self._state._get_or_create_unavailable_guild(self.guild_id, data=data.get('guild')) # type: ignore + if guild.me is None and self._client.user is not None: + guild._add_member(Member._from_client_user(user=self._client.user, guild=guild, state=self._state)) + + raw_channel = data.get('channel', {}) + channel_id = utils._get_as_snowflake(raw_channel, 'id') + if channel_id is not None and guild is not None: + self.channel = guild and guild._resolve_channel(channel_id) + + raw_ch_type = raw_channel.get('type') + if self.channel is None and raw_ch_type is not None: + factory, ch_type = _threaded_channel_factory(raw_ch_type) # type is never None + if factory is None: + logging.info('Unknown channel type {type} for channel ID {id}.'.format_map(raw_channel)) + else: + if ch_type in (ChannelType.group, ChannelType.private): + self.channel = factory(me=self._client.user, data=raw_channel, state=self._state) # type: ignore + elif guild is not None: + self.channel = factory(guild=guild, state=self._state, data=raw_channel) # type: ignore + self.message: Optional[Message] try: # The channel and message payloads are mismatched yet handled properly at runtime @@ -193,8 +271,11 @@ class Interaction(Generic[ClientT]): self._permissions: int = 0 self._app_permissions: int = int(data.get('app_permissions', 0)) - if self.guild_id: - guild = self._state._get_or_create_unavailable_guild(self.guild_id) + if guild is not None: + # Upgrade Message.guild in case it's missing with partial guild data + if self.message is not None and self.message.guild is None: + self.message.guild = guild + try: member = data['member'] # type: ignore # The key is optional and handled except KeyError: @@ -220,23 +301,15 @@ class Interaction(Generic[ClientT]): @property def guild(self) -> Optional[Guild]: """Optional[:class:`Guild`]: The guild the interaction was sent from.""" - return self._state and self._state._get_guild(self.guild_id) - - @utils.cached_slot_property('_cs_channel') - def channel(self) -> Optional[InteractionChannel]: - """Optional[Union[:class:`abc.GuildChannel`, :class:`PartialMessageable`, :class:`Thread`]]: The channel the interaction was sent from. + # The user.guild attribute is set in __init__ to the fallback guild if available + # Therefore, we can use that instead of recreating it every time this property is + # accessed + return (self._state and self._state._get_guild(self.guild_id)) or getattr(self.user, 'guild', None) - Note that due to a Discord limitation, DM channels are not resolved since there is - no data to complete them. These are :class:`PartialMessageable` instead. - """ - guild = self.guild - channel = guild and guild._resolve_channel(self.channel_id) - if channel is None: - if self.channel_id is not None: - type = ChannelType.text if self.guild_id is not None else ChannelType.private - return PartialMessageable(state=self._state, guild_id=self.guild_id, id=self.channel_id, type=type) - return None - return channel + @property + def channel_id(self) -> Optional[int]: + """Optional[:class:`int`]: The ID of the channel the interaction was sent from.""" + return self.channel.id if self.channel is not None else None @property def permissions(self) -> Permissions: @@ -336,6 +409,22 @@ class Interaction(Generic[ClientT]): """:class:`bool`: Returns ``True`` if the interaction is expired.""" return utils.utcnow() >= self.expires_at + def is_guild_integration(self) -> bool: + """:class:`bool`: Returns ``True`` if the interaction is a guild integration. + + .. versionadded:: 2.4 + """ + if self.guild_id: + return self.guild_id == self._integration_owners.get(0) + return False + + def is_user_integration(self) -> bool: + """:class:`bool`: Returns ``True`` if the interaction is a user integration. + + .. versionadded:: 2.4 + """ + return self.user.id == self._integration_owners.get(1) + async def original_response(self) -> InteractionMessage: """|coro| @@ -395,6 +484,7 @@ class Interaction(Generic[ClientT]): attachments: Sequence[Union[Attachment, File]] = MISSING, view: Optional[View] = MISSING, allowed_mentions: Optional[AllowedMentions] = None, + poll: Poll = MISSING, ) -> InteractionMessage: """|coro| @@ -429,6 +519,14 @@ class Interaction(Generic[ClientT]): view: Optional[:class:`~discord.ui.View`] The updated view to update this message with. If ``None`` is passed then the view is removed. + poll: :class:`Poll` + The poll to create when editing the message. + + .. versionadded:: 2.5 + + .. note:: + + This is only accepted when the response type is :attr:`InteractionResponseType.deferred_channel_message`. Raises ------- @@ -458,6 +556,7 @@ class Interaction(Generic[ClientT]): view=view, allowed_mentions=allowed_mentions, previous_allowed_mentions=previous_mentions, + poll=poll, ) as params: adapter = async_context.get() http = self._state.http @@ -550,6 +649,109 @@ class Interaction(Generic[ClientT]): return await translator.translate(string, locale=locale, context=context) +class InteractionCallbackActivityInstance: + """Represents an activity instance launched as an interaction response. + + .. versionadded:: 2.5 + + Attributes + ---------- + id: :class:`str` + The activity instance ID. + """ + + __slots__ = ('id',) + + def __init__(self, data: InteractionCallbackActivityPayload) -> None: + self.id: str = data['id'] + + +class InteractionCallbackResponse(Generic[ClientT]): + """Represents an interaction response callback. + + .. versionadded:: 2.5 + + Attributes + ---------- + id: :class:`int` + The interaction ID. + type: :class:`InteractionResponseType` + The interaction callback response type. + resource: Optional[Union[:class:`InteractionMessage`, :class:`InteractionCallbackActivityInstance`]] + The resource that the interaction response created. If a message was sent, this will be + a :class:`InteractionMessage`. If an activity was launched this will be a + :class:`InteractionCallbackActivityInstance`. In any other case, this will be ``None``. + message_id: Optional[:class:`int`] + The message ID of the resource. Only available if the resource is a :class:`InteractionMessage`. + activity_id: Optional[:class:`str`] + The activity ID of the resource. Only available if the resource is a :class:`InteractionCallbackActivityInstance`. + """ + + __slots__ = ( + '_state', + '_parent', + 'type', + 'id', + '_thinking', + '_ephemeral', + 'message_id', + 'activity_id', + 'resource', + ) + + def __init__( + self, + *, + data: InteractionCallbackPayload, + parent: Interaction[ClientT], + state: ConnectionState, + type: InteractionResponseType, + ) -> None: + self._state: ConnectionState = state + self._parent: Interaction[ClientT] = parent + self.type: InteractionResponseType = type + self._update(data) + + def __repr__(self) -> str: + return f'' + + def _update(self, data: InteractionCallbackPayload) -> None: + interaction = data['interaction'] + + self.id: int = int(interaction['id']) + self._thinking: bool = interaction.get('response_message_loading', False) + self._ephemeral: bool = interaction.get('response_message_ephemeral', False) + + self.message_id: Optional[int] = utils._get_as_snowflake(interaction, 'response_message_id') + self.activity_id: Optional[str] = interaction.get('activity_instance_id') + + self.resource: Optional[InteractionCallbackResource] = None + + resource = data.get('resource') + if resource is not None: + + self.type = try_enum(InteractionResponseType, resource['type']) + + message = resource.get('message') + activity_instance = resource.get('activity_instance') + if message is not None: + self.resource = InteractionMessage( + state=_InteractionMessageState(self._parent, self._state), # pyright: ignore[reportArgumentType] + channel=self._parent.channel, # type: ignore # channel should be the correct type here + data=message, + ) + elif activity_instance is not None: + self.resource = InteractionCallbackActivityInstance(activity_instance) + + def is_thinking(self) -> bool: + """:class:`bool`: Whether the response was a thinking defer.""" + return self._thinking + + def is_ephemeral(self) -> bool: + """:class:`bool`: Whether the response was ephemeral.""" + return self._ephemeral + + class InteractionResponse(Generic[ClientT]): """Represents a Discord interaction response. @@ -579,7 +781,12 @@ class InteractionResponse(Generic[ClientT]): """:class:`InteractionResponseType`: The type of response that was sent, ``None`` if response is not done.""" return self._response_type - async def defer(self, *, ephemeral: bool = False, thinking: bool = False) -> None: + async def defer( + self, + *, + ephemeral: bool = False, + thinking: bool = False, + ) -> Optional[InteractionCallbackResponse[ClientT]]: """|coro| Defers the interaction response. @@ -593,6 +800,9 @@ class InteractionResponse(Generic[ClientT]): - :attr:`InteractionType.component` - :attr:`InteractionType.modal_submit` + .. versionchanged:: 2.5 + This now returns a :class:`InteractionCallbackResponse` instance. + Parameters ----------- ephemeral: :class:`bool` @@ -611,6 +821,11 @@ class InteractionResponse(Generic[ClientT]): Deferring the interaction failed. InteractionResponded This interaction has already been responded to before. + + Returns + ------- + Optional[:class:`InteractionCallbackResponse`] + The interaction callback resource, or ``None``. """ if self._response_type: raise InteractionResponded(self._parent) @@ -635,7 +850,7 @@ class InteractionResponse(Generic[ClientT]): adapter = async_context.get() params = interaction_response_params(type=defer_type, data=data) http = parent._state.http - await adapter.create_interaction_response( + response = await adapter.create_interaction_response( parent.id, parent.token, session=parent._session, @@ -644,6 +859,12 @@ class InteractionResponse(Generic[ClientT]): params=params, ) self._response_type = InteractionResponseType(defer_type) + return InteractionCallbackResponse( + data=response, + parent=self._parent, + state=self._parent._state, + type=self._response_type, + ) async def pong(self) -> None: """|coro| @@ -692,11 +913,15 @@ class InteractionResponse(Generic[ClientT]): suppress_embeds: bool = False, silent: bool = False, delete_after: Optional[float] = None, - ) -> None: + poll: Poll = MISSING, + ) -> InteractionCallbackResponse[ClientT]: """|coro| Responds to this interaction by sending a message. + .. versionchanged:: 2.5 + This now returns a :class:`InteractionCallbackResponse` instance. + Parameters ----------- content: Optional[:class:`str`] @@ -735,6 +960,10 @@ class InteractionResponse(Generic[ClientT]): then it is silently ignored. .. versionadded:: 2.1 + poll: :class:`~discord.Poll` + The poll to send with this message. + + .. versionadded:: 2.4 Raises ------- @@ -746,6 +975,11 @@ class InteractionResponse(Generic[ClientT]): The length of ``embeds`` was invalid. InteractionResponded This interaction has already been responded to before. + + Returns + ------- + :class:`InteractionCallbackResponse` + The interaction callback data. """ if self._response_type: raise InteractionResponded(self._parent) @@ -772,10 +1006,11 @@ class InteractionResponse(Generic[ClientT]): allowed_mentions=allowed_mentions, flags=flags, view=view, + poll=poll, ) http = parent._state.http - await adapter.create_interaction_response( + response = await adapter.create_interaction_response( parent.id, parent.token, session=parent._session, @@ -806,6 +1041,13 @@ class InteractionResponse(Generic[ClientT]): asyncio.create_task(inner_call()) + return InteractionCallbackResponse( + data=response, + parent=self._parent, + state=self._parent._state, + type=self._response_type, + ) + async def edit_message( self, *, @@ -816,12 +1058,16 @@ class InteractionResponse(Generic[ClientT]): view: Optional[View] = MISSING, allowed_mentions: Optional[AllowedMentions] = MISSING, delete_after: Optional[float] = None, - ) -> None: + suppress_embeds: bool = MISSING, + ) -> Optional[InteractionCallbackResponse[ClientT]]: """|coro| Responds to this interaction by editing the original message of a component or modal interaction. + .. versionchanged:: 2.5 + This now returns a :class:`InteractionCallbackResponse` instance. + Parameters ----------- content: Optional[:class:`str`] @@ -851,6 +1097,13 @@ class InteractionResponse(Generic[ClientT]): then it is silently ignored. .. versionadded:: 2.2 + suppress_embeds: :class:`bool` + Whether to suppress embeds for the message. This removes + all the embeds if set to ``True``. If set to ``False`` + this brings the embeds back if they were suppressed. + Using this parameter requires :attr:`~.Permissions.manage_messages`. + + .. versionadded:: 2.4 Raises ------- @@ -860,6 +1113,11 @@ class InteractionResponse(Generic[ClientT]): You specified both ``embed`` and ``embeds``. InteractionResponded This interaction has already been responded to before. + + Returns + ------- + Optional[:class:`InteractionCallbackResponse`] + The interaction callback data, or ``None`` if editing the message was not possible. """ if self._response_type: raise InteractionResponded(self._parent) @@ -871,7 +1129,7 @@ class InteractionResponse(Generic[ClientT]): message_id = msg.id # If this was invoked via an application command then we can use its original interaction ID # Since this is used as a cache key for view updates - original_interaction_id = msg.interaction.id if msg.interaction is not None else None + original_interaction_id = msg.interaction_metadata.id if msg.interaction_metadata is not None else None else: message_id = None original_interaction_id = None @@ -882,6 +1140,12 @@ class InteractionResponse(Generic[ClientT]): if view is not MISSING and message_id is not None: state.prevent_view_updates_for(message_id) + if suppress_embeds is not MISSING: + flags = MessageFlags._from_value(0) + flags.suppress_embeds = suppress_embeds + else: + flags = MISSING + adapter = async_context.get() params = interaction_message_response_params( type=InteractionResponseType.message_update.value, @@ -892,10 +1156,11 @@ class InteractionResponse(Generic[ClientT]): attachments=attachments, previous_allowed_mentions=parent._state.allowed_mentions, allowed_mentions=allowed_mentions, + flags=flags, ) http = parent._state.http - await adapter.create_interaction_response( + response = await adapter.create_interaction_response( parent.id, parent.token, session=parent._session, @@ -920,11 +1185,21 @@ class InteractionResponse(Generic[ClientT]): asyncio.create_task(inner_call()) - async def send_modal(self, modal: Modal, /) -> None: + return InteractionCallbackResponse( + data=response, + parent=self._parent, + state=self._parent._state, + type=self._response_type, + ) + + async def send_modal(self, modal: Modal, /) -> InteractionCallbackResponse[ClientT]: """|coro| Responds to this interaction by sending a modal. + .. versionchanged:: 2.5 + This now returns a :class:`InteractionCallbackResponse` instance. + Parameters ----------- modal: :class:`~discord.ui.Modal` @@ -936,6 +1211,11 @@ class InteractionResponse(Generic[ClientT]): Sending the modal failed. InteractionResponded This interaction has already been responded to before. + + Returns + ------- + :class:`InteractionCallbackResponse` + The interaction callback data. """ if self._response_type: raise InteractionResponded(self._parent) @@ -946,7 +1226,7 @@ class InteractionResponse(Generic[ClientT]): http = parent._state.http params = interaction_response_params(InteractionResponseType.modal.value, modal.to_dict()) - await adapter.create_interaction_response( + response = await adapter.create_interaction_response( parent.id, parent.token, session=parent._session, @@ -958,6 +1238,13 @@ class InteractionResponse(Generic[ClientT]): self._parent._state.store_view(modal) self._response_type = InteractionResponseType.modal + return InteractionCallbackResponse( + data=response, + parent=self._parent, + state=self._parent._state, + type=self._response_type, + ) + async def autocomplete(self, choices: Sequence[Choice[ChoiceT]]) -> None: """|coro| @@ -1009,6 +1296,52 @@ class InteractionResponse(Generic[ClientT]): self._response_type = InteractionResponseType.autocomplete_result + async def launch_activity(self) -> InteractionCallbackResponse[ClientT]: + """|coro| + + Responds to this interaction by launching the activity associated with the app. + Only available for apps with activities enabled. + + .. versionadded:: 2.6 + + Raises + ------- + HTTPException + Launching the activity failed. + InteractionResponded + This interaction has already been responded to before. + + Returns + ------- + :class:`InteractionCallbackResponse` + The interaction callback data. + """ + if self._response_type: + raise InteractionResponded(self._parent) + + parent = self._parent + + adapter = async_context.get() + http = parent._state.http + + params = interaction_response_params(InteractionResponseType.launch_activity.value) + response = await adapter.create_interaction_response( + parent.id, + parent.token, + session=parent._session, + proxy=http.proxy, + proxy_auth=http.proxy_auth, + params=params, + ) + self._response_type = InteractionResponseType.launch_activity + + return InteractionCallbackResponse( + data=response, + parent=self._parent, + state=self._parent._state, + type=self._response_type, + ) + class _InteractionMessageState: __slots__ = ('_parent', '_interaction') @@ -1020,8 +1353,8 @@ class _InteractionMessageState: def _get_guild(self, guild_id): return self._parent._get_guild(guild_id) - def store_user(self, data): - return self._parent.store_user(data) + def store_user(self, data, *, cache: bool = True): + return self._parent.store_user(data, cache=cache) def create_user(self, data): return self._parent.create_user(data) @@ -1059,6 +1392,7 @@ class InteractionMessage(Message): view: Optional[View] = MISSING, allowed_mentions: Optional[AllowedMentions] = None, delete_after: Optional[float] = None, + poll: Poll = MISSING, ) -> InteractionMessage: """|coro| @@ -1093,6 +1427,15 @@ class InteractionMessage(Message): then it is silently ignored. .. versionadded:: 2.2 + poll: :class:`~discord.Poll` + The poll to create when editing the message. + + .. versionadded:: 2.5 + + .. note:: + + This is only accepted if the interaction response's :attr:`InteractionResponse.type` + attribute is :attr:`InteractionResponseType.deferred_channel_message`. Raises ------- @@ -1117,6 +1460,7 @@ class InteractionMessage(Message): attachments=attachments, view=view, allowed_mentions=allowed_mentions, + poll=poll, ) if delete_after is not None: await self.delete(delay=delete_after) diff --git a/discord/invite.py b/discord/invite.py index 6e00f36e3..362f97693 100644 --- a/discord/invite.py +++ b/discord/invite.py @@ -29,9 +29,10 @@ from .asset import Asset from .utils import parse_time, snowflake_time, _get_as_snowflake from .object import Object from .mixins import Hashable -from .enums import ChannelType, NSFWLevel, VerificationLevel, InviteTarget, try_enum +from .enums import ChannelType, NSFWLevel, VerificationLevel, InviteTarget, InviteType, try_enum from .appinfo import PartialAppInfo from .scheduled_event import ScheduledEvent +from .flags import InviteFlags __all__ = ( 'PartialInviteChannel', @@ -47,6 +48,7 @@ if TYPE_CHECKING: InviteGuild as InviteGuildPayload, GatewayInvite as GatewayInvitePayload, ) + from .types.guild import GuildFeature from .types.channel import ( PartialChannel as InviteChannelPayload, ) @@ -189,7 +191,7 @@ class PartialInviteGuild: self._state: ConnectionState = state self.id: int = id self.name: str = data['name'] - self.features: List[str] = data.get('features', []) + self.features: List[GuildFeature] = data.get('features', []) self._icon: Optional[str] = data.get('icon') self._banner: Optional[str] = data.get('banner') self._splash: Optional[str] = data.get('splash') @@ -295,6 +297,10 @@ class Invite(Hashable): Attributes ----------- + type: :class:`InviteType` + The type of the invite. + + .. versionadded: 2.4 max_age: Optional[:class:`int`] How long before the invite expires in seconds. A value of ``0`` indicates that it doesn't expire. @@ -373,6 +379,8 @@ class Invite(Hashable): 'expires_at', 'scheduled_event', 'scheduled_event_id', + 'type', + '_flags', ) BASE = 'https://discord.gg' @@ -386,6 +394,7 @@ class Invite(Hashable): channel: Optional[Union[PartialInviteChannel, GuildChannel]] = None, ): self._state: ConnectionState = state + self.type: InviteType = try_enum(InviteType, data.get('type', 0)) self.max_age: Optional[int] = data.get('max_age') self.code: str = data['code'] self.guild: Optional[InviteGuildType] = self._resolve_guild(data.get('guild'), guild) @@ -425,12 +434,13 @@ class Invite(Hashable): else None ) self.scheduled_event_id: Optional[int] = self.scheduled_event.id if self.scheduled_event else None + self._flags: int = data.get('flags', 0) @classmethod def from_incomplete(cls, *, state: ConnectionState, data: InvitePayload) -> Self: guild: Optional[Union[Guild, PartialInviteGuild]] try: - guild_data = data['guild'] + guild_data = data['guild'] # pyright: ignore[reportTypedDictNotRequiredAccess] except KeyError: # If we're here, then this is a group DM guild = None @@ -495,7 +505,7 @@ class Invite(Hashable): def __repr__(self) -> str: return ( - f'' ) @@ -516,6 +526,14 @@ class Invite(Hashable): url += '?event=' + str(self.scheduled_event_id) return url + @property + def flags(self) -> InviteFlags: + """:class:`InviteFlags`: Returns the flags for this invite. + + .. versionadded:: 2.6 + """ + return InviteFlags._from_value(self._flags) + def set_scheduled_event(self, scheduled_event: Snowflake, /) -> Self: """Sets the scheduled event for this invite. @@ -539,7 +557,7 @@ class Invite(Hashable): return self - async def delete(self, *, reason: Optional[str] = None) -> None: + async def delete(self, *, reason: Optional[str] = None) -> Self: """|coro| Revokes the instant invite. @@ -561,4 +579,5 @@ class Invite(Hashable): Revoking the invite failed. """ - await self._state.http.delete_invite(self.code, reason=reason) + data = await self._state.http.delete_invite(self.code, reason=reason) + return self.from_incomplete(state=self._state, data=data) diff --git a/discord/member.py b/discord/member.py index ee303f5bc..ed52600dd 100644 --- a/discord/member.py +++ b/discord/member.py @@ -35,14 +35,14 @@ import discord.abc from . import utils from .asset import Asset from .utils import MISSING -from .user import BaseUser, User, _UserTag -from .activity import create_activity, ActivityTypes +from .user import BaseUser, ClientUser, User, _UserTag from .permissions import Permissions -from .enums import Status, try_enum +from .enums import Status from .errors import ClientException from .colour import Colour from .object import Object from .flags import MemberFlags +from .presences import ClientStatus __all__ = ( 'VoiceState', @@ -57,17 +57,15 @@ if TYPE_CHECKING: from .channel import DMChannel, VoiceChannel, StageChannel from .flags import PublicUserFlags from .guild import Guild - from .types.activity import ( - ClientStatus as ClientStatusPayload, - PartialPresenceUpdate, - ) + from .activity import ActivityTypes + from .presences import RawPresenceUpdateEvent from .types.member import ( MemberWithUser as MemberWithUserPayload, Member as MemberPayload, UserWithMember as UserWithMemberPayload, ) from .types.gateway import GuildMemberUpdateEvent - from .types.user import User as UserPayload + from .types.user import User as UserPayload, AvatarDecorationData from .abc import Snowflake from .state import ConnectionState from .message import Message @@ -168,46 +166,6 @@ class VoiceState: return f'<{self.__class__.__name__} {inner}>' -class _ClientStatus: - __slots__ = ('_status', 'desktop', 'mobile', 'web') - - def __init__(self): - self._status: str = 'offline' - - self.desktop: Optional[str] = None - self.mobile: Optional[str] = None - self.web: Optional[str] = None - - def __repr__(self) -> str: - attrs = [ - ('_status', self._status), - ('desktop', self.desktop), - ('mobile', self.mobile), - ('web', self.web), - ] - inner = ' '.join('%s=%r' % t for t in attrs) - return f'<{self.__class__.__name__} {inner}>' - - def _update(self, status: str, data: ClientStatusPayload, /) -> None: - self._status = status - - self.desktop = data.get('desktop') - self.mobile = data.get('mobile') - self.web = data.get('web') - - @classmethod - def _copy(cls, client_status: Self, /) -> Self: - self = cls.__new__(cls) # bypass __init__ - - self._status = client_status._status - - self.desktop = client_status.desktop - self.mobile = client_status.mobile - self.web = client_status.web - - return self - - def flatten_user(cls: T) -> T: for attr, value in itertools.chain(BaseUser.__dict__.items(), User.__dict__.items()): # ignore private/special methods @@ -274,13 +232,14 @@ class Member(discord.abc.Messageable, _UserTag): .. describe:: str(x) - Returns the member's name with the discriminator. + Returns the member's handle (e.g. ``name`` or ``name#discriminator``). Attributes ---------- joined_at: Optional[:class:`datetime.datetime`] An aware datetime object that specifies the date and time in UTC that the member joined the guild. - If the member left and rejoined the guild, this will be the latest date. In certain cases, this can be ``None``. + If the member left and rejoined the guild, this will be the latest date. + This can be ``None``, such as when the member is a guest. activities: Tuple[Union[:class:`BaseActivity`, :class:`Spotify`]] The activities that the user is currently doing. @@ -293,7 +252,7 @@ class Member(discord.abc.Messageable, _UserTag): guild: :class:`Guild` The guild that the member belongs to. nick: Optional[:class:`str`] - The guild specific nickname of the user. + The guild specific nickname of the user. Takes precedence over the global name. pending: :class:`bool` Whether the member is pending member verification. @@ -303,9 +262,13 @@ class Member(discord.abc.Messageable, _UserTag): "Nitro boost" on the guild, if available. This could be ``None``. timed_out_until: Optional[:class:`datetime.datetime`] An aware datetime object that specifies the date and time in UTC that the member's time out will expire. - This will be set to ``None`` if the user is not timed out. + This will be set to ``None`` or a time in the past if the user is not timed out. .. versionadded:: 2.0 + client_status: :class:`ClientStatus` + Model which holds information about the status of the member on various clients/platforms via presence updates. + + .. versionadded:: 2.5 """ __slots__ = ( @@ -318,17 +281,20 @@ class Member(discord.abc.Messageable, _UserTag): 'nick', 'timed_out_until', '_permissions', - '_client_status', + 'client_status', '_user', '_state', '_avatar', + '_banner', '_flags', + '_avatar_decoration_data', ) if TYPE_CHECKING: name: str id: int discriminator: str + global_name: Optional[str] bot: bool system: bool created_at: datetime.datetime @@ -341,6 +307,8 @@ class Member(discord.abc.Messageable, _UserTag): banner: Optional[Asset] accent_color: Optional[Colour] accent_colour: Optional[Colour] + avatar_decoration: Optional[Asset] + avatar_decoration_sku_id: Optional[int] def __init__(self, *, data: MemberWithUserPayload, guild: Guild, state: ConnectionState): self._state: ConnectionState = state @@ -349,15 +317,17 @@ class Member(discord.abc.Messageable, _UserTag): self.joined_at: Optional[datetime.datetime] = utils.parse_time(data.get('joined_at')) self.premium_since: Optional[datetime.datetime] = utils.parse_time(data.get('premium_since')) self._roles: utils.SnowflakeList = utils.SnowflakeList(map(int, data['roles'])) - self._client_status: _ClientStatus = _ClientStatus() + self.client_status: ClientStatus = ClientStatus() self.activities: Tuple[ActivityTypes, ...] = () self.nick: Optional[str] = data.get('nick', None) self.pending: bool = data.get('pending', False) self._avatar: Optional[str] = data.get('avatar') + self._banner: Optional[str] = data.get('banner') self._permissions: Optional[int] self._flags: int = data['flags'] + self._avatar_decoration_data: Optional[AvatarDecorationData] = data.get('avatar_decoration_data') try: - self._permissions = int(data['permissions']) + self._permissions = int(data['permissions']) # pyright: ignore[reportTypedDictNotRequiredAccess] except KeyError: self._permissions = None @@ -368,7 +338,7 @@ class Member(discord.abc.Messageable, _UserTag): def __repr__(self) -> str: return ( - f'' ) @@ -387,6 +357,15 @@ class Member(discord.abc.Messageable, _UserTag): data['user'] = author._to_minimal_user_json() # type: ignore return cls(data=data, guild=message.guild, state=message._state) # type: ignore + @classmethod + def _from_client_user(cls, *, user: ClientUser, guild: Guild, state: ConnectionState) -> Self: + data = { + 'roles': [], + 'user': user._to_minimal_user_json(), + 'flags': 0, + } + return cls(data=data, guild=guild, state=state) # type: ignore + def _update_from_message(self, data: MemberPayload) -> None: self.joined_at = utils.parse_time(data.get('joined_at')) self.premium_since = utils.parse_time(data.get('premium_since')) @@ -414,7 +393,7 @@ class Member(discord.abc.Messageable, _UserTag): self._roles = utils.SnowflakeList(member._roles, is_sorted=True) self.joined_at = member.joined_at self.premium_since = member.premium_since - self._client_status = _ClientStatus._copy(member._client_status) + self.client_status = member.client_status self.guild = member.guild self.nick = member.nick self.pending = member.pending @@ -424,6 +403,8 @@ class Member(discord.abc.Messageable, _UserTag): self._permissions = member._permissions self._state = member._state self._avatar = member._avatar + self._banner = member._banner + self._avatar_decoration_data = member._avatar_decoration_data # Reference will not be copied unless necessary by PRESENCE_UPDATE # See below @@ -438,12 +419,12 @@ class Member(discord.abc.Messageable, _UserTag): # the nickname change is optional, # if it isn't in the payload then it didn't change try: - self.nick = data['nick'] + self.nick = data['nick'] # pyright: ignore[reportTypedDictNotRequiredAccess] except KeyError: pass try: - self.pending = data['pending'] + self.pending = data['pending'] # pyright: ignore[reportTypedDictNotRequiredAccess] except KeyError: pass @@ -451,31 +432,55 @@ class Member(discord.abc.Messageable, _UserTag): self.timed_out_until = utils.parse_time(data.get('communication_disabled_until')) self._roles = utils.SnowflakeList(map(int, data['roles'])) self._avatar = data.get('avatar') + self._banner = data.get('banner') self._flags = data.get('flags', 0) + self._avatar_decoration_data = data.get('avatar_decoration_data') - def _presence_update(self, data: PartialPresenceUpdate, user: UserPayload) -> Optional[Tuple[User, User]]: - self.activities = tuple(create_activity(d, self._state) for d in data['activities']) - self._client_status._update(data['status'], data['client_status']) + def _presence_update(self, raw: RawPresenceUpdateEvent, user: UserPayload) -> Optional[Tuple[User, User]]: + self.activities = raw.activities + self.client_status = raw.client_status if len(user) > 1: return self._update_inner_user(user) - return None def _update_inner_user(self, user: UserPayload) -> Optional[Tuple[User, User]]: u = self._user - original = (u.name, u._avatar, u.discriminator, u._public_flags) + original = ( + u.name, + u.discriminator, + u._avatar, + u.global_name, + u._public_flags, + u._avatar_decoration_data['sku_id'] if u._avatar_decoration_data is not None else None, + ) + + decoration_payload = user.get('avatar_decoration_data') # These keys seem to always be available - modified = (user['username'], user['avatar'], user['discriminator'], user.get('public_flags', 0)) + modified = ( + user['username'], + user['discriminator'], + user['avatar'], + user.get('global_name'), + user.get('public_flags', 0), + decoration_payload['sku_id'] if decoration_payload is not None else None, + ) if original != modified: to_return = User._copy(self._user) - u.name, u._avatar, u.discriminator, u._public_flags = modified + u.name, u.discriminator, u._avatar, u.global_name, u._public_flags, u._avatar_decoration_data = ( + user['username'], + user['discriminator'], + user['avatar'], + user.get('global_name'), + user.get('public_flags', 0), + decoration_payload, + ) # Signal to dispatch on_user_update return to_return, u @property def status(self) -> Status: """:class:`Status`: The member's overall status. If the value is unknown, then it will be a :class:`str` instead.""" - return try_enum(Status, self._client_status._status) + return self.client_status.status @property def raw_status(self) -> str: @@ -483,31 +488,36 @@ class Member(discord.abc.Messageable, _UserTag): .. versionadded:: 1.5 """ - return self._client_status._status + return self.client_status._status @status.setter def status(self, value: Status) -> None: # internal use only - self._client_status._status = str(value) + self.client_status._status = str(value) @property def mobile_status(self) -> Status: """:class:`Status`: The member's status on a mobile device, if applicable.""" - return try_enum(Status, self._client_status.mobile or 'offline') + return self.client_status.mobile_status @property def desktop_status(self) -> Status: """:class:`Status`: The member's status on the desktop client, if applicable.""" - return try_enum(Status, self._client_status.desktop or 'offline') + return self.client_status.desktop_status @property def web_status(self) -> Status: """:class:`Status`: The member's status on the web client, if applicable.""" - return try_enum(Status, self._client_status.web or 'offline') + return self.client_status.web_status def is_on_mobile(self) -> bool: - """:class:`bool`: A helper function that determines if a member is active on a mobile device.""" - return self._client_status.mobile is not None + """A helper function that determines if a member is active on a mobile device. + + Returns + ------- + :class:`bool` + """ + return self.client_status.is_on_mobile() @property def colour(self) -> Colour: @@ -552,7 +562,9 @@ class Member(discord.abc.Messageable, _UserTag): role = g.get_role(role_id) if role: result.append(role) - result.append(g.default_role) + default_role = g.default_role + if default_role: + result.append(default_role) result.sort() return result @@ -581,11 +593,11 @@ class Member(discord.abc.Messageable, _UserTag): def display_name(self) -> str: """:class:`str`: Returns the user's display name. - For regular users this is just their username, but - if they have a guild specific nickname then that + For regular users this is just their global name or their username, + but if they have a guild specific nickname then that is returned instead. """ - return self.nick or self.name + return self.nick or self.global_name or self.name @property def display_avatar(self) -> Asset: @@ -610,6 +622,28 @@ class Member(discord.abc.Messageable, _UserTag): 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 displayed banner, if any. + + This is the member's guild banner if available, otherwise it's their + global banner. If the member has no banner set then ``None`` is returned. + + .. versionadded:: 2.5 + """ + return self.guild_banner or self._user.banner + + @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.5 + """ + 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 @@ -833,7 +867,7 @@ class Member(discord.abc.Messageable, _UserTag): Raises ------- Forbidden - You do not have the proper permissions to the action requested. + You do not have the proper permissions to do the action requested. HTTPException The operation failed. TypeError @@ -925,7 +959,7 @@ class Member(discord.abc.Messageable, _UserTag): ClientException You are not connected to a voice channel. Forbidden - You do not have the proper permissions to the action requested. + You do not have the proper permissions to do the action requested. HTTPException The operation failed. """ @@ -1011,7 +1045,7 @@ class Member(discord.abc.Messageable, _UserTag): You must have :attr:`~Permissions.manage_roles` to use this, and the added :class:`Role`\s must appear lower in the list - of roles than the highest role of the member. + of roles than the highest role of the client. Parameters ----------- @@ -1050,7 +1084,7 @@ class Member(discord.abc.Messageable, _UserTag): You must have :attr:`~Permissions.manage_roles` to use this, and the removed :class:`Role`\s must appear lower in the list - of roles than the highest role of the member. + of roles than the highest role of the client. Parameters ----------- @@ -1088,6 +1122,40 @@ class Member(discord.abc.Messageable, _UserTag): for role in roles: await req(guild_id, user_id, role.id, reason=reason) + async def fetch_voice(self) -> VoiceState: + """|coro| + + Retrieves the current voice state from this member. + + .. versionadded:: 2.5 + + Raises + ------- + NotFound + The member is not in a voice channel. + Forbidden + You do not have permissions to get a voice state. + HTTPException + Retrieving the voice state failed. + + Returns + ------- + :class:`VoiceState` + The current voice state of the member. + """ + guild_id = self.guild.id + if self._state.self_id == self.id: + data = await self._state.http.get_my_voice_state(guild_id) + else: + data = await self._state.http.get_voice_state(guild_id, self.id) + + channel_id = data.get('channel_id') + channel: Optional[VocalGuildChannel] = None + if channel_id is not None: + channel = self.guild.get_channel(int(channel_id)) # type: ignore # must be voice channel here + + return VoiceState(data=data, channel=channel) + def get_role(self, role_id: int, /) -> Optional[Role]: """Returns a role with the given ID from roles which the member has. diff --git a/discord/message.py b/discord/message.py index 06cd9e3af..d6a26c7d0 100644 --- a/discord/message.py +++ b/discord/message.py @@ -32,6 +32,7 @@ from os import PathLike from typing import ( Dict, TYPE_CHECKING, + Literal, Sequence, Union, List, @@ -49,20 +50,21 @@ from .asset import Asset from .reaction import Reaction from .emoji import Emoji from .partial_emoji import PartialEmoji -from .enums import InteractionType, MessageType, ChannelType, try_enum +from .enums import InteractionType, MessageReferenceType, MessageType, ChannelType, try_enum from .errors import HTTPException from .components import _component_factory from .embeds import Embed from .member import Member -from .flags import MessageFlags +from .flags import MessageFlags, AttachmentFlags from .file import File -from .utils import escape_mentions, MISSING +from .utils import escape_mentions, MISSING, deprecated from .http import handle_message_parameters from .guild import Guild from .mixins import Hashable -from .sticker import StickerItem +from .sticker import StickerItem, GuildSticker from .threads import Thread from .channel import PartialMessageable +from .poll import Poll if TYPE_CHECKING: from typing_extensions import Self @@ -71,9 +73,14 @@ if TYPE_CHECKING: Message as MessagePayload, Attachment as AttachmentPayload, MessageReference as MessageReferencePayload, + MessageSnapshot as MessageSnapshotPayload, MessageApplication as MessageApplicationPayload, MessageActivity as MessageActivityPayload, RoleSubscriptionData as RoleSubscriptionDataPayload, + MessageInteractionMetadata as MessageInteractionMetadataPayload, + CallMessage as CallMessagePayload, + PurchaseNotificationResponse as PurchaseNotificationResponsePayload, + GuildProductPurchase as GuildProductPurchasePayload, ) from .types.interactions import MessageInteraction as MessageInteractionPayload @@ -106,9 +113,14 @@ __all__ = ( 'PartialMessage', 'MessageInteraction', 'MessageReference', + 'MessageSnapshot', 'DeletedReferencedMessage', 'MessageApplication', 'RoleSubscriptionInfo', + 'MessageInteractionMetadata', + 'CallMessage', + 'GuildProductPurchase', + 'PurchaseNotification', ) @@ -183,6 +195,18 @@ class Attachment(Hashable): Whether the attachment is ephemeral. .. versionadded:: 2.0 + duration: Optional[:class:`float`] + The duration of the audio file in seconds. Returns ``None`` if it's not a voice message. + + .. versionadded:: 2.3 + waveform: Optional[:class:`bytes`] + The waveform (amplitudes) of the audio in bytes. Returns ``None`` if it's not a voice message. + + .. versionadded:: 2.3 + title: Optional[:class:`str`] + The normalised version of the attachment's filename. + + .. versionadded:: 2.5 """ __slots__ = ( @@ -197,6 +221,10 @@ class Attachment(Hashable): 'content_type', 'description', 'ephemeral', + 'duration', + 'waveform', + '_flags', + 'title', ) def __init__(self, *, data: AttachmentPayload, state: ConnectionState): @@ -211,10 +239,27 @@ class Attachment(Hashable): self.content_type: Optional[str] = data.get('content_type') self.description: Optional[str] = data.get('description') self.ephemeral: bool = data.get('ephemeral', False) + self.duration: Optional[float] = data.get('duration_secs') + self.title: Optional[str] = data.get('title') + + waveform = data.get('waveform') + self.waveform: Optional[bytes] = utils._base64_to_bytes(waveform) if waveform is not None else None + + self._flags: int = data.get('flags', 0) + + @property + def flags(self) -> AttachmentFlags: + """:class:`AttachmentFlags`: The attachment's flags.""" + return AttachmentFlags._from_value(self._flags) def is_spoiler(self) -> bool: """:class:`bool`: Whether this attachment contains a spoiler.""" - return self.filename.startswith('SPOILER_') + # The flag is technically always present but no harm to check both + return self.filename.startswith('SPOILER_') or self.flags.spoiler + + def is_voice_message(self) -> bool: + """:class:`bool`: Whether this attachment is a voice message.""" + return self.duration is not None and self.waveform is not None def __repr__(self) -> str: return f'' @@ -423,6 +468,133 @@ class DeletedReferencedMessage: return self._parent.guild_id +class MessageSnapshot: + """Represents a message snapshot attached to a forwarded message. + + .. versionadded:: 2.5 + + Attributes + ----------- + type: :class:`MessageType` + The type of the forwarded message. + content: :class:`str` + The actual contents of the forwarded message. + embeds: List[:class:`Embed`] + A list of embeds the forwarded message has. + attachments: List[:class:`Attachment`] + A list of attachments given to the forwarded message. + created_at: :class:`datetime.datetime` + The forwarded message's time of creation. + flags: :class:`MessageFlags` + Extra features of the the message snapshot. + stickers: List[:class:`StickerItem`] + A list of sticker items given to the message. + components: List[Union[:class:`ActionRow`, :class:`Button`, :class:`SelectMenu`]] + A list of components in the message. + """ + + __slots__ = ( + '_cs_raw_channel_mentions', + '_cs_cached_message', + '_cs_raw_mentions', + '_cs_raw_role_mentions', + '_edited_timestamp', + 'attachments', + 'content', + 'embeds', + 'flags', + 'created_at', + 'type', + 'stickers', + 'components', + '_state', + ) + + @classmethod + def _from_value( + cls, + state: ConnectionState, + message_snapshots: Optional[List[Dict[Literal['message'], MessageSnapshotPayload]]], + ) -> List[Self]: + if not message_snapshots: + return [] + + return [cls(state, snapshot['message']) for snapshot in message_snapshots] + + def __init__(self, state: ConnectionState, data: MessageSnapshotPayload): + self.type: MessageType = try_enum(MessageType, data['type']) + self.content: str = data['content'] + self.embeds: List[Embed] = [Embed.from_dict(a) for a in data['embeds']] + self.attachments: List[Attachment] = [Attachment(data=a, state=state) for a in data['attachments']] + self.created_at: datetime.datetime = utils.parse_time(data['timestamp']) + self._edited_timestamp: Optional[datetime.datetime] = utils.parse_time(data['edited_timestamp']) + self.flags: MessageFlags = MessageFlags._from_value(data.get('flags', 0)) + self.stickers: List[StickerItem] = [StickerItem(data=d, state=state) for d in data.get('sticker_items', [])] + + self.components: List[MessageComponentType] = [] + for component_data in data.get('components', []): + component = _component_factory(component_data) + if component is not None: + self.components.append(component) + + self._state: ConnectionState = state + + def __repr__(self) -> str: + name = self.__class__.__name__ + return f'<{name} type={self.type!r} created_at={self.created_at!r} flags={self.flags!r}>' + + @utils.cached_slot_property('_cs_raw_mentions') + def raw_mentions(self) -> List[int]: + """List[:class:`int`]: A property that returns an array of user IDs matched with + the syntax of ``<@user_id>`` in the message content. + + This allows you to receive the user IDs of mentioned users + even in a private message context. + """ + return [int(x) for x in re.findall(r'<@!?([0-9]{15,20})>', self.content)] + + @utils.cached_slot_property('_cs_raw_channel_mentions') + def raw_channel_mentions(self) -> List[int]: + """List[:class:`int`]: A property that returns an array of channel IDs matched with + the syntax of ``<#channel_id>`` in the message content. + """ + return [int(x) for x in re.findall(r'<#([0-9]{15,20})>', self.content)] + + @utils.cached_slot_property('_cs_raw_role_mentions') + def raw_role_mentions(self) -> List[int]: + """List[:class:`int`]: A property that returns an array of role IDs matched with + the syntax of ``<@&role_id>`` in the message content. + """ + return [int(x) for x in re.findall(r'<@&([0-9]{15,20})>', self.content)] + + @utils.cached_slot_property('_cs_cached_message') + def cached_message(self) -> Optional[Message]: + """Optional[:class:`Message`]: Returns the cached message this snapshot points to, if any.""" + state = self._state + return ( + utils.find( + lambda m: ( + m.created_at == self.created_at + and m.edited_at == self.edited_at + and m.content == self.content + and m.embeds == self.embeds + and m.components == self.components + and m.stickers == self.stickers + and m.attachments == self.attachments + and m.flags == self.flags + ), + reversed(state._messages), + ) + if state._messages + else None + ) + + @property + def edited_at(self) -> Optional[datetime.datetime]: + """Optional[:class:`datetime.datetime`]: An aware UTC datetime object containing the edited time of the forwarded message.""" + return self._edited_timestamp + + class MessageReference: """Represents a reference to a :class:`~discord.Message`. @@ -433,14 +605,23 @@ class MessageReference: Attributes ----------- + type: :class:`MessageReferenceType` + The type of message reference. + + .. versionadded:: 2.5 message_id: Optional[:class:`int`] The id of the message referenced. + This can be ``None`` when this message reference was retrieved from + a system message of one of the following types: + + - :attr:`MessageType.channel_follow_add` + - :attr:`MessageType.thread_created` channel_id: :class:`int` The channel id of the message referenced. guild_id: Optional[:class:`int`] The guild id of the message referenced. fail_if_not_exists: :class:`bool` - Whether replying to the referenced message should raise :class:`HTTPException` + Whether the referenced message should raise :class:`HTTPException` if the message no longer exists or Discord could not fetch the message. .. versionadded:: 1.7 @@ -452,15 +633,22 @@ class MessageReference: If the message was resolved at a prior point but has since been deleted then this will be of type :class:`DeletedReferencedMessage`. - Currently, this is mainly the replied to message when a user replies to a message. - .. versionadded:: 1.6 """ - __slots__ = ('message_id', 'channel_id', 'guild_id', 'fail_if_not_exists', 'resolved', '_state') + __slots__ = ('type', 'message_id', 'channel_id', 'guild_id', 'fail_if_not_exists', 'resolved', '_state') - def __init__(self, *, message_id: int, channel_id: int, guild_id: Optional[int] = None, fail_if_not_exists: bool = True): + def __init__( + self, + *, + message_id: int, + channel_id: int, + guild_id: Optional[int] = None, + fail_if_not_exists: bool = True, + type: MessageReferenceType = MessageReferenceType.reply, + ): self._state: Optional[ConnectionState] = None + self.type: MessageReferenceType = type self.resolved: Optional[Union[Message, DeletedReferencedMessage]] = None self.message_id: Optional[int] = message_id self.channel_id: int = channel_id @@ -470,6 +658,7 @@ class MessageReference: @classmethod def with_state(cls, state: ConnectionState, data: MessageReferencePayload) -> Self: self = cls.__new__(cls) + self.type = try_enum(MessageReferenceType, data.get('type', 0)) self.message_id = utils._get_as_snowflake(data, 'message_id') self.channel_id = int(data['channel_id']) self.guild_id = utils._get_as_snowflake(data, 'guild_id') @@ -479,7 +668,13 @@ class MessageReference: return self @classmethod - def from_message(cls, message: PartialMessage, *, fail_if_not_exists: bool = True) -> Self: + def from_message( + cls, + message: PartialMessage, + *, + fail_if_not_exists: bool = True, + type: MessageReferenceType = MessageReferenceType.reply, + ) -> Self: """Creates a :class:`MessageReference` from an existing :class:`~discord.Message`. .. versionadded:: 1.6 @@ -489,10 +684,14 @@ class MessageReference: message: :class:`~discord.Message` The message to be converted into a reference. fail_if_not_exists: :class:`bool` - Whether replying to the referenced message should raise :class:`HTTPException` + Whether the referenced message should raise :class:`HTTPException` if the message no longer exists or Discord could not fetch the message. .. versionadded:: 1.7 + type: :class:`~discord.MessageReferenceType` + The type of message reference this is. + + .. versionadded:: 2.5 Returns ------- @@ -504,6 +703,7 @@ class MessageReference: channel_id=message.channel.id, guild_id=getattr(message.guild, 'id', None), fail_if_not_exists=fail_if_not_exists, + type=type, ) self._state = message._state return self @@ -526,7 +726,9 @@ class MessageReference: return f'' def to_dict(self) -> MessageReferencePayload: - result: Dict[str, Any] = {'message_id': self.message_id} if self.message_id is not None else {} + result: Dict[str, Any] = ( + {'type': self.type.value, 'message_id': self.message_id} if self.message_id is not None else {} + ) result['channel_id'] = self.channel_id if self.guild_id is not None: result['guild_id'] = self.guild_id @@ -577,7 +779,7 @@ class MessageInteraction(Hashable): self.user: Union[User, Member] = MISSING try: - payload = data['member'] + payload = data['member'] # pyright: ignore[reportTypedDictNotRequiredAccess] except KeyError: self.user = state.create_user(data['user']) else: @@ -598,6 +800,155 @@ class MessageInteraction(Hashable): return utils.snowflake_time(self.id) +class MessageInteractionMetadata(Hashable): + """Represents the interaction metadata of a :class:`Message` if + it was sent in response to an interaction. + + .. versionadded:: 2.4 + + .. container:: operations + + .. describe:: x == y + + Checks if two message interactions are equal. + + .. describe:: x != y + + Checks if two message interactions are not equal. + + .. describe:: hash(x) + + Returns the message interaction's hash. + + Attributes + ----------- + id: :class:`int` + The interaction ID. + type: :class:`InteractionType` + The interaction type. + user: :class:`User` + The user that invoked the interaction. + original_response_message_id: Optional[:class:`int`] + The ID of the original response message if the message is a follow-up. + interacted_message_id: Optional[:class:`int`] + The ID of the message that containes the interactive components, if applicable. + modal_interaction: Optional[:class:`.MessageInteractionMetadata`] + The metadata of the modal submit interaction that triggered this interaction, if applicable. + target_user: Optional[:class:`User`] + The user the command was run on, only applicable to user context menus. + + .. versionadded:: 2.5 + target_message_id: Optional[:class:`int`] + The ID of the message the command was run on, only applicable to message context menus. + + .. versionadded:: 2.5 + """ + + __slots__: Tuple[str, ...] = ( + 'id', + 'type', + 'user', + 'original_response_message_id', + 'interacted_message_id', + 'modal_interaction', + 'target_user', + 'target_message_id', + '_integration_owners', + '_state', + '_guild', + ) + + def __init__(self, *, state: ConnectionState, guild: Optional[Guild], data: MessageInteractionMetadataPayload) -> None: + self._guild: Optional[Guild] = guild + self._state: ConnectionState = state + + self.id: int = int(data['id']) + self.type: InteractionType = try_enum(InteractionType, data['type']) + self.user: User = state.create_user(data['user']) + self._integration_owners: Dict[int, int] = { + int(key): int(value) for key, value in data.get('authorizing_integration_owners', {}).items() + } + + self.original_response_message_id: Optional[int] = None + try: + self.original_response_message_id = int(data['original_response_message_id']) # type: ignore # EAFP + except KeyError: + pass + + self.interacted_message_id: Optional[int] = None + try: + self.interacted_message_id = int(data['interacted_message_id']) # type: ignore # EAFP + except KeyError: + pass + + self.modal_interaction: Optional[MessageInteractionMetadata] = None + try: + self.modal_interaction = MessageInteractionMetadata( + state=state, guild=guild, data=data['triggering_interaction_metadata'] # type: ignore # EAFP + ) + except KeyError: + pass + + self.target_user: Optional[User] = None + try: + self.target_user = state.create_user(data['target_user']) # type: ignore # EAFP + except KeyError: + pass + + self.target_message_id: Optional[int] = None + try: + self.target_message_id = int(data['target_message_id']) # type: ignore # EAFP + except KeyError: + pass + + def __repr__(self) -> str: + return f'' + + @property + def created_at(self) -> datetime.datetime: + """:class:`datetime.datetime`: The interaction's creation time in UTC.""" + return utils.snowflake_time(self.id) + + @property + def original_response_message(self) -> Optional[Message]: + """Optional[:class:`~discord.Message`]: The original response message if the message + is a follow-up and is found in cache. + """ + if self.original_response_message_id: + return self._state._get_message(self.original_response_message_id) + return None + + @property + def interacted_message(self) -> Optional[Message]: + """Optional[:class:`~discord.Message`]: The message that + containes the interactive components, if applicable and is found in cache. + """ + if self.interacted_message_id: + return self._state._get_message(self.interacted_message_id) + return None + + @property + def target_message(self) -> Optional[Message]: + """Optional[:class:`~discord.Message`]: The target message, if applicable and is found in cache. + + .. versionadded:: 2.5 + """ + if self.target_message_id: + return self._state._get_message(self.target_message_id) + return None + + def is_guild_integration(self) -> bool: + """:class:`bool`: Returns ``True`` if the interaction is a guild integration.""" + if self._guild: + return self._guild.id == self._integration_owners.get(0) + + return False + + def is_user_integration(self) -> bool: + """:class:`bool`: Returns ``True`` if the interaction is a user integration.""" + return self.user.id == self._integration_owners.get(1) + + def flatten_handlers(cls: Type[Message]) -> Type[Message]: prefix = len('_handle_') handlers = [ @@ -638,6 +989,9 @@ class MessageApplication: self._icon: Optional[str] = data['icon'] self._cover_image: Optional[str] = data.get('cover_image') + def __str__(self) -> str: + return self.name + def __repr__(self) -> str: return f'' @@ -658,6 +1012,51 @@ class MessageApplication: return None +class CallMessage: + """Represents a message's call data in a private channel from a :class:`~discord.Message`. + + .. versionadded:: 2.5 + + Attributes + ----------- + ended_timestamp: Optional[:class:`datetime.datetime`] + The timestamp the call has ended. + participants: List[:class:`User`] + A list of users that participated in the call. + """ + + __slots__ = ('_message', 'ended_timestamp', 'participants') + + def __repr__(self) -> str: + return f'' + + def __init__(self, *, state: ConnectionState, message: Message, data: CallMessagePayload): + self._message: Message = message + self.ended_timestamp: Optional[datetime.datetime] = utils.parse_time(data.get('ended_timestamp')) + self.participants: List[User] = [] + + for user_id in data['participants']: + user_id = int(user_id) + if user_id == self._message.author.id: + self.participants.append(self._message.author) # type: ignore # can't be a Member here + else: + user = state.get_user(user_id) + if user is not None: + self.participants.append(user) + + @property + def duration(self) -> datetime.timedelta: + """:class:`datetime.timedelta`: The duration the call has lasted or is already ongoing.""" + if self.ended_timestamp is None: + return utils.utcnow() - self._message.created_at + else: + return self.ended_timestamp - self._message.created_at + + def is_ended(self) -> bool: + """:class:`bool`: Whether the call is ended or not.""" + return self.ended_timestamp is not None + + class RoleSubscriptionInfo: """Represents a message's role subscription information. @@ -691,6 +1090,59 @@ class RoleSubscriptionInfo: self.is_renewal: bool = data['is_renewal'] +class GuildProductPurchase: + """Represents a message's guild product that the user has purchased. + + .. versionadded:: 2.5 + + Attributes + ----------- + listing_id: :class:`int` + The ID of the listing that the user has purchased. + product_name: :class:`str` + The name of the product that the user has purchased. + """ + + __slots__ = ('listing_id', 'product_name') + + def __init__(self, data: GuildProductPurchasePayload) -> None: + self.listing_id: int = int(data['listing_id']) + self.product_name: str = data['product_name'] + + def __hash__(self) -> int: + return self.listing_id >> 22 + + def __eq__(self, other: object) -> bool: + return isinstance(other, GuildProductPurchase) and other.listing_id == self.listing_id + + def __ne__(self, other: object) -> bool: + return not self.__eq__(other) + + +class PurchaseNotification: + """Represents a message's purchase notification data. + + This is currently only attached to messages of type :attr:`MessageType.purchase_notification`. + + .. versionadded:: 2.5 + + Attributes + ----------- + guild_product_purchase: Optional[:class:`GuildProductPurchase`] + The guild product purchase that prompted the message. + """ + + __slots__ = ('_type', 'guild_product_purchase') + + def __init__(self, data: PurchaseNotificationResponsePayload) -> None: + self._type: int = data['type'] + + self.guild_product_purchase: Optional[GuildProductPurchase] = None + guild_product_purchase = data.get('guild_product_purchase') + if guild_product_purchase is not None: + self.guild_product_purchase = GuildProductPurchase(guild_product_purchase) + + class PartialMessage(Hashable): """Represents a partial message to aid with working messages when only a message and channel ID are present. @@ -700,6 +1152,7 @@ class PartialMessage(Hashable): - :meth:`TextChannel.get_partial_message` - :meth:`VoiceChannel.get_partial_message` + - :meth:`StageChannel.get_partial_message` - :meth:`Thread.get_partial_message` - :meth:`DMChannel.get_partial_message` @@ -723,7 +1176,7 @@ class PartialMessage(Hashable): Attributes ----------- - channel: Union[:class:`PartialMessageable`, :class:`TextChannel`, :class:`VoiceChannel`, :class:`Thread`, :class:`DMChannel`] + channel: Union[:class:`PartialMessageable`, :class:`TextChannel`, :class:`StageChannel`, :class:`VoiceChannel`, :class:`Thread`, :class:`DMChannel`] The channel associated with this partial message. id: :class:`int` The message ID. @@ -737,6 +1190,7 @@ class PartialMessage(Hashable): if not isinstance(channel, PartialMessageable) and channel.type not in ( ChannelType.text, ChannelType.voice, + ChannelType.stage_voice, ChannelType.news, ChannelType.private, ChannelType.news_thread, @@ -744,7 +1198,7 @@ class PartialMessage(Hashable): ChannelType.private_thread, ): raise TypeError( - f'expected PartialMessageable, TextChannel, VoiceChannel, DMChannel or Thread not {type(channel)!r}' + f'expected PartialMessageable, TextChannel, StageChannel, VoiceChannel, DMChannel or Thread not {type(channel)!r}' ) self.channel: MessageableChannel = channel @@ -776,6 +1230,20 @@ class PartialMessage(Hashable): guild_id = getattr(self.guild, 'id', '@me') return f'https://discord.com/channels/{guild_id}/{self.channel.id}/{self.id}' + @property + def thread(self) -> Optional[Thread]: + """Optional[:class:`Thread`]: The public thread created from this message, if it exists. + + .. note:: + + This does not retrieve archived threads, as they are not retained in the internal + cache. Use :meth:`fetch_thread` instead. + + .. versionadded:: 2.4 + """ + if self.guild is not None: + return self.guild.get_thread(self.id) + async def fetch(self) -> Message: """|coro| @@ -934,6 +1402,8 @@ class PartialMessage(Hashable): Forbidden Tried to suppress a message without permissions or edited a message's content or embed that isn't yours. + NotFound + This message does not exist. TypeError You specified both ``embed`` and ``embeds`` @@ -1213,7 +1683,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. @@ -1252,6 +1722,124 @@ class PartialMessage(Hashable): ) return Thread(guild=self.guild, state=self._state, data=data) + async def fetch_thread(self) -> Thread: + """|coro| + + Retrieves the public thread attached to this message. + + .. note:: + + This method is an API call. For general usage, consider :attr:`thread` instead. + + .. versionadded:: 2.4 + + Raises + ------- + InvalidData + An unknown channel type was received from Discord + or the guild the thread belongs to is not the same + as the one in this object points to. + HTTPException + Retrieving the thread failed. + NotFound + There is no thread attached to this message. + Forbidden + You do not have permission to fetch this channel. + + Returns + -------- + :class:`.Thread` + The public thread attached to this message. + """ + if self.guild is None: + raise ValueError('This message does not have guild info attached.') + + return await self.guild.fetch_channel(self.id) # type: ignore # Can only be Thread in this case + + @overload + async def reply( + self, + content: Optional[str] = ..., + *, + tts: bool = ..., + embed: Embed = ..., + file: File = ..., + stickers: Sequence[Union[GuildSticker, StickerItem]] = ..., + delete_after: float = ..., + nonce: Union[str, int] = ..., + allowed_mentions: AllowedMentions = ..., + reference: Union[Message, MessageReference, PartialMessage] = ..., + mention_author: bool = ..., + view: View = ..., + suppress_embeds: bool = ..., + silent: bool = ..., + poll: Poll = ..., + ) -> Message: + ... + + @overload + async def reply( + self, + content: Optional[str] = ..., + *, + tts: bool = ..., + embed: Embed = ..., + files: Sequence[File] = ..., + stickers: Sequence[Union[GuildSticker, StickerItem]] = ..., + delete_after: float = ..., + nonce: Union[str, int] = ..., + allowed_mentions: AllowedMentions = ..., + reference: Union[Message, MessageReference, PartialMessage] = ..., + mention_author: bool = ..., + view: View = ..., + suppress_embeds: bool = ..., + silent: bool = ..., + poll: Poll = ..., + ) -> Message: + ... + + @overload + async def reply( + self, + content: Optional[str] = ..., + *, + tts: bool = ..., + embeds: Sequence[Embed] = ..., + file: File = ..., + stickers: Sequence[Union[GuildSticker, StickerItem]] = ..., + delete_after: float = ..., + nonce: Union[str, int] = ..., + allowed_mentions: AllowedMentions = ..., + reference: Union[Message, MessageReference, PartialMessage] = ..., + mention_author: bool = ..., + view: View = ..., + suppress_embeds: bool = ..., + silent: bool = ..., + poll: Poll = ..., + ) -> Message: + ... + + @overload + async def reply( + self, + content: Optional[str] = ..., + *, + tts: bool = ..., + embeds: Sequence[Embed] = ..., + files: Sequence[File] = ..., + stickers: Sequence[Union[GuildSticker, StickerItem]] = ..., + delete_after: float = ..., + nonce: Union[str, int] = ..., + allowed_mentions: AllowedMentions = ..., + reference: Union[Message, MessageReference, PartialMessage] = ..., + mention_author: bool = ..., + view: View = ..., + suppress_embeds: bool = ..., + silent: bool = ..., + poll: Poll = ..., + ) -> Message: + ... + async def reply(self, content: Optional[str] = None, **kwargs: Any) -> Message: """|coro| @@ -1283,7 +1871,36 @@ class PartialMessage(Hashable): return await self.channel.send(content, reference=self, **kwargs) - def to_reference(self, *, fail_if_not_exists: bool = True) -> MessageReference: + async def end_poll(self) -> Message: + """|coro| + + Ends the :class:`Poll` attached to this message. + + This can only be done if you are the message author. + + If the poll was successfully ended, then it returns the updated :class:`Message`. + + Raises + ------ + ~discord.HTTPException + Ending the poll failed. + + Returns + ------- + :class:`.Message` + The updated message. + """ + + data = await self._state.http.end_poll(self.channel.id, self.id) + + return Message(state=self._state, channel=self.channel, data=data) + + def to_reference( + self, + *, + fail_if_not_exists: bool = True, + type: MessageReferenceType = MessageReferenceType.reply, + ) -> MessageReference: """Creates a :class:`~discord.MessageReference` from the current message. .. versionadded:: 1.6 @@ -1291,10 +1908,14 @@ class PartialMessage(Hashable): Parameters ---------- fail_if_not_exists: :class:`bool` - Whether replying using the message reference should raise :class:`HTTPException` + Whether the referenced message should raise :class:`HTTPException` if the message no longer exists or Discord could not fetch the message. .. versionadded:: 1.7 + type: :class:`MessageReferenceType` + The type of message reference. + + .. versionadded:: 2.5 Returns --------- @@ -1302,7 +1923,44 @@ class PartialMessage(Hashable): The reference to this message. """ - return MessageReference.from_message(self, fail_if_not_exists=fail_if_not_exists) + return MessageReference.from_message(self, fail_if_not_exists=fail_if_not_exists, type=type) + + async def forward( + self, + destination: MessageableChannel, + *, + fail_if_not_exists: bool = True, + ) -> Message: + """|coro| + + Forwards this message to a channel. + + .. versionadded:: 2.5 + + Parameters + ---------- + destination: :class:`~discord.abc.Messageable` + The channel to forward this message to. + fail_if_not_exists: :class:`bool` + Whether replying using the message reference should raise :class:`HTTPException` + if the message no longer exists or Discord could not fetch the message. + + Raises + ------ + ~discord.HTTPException + Forwarding the message failed. + + Returns + ------- + :class:`.Message` + The message sent to the channel. + """ + reference = self.to_reference( + fail_if_not_exists=fail_if_not_exists, + type=MessageReferenceType.forward, + ) + ret = await destination.send(reference=reference) + return ret def to_message_reference_dict(self) -> MessageReferencePayload: data: MessageReferencePayload = { @@ -1357,13 +2015,20 @@ class Message(PartialMessage, Hashable): A list of embeds the message has. If :attr:`Intents.message_content` is not enabled this will always be an empty list unless the bot is mentioned or the message is a direct message. - channel: Union[:class:`TextChannel`, :class:`VoiceChannel`, :class:`Thread`, :class:`DMChannel`, :class:`GroupChannel`, :class:`PartialMessageable`] + channel: Union[:class:`TextChannel`, :class:`StageChannel`, :class:`VoiceChannel`, :class:`Thread`, :class:`DMChannel`, :class:`GroupChannel`, :class:`PartialMessageable`] The :class:`TextChannel` or :class:`Thread` that the message was sent from. Could be a :class:`DMChannel` or :class:`GroupChannel` if it's a private message. reference: Optional[:class:`~discord.MessageReference`] - The message that this message references. This is only applicable to messages of - type :attr:`MessageType.pins_add`, crossposted messages created by a - followed channel integration, or message replies. + The message that this message references. This is only applicable to + message replies (:attr:`MessageType.reply`), crossposted messages created by + a followed channel integration, forwarded messages, and messages of type: + + - :attr:`MessageType.pins_add` + - :attr:`MessageType.channel_follow_add` + - :attr:`MessageType.thread_created` + - :attr:`MessageType.thread_starter_message` + - :attr:`MessageType.poll_result` + - :attr:`MessageType.context_menu_command` .. versionadded:: 1.5 @@ -1432,10 +2097,6 @@ class Message(PartialMessage, Hashable): If :attr:`Intents.message_content` is not enabled this will always be an empty list unless the bot is mentioned or the message is a direct message. - .. versionadded:: 2.0 - interaction: Optional[:class:`MessageInteraction`] - The interaction that this message is a response to. - .. versionadded:: 2.0 role_subscription: Optional[:class:`RoleSubscriptionInfo`] The data of the role subscription purchase or renewal that prompted this @@ -1454,6 +2115,26 @@ class Message(PartialMessage, Hashable): .. versionadded:: 2.2 guild: Optional[:class:`Guild`] The guild that the message belongs to, if applicable. + interaction_metadata: Optional[:class:`.MessageInteractionMetadata`] + The metadata of the interaction that this message is a response to. + + .. versionadded:: 2.4 + poll: Optional[:class:`Poll`] + The poll attached to this message. + + .. versionadded:: 2.4 + call: Optional[:class:`CallMessage`] + The call associated with this message. + + .. versionadded:: 2.5 + purchase_notification: Optional[:class:`PurchaseNotification`] + The data of the purchase notification that prompted this :attr:`MessageType.purchase_notification` message. + + .. versionadded:: 2.5 + message_snapshots: List[:class:`MessageSnapshot`] + The message snapshots attached to this message. + + .. versionadded:: 2.5 """ __slots__ = ( @@ -1464,6 +2145,7 @@ class Message(PartialMessage, Hashable): '_cs_raw_channel_mentions', '_cs_raw_role_mentions', '_cs_system_content', + '_thread', 'tts', 'content', 'webhook_id', @@ -1483,10 +2165,15 @@ class Message(PartialMessage, Hashable): 'activity', 'stickers', 'components', - 'interaction', + '_interaction', 'role_subscription', 'application_id', 'position', + 'interaction_metadata', + 'poll', + 'call', + 'purchase_notification', + 'message_snapshots', ) if TYPE_CHECKING: @@ -1511,20 +2198,28 @@ class Message(PartialMessage, Hashable): self._state: ConnectionState = state self.webhook_id: Optional[int] = utils._get_as_snowflake(data, 'webhook_id') self.reactions: List[Reaction] = [Reaction(message=self, data=d) for d in data.get('reactions', [])] - self.attachments: List[Attachment] = [Attachment(data=a, state=self._state) for a in data['attachments']] - self.embeds: List[Embed] = [Embed.from_dict(a) for a in data['embeds']] + self.attachments: List[Attachment] = [Attachment(data=a, state=self._state) for a in data.get('attachments', [])] + self.embeds: List[Embed] = [Embed.from_dict(a) for a in data.get('embeds', [])] self.activity: Optional[MessageActivityPayload] = data.get('activity') - self._edited_timestamp: Optional[datetime.datetime] = utils.parse_time(data['edited_timestamp']) + self._edited_timestamp: Optional[datetime.datetime] = utils.parse_time(data.get('edited_timestamp')) self.type: MessageType = try_enum(MessageType, data['type']) - self.pinned: bool = data['pinned'] + self.pinned: bool = data.get('pinned', False) self.flags: MessageFlags = MessageFlags._from_value(data.get('flags', 0)) - self.mention_everyone: bool = data['mention_everyone'] - self.tts: bool = data['tts'] + self.mention_everyone: bool = data.get('mention_everyone', False) + self.tts: bool = data.get('tts', False) self.content: str = data['content'] self.nonce: Optional[Union[int, str]] = data.get('nonce') self.position: Optional[int] = data.get('position') self.application_id: Optional[int] = utils._get_as_snowflake(data, 'application_id') self.stickers: List[StickerItem] = [StickerItem(data=d, state=state) for d in data.get('sticker_items', [])] + self.message_snapshots: List[MessageSnapshot] = MessageSnapshot._from_value(state, data.get('message_snapshots')) + + self.poll: Optional[Poll] = None + try: + poll = data['poll'] # pyright: ignore[reportTypedDictNotRequiredAccess] + self.poll = Poll._from_data(data=poll, message=self, state=state) + except KeyError: + pass try: # if the channel doesn't have a guild attribute, we handle that @@ -1532,23 +2227,47 @@ class Message(PartialMessage, Hashable): except AttributeError: self.guild = state._get_guild(utils._get_as_snowflake(data, 'guild_id')) - self.interaction: Optional[MessageInteraction] = None + self._thread: Optional[Thread] = None + + if self.guild is not None: + try: + thread = data['thread'] # pyright: ignore[reportTypedDictNotRequiredAccess] + except KeyError: + pass + else: + self._thread = self.guild.get_thread(int(thread['id'])) + + if self._thread is not None: + self._thread._update(thread) + else: + self._thread = Thread(guild=self.guild, state=state, data=thread) + self._interaction: Optional[MessageInteraction] = None + + # deprecated try: - interaction = data['interaction'] + interaction = data['interaction'] # pyright: ignore[reportTypedDictNotRequiredAccess] except KeyError: pass else: - self.interaction = MessageInteraction(state=state, guild=self.guild, data=interaction) + self._interaction = MessageInteraction(state=state, guild=self.guild, data=interaction) + self.interaction_metadata: Optional[MessageInteractionMetadata] = None try: - ref = data['message_reference'] + interaction_metadata = data['interaction_metadata'] # pyright: ignore[reportTypedDictNotRequiredAccess] + except KeyError: + pass + else: + self.interaction_metadata = MessageInteractionMetadata(state=state, guild=self.guild, data=interaction_metadata) + + try: + ref = data['message_reference'] # pyright: ignore[reportTypedDictNotRequiredAccess] except KeyError: self.reference = None else: self.reference = ref = MessageReference.with_state(state, ref) try: - resolved = data['referenced_message'] + resolved = data['referenced_message'] # pyright: ignore[reportTypedDictNotRequiredAccess] except KeyError: pass else: @@ -1566,9 +2285,16 @@ class Message(PartialMessage, Hashable): # the channel will be the correct type here ref.resolved = self.__class__(channel=chan, data=resolved, state=state) # type: ignore + if self.type is MessageType.poll_result: + if isinstance(self.reference.resolved, self.__class__): + self._state._update_poll_results(self, self.reference.resolved) + else: + if self.reference.message_id: + self._state._update_poll_results(self, self.reference.message_id) + self.application: Optional[MessageApplication] = None try: - application = data['application'] + application = data['application'] # pyright: ignore[reportTypedDictNotRequiredAccess] except KeyError: pass else: @@ -1576,15 +2302,23 @@ class Message(PartialMessage, Hashable): self.role_subscription: Optional[RoleSubscriptionInfo] = None try: - role_subscription = data['role_subscription_data'] + role_subscription = data['role_subscription_data'] # pyright: ignore[reportTypedDictNotRequiredAccess] except KeyError: pass else: self.role_subscription = RoleSubscriptionInfo(role_subscription) - for handler in ('author', 'member', 'mentions', 'mention_roles', 'components'): + self.purchase_notification: Optional[PurchaseNotification] = None + try: + purchase_notification = data['purchase_notification'] # pyright: ignore[reportTypedDictNotRequiredAccess] + except KeyError: + pass + else: + self.purchase_notification = PurchaseNotification(purchase_notification) + + for handler in ('author', 'member', 'mentions', 'mention_roles', 'components', 'call'): try: - getattr(self, f'_handle_{handler}')(data[handler]) + getattr(self, f'_handle_{handler}')(data[handler]) # type: ignore except KeyError: continue @@ -1708,7 +2442,7 @@ class Message(PartialMessage, Hashable): self.nonce = value def _handle_author(self, author: UserPayload) -> None: - self.author = self._state.store_user(author) + self.author = self._state.store_user(author, cache=self.webhook_id is None) if isinstance(self.guild, Guild): found = self.guild.get_member(self.author.id) if found is not None: @@ -1727,7 +2461,6 @@ class Message(PartialMessage, Hashable): author._update_from_message(member) # type: ignore except AttributeError: # It's a user here - # TODO: consider adding to cache here self.author = Member._from_message(message=self, data=member) def _handle_mentions(self, mentions: List[UserWithMemberPayload]) -> None: @@ -1764,7 +2497,17 @@ class Message(PartialMessage, Hashable): self.components.append(component) def _handle_interaction(self, data: MessageInteractionPayload): - self.interaction = MessageInteraction(state=self._state, guild=self.guild, data=data) + self._interaction = MessageInteraction(state=self._state, guild=self.guild, data=data) + + def _handle_interaction_metadata(self, data: MessageInteractionMetadataPayload): + self.interaction_metadata = MessageInteractionMetadata(state=self._state, guild=self.guild, data=data) + + def _handle_call(self, data: CallMessagePayload): + self.call: Optional[CallMessage] + if data is not None: + self.call = CallMessage(state=self._state, message=self, data=data) + else: + self.call = None def _rebind_cached_references( self, @@ -1875,6 +2618,32 @@ class Message(PartialMessage, Hashable): """Optional[:class:`datetime.datetime`]: An aware UTC datetime object containing the edited time of the message.""" return self._edited_timestamp + @property + def thread(self) -> Optional[Thread]: + """Optional[:class:`Thread`]: The public thread created from this message, if it exists. + + .. note:: + + For messages received via the gateway this does not retrieve archived threads, as they + are not retained in the internal cache. Use :meth:`fetch_thread` instead. + + .. versionadded:: 2.4 + """ + if self.guild is not None: + # Fall back to guild threads in case one was created after the message + return self._thread or self.guild.get_thread(self.id) + + @property + @deprecated('interaction_metadata') + def interaction(self) -> Optional[MessageInteraction]: + """Optional[:class:`~discord.MessageInteraction`]: The interaction that this message is a response to. + + .. versionadded:: 2.0 + .. deprecated:: 2.4 + This attribute is deprecated and will be removed in a future version. Use :attr:`.interaction_metadata` instead. + """ + return self._interaction + def is_system(self) -> bool: """:class:`bool`: Whether the message is a system message. @@ -1889,6 +2658,7 @@ class Message(PartialMessage, Hashable): MessageType.chat_input_command, MessageType.context_menu_command, MessageType.thread_starter_message, + MessageType.poll_result, ) @utils.cached_slot_property('_cs_system_content') @@ -1923,7 +2693,7 @@ class Message(PartialMessage, Hashable): return f'{self.author.name} changed the channel name: **{self.content}**' if self.type is MessageType.channel_icon_change: - return f'{self.author.name} changed the channel icon.' + return f'{self.author.name} changed the group icon.' if self.type is MessageType.pins_add: return f'{self.author.name} pinned a message to this channel.' @@ -2010,10 +2780,68 @@ class Message(PartialMessage, Hashable): return 'Wondering who to invite?\nStart by inviting anyone who can help you build the server!' if self.type is MessageType.role_subscription_purchase and self.role_subscription is not None: - # TODO: figure out how the message looks like for is_renewal: true total_months = self.role_subscription.total_months_subscribed months = '1 month' if total_months == 1 else f'{total_months} months' - return f'{self.author.name} joined {self.role_subscription.tier_name} and has been a subscriber of {self.guild} for {months}!' + action = 'renewed' if self.role_subscription.is_renewal else 'joined' + return f'{self.author.name} {action} **{self.role_subscription.tier_name}** and has been a subscriber of {self.guild} for {months}!' + + if self.type is MessageType.stage_start: + return f'{self.author.name} started **{self.content}**.' + + if self.type is MessageType.stage_end: + return f'{self.author.name} ended **{self.content}**.' + + if self.type is MessageType.stage_speaker: + return f'{self.author.name} is now a speaker.' + + if self.type is MessageType.stage_raise_hand: + return f'{self.author.name} requested to speak.' + + if self.type is MessageType.stage_topic: + return f'{self.author.name} changed Stage topic: **{self.content}**.' + + if self.type is MessageType.guild_incident_alert_mode_enabled: + dt = utils.parse_time(self.content) + dt_content = utils.format_dt(dt) + return f'{self.author.name} enabled security actions until {dt_content}.' + + if self.type is MessageType.guild_incident_alert_mode_disabled: + return f'{self.author.name} disabled security actions.' + + if self.type is MessageType.guild_incident_report_raid: + return f'{self.author.name} reported a raid in {self.guild}.' + + if self.type is MessageType.guild_incident_report_false_alarm: + return f'{self.author.name} reported a false alarm in {self.guild}.' + + if self.type is MessageType.call: + call_ended = self.call.ended_timestamp is not None # type: ignore # call can't be None here + missed = self._state.user not in self.call.participants # type: ignore # call can't be None here + + if call_ended: + duration = utils._format_call_duration(self.call.duration) # type: ignore # call can't be None here + if missed: + return 'You missed a call from {0.author.name} that lasted {1}.'.format(self, duration) + else: + return '{0.author.name} started a call that lasted {1}.'.format(self, duration) + else: + if missed: + return '{0.author.name} started a call. \N{EM DASH} Join the call'.format(self) + else: + return '{0.author.name} started a call.'.format(self) + + if self.type is MessageType.purchase_notification and self.purchase_notification is not None: + guild_product_purchase = self.purchase_notification.guild_product_purchase + if guild_product_purchase is not None: + return f'{self.author.name} has purchased {guild_product_purchase.product_name}!' + + if self.type is MessageType.poll_result: + embed = self.embeds[0] # Will always have 1 embed + poll_title = utils.get( + embed.fields, + name='poll_question_text', + ) + return f'{self.author.display_name}\'s poll {poll_title.value} has closed.' # type: ignore # Fallback for unknown message types return '' @@ -2125,6 +2953,8 @@ class Message(PartialMessage, Hashable): Forbidden Tried to suppress a message without permissions or edited a message's content or embed that isn't yours. + NotFound + This message does not exist. TypeError You specified both ``embed`` and ``embeds`` diff --git a/discord/object.py b/discord/object.py index 2243a0408..885ad4dc2 100644 --- a/discord/object.py +++ b/discord/object.py @@ -102,7 +102,7 @@ class Object(Hashable): return f'' def __eq__(self, other: object) -> bool: - if isinstance(other, self.type): + if isinstance(other, (self.type, self.__class__)): return self.id == other.id return NotImplemented diff --git a/discord/oggparse.py b/discord/oggparse.py index 09ee8b984..25c49ee50 100644 --- a/discord/oggparse.py +++ b/discord/oggparse.py @@ -99,7 +99,7 @@ class OggStream: elif not head: return None else: - raise OggError('invalid header magic') + raise OggError(f'invalid header magic {head}') def _iter_pages(self) -> Generator[OggPage, None, None]: page = self._next_page() diff --git a/discord/opus.py b/discord/opus.py index 7cbdd8386..971675f73 100644 --- a/discord/opus.py +++ b/discord/opus.py @@ -39,10 +39,17 @@ from .errors import DiscordException if TYPE_CHECKING: T = TypeVar('T') + APPLICATION_CTL = Literal['audio', 'voip', 'lowdelay'] BAND_CTL = Literal['narrow', 'medium', 'wide', 'superwide', 'full'] SIGNAL_CTL = Literal['auto', 'voice', 'music'] +class ApplicationCtl(TypedDict): + audio: int + voip: int + lowdelay: int + + class BandCtl(TypedDict): narrow: int medium: int @@ -65,6 +72,8 @@ __all__ = ( _log = logging.getLogger(__name__) +OPUS_SILENCE = b'\xF8\xFF\xFE' + c_int_ptr = ctypes.POINTER(ctypes.c_int) c_int16_ptr = ctypes.POINTER(ctypes.c_int16) c_float_ptr = ctypes.POINTER(ctypes.c_float) @@ -90,9 +99,10 @@ OK = 0 BAD_ARG = -1 # Encoder CTLs -APPLICATION_AUDIO = 2049 -APPLICATION_VOIP = 2048 -APPLICATION_LOWDELAY = 2051 +APPLICATION_AUDIO = 'audio' +APPLICATION_VOIP = 'voip' +APPLICATION_LOWDELAY = 'lowdelay' +# These remain as strings for backwards compat CTL_SET_BITRATE = 4002 CTL_SET_BANDWIDTH = 4008 @@ -105,6 +115,12 @@ CTL_SET_GAIN = 4034 CTL_LAST_PACKET_DURATION = 4039 # fmt: on +application_ctl: ApplicationCtl = { + 'audio': 2049, + 'voip': 2048, + 'lowdelay': 2051, +} + band_ctl: BandCtl = { 'narrow': 1101, 'medium': 1102, @@ -319,16 +335,38 @@ class _OpusStruct: class Encoder(_OpusStruct): - def __init__(self, application: int = APPLICATION_AUDIO): - _OpusStruct.get_opus_version() - - self.application: int = application + def __init__( + self, + *, + application: APPLICATION_CTL = 'audio', + bitrate: int = 128, + fec: bool = True, + expected_packet_loss: float = 0.15, + bandwidth: BAND_CTL = 'full', + signal_type: SIGNAL_CTL = 'auto', + ): + if application not in application_ctl: + raise ValueError(f'{application} is not a valid application setting. Try one of: {"".join(application_ctl)}') + + if not 16 <= bitrate <= 512: + raise ValueError(f'bitrate must be between 16 and 512, not {bitrate}') + + if not 0 < expected_packet_loss <= 1.0: + raise ValueError( + f'expected_packet_loss must be a positive number less than or equal to 1, not {expected_packet_loss}' + ) + + _OpusStruct.get_opus_version() # lazy loads the opus library + + self.application: int = application_ctl[application] self._state: EncoderStruct = self._create_state() - self.set_bitrate(128) - self.set_fec(True) - self.set_expected_packet_loss_percent(0.15) - self.set_bandwidth('full') - self.set_signal_type('auto') + + self.set_bitrate(bitrate) + self.set_fec(fec) + if fec: + self.set_expected_packet_loss_percent(expected_packet_loss) + self.set_bandwidth(bandwidth) + self.set_signal_type(signal_type) def __del__(self) -> None: if hasattr(self, '_state'): @@ -355,7 +393,7 @@ class Encoder(_OpusStruct): def set_signal_type(self, req: SIGNAL_CTL) -> None: if req not in signal_ctl: - raise KeyError(f'{req!r} is not a valid bandwidth setting. Try one of: {",".join(signal_ctl)}') + raise KeyError(f'{req!r} is not a valid signal type setting. Try one of: {",".join(signal_ctl)}') k = signal_ctl[req] _lib.opus_encoder_ctl(self._state, CTL_SET_SIGNAL, k) @@ -454,7 +492,9 @@ class Decoder(_OpusStruct): channel_count = self.CHANNELS else: frames = self.packet_get_nb_frames(data) - channel_count = self.packet_get_nb_channels(data) + # Discord silent frames erroneously present themselves as 1 channel instead of 2 + # Therefore we need to hardcode the number instead of using packet_get_nb_channels + channel_count = self.CHANNELS samples_per_frame = self.packet_get_samples_per_frame(data) frame_size = frames * samples_per_frame diff --git a/discord/partial_emoji.py b/discord/partial_emoji.py index 17c528c52..7d366949c 100644 --- a/discord/partial_emoji.py +++ b/discord/partial_emoji.py @@ -94,7 +94,7 @@ class PartialEmoji(_EmojiTag, AssetMixin): __slots__ = ('animated', 'name', 'id', '_state') - _CUSTOM_EMOJI_RE = re.compile(r'a)?:?(?P[A-Za-z0-9\_]+):(?P[0-9]{13,20})>?') + _CUSTOM_EMOJI_RE = re.compile(r'a)?:)?(?P[A-Za-z0-9\_]+):(?P[0-9]{13,20})>?') if TYPE_CHECKING: id: Optional[int] diff --git a/discord/permissions.py b/discord/permissions.py index 74c7173e4..c234ad5f3 100644 --- a/discord/permissions.py +++ b/discord/permissions.py @@ -119,6 +119,12 @@ class Permissions(BaseFlags): to be, for example, constructed as a dict or a list of pairs. Note that aliases are not shown. + .. describe:: bool(b) + + Returns whether the permissions object has any permissions set to ``True``. + + .. versionadded:: 2.0 + Attributes ----------- value: :class:`int` @@ -135,9 +141,12 @@ class Permissions(BaseFlags): self.value = permissions for key, value in kwargs.items(): - if key not in self.VALID_FLAGS: - raise TypeError(f'{key!r} is not a valid permission name.') - setattr(self, key, value) + try: + flag = self.VALID_FLAGS[key] + except KeyError: + raise TypeError(f'{key!r} is not a valid permission name.') from None + else: + self._set_flag(flag, value) def is_subset(self, other: Permissions) -> bool: """Returns ``True`` if self has the same or fewer permissions as other.""" @@ -177,7 +186,8 @@ class Permissions(BaseFlags): """A factory method that creates a :class:`Permissions` with all permissions set to ``True``. """ - return cls(0b11111111111111111111111111111111111111111) + # Some of these are 0 because we don't want to set unnecessary bits + return cls(0b0000_0000_0000_0110_0111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111) @classmethod def _timeout_mask(cls) -> int: @@ -198,13 +208,29 @@ class Permissions(BaseFlags): base.send_messages_in_threads = False return base + @classmethod + def _user_installed_permissions(cls, *, in_guild: bool) -> Self: + base = cls.none() + base.send_messages = True + base.attach_files = True + base.embed_links = True + base.external_emojis = True + base.send_voice_messages = True + if in_guild: + # Logically this is False but if not set to True, + # permissions just become 0. + base.read_messages = True + base.send_tts_messages = True + base.send_messages_in_threads = True + return base + @classmethod def all_channel(cls) -> Self: """A :class:`Permissions` with all channel-specific permissions set to ``True`` and the guild-specific ones set to ``False``. The guild-specific permissions are currently: - - :attr:`manage_emojis` + - :attr:`manage_expressions` - :attr:`view_audit_log` - :attr:`view_guild_insights` - :attr:`manage_guild` @@ -213,6 +239,11 @@ class Permissions(BaseFlags): - :attr:`kick_members` - :attr:`ban_members` - :attr:`administrator` + - :attr:`create_expressions` + - :attr:`moderate_members` + - :attr:`create_events` + - :attr:`manage_events` + - :attr:`view_creator_monetization_analytics` .. versionchanged:: 1.7 Added :attr:`stream`, :attr:`priority_speaker` and :attr:`use_application_commands` permissions. @@ -221,8 +252,15 @@ class Permissions(BaseFlags): Added :attr:`create_public_threads`, :attr:`create_private_threads`, :attr:`manage_threads`, :attr:`use_external_stickers`, :attr:`send_messages_in_threads` and :attr:`request_to_speak` permissions. + + .. versionchanged:: 2.3 + Added :attr:`use_soundboard`, :attr:`create_expressions` permissions. + + .. versionchanged:: 2.4 + Added :attr:`send_polls`, :attr:`send_voice_messages`, attr:`use_external_sounds`, + :attr:`use_embedded_activities`, and :attr:`use_external_apps` permissions. """ - return cls(0b111110110110011111101111111111101010001) + return cls(0b0000_0000_0000_0110_0110_0100_1111_1101_1011_0011_1111_0111_1111_1111_0101_0001) @classmethod def general(cls) -> Self: @@ -234,8 +272,14 @@ class Permissions(BaseFlags): permissions :attr:`administrator`, :attr:`create_instant_invite`, :attr:`kick_members`, :attr:`ban_members`, :attr:`change_nickname` and :attr:`manage_nicknames` are no longer part of the general permissions. + + .. versionchanged:: 2.3 + Added :attr:`create_expressions` permission. + + .. versionchanged:: 2.4 + Added :attr:`view_creator_monetization_analytics` permission. """ - return cls(0b01110000000010000000010010110000) + return cls(0b0000_0000_0000_0000_0000_1010_0000_0000_0111_0000_0000_1000_0000_0100_1011_0000) @classmethod def membership(cls) -> Self: @@ -244,7 +288,7 @@ class Permissions(BaseFlags): .. versionadded:: 1.7 """ - return cls(0b10000000000001100000000000000000000000111) + return cls(0b0000_0000_0000_0000_0000_0001_0000_0000_0000_1100_0000_0000_0000_0000_0000_0111) @classmethod def text(cls) -> Self: @@ -258,14 +302,20 @@ class Permissions(BaseFlags): .. versionchanged:: 2.0 Added :attr:`create_public_threads`, :attr:`create_private_threads`, :attr:`manage_threads`, :attr:`send_messages_in_threads` and :attr:`use_external_stickers` permissions. + + .. versionchanged:: 2.3 + Added :attr:`send_voice_messages` permission. + + .. versionchanged:: 2.4 + Added :attr:`send_polls` and :attr:`use_external_apps` permissions. """ - return cls(0b111110010000000000001111111100001000000) + return cls(0b0000_0000_0000_0110_0100_0000_0111_1100_1000_0000_0000_0111_1111_1000_0100_0000) @classmethod def voice(cls) -> Self: """A factory method that creates a :class:`Permissions` with all "Voice" permissions from the official Discord UI set to ``True``.""" - return cls(0b1000000000000011111100000000001100000000) + return cls(0b0000_0000_0000_0000_0010_0100_1000_0000_0000_0011_1111_0000_0000_0011_0000_0000) @classmethod def stage(cls) -> Self: @@ -290,7 +340,7 @@ class Permissions(BaseFlags): .. versionchanged:: 2.0 Added :attr:`manage_channels` permission and removed :attr:`request_to_speak` permission. """ - return cls(0b1010000000000000000010000) + return cls(0b0000_0000_0000_0000_0000_0000_0000_0000_0000_0001_0100_0000_0000_0000_0001_0000) @classmethod def elevated(cls) -> Self: @@ -305,13 +355,32 @@ class Permissions(BaseFlags): - :attr:`manage_messages` - :attr:`manage_roles` - :attr:`manage_webhooks` - - :attr:`manage_emojis_and_stickers` + - :attr:`manage_expressions` - :attr:`manage_threads` - :attr:`moderate_members` .. versionadded:: 2.0 """ - return cls(0b10000010001110000000000000010000000111110) + return cls(0b0000_0000_0000_0000_0000_0001_0000_0100_0111_0000_0000_0000_0010_0000_0011_1110) + + @classmethod + def apps(cls) -> Self: + """A factory method that creates a :class:`Permissions` with all + "Apps" permissions from the official Discord UI set to ``True``. + + + .. versionadded:: 2.6 + """ + return cls(0b0000_0000_0000_0100_0000_0000_1000_0000_1000_0000_0000_0000_0000_0000_0000_0000) + + @classmethod + def events(cls) -> Self: + """A factory method that creates a :class:`Permissions` with all + "Events" permissions from the official Discord UI set to ``True``. + + .. versionadded:: 2.4 + """ + return cls(0b0000_0000_0000_0000_0001_0000_0000_0010_0000_0000_0000_0000_0000_0000_0000_0000) @classmethod def advanced(cls) -> Self: @@ -335,8 +404,9 @@ class Permissions(BaseFlags): A list of key/value pairs to bulk update permissions with. """ for key, value in kwargs.items(): - if key in self.VALID_FLAGS: - setattr(self, key, value) + flag = self.VALID_FLAGS.get(key) + if flag is not None: + self._set_flag(flag, value) def handle_overwrite(self, allow: int, deny: int) -> None: # Basically this is what's happening here. @@ -544,13 +614,21 @@ class Permissions(BaseFlags): return 1 << 29 @flag_value + def manage_expressions(self) -> int: + """:class:`bool`: Returns ``True`` if a user can edit or delete emojis, stickers, and soundboard sounds. + + .. versionadded:: 2.3 + """ + return 1 << 30 + + @make_permission_alias('manage_expressions') def manage_emojis(self) -> int: - """:class:`bool`: Returns ``True`` if a user can create, edit, or delete emojis.""" + """:class:`bool`: An alias for :attr:`manage_expressions`.""" return 1 << 30 - @make_permission_alias('manage_emojis') + @make_permission_alias('manage_expressions') def manage_emojis_and_stickers(self) -> int: - """:class:`bool`: An alias for :attr:`manage_emojis`. + """:class:`bool`: An alias for :attr:`manage_expressions`. .. versionadded:: 2.0 """ @@ -644,6 +722,78 @@ class Permissions(BaseFlags): """ return 1 << 40 + @flag_value + def view_creator_monetization_analytics(self) -> int: + """:class:`bool`: Returns ``True`` if a user can view role subscription insights. + + .. versionadded:: 2.4 + """ + return 1 << 41 + + @flag_value + def use_soundboard(self) -> int: + """:class:`bool`: Returns ``True`` if a user can use the soundboard. + + .. versionadded:: 2.3 + """ + return 1 << 42 + + @flag_value + def create_expressions(self) -> int: + """:class:`bool`: Returns ``True`` if a user can create emojis, stickers, and soundboard sounds. + + .. versionadded:: 2.3 + """ + return 1 << 43 + + @flag_value + def create_events(self) -> int: + """:class:`bool`: Returns ``True`` if a user can create guild events. + + .. versionadded:: 2.4 + """ + return 1 << 44 + + @flag_value + def use_external_sounds(self) -> int: + """:class:`bool`: Returns ``True`` if a user can use sounds from other guilds. + + .. versionadded:: 2.3 + """ + return 1 << 45 + + @flag_value + def send_voice_messages(self) -> int: + """:class:`bool`: Returns ``True`` if a user can send voice messages. + + .. versionadded:: 2.3 + """ + return 1 << 46 + + @flag_value + def send_polls(self) -> int: + """:class:`bool`: Returns ``True`` if a user can send poll messages. + + .. versionadded:: 2.4 + """ + return 1 << 49 + + @make_permission_alias('send_polls') + def create_polls(self) -> int: + """:class:`bool`: An alias for :attr:`send_polls`. + + .. versionadded:: 2.4 + """ + return 1 << 49 + + @flag_value + def use_external_apps(self) -> int: + """:class:`bool`: Returns ``True`` if a user can use external apps. + + .. versionadded:: 2.4 + """ + return 1 << 50 + def _augment_from_permissions(cls): cls.VALID_NAMES = set(Permissions.VALID_FLAGS) @@ -745,6 +895,7 @@ class PermissionOverwrite: manage_roles: Optional[bool] manage_permissions: Optional[bool] manage_webhooks: Optional[bool] + manage_expressions: Optional[bool] manage_emojis: Optional[bool] manage_emojis_and_stickers: Optional[bool] use_application_commands: Optional[bool] @@ -758,6 +909,14 @@ class PermissionOverwrite: use_external_stickers: Optional[bool] use_embedded_activities: Optional[bool] moderate_members: Optional[bool] + use_soundboard: Optional[bool] + use_external_sounds: Optional[bool] + send_voice_messages: Optional[bool] + create_expressions: Optional[bool] + create_events: Optional[bool] + send_polls: Optional[bool] + create_polls: Optional[bool] + use_external_apps: Optional[bool] def __init__(self, **kwargs: Optional[bool]): self._values: Dict[str, Optional[bool]] = {} diff --git a/discord/player.py b/discord/player.py index 2030442af..bad6da88e 100644 --- a/discord/player.py +++ b/discord/player.py @@ -25,6 +25,7 @@ from __future__ import annotations import threading import subprocess +import warnings import audioop import asyncio import logging @@ -39,7 +40,7 @@ from typing import Any, Callable, Generic, IO, Optional, TYPE_CHECKING, Tuple, T from .enums import SpeakingState from .errors import ClientException -from .opus import Encoder as OpusEncoder +from .opus import Encoder as OpusEncoder, OPUS_SILENCE from .oggparse import OggStream from .utils import MISSING @@ -145,6 +146,8 @@ class FFmpegAudio(AudioSource): .. versionadded:: 1.3 """ + BLOCKSIZE: int = io.DEFAULT_BUFFER_SIZE + def __init__( self, source: Union[str, io.BufferedIOBase], @@ -153,12 +156,25 @@ class FFmpegAudio(AudioSource): args: Any, **subprocess_kwargs: Any, ): - piping = subprocess_kwargs.get('stdin') == subprocess.PIPE - if piping and isinstance(source, str): + piping_stdin = subprocess_kwargs.get('stdin') == subprocess.PIPE + if piping_stdin and isinstance(source, str): raise TypeError("parameter conflict: 'source' parameter cannot be a string when piping to stdin") + stderr: Optional[IO[bytes]] = subprocess_kwargs.pop('stderr', None) + + if stderr == subprocess.PIPE: + warnings.warn("Passing subprocess.PIPE does nothing", DeprecationWarning, stacklevel=3) + stderr = None + + piping_stderr = False + if stderr is not None: + try: + stderr.fileno() + except Exception: + piping_stderr = True + args = [executable, *args] - kwargs = {'stdout': subprocess.PIPE} + kwargs = {'stdout': subprocess.PIPE, 'stderr': subprocess.PIPE if piping_stderr else stderr} kwargs.update(subprocess_kwargs) # Ensure attribute is assigned even in the case of errors @@ -166,15 +182,24 @@ class FFmpegAudio(AudioSource): self._process = self._spawn_process(args, **kwargs) self._stdout: IO[bytes] = self._process.stdout # type: ignore # process stdout is explicitly set self._stdin: Optional[IO[bytes]] = None - self._pipe_thread: Optional[threading.Thread] = None + self._stderr: Optional[IO[bytes]] = None + self._pipe_writer_thread: Optional[threading.Thread] = None + self._pipe_reader_thread: Optional[threading.Thread] = None - if piping: - n = f'popen-stdin-writer:{id(self):#x}' + if piping_stdin: + n = f'popen-stdin-writer:pid-{self._process.pid}' self._stdin = self._process.stdin - self._pipe_thread = threading.Thread(target=self._pipe_writer, args=(source,), daemon=True, name=n) - self._pipe_thread.start() + self._pipe_writer_thread = threading.Thread(target=self._pipe_writer, args=(source,), daemon=True, name=n) + self._pipe_writer_thread.start() + + if piping_stderr: + n = f'popen-stderr-reader:pid-{self._process.pid}' + self._stderr = self._process.stderr + self._pipe_reader_thread = threading.Thread(target=self._pipe_reader, args=(stderr,), daemon=True, name=n) + self._pipe_reader_thread.start() def _spawn_process(self, args: Any, **subprocess_kwargs: Any) -> subprocess.Popen: + _log.debug('Spawning ffmpeg process with command: %s', args) process = None try: process = subprocess.Popen(args, creationflags=CREATE_NO_WINDOW, **subprocess_kwargs) @@ -187,7 +212,8 @@ class FFmpegAudio(AudioSource): return process def _kill_process(self) -> None: - proc = self._process + # this function gets called in __del__ so instance attributes might not even exist + proc = getattr(self, '_process', MISSING) if proc is MISSING: return @@ -207,10 +233,10 @@ class FFmpegAudio(AudioSource): def _pipe_writer(self, source: io.BufferedIOBase) -> None: while self._process: - # arbitrarily large read size - data = source.read(8192) + data = source.read(self.BLOCKSIZE) if not data: - self._process.terminate() + if self._stdin is not None: + self._stdin.close() return try: if self._stdin is not None: @@ -221,9 +247,27 @@ class FFmpegAudio(AudioSource): self._process.terminate() return + def _pipe_reader(self, dest: IO[bytes]) -> None: + while self._process: + if self._stderr is None: + return + try: + data: bytes = self._stderr.read(self.BLOCKSIZE) + except Exception: + _log.debug('Read error for %s, this is probably not a problem', self, exc_info=True) + return + if data is None: + return + try: + dest.write(data) + except Exception: + _log.exception('Write error for %s', self) + self._stderr.close() + return + def cleanup(self) -> None: self._kill_process() - self._process = self._stdout = self._stdin = MISSING + self._process = self._stdout = self._stdin = self._stderr = MISSING class FFmpegPCMAudio(FFmpegAudio): @@ -244,12 +288,17 @@ class FFmpegPCMAudio(FFmpegAudio): passed to the stdin of ffmpeg. executable: :class:`str` The executable name (and path) to use. Defaults to ``ffmpeg``. + + .. warning:: + + Since this class spawns a subprocess, care should be taken to not + pass in an arbitrary executable name when using this parameter. + pipe: :class:`bool` If ``True``, denotes that ``source`` parameter will be passed to the stdin of ffmpeg. Defaults to ``False``. stderr: Optional[:term:`py:file object`] A file-like object to pass to the Popen constructor. - Could also be an instance of ``subprocess.PIPE``. before_options: Optional[:class:`str`] Extra command line arguments to pass to ffmpeg before the ``-i`` flag. options: Optional[:class:`str`] @@ -267,7 +316,7 @@ class FFmpegPCMAudio(FFmpegAudio): *, executable: str = 'ffmpeg', pipe: bool = False, - stderr: Optional[IO[str]] = None, + stderr: Optional[IO[bytes]] = None, before_options: Optional[str] = None, options: Optional[str] = None, ) -> None: @@ -279,7 +328,14 @@ class FFmpegPCMAudio(FFmpegAudio): args.append('-i') args.append('-' if pipe else source) - args.extend(('-f', 's16le', '-ar', '48000', '-ac', '2', '-loglevel', 'warning')) + + # fmt: off + args.extend(('-f', 's16le', + '-ar', '48000', + '-ac', '2', + '-loglevel', 'warning', + '-blocksize', str(self.BLOCKSIZE))) + # fmt: on if isinstance(options, str): args.extend(shlex.split(options)) @@ -342,12 +398,17 @@ class FFmpegOpusAudio(FFmpegAudio): executable: :class:`str` The executable name (and path) to use. Defaults to ``ffmpeg``. + + .. warning:: + + Since this class spawns a subprocess, care should be taken to not + pass in an arbitrary executable name when using this parameter. + pipe: :class:`bool` If ``True``, denotes that ``source`` parameter will be passed to the stdin of ffmpeg. Defaults to ``False``. stderr: Optional[:term:`py:file object`] A file-like object to pass to the Popen constructor. - Could also be an instance of ``subprocess.PIPE``. before_options: Optional[:class:`str`] Extra command line arguments to pass to ffmpeg before the ``-i`` flag. options: Optional[:class:`str`] @@ -380,7 +441,7 @@ class FFmpegOpusAudio(FFmpegAudio): args.append('-i') args.append('-' if pipe else source) - codec = 'copy' if codec in ('opus', 'libopus') else 'libopus' + codec = 'copy' if codec in ('opus', 'libopus', 'copy') else 'libopus' bitrate = bitrate if bitrate is not None else 128 # fmt: off @@ -390,7 +451,10 @@ class FFmpegOpusAudio(FFmpegAudio): '-ar', '48000', '-ac', '2', '-b:a', f'{bitrate}k', - '-loglevel', 'warning')) + '-loglevel', 'warning', + '-fec', 'true', + '-packet_loss', '15', + '-blocksize', str(self.BLOCKSIZE))) # fmt: on if isinstance(options, str): @@ -524,22 +588,26 @@ class FFmpegOpusAudio(FFmpegAudio): loop = asyncio.get_running_loop() try: codec, bitrate = await loop.run_in_executor(None, lambda: probefunc(source, executable)) - except Exception: + except (KeyboardInterrupt, SystemExit): + raise + except BaseException: if not fallback: _log.exception("Probe '%s' using '%s' failed", method, executable) - return # type: ignore + return None, None _log.exception("Probe '%s' using '%s' failed, trying fallback", method, executable) try: codec, bitrate = await loop.run_in_executor(None, lambda: fallback(source, executable)) - except Exception: + except (KeyboardInterrupt, SystemExit): + raise + except BaseException: _log.exception("Fallback probe using '%s' failed", executable) else: _log.debug("Fallback probe found codec=%s, bitrate=%s", codec, bitrate) else: _log.debug("Probe found codec=%s, bitrate=%s", codec, bitrate) - finally: - return codec, bitrate + + return codec, bitrate @staticmethod def _probe_codec_native(source, executable: str = 'ffmpeg') -> Tuple[Optional[str], Optional[int]]: @@ -642,8 +710,7 @@ class AudioPlayer(threading.Thread): *, after: Optional[Callable[[Optional[Exception]], Any]] = None, ) -> None: - threading.Thread.__init__(self) - self.daemon: bool = True + super().__init__(daemon=True, name=f'audio-player:{id(self):#x}') self.source: AudioSource = source self.client: VoiceClient = client self.after: Optional[Callable[[Optional[Exception]], Any]] = after @@ -652,7 +719,6 @@ class AudioPlayer(threading.Thread): self._resumed: threading.Event = threading.Event() self._resumed.set() # we are not paused self._current_error: Optional[Exception] = None - self._connected: threading.Event = client._connected self._lock: threading.Lock = threading.Lock() if after is not None and not callable(after): @@ -663,36 +729,47 @@ class AudioPlayer(threading.Thread): self._start = time.perf_counter() # getattr lookup speed ups - play_audio = self.client.send_audio_packet + client = self.client + play_audio = client.send_audio_packet self._speak(SpeakingState.voice) while not self._end.is_set(): # are we paused? if not self._resumed.is_set(): + self.send_silence() # wait until we aren't self._resumed.wait() continue - # are we disconnected from voice? - if not self._connected.is_set(): - # wait until we are connected - self._connected.wait() - # reset our internal data - self.loops = 0 - self._start = time.perf_counter() - - self.loops += 1 data = self.source.read() if not data: self.stop() break + # are we disconnected from voice? + if not client.is_connected(): + _log.debug('Not connected, waiting for %ss...', client.timeout) + # wait until we are connected, but not forever + connected = client.wait_until_connected(client.timeout) + if self._end.is_set() or not connected: + _log.debug('Aborting playback') + return + _log.debug('Reconnected, resuming playback') + self._speak(SpeakingState.voice) + # reset our internal data + self.loops = 0 + self._start = time.perf_counter() + play_audio(data, encode=not self.source.is_opus()) + self.loops += 1 next_time = self._start + self.DELAY * self.loops delay = max(0, self.DELAY + (next_time - time.perf_counter())) time.sleep(delay) + if client.is_connected(): + self.send_silence() + def run(self) -> None: try: self._do_run() @@ -738,7 +815,7 @@ class AudioPlayer(threading.Thread): def is_paused(self) -> bool: return not self._end.is_set() and not self._resumed.is_set() - def _set_source(self, source: AudioSource) -> None: + def set_source(self, source: AudioSource) -> None: with self._lock: self.pause(update_speaking=False) self.source = source @@ -749,3 +826,11 @@ class AudioPlayer(threading.Thread): asyncio.run_coroutine_threadsafe(self.client.ws.speak(speaking), self.client.client.loop) except Exception: _log.exception("Speaking call in player failed") + + def send_silence(self, count: int = 5) -> None: + try: + for n in range(count): + self.client.send_audio_packet(OPUS_SILENCE, encode=False) + except Exception: + # Any possible error (probably a socket error) is so inconsequential it's not even worth logging + pass diff --git a/discord/poll.py b/discord/poll.py new file mode 100644 index 000000000..6ab680abd --- /dev/null +++ b/discord/poll.py @@ -0,0 +1,672 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-present Rapptz + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +""" + +from __future__ import annotations + + +from typing import Optional, List, TYPE_CHECKING, Union, AsyncIterator, Dict + +import datetime + +from .enums import PollLayoutType, try_enum, MessageType +from . import utils +from .emoji import PartialEmoji, Emoji +from .user import User +from .object import Object +from .errors import ClientException + +if TYPE_CHECKING: + from typing_extensions import Self + + from .message import Message + from .abc import Snowflake + from .state import ConnectionState + from .member import Member + + from .types.poll import ( + PollCreate as PollCreatePayload, + PollMedia as PollMediaPayload, + PollAnswerCount as PollAnswerCountPayload, + Poll as PollPayload, + PollAnswerWithID as PollAnswerWithIDPayload, + PollResult as PollResultPayload, + PollAnswer as PollAnswerPayload, + ) + + +__all__ = ( + 'Poll', + 'PollAnswer', + 'PollMedia', +) + +MISSING = utils.MISSING +PollMediaEmoji = Union[PartialEmoji, Emoji, str] + + +class PollMedia: + """Represents the poll media for a poll item. + + .. versionadded:: 2.4 + + Attributes + ---------- + text: :class:`str` + The displayed text. + emoji: Optional[Union[:class:`PartialEmoji`, :class:`Emoji`]] + The attached emoji for this media. This is only valid for poll answers. + """ + + __slots__ = ('text', 'emoji') + + def __init__(self, /, text: str, emoji: Optional[PollMediaEmoji] = None) -> None: + self.text: str = text + self.emoji: Optional[Union[PartialEmoji, Emoji]] = PartialEmoji.from_str(emoji) if isinstance(emoji, str) else emoji + + def __repr__(self) -> str: + return f'' + + def to_dict(self) -> PollMediaPayload: + payload: PollMediaPayload = {'text': self.text} + + if self.emoji is not None: + payload['emoji'] = self.emoji._to_partial().to_dict() + + return payload + + @classmethod + def from_dict(cls, *, data: PollMediaPayload) -> Self: + emoji = data.get('emoji') + + if emoji: + return cls(text=data['text'], emoji=PartialEmoji.from_dict(emoji)) + return cls(text=data['text']) + + +class PollAnswer: + """Represents a poll's answer. + + .. container:: operations + + .. describe:: str(x) + + Returns this answer's text, if any. + + .. versionadded:: 2.4 + + Attributes + ---------- + id: :class:`int` + The ID of this answer. + media: :class:`PollMedia` + The display data for this answer. + self_voted: :class:`bool` + Whether the current user has voted to this answer or not. + """ + + __slots__ = ( + 'media', + 'id', + '_state', + '_message', + '_vote_count', + 'self_voted', + '_poll', + '_victor', + ) + + def __init__( + self, + *, + message: Optional[Message], + poll: Poll, + data: PollAnswerWithIDPayload, + ) -> None: + self.media: PollMedia = PollMedia.from_dict(data=data['poll_media']) + self.id: int = int(data['answer_id']) + self._message: Optional[Message] = message + self._state: Optional[ConnectionState] = message._state if message else None + self._vote_count: int = 0 + self.self_voted: bool = False + self._poll: Poll = poll + self._victor: bool = False + + def _handle_vote_event(self, added: bool, self_voted: bool) -> None: + if added: + self._vote_count += 1 + else: + self._vote_count -= 1 + self.self_voted = self_voted + + def _update_with_results(self, payload: PollAnswerCountPayload) -> None: + self._vote_count = int(payload['count']) + self.self_voted = payload['me_voted'] + + def __str__(self) -> str: + return self.media.text + + def __repr__(self) -> str: + return f'' + + @classmethod + def from_params( + cls, + id: int, + text: str, + emoji: Optional[PollMediaEmoji] = None, + *, + poll: Poll, + message: Optional[Message], + ) -> Self: + poll_media: PollMediaPayload = {'text': text} + if emoji is not None: + emoji = PartialEmoji.from_str(emoji) if isinstance(emoji, str) else emoji._to_partial() + emoji_data = emoji.to_dict() + # No need to remove animated key as it will be ignored + poll_media['emoji'] = emoji_data + + payload: PollAnswerWithIDPayload = {'answer_id': id, 'poll_media': poll_media} + + return cls(data=payload, message=message, poll=poll) + + @property + def text(self) -> str: + """:class:`str`: Returns this answer's displayed text.""" + return self.media.text + + @property + def emoji(self) -> Optional[Union[PartialEmoji, Emoji]]: + """Optional[Union[:class:`Emoji`, :class:`PartialEmoji`]]: Returns this answer's displayed + emoji, if any. + """ + return self.media.emoji + + @property + def vote_count(self) -> int: + """:class:`int`: Returns an approximate count of votes for this answer. + + If the poll is finished, the count is exact. + """ + return self._vote_count + + @property + def poll(self) -> Poll: + """:class:`Poll`: Returns the parent poll of this answer.""" + return self._poll + + def _to_dict(self) -> PollAnswerPayload: + return { + 'poll_media': self.media.to_dict(), + } + + @property + def victor(self) -> bool: + """:class:`bool`: Whether the answer is the one that had the most + votes when the poll ended. + + .. versionadded:: 2.5 + + .. note:: + + If the poll has not ended, this will always return ``False``. + """ + return self._victor + + async def voters( + self, *, limit: Optional[int] = None, after: Optional[Snowflake] = None + ) -> AsyncIterator[Union[User, Member]]: + """Returns an :term:`asynchronous iterator` representing the users that have voted on this answer. + + The ``after`` parameter must represent a user + and meet the :class:`abc.Snowflake` abc. + + This can only be called when the parent poll was sent to a message. + + Examples + -------- + + Usage :: + + async for voter in poll_answer.voters(): + print(f'{voter} has voted for {poll_answer}!') + + Flattening into a list: :: + + voters = [voter async for voter in poll_answer.voters()] + # voters is now a list of User + + Parameters + ---------- + limit: Optional[:class:`int`] + The maximum number of results to return. + If not provided, returns all the users who + voted on this poll answer. + after: Optional[:class:`abc.Snowflake`] + For pagination, voters are sorted by member. + + Raises + ------ + HTTPException + Retrieving the users failed. + + Yields + ------ + Union[:class:`User`, :class:`Member`] + The member (if retrievable) or the user that has voted + on this poll answer. The case where it can be a :class:`Member` + is in a guild message context. Sometimes it can be a :class:`User` + if the member has left the guild or if the member is not cached. + """ + + if not self._message or not self._state: # Make type checker happy + raise ClientException('You cannot fetch users to a poll not sent with a message') + + if limit is None: + if not self._message.poll: + limit = 100 + else: + limit = self.vote_count or 100 + + while limit > 0: + retrieve = min(limit, 100) + + message = self._message + guild = self._message.guild + state = self._state + after_id = after.id if after else None + + data = await state.http.get_poll_answer_voters( + message.channel.id, message.id, self.id, after=after_id, limit=retrieve + ) + users = data['users'] + + if len(users) == 0: + # No more voters to fetch, terminate loop + break + + limit -= len(users) + after = Object(id=int(users[-1]['id'])) + + if not guild or isinstance(guild, Object): + for raw_user in reversed(users): + yield User(state=self._state, data=raw_user) + continue + + for raw_member in reversed(users): + member_id = int(raw_member['id']) + member = guild.get_member(member_id) + + yield member or User(state=self._state, data=raw_member) + + +class Poll: + """Represents a message's Poll. + + .. versionadded:: 2.4 + + Parameters + ---------- + question: Union[:class:`PollMedia`, :class:`str`] + The poll's displayed question. The text can be up to 300 characters. + duration: :class:`datetime.timedelta` + The duration of the poll. Duration must be in hours. + multiple: :class:`bool` + Whether users are allowed to select more than one answer. + Defaults to ``False``. + layout_type: :class:`PollLayoutType` + The layout type of the poll. Defaults to :attr:`PollLayoutType.default`. + + Attributes + ----------- + duration: :class:`datetime.timedelta` + The duration of the poll. + multiple: :class:`bool` + Whether users are allowed to select more than one answer. + layout_type: :class:`PollLayoutType` + The layout type of the poll. + """ + + __slots__ = ( + 'multiple', + '_answers', + 'duration', + 'layout_type', + '_question_media', + '_message', + '_expiry', + '_finalized', + '_state', + '_total_votes', + '_victor_answer_id', + ) + + def __init__( + self, + question: Union[PollMedia, str], + duration: datetime.timedelta, + *, + multiple: bool = False, + layout_type: PollLayoutType = PollLayoutType.default, + ) -> None: + self._question_media: PollMedia = PollMedia(text=question, emoji=None) if isinstance(question, str) else question + self._answers: Dict[int, PollAnswer] = {} + self.duration: datetime.timedelta = duration + + self.multiple: bool = multiple + self.layout_type: PollLayoutType = layout_type + + # NOTE: These attributes are set manually when calling + # _from_data, so it should be ``None`` now. + self._message: Optional[Message] = None + self._state: Optional[ConnectionState] = None + self._finalized: bool = False + self._expiry: Optional[datetime.datetime] = None + self._total_votes: Optional[int] = None + self._victor_answer_id: Optional[int] = None + + def _update(self, message: Message) -> None: + self._state = message._state + self._message = message + + if not message.poll: + return + + # The message's poll contains the more up to date data. + self._expiry = message.poll.expires_at + self._finalized = message.poll._finalized + self._answers = message.poll._answers + self._update_results_from_message(message) + + def _update_results_from_message(self, message: Message) -> None: + if message.type != MessageType.poll_result or not message.embeds: + return + + result_embed = message.embeds[0] # Will always have 1 embed + fields: Dict[str, str] = {field.name: field.value for field in result_embed.fields} # type: ignore + + total_votes = fields.get('total_votes') + + if total_votes is not None: + self._total_votes = int(total_votes) + + victor_answer = fields.get('victor_answer_id') + + if victor_answer is None: + return # Can't do anything else without the victor answer + + self._victor_answer_id = int(victor_answer) + + victor_answer_votes = fields['victor_answer_votes'] + + answer = self._answers[self._victor_answer_id] + answer._victor = True + answer._vote_count = int(victor_answer_votes) + self._answers[answer.id] = answer # Ensure update + + def _update_results(self, data: PollResultPayload) -> None: + self._finalized = data['is_finalized'] + + for count in data['answer_counts']: + answer = self.get_answer(int(count['id'])) + if not answer: + continue + + answer._update_with_results(count) + + def _handle_vote(self, answer_id: int, added: bool, self_voted: bool = False): + answer = self.get_answer(answer_id) + if not answer: + return + + answer._handle_vote_event(added, self_voted) + + @classmethod + def _from_data(cls, *, data: PollPayload, message: Message, state: ConnectionState) -> Self: + multiselect = data.get('allow_multiselect', False) + layout_type = try_enum(PollLayoutType, data.get('layout_type', 1)) + question_data = data.get('question') + question = question_data.get('text') + expiry = utils.parse_time(data['expiry']) # If obtained via API, then expiry is set. + # expiry - message.created_at may be a few nanos away from the actual duration + duration = datetime.timedelta(hours=round((expiry - message.created_at).total_seconds() / 3600)) + # self.created_at = message.created_at + + self = cls( + duration=duration, + multiple=multiselect, + layout_type=layout_type, + question=question, + ) + self._answers = { + int(answer['answer_id']): PollAnswer(data=answer, message=message, poll=self) for answer in data['answers'] + } + self._message = message + self._state = state + self._expiry = expiry + + try: + self._update_results(data['results']) + except KeyError: + pass + + return self + + def _to_dict(self) -> PollCreatePayload: + data: PollCreatePayload = { + 'allow_multiselect': self.multiple, + 'question': self._question_media.to_dict(), + 'duration': self.duration.total_seconds() / 3600, + 'layout_type': self.layout_type.value, + 'answers': [answer._to_dict() for answer in self.answers], + } + return data + + def __repr__(self) -> str: + return f"" + + @property + def question(self) -> str: + """:class:`str`: Returns this poll's question string.""" + return self._question_media.text + + @property + def answers(self) -> List[PollAnswer]: + """List[:class:`PollAnswer`]: Returns a read-only copy of the answers.""" + return list(self._answers.values()) + + @property + def victor_answer_id(self) -> Optional[int]: + """Optional[:class:`int`]: The victor answer ID. + + .. versionadded:: 2.5 + + .. note:: + + This will **always** be ``None`` for polls that have not yet finished. + """ + return self._victor_answer_id + + @property + def victor_answer(self) -> Optional[PollAnswer]: + """Optional[:class:`PollAnswer`]: The victor answer. + + .. versionadded:: 2.5 + + .. note:: + + This will **always** be ``None`` for polls that have not yet finished. + """ + if self.victor_answer_id is None: + return None + return self.get_answer(self.victor_answer_id) + + @property + def expires_at(self) -> Optional[datetime.datetime]: + """Optional[:class:`datetime.datetime`]: A datetime object representing the poll expiry. + + .. note:: + + This will **always** be ``None`` for stateless polls. + """ + return self._expiry + + @property + def created_at(self) -> Optional[datetime.datetime]: + """Optional[:class:`datetime.datetime`]: Returns the poll's creation time. + + .. note:: + + This will **always** be ``None`` for stateless polls. + """ + + if not self._message: + return + return self._message.created_at + + @property + def message(self) -> Optional[Message]: + """Optional[:class:`Message`]: The message this poll is from.""" + return self._message + + @property + def total_votes(self) -> int: + """:class:`int`: Returns the sum of all the answer votes. + + If the poll has not yet finished, this is an approximate vote count. + + .. versionchanged:: 2.5 + This now returns an exact vote count when updated from its poll results message. + """ + if self._total_votes is not None: + return self._total_votes + return sum([answer.vote_count for answer in self.answers]) + + def is_finalised(self) -> bool: + """:class:`bool`: Returns whether the poll has finalised. + + This always returns ``False`` for stateless polls. + """ + return self._finalized + + is_finalized = is_finalised + + def copy(self) -> Self: + """Returns a stateless copy of this poll. + + This is meant to be used when you want to edit a stateful poll. + + Returns + ------- + :class:`Poll` + The copy of the poll. + """ + + new = self.__class__(question=self.question, duration=self.duration) + + # We want to return a stateless copy of the poll, so we should not + # override new._answers as our answers may contain a state + for answer in self.answers: + new.add_answer(text=answer.text, emoji=answer.emoji) + + return new + + def add_answer( + self, + *, + text: str, + emoji: Optional[Union[PartialEmoji, Emoji, str]] = None, + ) -> Self: + """Appends a new answer to this poll. + + Parameters + ---------- + text: :class:`str` + The text label for this poll answer. Can be up to 55 + characters. + emoji: Union[:class:`PartialEmoji`, :class:`Emoji`, :class:`str`] + The emoji to display along the text. + + Raises + ------ + ClientException + Cannot append answers to a poll that is active. + + Returns + ------- + :class:`Poll` + This poll with the new answer appended. This allows fluent-style chaining. + """ + + if self._message: + raise ClientException('Cannot append answers to a poll that is active') + + answer = PollAnswer.from_params(id=len(self.answers) + 1, text=text, emoji=emoji, message=self._message, poll=self) + self._answers[answer.id] = answer + return self + + def get_answer( + self, + /, + id: int, + ) -> Optional[PollAnswer]: + """Returns the answer with the provided ID or ``None`` if not found. + + Parameters + ---------- + id: :class:`int` + The ID of the answer to get. + + Returns + ------- + Optional[:class:`PollAnswer`] + The answer. + """ + + return self._answers.get(id) + + async def end(self) -> Self: + """|coro| + + Ends the poll. + + Raises + ------ + ClientException + This poll has no attached message. + HTTPException + Ending the poll failed. + + Returns + ------- + :class:`Poll` + The updated poll. + """ + + if not self._message or not self._state: # Make type checker happy + raise ClientException('This poll has no attached message.') + + message = await self._message.end_poll() + self._update(message) + + return self diff --git a/discord/presences.py b/discord/presences.py new file mode 100644 index 000000000..7fec2a09d --- /dev/null +++ b/discord/presences.py @@ -0,0 +1,150 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-present Rapptz + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +""" +from __future__ import annotations + +from typing import TYPE_CHECKING, Optional, Tuple + +from .activity import create_activity +from .enums import Status, try_enum +from .utils import MISSING, _get_as_snowflake, _RawReprMixin + +if TYPE_CHECKING: + from typing_extensions import Self + + from .activity import ActivityTypes + from .guild import Guild + from .state import ConnectionState + from .types.activity import ClientStatus as ClientStatusPayload, PartialPresenceUpdate + + +__all__ = ( + 'RawPresenceUpdateEvent', + 'ClientStatus', +) + + +class ClientStatus: + """Represents the :ddocs:`Client Status Object ` from Discord, + which holds information about the status of the user on various clients/platforms, with additional helpers. + + .. versionadded:: 2.5 + """ + + __slots__ = ('_status', 'desktop', 'mobile', 'web') + + def __init__(self, *, status: str = MISSING, data: ClientStatusPayload = MISSING) -> None: + self._status: str = status or 'offline' + + data = data or {} + self.desktop: Optional[str] = data.get('desktop') + self.mobile: Optional[str] = data.get('mobile') + self.web: Optional[str] = data.get('web') + + def __repr__(self) -> str: + attrs = [ + ('_status', self._status), + ('desktop', self.desktop), + ('mobile', self.mobile), + ('web', self.web), + ] + inner = ' '.join('%s=%r' % t for t in attrs) + return f'<{self.__class__.__name__} {inner}>' + + def _update(self, status: str, data: ClientStatusPayload, /) -> None: + self._status = status + + self.desktop = data.get('desktop') + self.mobile = data.get('mobile') + self.web = data.get('web') + + @classmethod + def _copy(cls, client_status: Self, /) -> Self: + self = cls.__new__(cls) # bypass __init__ + + self._status = client_status._status + + self.desktop = client_status.desktop + self.mobile = client_status.mobile + self.web = client_status.web + + return self + + @property + def status(self) -> Status: + """:class:`Status`: The user's overall status. If the value is unknown, then it will be a :class:`str` instead.""" + return try_enum(Status, self._status) + + @property + def raw_status(self) -> str: + """:class:`str`: The user's overall status as a string value.""" + return self._status + + @property + def mobile_status(self) -> Status: + """:class:`Status`: The user's status on a mobile device, if applicable.""" + return try_enum(Status, self.mobile or 'offline') + + @property + def desktop_status(self) -> Status: + """:class:`Status`: The user's status on the desktop client, if applicable.""" + return try_enum(Status, self.desktop or 'offline') + + @property + def web_status(self) -> Status: + """:class:`Status`: The user's status on the web client, if applicable.""" + return try_enum(Status, self.web or 'offline') + + def is_on_mobile(self) -> bool: + """:class:`bool`: A helper function that determines if a user is active on a mobile device.""" + return self.mobile is not None + + +class RawPresenceUpdateEvent(_RawReprMixin): + """Represents the payload for a :func:`on_raw_presence_update` event. + + .. versionadded:: 2.5 + + Attributes + ---------- + user_id: :class:`int` + The ID of the user that triggered the presence update. + guild_id: Optional[:class:`int`] + The guild ID for the users presence update. Could be ``None``. + guild: Optional[:class:`Guild`] + The guild associated with the presence update and user. Could be ``None``. + client_status: :class:`ClientStatus` + The :class:`~.ClientStatus` model which holds information about the status of the user on various clients. + activities: Tuple[Union[:class:`BaseActivity`, :class:`Spotify`]] + The activities the user is currently doing. Due to a Discord API limitation, a user's Spotify activity may not appear + if they are listening to a song with a title longer than ``128`` characters. See :issue:`1738` for more information. + """ + + __slots__ = ('user_id', 'guild_id', 'guild', 'client_status', 'activities') + + def __init__(self, *, data: PartialPresenceUpdate, state: ConnectionState) -> None: + self.user_id: int = int(data['user']['id']) + self.client_status: ClientStatus = ClientStatus(status=data['status'], data=data['client_status']) + self.activities: Tuple[ActivityTypes, ...] = tuple(create_activity(d, state) for d in data['activities']) + self.guild_id: Optional[int] = _get_as_snowflake(data, 'guild_id') + self.guild: Optional[Guild] = state._get_guild(self.guild_id) diff --git a/discord/raw_models.py b/discord/raw_models.py index 870a1efd3..8304559a1 100644 --- a/discord/raw_models.py +++ b/discord/raw_models.py @@ -25,13 +25,16 @@ DEALINGS IN THE SOFTWARE. from __future__ import annotations import datetime -from typing import TYPE_CHECKING, Optional, Set, List, Tuple, Union +from typing import TYPE_CHECKING, Literal, Optional, Set, List, Union -from .enums import ChannelType, try_enum -from .utils import _get_as_snowflake +from .enums import ChannelType, try_enum, ReactionType +from .utils import _get_as_snowflake, _RawReprMixin from .app_commands import AppCommandPermissions +from .colour import Colour if TYPE_CHECKING: + from typing_extensions import Self + from .types.gateway import ( MessageDeleteEvent, MessageDeleteBulkEvent as BulkMessageDeleteEvent, @@ -46,6 +49,7 @@ if TYPE_CHECKING: ThreadMembersUpdate, TypingStartEvent, GuildMemberRemoveEvent, + PollVoteActionEvent, ) from .types.command import GuildApplicationCommandPermissions from .message import Message @@ -57,6 +61,7 @@ if TYPE_CHECKING: from .guild import Guild ReactionActionEvent = Union[MessageReactionAddEvent, MessageReactionRemoveEvent] + ReactionActionType = Literal['REACTION_ADD', 'REACTION_REMOVE'] __all__ = ( @@ -73,17 +78,10 @@ __all__ = ( 'RawTypingEvent', 'RawMemberRemoveEvent', 'RawAppCommandPermissionsUpdateEvent', + 'RawPollVoteActionEvent', ) -class _RawReprMixin: - __slots__: Tuple[str, ...] = () - - def __repr__(self) -> str: - value = ' '.join(f'{attr}={getattr(self, attr)!r}' for attr in self.__slots__) - return f'<{self.__class__.__name__} {value}>' - - class RawMessageDeleteEvent(_RawReprMixin): """Represents the event payload for a :func:`on_raw_message_delete` event. @@ -106,7 +104,7 @@ class RawMessageDeleteEvent(_RawReprMixin): self.channel_id: int = int(data['channel_id']) self.cached_message: Optional[Message] = None try: - self.guild_id: Optional[int] = int(data['guild_id']) + self.guild_id: Optional[int] = int(data['guild_id']) # pyright: ignore[reportTypedDictNotRequiredAccess] except KeyError: self.guild_id: Optional[int] = None @@ -134,7 +132,7 @@ class RawBulkMessageDeleteEvent(_RawReprMixin): self.cached_messages: List[Message] = [] try: - self.guild_id: Optional[int] = int(data['guild_id']) + self.guild_id: Optional[int] = int(data['guild_id']) # pyright: ignore[reportTypedDictNotRequiredAccess] except KeyError: self.guild_id: Optional[int] = None @@ -156,24 +154,26 @@ class RawMessageUpdateEvent(_RawReprMixin): .. versionadded:: 1.7 data: :class:`dict` - The raw data given by the :ddocs:`gateway ` + The raw data given by the :ddocs:`gateway ` cached_message: Optional[:class:`Message`] The cached message, if found in the internal message cache. Represents the message before it is modified by the data in :attr:`RawMessageUpdateEvent.data`. + message: :class:`Message` + The updated message. + + .. versionadded:: 2.5 """ - __slots__ = ('message_id', 'channel_id', 'guild_id', 'data', 'cached_message') + __slots__ = ('message_id', 'channel_id', 'guild_id', 'data', 'cached_message', 'message') - def __init__(self, data: MessageUpdateEvent) -> None: - self.message_id: int = int(data['id']) - self.channel_id: int = int(data['channel_id']) + def __init__(self, data: MessageUpdateEvent, message: Message) -> None: + self.message_id: int = message.id + self.channel_id: int = message.channel.id self.data: MessageUpdateEvent = data + self.message: Message = message self.cached_message: Optional[Message] = None - try: - self.guild_id: Optional[int] = int(data['guild_id']) - except KeyError: - self.guild_id: Optional[int] = None + self.guild_id: Optional[int] = message.guild.id if message.guild else None class RawReactionActionEvent(_RawReprMixin): @@ -196,30 +196,70 @@ class RawReactionActionEvent(_RawReprMixin): The member who added the reaction. Only available if ``event_type`` is ``REACTION_ADD`` and the reaction is inside a guild. .. versionadded:: 1.3 + message_author_id: Optional[:class:`int`] + The author ID of the message being reacted to. Only available if ``event_type`` is ``REACTION_ADD``. + .. versionadded:: 2.4 event_type: :class:`str` The event type that triggered this action. Can be ``REACTION_ADD`` for reaction addition or ``REACTION_REMOVE`` for reaction removal. .. versionadded:: 1.3 + burst: :class:`bool` + Whether the reaction was a burst reaction, also known as a "super reaction". + + .. versionadded:: 2.4 + burst_colours: List[:class:`Colour`] + A list of colours used for burst reaction animation. Only available if ``burst`` is ``True`` + and if ``event_type`` is ``REACTION_ADD``. + + .. versionadded:: 2.0 + type: :class:`ReactionType` + The type of the reaction. + + .. versionadded:: 2.4 """ - __slots__ = ('message_id', 'user_id', 'channel_id', 'guild_id', 'emoji', 'event_type', 'member') + __slots__ = ( + 'message_id', + 'user_id', + 'channel_id', + 'guild_id', + 'emoji', + 'event_type', + 'member', + 'message_author_id', + 'burst', + 'burst_colours', + 'type', + ) - def __init__(self, data: ReactionActionEvent, emoji: PartialEmoji, event_type: str) -> None: + def __init__(self, data: ReactionActionEvent, emoji: PartialEmoji, event_type: ReactionActionType) -> None: self.message_id: int = int(data['message_id']) self.channel_id: int = int(data['channel_id']) self.user_id: int = int(data['user_id']) self.emoji: PartialEmoji = emoji - self.event_type: str = event_type + self.event_type: ReactionActionType = event_type self.member: Optional[Member] = None + self.message_author_id: Optional[int] = _get_as_snowflake(data, 'message_author_id') + self.burst: bool = data.get('burst', False) + self.burst_colours: List[Colour] = [Colour.from_str(c) for c in data.get('burst_colours', [])] + self.type: ReactionType = try_enum(ReactionType, data['type']) try: - self.guild_id: Optional[int] = int(data['guild_id']) + self.guild_id: Optional[int] = int(data['guild_id']) # pyright: ignore[reportTypedDictNotRequiredAccess] except KeyError: self.guild_id: Optional[int] = None + @property + def burst_colors(self) -> List[Colour]: + """An alias of :attr:`burst_colours`. + + .. versionadded:: 2.4 + """ + return self.burst_colours + class RawReactionClearEvent(_RawReprMixin): """Represents the payload for a :func:`on_raw_reaction_clear` event. @@ -241,7 +281,7 @@ class RawReactionClearEvent(_RawReprMixin): self.channel_id: int = int(data['channel_id']) try: - self.guild_id: Optional[int] = int(data['guild_id']) + self.guild_id: Optional[int] = int(data['guild_id']) # pyright: ignore[reportTypedDictNotRequiredAccess] except KeyError: self.guild_id: Optional[int] = None @@ -271,7 +311,7 @@ class RawReactionClearEmojiEvent(_RawReprMixin): self.channel_id: int = int(data['channel_id']) try: - self.guild_id: Optional[int] = int(data['guild_id']) + self.guild_id: Optional[int] = int(data['guild_id']) # pyright: ignore[reportTypedDictNotRequiredAccess] except KeyError: self.guild_id: Optional[int] = None @@ -298,7 +338,9 @@ class RawIntegrationDeleteEvent(_RawReprMixin): self.guild_id: int = int(data['guild_id']) try: - self.application_id: Optional[int] = int(data['application_id']) + self.application_id: Optional[int] = int( + data['application_id'] # pyright: ignore[reportTypedDictNotRequiredAccess] + ) except KeyError: self.application_id: Optional[int] = None @@ -319,7 +361,7 @@ class RawThreadUpdateEvent(_RawReprMixin): parent_id: :class:`int` The ID of the channel the thread belongs to. data: :class:`dict` - The raw data given by the :ddocs:`gateway ` + The raw data given by the :ddocs:`gateway ` thread: Optional[:class:`discord.Thread`] The thread, if it could be found in the internal cache. """ @@ -363,6 +405,20 @@ class RawThreadDeleteEvent(_RawReprMixin): self.parent_id: int = int(data['parent_id']) self.thread: Optional[Thread] = None + @classmethod + def _from_thread(cls, thread: Thread) -> Self: + data: ThreadDeleteEvent = { + 'id': thread.id, + 'type': thread.type.value, + 'guild_id': thread.guild.id, + 'parent_id': thread.parent_id, + } + + instance = cls(data) + instance.thread = thread + + return instance + class RawThreadMembersUpdate(_RawReprMixin): """Represents the payload for a :func:`on_raw_thread_member_remove` event. @@ -378,7 +434,7 @@ class RawThreadMembersUpdate(_RawReprMixin): member_count: :class:`int` The approximate number of members in the thread. This caps at 50. data: :class:`dict` - The raw data given by the :ddocs:`gateway `. + The raw data given by the :ddocs:`gateway `. """ __slots__ = ('thread_id', 'guild_id', 'member_count', 'data') @@ -467,3 +523,33 @@ class RawAppCommandPermissionsUpdateEvent(_RawReprMixin): self.permissions: List[AppCommandPermissions] = [ AppCommandPermissions(data=perm, guild=self.guild, state=state) for perm in data['permissions'] ] + + +class RawPollVoteActionEvent(_RawReprMixin): + """Represents the payload for a :func:`on_raw_poll_vote_add` or :func:`on_raw_poll_vote_remove` + event. + + .. versionadded:: 2.4 + + Attributes + ---------- + user_id: :class:`int` + The ID of the user that added or removed a vote. + channel_id: :class:`int` + The channel ID where the poll vote action took place. + message_id: :class:`int` + The message ID that contains the poll the user added or removed their vote on. + guild_id: Optional[:class:`int`] + The guild ID where the vote got added or removed, if applicable.. + answer_id: :class:`int` + The poll answer's ID the user voted on. + """ + + __slots__ = ('user_id', 'channel_id', 'message_id', 'guild_id', 'answer_id') + + def __init__(self, data: PollVoteActionEvent) -> None: + self.user_id: int = int(data['user_id']) + self.channel_id: int = int(data['channel_id']) + self.message_id: int = int(data['message_id']) + self.guild_id: Optional[int] = _get_as_snowflake(data, 'guild_id') + self.answer_id: int = int(data['answer_id']) diff --git a/discord/reaction.py b/discord/reaction.py index 5f50ec8f4..9fd933b0a 100644 --- a/discord/reaction.py +++ b/discord/reaction.py @@ -27,6 +27,7 @@ from typing import TYPE_CHECKING, AsyncIterator, Union, Optional from .user import User from .object import Object +from .enums import ReactionType # fmt: off __all__ = ( @@ -74,22 +75,41 @@ class Reaction: emoji: Union[:class:`Emoji`, :class:`PartialEmoji`, :class:`str`] The reaction emoji. May be a custom emoji, or a unicode emoji. count: :class:`int` - Number of times this reaction was made + Number of times this reaction was made. This is a sum of :attr:`normal_count` and :attr:`burst_count`. me: :class:`bool` If the user sent this reaction. message: :class:`Message` Message this reaction is for. + me_burst: :class:`bool` + If the user sent this super reaction. + + .. versionadded:: 2.4 + normal_count: :class:`int` + The number of times this reaction was made using normal reactions. + This is not available in the gateway events such as :func:`on_reaction_add` + or :func:`on_reaction_remove`. + + .. versionadded:: 2.4 + burst_count: :class:`int` + The number of times this reaction was made using super reactions. + This is not available in the gateway events such as :func:`on_reaction_add` + or :func:`on_reaction_remove`. + + .. versionadded:: 2.4 """ - __slots__ = ('message', 'count', 'emoji', 'me') + __slots__ = ('message', 'count', 'emoji', 'me', 'me_burst', 'normal_count', 'burst_count') def __init__(self, *, message: Message, data: ReactionPayload, emoji: Optional[Union[PartialEmoji, Emoji, str]] = None): self.message: Message = message self.emoji: Union[PartialEmoji, Emoji, str] = emoji or message._state.get_reaction_emoji(data['emoji']) self.count: int = data.get('count', 1) self.me: bool = data['me'] + details = data.get('count_details', {}) + self.normal_count: int = details.get('normal', 0) + self.burst_count: int = details.get('burst', 0) + self.me_burst: bool = data.get('me_burst', False) - # TODO: typeguard def is_custom_emoji(self) -> bool: """:class:`bool`: If this is a custom emoji.""" return not isinstance(self.emoji, str) @@ -166,7 +186,7 @@ class Reaction: await self.message.clear_reaction(self.emoji) async def users( - self, *, limit: Optional[int] = None, after: Optional[Snowflake] = None + self, *, limit: Optional[int] = None, after: Optional[Snowflake] = None, type: Optional[ReactionType] = None ) -> AsyncIterator[Union[Member, User]]: """Returns an :term:`asynchronous iterator` representing the users that have reacted to the message. @@ -201,6 +221,11 @@ class Reaction: reacted to the message. after: Optional[:class:`abc.Snowflake`] For pagination, reactions are sorted by member. + type: Optional[:class:`ReactionType`] + The type of reaction to return users from. + If not provided, Discord only returns users of reactions with type ``normal``. + + .. versionadded:: 2.4 Raises -------- @@ -232,7 +257,14 @@ class Reaction: state = message._state after_id = after.id if after else None - data = await state.http.get_reaction_users(message.channel.id, message.id, emoji, retrieve, after=after_id) + data = await state.http.get_reaction_users( + message.channel.id, + message.id, + emoji, + retrieve, + after=after_id, + type=type.value if type is not None else None, + ) if data: limit -= len(data) diff --git a/discord/role.py b/discord/role.py index 97c8f722a..d7fe1e08b 100644 --- a/discord/role.py +++ b/discord/role.py @@ -23,13 +23,14 @@ DEALINGS IN THE SOFTWARE. """ from __future__ import annotations -from typing import Any, Dict, List, Optional, Union, TYPE_CHECKING +from typing import Any, Dict, List, Optional, Union, overload, TYPE_CHECKING from .asset import Asset from .permissions import Permissions from .colour import Colour from .mixins import Hashable from .utils import snowflake_time, _bytes_to_base64_data, _get_as_snowflake, MISSING +from .flags import RoleFlags __all__ = ( 'RoleTags', @@ -219,6 +220,7 @@ class Role(Hashable): 'hoist', 'guild', 'tags', + '_flags', '_state', ) @@ -281,9 +283,10 @@ class Role(Hashable): self.managed: bool = data.get('managed', False) self.mentionable: bool = data.get('mentionable', False) self.tags: Optional[RoleTags] + self._flags: int = data.get('flags', 0) try: - self.tags = RoleTags(data['tags']) + self.tags = RoleTags(data['tags']) # pyright: ignore[reportTypedDictNotRequiredAccess] except KeyError: self.tags = None @@ -379,6 +382,14 @@ class Role(Hashable): role_id = self.id return [member for member in all_members if member._roles.has(role_id)] + @property + def flags(self) -> RoleFlags: + """:class:`RoleFlags`: Returns the role's flags. + + .. versionadded:: 2.4 + """ + return RoleFlags._from_value(self._flags) + async def _move(self, position: int, reason: Optional[str]) -> None: if position <= 0: raise ValueError("Cannot move role to position 0 or below") @@ -511,6 +522,112 @@ class Role(Hashable): data = await self._state.http.edit_role(self.guild.id, self.id, reason=reason, **payload) return Role(guild=self.guild, data=data, state=self._state) + @overload + async def move(self, *, beginning: bool, offset: int = ..., reason: Optional[str] = ...): + ... + + @overload + async def move(self, *, end: bool, offset: int = ..., reason: Optional[str] = ...): + ... + + @overload + async def move(self, *, above: Role, offset: int = ..., reason: Optional[str] = ...): + ... + + @overload + async def move(self, *, below: Role, offset: int = ..., reason: Optional[str] = ...): + ... + + async def move( + self, + *, + beginning: bool = MISSING, + end: bool = MISSING, + above: Role = MISSING, + below: Role = MISSING, + offset: int = 0, + reason: Optional[str] = None, + ): + """|coro| + + A rich interface to help move a role relative to other roles. + + You must have :attr:`~discord.Permissions.manage_roles` to do this, + and you cannot move roles above the client's top role in the guild. + + .. versionadded:: 2.5 + + Parameters + ----------- + beginning: :class:`bool` + Whether to move this at the beginning of the role list, above the default role. + This is mutually exclusive with `end`, `above`, and `below`. + end: :class:`bool` + Whether to move this at the end of the role list. + This is mutually exclusive with `beginning`, `above`, and `below`. + above: :class:`Role` + The role that should be above our current role. + This mutually exclusive with `beginning`, `end`, and `below`. + below: :class:`Role` + The role that should be below our current role. + This mutually exclusive with `beginning`, `end`, and `above`. + offset: :class:`int` + The number of roles to offset the move by. For example, + an offset of ``2`` with ``beginning=True`` would move + it 2 above the beginning. A positive number moves it above + while a negative number moves it below. Note that this + number is relative and computed after the ``beginning``, + ``end``, ``before``, and ``after`` parameters. + reason: Optional[:class:`str`] + The reason for editing this role. Shows up on the audit log. + + Raises + ------- + Forbidden + You cannot move the role there, or lack permissions to do so. + HTTPException + Moving the role failed. + TypeError + A bad mix of arguments were passed. + ValueError + An invalid role was passed. + + Returns + -------- + List[:class:`Role`] + A list of all the roles in the guild. + """ + if sum(bool(a) for a in (beginning, end, above, below)) > 1: + raise TypeError('Only one of [beginning, end, above, below] can be used.') + + target = above or below + guild = self.guild + guild_roles = guild.roles + + if target: + if target not in guild_roles: + raise ValueError('Target role is from a different guild') + if above == guild.default_role: + raise ValueError('Role cannot be moved below the default role') + if self == target: + raise ValueError('Target role cannot be itself') + + roles = [r for r in guild_roles if r != self] + if beginning: + index = 1 + elif end: + index = len(roles) + elif above in roles: + index = roles.index(above) + elif below in roles: + index = roles.index(below) + 1 + else: + index = guild_roles.index(self) + roles.insert(max((index + offset), 1), self) + + payload: List[RolePositionUpdate] = [{'id': role.id, 'position': idx} for idx, role in enumerate(roles)] + await self._state.http.move_role_position(guild.id, payload, reason=reason) + async def delete(self, *, reason: Optional[str] = None) -> None: """|coro| diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index 9f8bd9920..f74ae6706 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -25,7 +25,7 @@ DEALINGS IN THE SOFTWARE. from __future__ import annotations from datetime import datetime -from typing import TYPE_CHECKING, AsyncIterator, Dict, Optional, Union +from typing import TYPE_CHECKING, AsyncIterator, Dict, Optional, Union, overload, Literal from .asset import Asset from .enums import EventStatus, EntityType, PrivacyLevel, try_enum @@ -298,6 +298,87 @@ class ScheduledEvent(Hashable): return await self.__modify_status(EventStatus.cancelled, reason) + @overload + async def edit( + self, + *, + name: str = ..., + description: str = ..., + start_time: datetime = ..., + end_time: Optional[datetime] = ..., + privacy_level: PrivacyLevel = ..., + status: EventStatus = ..., + image: bytes = ..., + reason: Optional[str] = ..., + ) -> ScheduledEvent: + ... + + @overload + async def edit( + self, + *, + name: str = ..., + description: str = ..., + channel: Snowflake, + start_time: datetime = ..., + end_time: Optional[datetime] = ..., + privacy_level: PrivacyLevel = ..., + entity_type: Literal[EntityType.voice, EntityType.stage_instance], + status: EventStatus = ..., + image: bytes = ..., + reason: Optional[str] = ..., + ) -> ScheduledEvent: + ... + + @overload + async def edit( + self, + *, + name: str = ..., + description: str = ..., + start_time: datetime = ..., + end_time: datetime = ..., + privacy_level: PrivacyLevel = ..., + entity_type: Literal[EntityType.external], + status: EventStatus = ..., + image: bytes = ..., + location: str, + reason: Optional[str] = ..., + ) -> ScheduledEvent: + ... + + @overload + async def edit( + self, + *, + name: str = ..., + description: str = ..., + channel: Union[VoiceChannel, StageChannel], + start_time: datetime = ..., + end_time: Optional[datetime] = ..., + privacy_level: PrivacyLevel = ..., + status: EventStatus = ..., + image: bytes = ..., + reason: Optional[str] = ..., + ) -> ScheduledEvent: + ... + + @overload + async def edit( + self, + *, + name: str = ..., + description: str = ..., + start_time: datetime = ..., + end_time: datetime = ..., + privacy_level: PrivacyLevel = ..., + status: EventStatus = ..., + image: bytes = ..., + location: str, + reason: Optional[str] = ..., + ) -> ScheduledEvent: + ... + async def edit( self, *, @@ -414,24 +495,34 @@ class ScheduledEvent(Hashable): payload['image'] = image_as_str entity_type = entity_type or getattr(channel, '_scheduled_event_entity_type', MISSING) - if entity_type is None: - raise TypeError( - f'invalid GuildChannel type passed, must be VoiceChannel or StageChannel not {channel.__class__.__name__}' - ) - - if entity_type is not MISSING: + if entity_type is MISSING: + if channel and isinstance(channel, Object): + if channel.type is VoiceChannel: + entity_type = EntityType.voice + elif channel.type is StageChannel: + entity_type = EntityType.stage_instance + elif location not in (MISSING, None): + entity_type = EntityType.external + else: if not isinstance(entity_type, EntityType): raise TypeError('entity_type must be of type EntityType') payload['entity_type'] = entity_type.value + if entity_type is None: + raise TypeError( + f'invalid GuildChannel type passed, must be VoiceChannel or StageChannel not {channel.__class__.__name__}' + ) + _entity_type = entity_type or self.entity_type + _entity_type_changed = _entity_type is not self.entity_type if _entity_type in (EntityType.stage_instance, EntityType.voice): if channel is MISSING or channel is None: - raise TypeError('channel must be set when entity_type is voice or stage_instance') - - payload['channel_id'] = channel.id + if _entity_type_changed: + raise TypeError('channel must be set when entity_type is voice or stage_instance') + else: + payload['channel_id'] = channel.id if location not in (MISSING, None): raise TypeError('location cannot be set when entity_type is voice or stage_instance') @@ -442,11 +533,12 @@ class ScheduledEvent(Hashable): payload['channel_id'] = None if location is MISSING or location is None: - raise TypeError('location must be set when entity_type is external') - - metadata['location'] = location + if _entity_type_changed: + raise TypeError('location must be set when entity_type is external') + else: + metadata['location'] = location - if end_time is MISSING or end_time is None: + if not self.end_time and (end_time is MISSING or end_time is None): raise TypeError('end_time must be set when entity_type is external') if end_time is not MISSING: diff --git a/discord/shard.py b/discord/shard.py index cddb2d29f..cd10cc265 100644 --- a/discord/shard.py +++ b/discord/shard.py @@ -47,13 +47,16 @@ from .enums import Status from typing import TYPE_CHECKING, Any, Callable, Tuple, Type, Optional, List, Dict if TYPE_CHECKING: + from typing_extensions import Unpack from .gateway import DiscordWebSocket from .activity import BaseActivity from .flags import Intents + from .types.gateway import SessionStartLimit __all__ = ( 'AutoShardedClient', 'ShardInfo', + 'SessionStartLimits', ) _log = logging.getLogger(__name__) @@ -192,6 +195,10 @@ class Shard: self.ws = await asyncio.wait_for(coro, timeout=60.0) except self._handled_exceptions as e: await self._handle_disconnect(e) + except ReconnectWebSocket as e: + _log.debug('Somehow got a signal to %s while trying to %s shard ID %s.', e.op, exc.op, self.id) + op = EventType.resume if e.resume else EventType.identify + self._queue_put(EventItem(op, self, e)) except asyncio.CancelledError: return except Exception as e: @@ -289,6 +296,32 @@ class ShardInfo: return self._parent.ws.is_ratelimited() +class SessionStartLimits: + """A class that holds info about session start limits + + .. versionadded:: 2.5 + + Attributes + ---------- + total: :class:`int` + The total number of session starts the current user is allowed + remaining: :class:`int` + Remaining remaining number of session starts the current user is allowed + reset_after: :class:`int` + The number of milliseconds until the limit resets + max_concurrency: :class:`int` + The number of identify requests allowed per 5 seconds + """ + + __slots__ = ("total", "remaining", "reset_after", "max_concurrency") + + def __init__(self, **kwargs: Unpack[SessionStartLimit]): + self.total: int = kwargs['total'] + self.remaining: int = kwargs['remaining'] + self.reset_after: int = kwargs['reset_after'] + self.max_concurrency: int = kwargs['max_concurrency'] + + class AutoShardedClient(Client): """A client similar to :class:`Client` except it handles the complications of sharding for the user into a more manageable and transparent single @@ -322,6 +355,11 @@ class AutoShardedClient(Client): ------------ shard_ids: Optional[List[:class:`int`]] An optional list of shard_ids to launch the shards with. + shard_connect_timeout: Optional[:class:`float`] + The maximum number of seconds to wait before timing out when launching a shard. + Defaults to 180 seconds. + + .. versionadded:: 2.4 """ if TYPE_CHECKING: @@ -330,6 +368,8 @@ class AutoShardedClient(Client): def __init__(self, *args: Any, intents: Intents, **kwargs: Any) -> None: kwargs.pop('shard_id', None) self.shard_ids: Optional[List[int]] = kwargs.pop('shard_ids', None) + self.shard_connect_timeout: Optional[float] = kwargs.pop('shard_connect_timeout', 180.0) + super().__init__(*args, intents=intents, **kwargs) if self.shard_ids is not None: @@ -404,10 +444,37 @@ class AutoShardedClient(Client): """Mapping[int, :class:`ShardInfo`]: Returns a mapping of shard IDs to their respective info object.""" return {shard_id: ShardInfo(parent, self.shard_count) for shard_id, parent in self.__shards.items()} + async def fetch_session_start_limits(self) -> SessionStartLimits: + """|coro| + + Get the session start limits. + + This is not typically needed, and will be handled for you by default. + + At the point where you are launching multiple instances + with manual shard ranges and are considered required to use large bot + sharding by Discord, this function when used along IPC and a + before_identity_hook can speed up session start. + + .. versionadded:: 2.5 + + Returns + ------- + :class:`SessionStartLimits` + A class containing the session start limits + + Raises + ------ + GatewayNotFound + The gateway was unreachable + """ + _, _, limits = await self.http.get_bot_gateway() + return SessionStartLimits(**limits) + async def launch_shard(self, gateway: yarl.URL, shard_id: int, *, initial: bool = False) -> None: try: coro = DiscordWebSocket.from_client(self, initial=initial, gateway=gateway, shard_id=shard_id) - ws = await asyncio.wait_for(coro, timeout=180.0) + ws = await asyncio.wait_for(coro, timeout=self.shard_connect_timeout) except Exception: _log.exception('Failed to connect for shard_id: %s. Retrying...', shard_id) await asyncio.sleep(5.0) @@ -423,7 +490,7 @@ class AutoShardedClient(Client): if self.shard_count is None: self.shard_count: int - self.shard_count, gateway_url = await self.http.get_bot_gateway() + self.shard_count, gateway_url, _session_start_limit = await self.http.get_bot_gateway() gateway = yarl.URL(gateway_url) else: gateway = DiscordWebSocket.DEFAULT_GATEWAY @@ -450,10 +517,10 @@ class AutoShardedClient(Client): if item.type == EventType.close: await self.close() if isinstance(item.error, ConnectionClosed): - if item.error.code != 1000: - raise item.error if item.error.code == 4014: raise PrivilegedIntentsRequired(item.shard.id) from None + if item.error.code != 1000: + raise item.error return elif item.type in (EventType.identify, EventType.resume): await item.shard.reidentify(item.error) @@ -470,18 +537,21 @@ class AutoShardedClient(Client): Closes the connection to Discord. """ - if self.is_closed(): - return + if self._closing_task: + return await self._closing_task + + async def _close(): + await self._connection.close() - self._closed = True - await self._connection.close() + to_close = [asyncio.ensure_future(shard.close(), loop=self.loop) for shard in self.__shards.values()] + if to_close: + await asyncio.wait(to_close) - to_close = [asyncio.ensure_future(shard.close(), loop=self.loop) for shard in self.__shards.values()] - if to_close: - await asyncio.wait(to_close) + await self.http.close() + self.__queue.put_nowait(EventItem(EventType.clean_close, None, None)) - await self.http.close() - self.__queue.put_nowait(EventItem(EventType.clean_close, None, None)) + self._closing_task = asyncio.create_task(_close()) + await self._closing_task async def change_presence( self, diff --git a/discord/sku.py b/discord/sku.py new file mode 100644 index 000000000..3516370b4 --- /dev/null +++ b/discord/sku.py @@ -0,0 +1,359 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-present Rapptz + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +""" + + +from __future__ import annotations + +from typing import AsyncIterator, Optional, TYPE_CHECKING + +from datetime import datetime + +from . import utils +from .enums import try_enum, SKUType, EntitlementType +from .flags import SKUFlags +from .object import Object +from .subscription import Subscription + +if TYPE_CHECKING: + from .abc import SnowflakeTime, Snowflake + from .guild import Guild + from .state import ConnectionState + from .types.sku import ( + SKU as SKUPayload, + Entitlement as EntitlementPayload, + ) + from .user import User + +__all__ = ( + 'SKU', + 'Entitlement', +) + + +class SKU: + """Represents a premium offering as a stock-keeping unit (SKU). + + .. versionadded:: 2.4 + + Attributes + ----------- + id: :class:`int` + The SKU's ID. + type: :class:`SKUType` + The type of the SKU. + application_id: :class:`int` + The ID of the application that the SKU belongs to. + name: :class:`str` + The consumer-facing name of the premium offering. + slug: :class:`str` + A system-generated URL slug based on the SKU name. + """ + + __slots__ = ( + '_state', + 'id', + 'type', + 'application_id', + 'name', + 'slug', + '_flags', + ) + + def __init__(self, *, state: ConnectionState, data: SKUPayload): + self._state: ConnectionState = state + self.id: int = int(data['id']) + self.type: SKUType = try_enum(SKUType, data['type']) + self.application_id: int = int(data['application_id']) + self.name: str = data['name'] + self.slug: str = data['slug'] + self._flags: int = data['flags'] + + def __repr__(self) -> str: + return f'' + + @property + def flags(self) -> SKUFlags: + """:class:`SKUFlags`: Returns the flags of the SKU.""" + return SKUFlags._from_value(self._flags) + + @property + def created_at(self) -> datetime: + """:class:`datetime.datetime`: Returns the sku's creation time in UTC.""" + return utils.snowflake_time(self.id) + + async def fetch_subscription(self, subscription_id: int, /) -> Subscription: + """|coro| + + Retrieves a :class:`.Subscription` with the specified ID. + + .. versionadded:: 2.5 + + Parameters + ----------- + subscription_id: :class:`int` + The subscription's ID to fetch from. + + Raises + ------- + NotFound + An subscription with this ID does not exist. + HTTPException + Fetching the subscription failed. + + Returns + -------- + :class:`.Subscription` + The subscription you requested. + """ + data = await self._state.http.get_sku_subscription(self.id, subscription_id) + return Subscription(data=data, state=self._state) + + async def subscriptions( + self, + *, + limit: Optional[int] = 50, + before: Optional[SnowflakeTime] = None, + after: Optional[SnowflakeTime] = None, + user: Snowflake, + ) -> AsyncIterator[Subscription]: + """Retrieves an :term:`asynchronous iterator` of the :class:`.Subscription` that SKU has. + + .. versionadded:: 2.5 + + Examples + --------- + + Usage :: + + async for subscription in sku.subscriptions(limit=100, user=user): + print(subscription.user_id, subscription.current_period_end) + + Flattening into a list :: + + subscriptions = [subscription async for subscription in sku.subscriptions(limit=100, user=user)] + # subscriptions is now a list of Subscription... + + All parameters are optional. + + Parameters + ----------- + limit: Optional[:class:`int`] + The number of subscriptions to retrieve. If ``None``, it retrieves every subscription for this SKU. + Note, however, that this would make it a slow operation. Defaults to ``100``. + before: Optional[Union[:class:`~discord.abc.Snowflake`, :class:`datetime.datetime`]] + Retrieve subscriptions before this date or entitlement. + If a datetime is provided, it is recommended to use a UTC aware datetime. + If the datetime is naive, it is assumed to be local time. + after: Optional[Union[:class:`~discord.abc.Snowflake`, :class:`datetime.datetime`]] + Retrieve subscriptions after this date or entitlement. + If a datetime is provided, it is recommended to use a UTC aware datetime. + If the datetime is naive, it is assumed to be local time. + user: :class:`~discord.abc.Snowflake` + The user to filter by. + + Raises + ------- + HTTPException + Fetching the subscriptions failed. + TypeError + Both ``after`` and ``before`` were provided, as Discord does not + support this type of pagination. + + Yields + -------- + :class:`.Subscription` + The subscription with the SKU. + """ + + if before is not None and after is not None: + raise TypeError('subscriptions pagination does not support both before and after') + + # This endpoint paginates in ascending order. + endpoint = self._state.http.list_sku_subscriptions + + async def _before_strategy(retrieve: int, before: Optional[Snowflake], limit: Optional[int]): + before_id = before.id if before else None + data = await endpoint(self.id, before=before_id, limit=retrieve, user_id=user.id) + + if data: + if limit is not None: + limit -= len(data) + + before = Object(id=int(data[0]['id'])) + + return data, before, limit + + async def _after_strategy(retrieve: int, after: Optional[Snowflake], limit: Optional[int]): + after_id = after.id if after else None + data = await endpoint( + self.id, + after=after_id, + limit=retrieve, + user_id=user.id, + ) + + if data: + if limit is not None: + limit -= len(data) + + after = Object(id=int(data[-1]['id'])) + + return data, after, limit + + if isinstance(before, datetime): + before = Object(id=utils.time_snowflake(before, high=False)) + if isinstance(after, datetime): + after = Object(id=utils.time_snowflake(after, high=True)) + + if before: + strategy, state = _before_strategy, before + else: + strategy, state = _after_strategy, after + + while True: + retrieve = 100 if limit is None else min(limit, 100) + if retrieve < 1: + return + + data, state, limit = await strategy(retrieve, state, limit) + + # Terminate loop on next iteration; there's no data left after this + if len(data) < 100: + limit = 0 + + for e in data: + yield Subscription(data=e, state=self._state) + + +class Entitlement: + """Represents an entitlement from user or guild which has been granted access to a premium offering. + + .. versionadded:: 2.4 + + Attributes + ----------- + id: :class:`int` + The entitlement's ID. + sku_id: :class:`int` + The ID of the SKU that the entitlement belongs to. + application_id: :class:`int` + The ID of the application that the entitlement belongs to. + user_id: Optional[:class:`int`] + The ID of the user that is granted access to the entitlement. + type: :class:`EntitlementType` + The type of the entitlement. + deleted: :class:`bool` + Whether the entitlement has been deleted. + starts_at: Optional[:class:`datetime.datetime`] + A UTC start date which the entitlement is valid. Not present when using test entitlements. + ends_at: Optional[:class:`datetime.datetime`] + A UTC date which entitlement is no longer valid. Not present when using test entitlements. + guild_id: Optional[:class:`int`] + The ID of the guild that is granted access to the entitlement + consumed: :class:`bool` + For consumable items, whether the entitlement has been consumed. + """ + + __slots__ = ( + '_state', + 'id', + 'sku_id', + 'application_id', + 'user_id', + 'type', + 'deleted', + 'starts_at', + 'ends_at', + 'guild_id', + 'consumed', + ) + + def __init__(self, state: ConnectionState, data: EntitlementPayload): + self._state: ConnectionState = state + self.id: int = int(data['id']) + self.sku_id: int = int(data['sku_id']) + self.application_id: int = int(data['application_id']) + self.user_id: Optional[int] = utils._get_as_snowflake(data, 'user_id') + self.type: EntitlementType = try_enum(EntitlementType, data['type']) + self.deleted: bool = data['deleted'] + self.starts_at: Optional[datetime] = utils.parse_time(data.get('starts_at', None)) + self.ends_at: Optional[datetime] = utils.parse_time(data.get('ends_at', None)) + self.guild_id: Optional[int] = utils._get_as_snowflake(data, 'guild_id') + self.consumed: bool = data.get('consumed', False) + + def __repr__(self) -> str: + return f'' + + @property + def user(self) -> Optional[User]: + """Optional[:class:`User`]: The user that is granted access to the entitlement.""" + if self.user_id is None: + return None + return self._state.get_user(self.user_id) + + @property + def guild(self) -> Optional[Guild]: + """Optional[:class:`Guild`]: The guild that is granted access to the entitlement.""" + return self._state._get_guild(self.guild_id) + + @property + def created_at(self) -> datetime: + """:class:`datetime.datetime`: Returns the entitlement's creation time in UTC.""" + return utils.snowflake_time(self.id) + + def is_expired(self) -> bool: + """:class:`bool`: Returns ``True`` if the entitlement is expired. Will be always False for test entitlements.""" + if self.ends_at is None: + return False + return utils.utcnow() >= self.ends_at + + async def consume(self) -> None: + """|coro| + + Marks a one-time purchase entitlement as consumed. + + Raises + ------- + NotFound + The entitlement could not be found. + HTTPException + Consuming the entitlement failed. + """ + + await self._state.http.consume_entitlement(self.application_id, self.id) + + async def delete(self) -> None: + """|coro| + + Deletes the entitlement. + + Raises + ------- + NotFound + The entitlement could not be found. + HTTPException + Deleting the entitlement failed. + """ + + await self._state.http.delete_entitlement(self.application_id, self.id) diff --git a/discord/soundboard.py b/discord/soundboard.py new file mode 100644 index 000000000..3351aacb7 --- /dev/null +++ b/discord/soundboard.py @@ -0,0 +1,325 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-present Rapptz + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Optional + +from . import utils +from .mixins import Hashable +from .partial_emoji import PartialEmoji, _EmojiTag +from .user import User +from .utils import MISSING +from .asset import Asset, AssetMixin + +if TYPE_CHECKING: + import datetime + from typing import Dict, Any + + from .types.soundboard import ( + BaseSoundboardSound as BaseSoundboardSoundPayload, + SoundboardDefaultSound as SoundboardDefaultSoundPayload, + SoundboardSound as SoundboardSoundPayload, + ) + from .state import ConnectionState + from .guild import Guild + from .message import EmojiInputType + +__all__ = ('BaseSoundboardSound', 'SoundboardDefaultSound', 'SoundboardSound') + + +class BaseSoundboardSound(Hashable, AssetMixin): + """Represents a generic Discord soundboard sound. + + .. versionadded:: 2.5 + + .. container:: operations + + .. describe:: x == y + + Checks if two sounds are equal. + + .. describe:: x != y + + Checks if two sounds are not equal. + + .. describe:: hash(x) + + Returns the sound's hash. + + Attributes + ------------ + id: :class:`int` + The ID of the sound. + volume: :class:`float` + The volume of the sound as floating point percentage (e.g. ``1.0`` for 100%). + """ + + __slots__ = ('_state', 'id', 'volume') + + def __init__(self, *, state: ConnectionState, data: BaseSoundboardSoundPayload): + self._state: ConnectionState = state + self.id: int = int(data['sound_id']) + self._update(data) + + def __eq__(self, other: object) -> bool: + if isinstance(other, self.__class__): + return self.id == other.id + return NotImplemented + + def __ne__(self, other: object) -> bool: + return not self.__eq__(other) + + def _update(self, data: BaseSoundboardSoundPayload): + self.volume: float = data['volume'] + + @property + def url(self) -> str: + """:class:`str`: Returns the URL of the sound.""" + return f'{Asset.BASE}/soundboard-sounds/{self.id}' + + +class SoundboardDefaultSound(BaseSoundboardSound): + """Represents a Discord soundboard default sound. + + .. versionadded:: 2.5 + + .. container:: operations + + .. describe:: x == y + + Checks if two sounds are equal. + + .. describe:: x != y + + Checks if two sounds are not equal. + + .. describe:: hash(x) + + Returns the sound's hash. + + Attributes + ------------ + id: :class:`int` + The ID of the sound. + volume: :class:`float` + The volume of the sound as floating point percentage (e.g. ``1.0`` for 100%). + name: :class:`str` + The name of the sound. + emoji: :class:`PartialEmoji` + The emoji of the sound. + """ + + __slots__ = ('name', 'emoji') + + def __init__(self, *, state: ConnectionState, data: SoundboardDefaultSoundPayload): + self.name: str = data['name'] + self.emoji: PartialEmoji = PartialEmoji(name=data['emoji_name']) + super().__init__(state=state, data=data) + + def __repr__(self) -> str: + attrs = [ + ('id', self.id), + ('name', self.name), + ('volume', self.volume), + ('emoji', self.emoji), + ] + inner = ' '.join('%s=%r' % t for t in attrs) + return f"<{self.__class__.__name__} {inner}>" + + +class SoundboardSound(BaseSoundboardSound): + """Represents a Discord soundboard sound. + + .. versionadded:: 2.5 + + .. container:: operations + + .. describe:: x == y + + Checks if two sounds are equal. + + .. describe:: x != y + + Checks if two sounds are not equal. + + .. describe:: hash(x) + + Returns the sound's hash. + + Attributes + ------------ + id: :class:`int` + The ID of the sound. + volume: :class:`float` + The volume of the sound as floating point percentage (e.g. ``1.0`` for 100%). + name: :class:`str` + The name of the sound. + emoji: Optional[:class:`PartialEmoji`] + The emoji of the sound. ``None`` if no emoji is set. + guild: :class:`Guild` + The guild in which the sound is uploaded. + available: :class:`bool` + Whether this sound is available for use. + """ + + __slots__ = ('_state', 'name', 'emoji', '_user', 'available', '_user_id', 'guild') + + def __init__(self, *, guild: Guild, state: ConnectionState, data: SoundboardSoundPayload): + super().__init__(state=state, data=data) + self.guild = guild + self._user_id = utils._get_as_snowflake(data, 'user_id') + self._user = data.get('user') + + self._update(data) + + def __repr__(self) -> str: + attrs = [ + ('id', self.id), + ('name', self.name), + ('volume', self.volume), + ('emoji', self.emoji), + ('user', self.user), + ] + inner = ' '.join('%s=%r' % t for t in attrs) + return f"<{self.__class__.__name__} {inner}>" + + def _update(self, data: SoundboardSoundPayload): + super()._update(data) + + self.name: str = data['name'] + self.emoji: Optional[PartialEmoji] = None + + emoji_id = utils._get_as_snowflake(data, 'emoji_id') + emoji_name = data['emoji_name'] + if emoji_id is not None or emoji_name is not None: + self.emoji = PartialEmoji(id=emoji_id, name=emoji_name) # type: ignore # emoji_name cannot be None here + + self.available: bool = data['available'] + + @property + def created_at(self) -> datetime.datetime: + """:class:`datetime.datetime`: Returns the snowflake's creation time in UTC.""" + return utils.snowflake_time(self.id) + + @property + def user(self) -> Optional[User]: + """Optional[:class:`User`]: The user who uploaded the sound.""" + if self._user is None: + if self._user_id is None: + return None + return self._state.get_user(self._user_id) + return User(state=self._state, data=self._user) + + async def edit( + self, + *, + name: str = MISSING, + volume: Optional[float] = MISSING, + emoji: Optional[EmojiInputType] = MISSING, + reason: Optional[str] = None, + ): + """|coro| + + Edits the soundboard sound. + + You must have :attr:`~Permissions.manage_expressions` to edit the sound. + If the sound was created by the client, you must have either :attr:`~Permissions.manage_expressions` + or :attr:`~Permissions.create_expressions`. + + Parameters + ---------- + name: :class:`str` + The new name of the sound. Must be between 2 and 32 characters. + volume: Optional[:class:`float`] + The new volume of the sound. Must be between 0 and 1. + emoji: Optional[Union[:class:`Emoji`, :class:`PartialEmoji`, :class:`str`]] + The new emoji of the sound. + reason: Optional[:class:`str`] + The reason for editing this sound. Shows up on the audit log. + + Raises + ------- + Forbidden + You do not have permissions to edit the soundboard sound. + HTTPException + Editing the soundboard sound failed. + + Returns + ------- + :class:`SoundboardSound` + The newly updated soundboard sound. + """ + + payload: Dict[str, Any] = {} + + if name is not MISSING: + payload['name'] = name + + if volume is not MISSING: + payload['volume'] = volume + + if emoji is not MISSING: + if emoji is None: + payload['emoji_id'] = None + payload['emoji_name'] = None + else: + if isinstance(emoji, _EmojiTag): + partial_emoji = emoji._to_partial() + elif isinstance(emoji, str): + partial_emoji = PartialEmoji.from_str(emoji) + else: + partial_emoji = None + + if partial_emoji is not None: + if partial_emoji.id is None: + payload['emoji_name'] = partial_emoji.name + else: + payload['emoji_id'] = partial_emoji.id + + data = await self._state.http.edit_soundboard_sound(self.guild.id, self.id, reason=reason, **payload) + return SoundboardSound(guild=self.guild, state=self._state, data=data) + + async def delete(self, *, reason: Optional[str] = None) -> None: + """|coro| + + Deletes the soundboard sound. + + You must have :attr:`~Permissions.manage_expressions` to delete the sound. + If the sound was created by the client, you must have either :attr:`~Permissions.manage_expressions` + or :attr:`~Permissions.create_expressions`. + + Parameters + ----------- + reason: Optional[:class:`str`] + The reason for deleting this sound. Shows up on the audit log. + + Raises + ------- + Forbidden + You do not have permissions to delete the soundboard sound. + HTTPException + Deleting the soundboard sound failed. + """ + await self._state.http.delete_soundboard_sound(self.guild.id, self.id, reason=reason) diff --git a/discord/state.py b/discord/state.py index 8b556f28c..0fbeadea2 100644 --- a/discord/state.py +++ b/discord/state.py @@ -32,6 +32,7 @@ from typing import ( Dict, Optional, TYPE_CHECKING, + Type, Union, Callable, Any, @@ -52,6 +53,7 @@ import os from .guild import Guild from .activity import BaseActivity +from .sku import Entitlement from .user import User, ClientUser from .emoji import Emoji from .mentions import AllowedMentions @@ -60,6 +62,7 @@ from .message import Message from .channel import * from .channel import _channel_factory from .raw_models import * +from .presences import RawPresenceUpdateEvent from .member import Member from .role import Role from .enums import ChannelType, try_enum, Status @@ -76,6 +79,9 @@ from .sticker import GuildSticker from .automod import AutoModRule, AutoModAction from .audit_logs import AuditLogEntry from ._types import ClientT +from .soundboard import SoundboardSound +from .subscription import Subscription + if TYPE_CHECKING: from .abc import PrivateChannel @@ -84,7 +90,10 @@ if TYPE_CHECKING: from .http import HTTPClient from .voice_client import VoiceProtocol from .gateway import DiscordWebSocket + from .ui.item import Item + from .ui.dynamic import DynamicItem from .app_commands import CommandTree, Translator + from .poll import Poll from .types.automod import AutoModerationRule, AutoModerationActionExecution from .types.snowflake import Snowflake @@ -106,12 +115,14 @@ class ChunkRequest: def __init__( self, guild_id: int, + shard_id: int, loop: asyncio.AbstractEventLoop, resolver: Callable[[int], Any], *, cache: bool = True, ) -> None: self.guild_id: int = guild_id + self.shard_id: int = shard_id self.resolver: Callable[[int], Any] = resolver self.loop: asyncio.AbstractEventLoop = loop self.cache: bool = cache @@ -251,6 +262,10 @@ class ConnectionState(Generic[ClientT]): if not intents.members or cache_flags._empty: self.store_user = self.store_user_no_intents + self.raw_presence_flag: bool = options.get('enable_raw_presences', utils.MISSING) + if self.raw_presence_flag is utils.MISSING: + self.raw_presence_flag = not intents.members and intents.presences + self.parsers: Dict[str, Callable[[Any], None]] self.parsers = parsers = {} for attr, func in inspect.getmembers(self): @@ -259,6 +274,13 @@ class ConnectionState(Generic[ClientT]): self.clear() + # For some reason Discord still sends emoji/sticker data in payloads + # This makes it hard to actually swap out the appropriate store methods + # So this is checked instead, it's a small penalty to pay + @property + def cache_guild_expressions(self) -> bool: + return self._intents.emojis_and_stickers + async def close(self) -> None: for voice in self.voice_clients: try: @@ -304,6 +326,16 @@ class ConnectionState(Generic[ClientT]): for key in removed: del self._chunk_requests[key] + def clear_chunk_requests(self, shard_id: int | None) -> None: + removed = [] + for key, request in self._chunk_requests.items(): + if shard_id is None or request.shard_id == shard_id: + request.done() + removed.append(key) + + for key in removed: + del self._chunk_requests[key] + def call_handlers(self, key: str, *args: Any, **kwargs: Any) -> None: try: func = self.handlers[key] @@ -349,18 +381,18 @@ class ConnectionState(Generic[ClientT]): for vc in self.voice_clients: vc.main_ws = ws # type: ignore # Silencing the unknown attribute (ok at runtime). - def store_user(self, data: Union[UserPayload, PartialUserPayload]) -> User: + def store_user(self, data: Union[UserPayload, PartialUserPayload], *, cache: bool = True) -> User: # this way is 300% faster than `dict.setdefault`. user_id = int(data['id']) try: return self._users[user_id] except KeyError: user = User(state=self, data=data) - if user.discriminator != '0000': + if cache: self._users[user_id] = user return user - def store_user_no_intents(self, data: Union[UserPayload, PartialUserPayload]) -> User: + def store_user_no_intents(self, data: Union[UserPayload, PartialUserPayload], *, cache: bool = True) -> User: return User(state=self, data=data) def create_user(self, data: Union[UserPayload, PartialUserPayload]) -> User: @@ -388,6 +420,12 @@ class ConnectionState(Generic[ClientT]): def prevent_view_updates_for(self, message_id: int) -> Optional[View]: return self._view_store.remove_message_tracking(message_id) + def store_dynamic_items(self, *items: Type[DynamicItem[Item[Any]]]) -> None: + self._view_store.add_dynamic_items(*items) + + def remove_dynamic_items(self, *items: Type[DynamicItem[Item[Any]]]) -> None: + self._view_store.remove_dynamic_items(*items) + @property def persistent_views(self) -> Sequence[View]: return self._view_store.persistent_views @@ -400,8 +438,8 @@ class ConnectionState(Generic[ClientT]): # the keys of self._guilds are ints return self._guilds.get(guild_id) # type: ignore - def _get_or_create_unavailable_guild(self, guild_id: int) -> Guild: - return self._guilds.get(guild_id) or Guild._create_unavailable(state=self, guild_id=guild_id) + def _get_or_create_unavailable_guild(self, guild_id: int, *, data: Optional[Dict[str, Any]] = None) -> Guild: + return self._guilds.get(guild_id) or Guild._create_unavailable(state=self, guild_id=guild_id, data=data) def _add_guild(self, guild: Guild) -> None: self._guilds[guild.id] = guild @@ -425,6 +463,14 @@ class ConnectionState(Generic[ClientT]): def stickers(self) -> Sequence[GuildSticker]: return utils.SequenceProxy(self._stickers.values()) + @property + def soundboard_sounds(self) -> List[SoundboardSound]: + all_sounds = [] + for guild in self.guilds: + all_sounds.extend(guild.soundboard_sounds) + + return all_sounds + def get_emoji(self, emoji_id: Optional[int]) -> Optional[Emoji]: # the keys of self._emojis are ints return self._emojis.get(emoji_id) # type: ignore @@ -494,7 +540,7 @@ class ConnectionState(Generic[ClientT]): ) -> Tuple[Union[Channel, Thread], Optional[Guild]]: channel_id = int(data['channel_id']) try: - guild_id = guild_id or int(data['guild_id']) + guild_id = guild_id or int(data['guild_id']) # pyright: ignore[reportTypedDictNotRequiredAccess] guild = self._get_guild(guild_id) except KeyError: channel = DMChannel._from_message(self, channel_id) @@ -504,6 +550,34 @@ class ConnectionState(Generic[ClientT]): return channel or PartialMessageable(state=self, guild_id=guild_id, id=channel_id), guild + def _update_poll_counts(self, message: Message, answer_id: int, added: bool, self_voted: bool = False) -> Optional[Poll]: + poll = message.poll + if not poll: + return + poll._handle_vote(answer_id, added, self_voted) + return poll + + def _update_poll_results(self, from_: Message, to: Union[Message, int]) -> None: + if isinstance(to, Message): + cached = self._get_message(to.id) + elif isinstance(to, int): + cached = self._get_message(to) + + if cached is None: + return + + to = cached + else: + return + + if to.poll is None: + return + + to.poll._update_results_from_message(from_) + + if cached is not None and cached.poll: + cached.poll._update_results_from_message(from_) + async def chunker( self, guild_id: int, query: str = '', limit: int = 0, presences: bool = False, *, nonce: Optional[str] = None ) -> None: @@ -518,7 +592,7 @@ class ConnectionState(Generic[ClientT]): if ws is None: raise RuntimeError('Somehow do not have a websocket for this guild_id') - request = ChunkRequest(guild.id, self.loop, self._get_guild, cache=cache) + request = ChunkRequest(guild.id, guild.shard_id, self.loop, self._get_guild, cache=cache) self._chunk_requests[request.nonce] = request try: @@ -585,6 +659,7 @@ class ConnectionState(Generic[ClientT]): self._ready_state: asyncio.Queue[Guild] = asyncio.Queue() self.clear(views=False) + self.clear_chunk_requests(None) self.user = user = ClientUser(state=self, data=data['user']) self._users[user.id] = user # type: ignore @@ -614,7 +689,7 @@ class ConnectionState(Generic[ClientT]): if self._messages is not None: self._messages.append(message) # we ensure that the channel is either a TextChannel, VoiceChannel, or Thread - if channel and channel.__class__ in (TextChannel, VoiceChannel, Thread): + if channel and channel.__class__ in (TextChannel, VoiceChannel, Thread, StageChannel): channel.last_message_id = message.id # type: ignore def parse_message_delete(self, data: gw.MessageDeleteEvent) -> None: @@ -641,23 +716,27 @@ class ConnectionState(Generic[ClientT]): self._messages.remove(msg) # type: ignore def parse_message_update(self, data: gw.MessageUpdateEvent) -> None: - raw = RawMessageUpdateEvent(data) - message = self._get_message(raw.message_id) - if message is not None: - older_message = copy.copy(message) + channel, _ = self._get_guild_channel(data) + # channel would be the correct type here + updated_message = Message(channel=channel, data=data, state=self) # type: ignore + + raw = RawMessageUpdateEvent(data=data, message=updated_message) + cached_message = self._get_message(updated_message.id) + if cached_message is not None: + older_message = copy.copy(cached_message) raw.cached_message = older_message self.dispatch('raw_message_edit', raw) - message._update(data) + cached_message._update(data) # Coerce the `after` parameter to take the new updated Member # ref: #5999 - older_message.author = message.author - self.dispatch('message_edit', older_message, message) + older_message.author = updated_message.author + self.dispatch('message_edit', older_message, updated_message) else: self.dispatch('raw_message_edit', raw) if 'components' in data: try: - entity_id = int(data['interaction']['id']) + entity_id = int(data['interaction']['id']) # pyright: ignore[reportTypedDictNotRequiredAccess] except (KeyError, ValueError): entity_id = raw.message_id @@ -753,22 +832,24 @@ class ConnectionState(Generic[ClientT]): self.dispatch('interaction', interaction) def parse_presence_update(self, data: gw.PresenceUpdateEvent) -> None: - guild_id = utils._get_as_snowflake(data, 'guild_id') - # guild_id won't be None here - guild = self._get_guild(guild_id) - if guild is None: - _log.debug('PRESENCE_UPDATE referencing an unknown guild ID: %s. Discarding.', guild_id) + raw = RawPresenceUpdateEvent(data=data, state=self) + + if self.raw_presence_flag: + self.dispatch('raw_presence_update', raw) + + if raw.guild is None: + _log.debug('PRESENCE_UPDATE referencing an unknown guild ID: %s. Discarding.', raw.guild_id) return - user = data['user'] - member_id = int(user['id']) - member = guild.get_member(member_id) + member = raw.guild.get_member(raw.user_id) + if member is None: - _log.debug('PRESENCE_UPDATE referencing an unknown member ID: %s. Discarding', member_id) + _log.debug('PRESENCE_UPDATE referencing an unknown member ID: %s. Discarding', raw.user_id) return old_member = Member._copy(member) - user_update = member._presence_update(data=data, user=user) + user_update = member._presence_update(raw=raw, user=data['user']) + if user_update: self.dispatch('user_update', user_update[0], user_update[1]) @@ -801,6 +882,12 @@ class ConnectionState(Generic[ClientT]): guild._scheduled_events.pop(s.id) self.dispatch('scheduled_event_delete', s) + threads = guild._remove_threads_by_channel(channel_id) + + for thread in threads: + self.dispatch('thread_delete', thread) + self.dispatch('raw_thread_delete', RawThreadDeleteEvent._from_thread(thread)) + def parse_channel_update(self, data: gw.ChannelUpdateEvent) -> None: channel_type = try_enum(ChannelType, data.get('type')) channel_id = int(data['id']) @@ -848,7 +935,7 @@ class ConnectionState(Generic[ClientT]): def parse_channel_pins_update(self, data: gw.ChannelPinsUpdateEvent) -> None: channel_id = int(data['channel_id']) try: - guild = self._get_guild(int(data['guild_id'])) + guild = self._get_guild(int(data['guild_id'])) # pyright: ignore[reportTypedDictNotRequiredAccess] except KeyError: guild = None channel = self._get_private_channel(channel_id) @@ -930,7 +1017,7 @@ class ConnectionState(Generic[ClientT]): return try: - channel_ids = {int(i) for i in data['channel_ids']} + channel_ids = {int(i) for i in data['channel_ids']} # pyright: ignore[reportTypedDictNotRequiredAccess] except KeyError: # If not provided, then the entire guild is being synced # So all previous thread data should be overwritten @@ -1108,6 +1195,7 @@ class ConnectionState(Generic[ClientT]): integrations={}, app_commands={}, automod_rules={}, + webhooks={}, data=data, guild=guild, ) @@ -1186,7 +1274,9 @@ class ConnectionState(Generic[ClientT]): cache = cache or self.member_cache_flags.joined request = self._chunk_requests.get(guild.id) if request is None: - self._chunk_requests[guild.id] = request = ChunkRequest(guild.id, self.loop, self._get_guild, cache=cache) + self._chunk_requests[guild.id] = request = ChunkRequest( + guild.id, guild.shard_id, self.loop, self._get_guild, cache=cache + ) await self.chunker(guild.id, nonce=request.nonce) if wait: @@ -1347,8 +1437,10 @@ class ConnectionState(Generic[ClientT]): user = presence['user'] member_id = user['id'] member = member_dict.get(member_id) + if member is not None: - member._presence_update(presence, user) + raw_presence = RawPresenceUpdateEvent(data=presence, state=self) + member._presence_update(raw_presence, user) complete = data.get('chunk_index', 0) + 1 == data.get('chunk_count') self.process_chunk_requests(guild_id, data.get('nonce'), members, complete) @@ -1461,12 +1553,8 @@ class ConnectionState(Generic[ClientT]): def parse_guild_scheduled_event_delete(self, data: gw.GuildScheduledEventDeleteEvent) -> None: guild = self._get_guild(int(data['guild_id'])) if guild is not None: - try: - scheduled_event = guild._scheduled_events.pop(int(data['id'])) - except KeyError: - pass - else: - self.dispatch('scheduled_event_delete', scheduled_event) + scheduled_event = guild._scheduled_events.pop(int(data['id']), ScheduledEvent(state=self, data=data)) + self.dispatch('scheduled_event_delete', scheduled_event) else: _log.debug('SCHEDULED_EVENT_DELETE referencing unknown guild ID: %s. Discarding.', data['guild_id']) @@ -1508,6 +1596,63 @@ class ConnectionState(Generic[ClientT]): else: _log.debug('SCHEDULED_EVENT_USER_REMOVE referencing unknown guild ID: %s. Discarding.', data['guild_id']) + def parse_guild_soundboard_sound_create(self, data: gw.GuildSoundBoardSoundCreateEvent) -> None: + guild_id = int(data['guild_id']) # type: ignore # can't be None here + guild = self._get_guild(guild_id) + if guild is not None: + sound = SoundboardSound(guild=guild, state=self, data=data) + guild._add_soundboard_sound(sound) + self.dispatch('soundboard_sound_create', sound) + else: + _log.debug('GUILD_SOUNDBOARD_SOUND_CREATE referencing unknown guild ID: %s. Discarding.', guild_id) + + def _update_and_dispatch_sound_update(self, sound: SoundboardSound, data: gw.GuildSoundBoardSoundUpdateEvent): + old_sound = copy.copy(sound) + sound._update(data) + self.dispatch('soundboard_sound_update', old_sound, sound) + + def parse_guild_soundboard_sound_update(self, data: gw.GuildSoundBoardSoundUpdateEvent) -> None: + guild_id = int(data['guild_id']) # type: ignore # can't be None here + guild = self._get_guild(guild_id) + if guild is not None: + sound_id = int(data['sound_id']) + sound = guild.get_soundboard_sound(sound_id) + if sound is not None: + self._update_and_dispatch_sound_update(sound, data) + else: + _log.warning('GUILD_SOUNDBOARD_SOUND_UPDATE referencing unknown sound ID: %s. Discarding.', sound_id) + else: + _log.debug('GUILD_SOUNDBOARD_SOUND_UPDATE referencing unknown guild ID: %s. Discarding.', guild_id) + + def parse_guild_soundboard_sound_delete(self, data: gw.GuildSoundBoardSoundDeleteEvent) -> None: + guild_id = int(data['guild_id']) + guild = self._get_guild(guild_id) + if guild is not None: + sound_id = int(data['sound_id']) + sound = guild.get_soundboard_sound(sound_id) + if sound is not None: + guild._remove_soundboard_sound(sound) + self.dispatch('soundboard_sound_delete', sound) + else: + _log.warning('GUILD_SOUNDBOARD_SOUND_DELETE referencing unknown sound ID: %s. Discarding.', sound_id) + else: + _log.debug('GUILD_SOUNDBOARD_SOUND_DELETE referencing unknown guild ID: %s. Discarding.', guild_id) + + def parse_guild_soundboard_sounds_update(self, data: gw.GuildSoundBoardSoundsUpdateEvent) -> None: + guild_id = int(data['guild_id']) + guild = self._get_guild(guild_id) + if guild is None: + _log.debug('GUILD_SOUNDBOARD_SOUNDS_UPDATE referencing unknown guild ID: %s. Discarding.', guild_id) + return + + for raw_sound in data['soundboard_sounds']: + sound_id = int(raw_sound['sound_id']) + sound = guild.get_soundboard_sound(sound_id) + if sound is not None: + self._update_and_dispatch_sound_update(sound, raw_sound) + else: + _log.warning('GUILD_SOUNDBOARD_SOUNDS_UPDATE referencing unknown sound ID: %s. Discarding.', sound_id) + def parse_application_command_permissions_update(self, data: GuildApplicationCommandPermissionsPayload): raw = RawAppCommandPermissionsUpdateEvent(data=data, state=self) self.dispatch('raw_app_command_permissions_update', raw) @@ -1538,6 +1683,14 @@ class ConnectionState(Generic[ClientT]): else: _log.debug('VOICE_STATE_UPDATE referencing an unknown member ID: %s. Discarding.', data['user_id']) + def parse_voice_channel_effect_send(self, data: gw.VoiceChannelEffectSendEvent): + guild = self._get_guild(int(data['guild_id'])) + if guild is not None: + effect = VoiceChannelEffect(state=self, data=data, guild=guild) + self.dispatch('voice_channel_effect', effect) + else: + _log.debug('VOICE_CHANNEL_EFFECT_SEND referencing an unknown guild ID: %s. Discarding.', data['guild_id']) + def parse_voice_server_update(self, data: gw.VoiceServerUpdateEvent) -> None: key_id = int(data['guild_id']) @@ -1553,7 +1706,8 @@ class ConnectionState(Generic[ClientT]): if channel is not None: if isinstance(channel, DMChannel): - channel.recipient = raw.user + if raw.user is not None and raw.user not in channel.recipients: + channel.recipients.append(raw.user) elif guild is not None: raw.user = guild.get_member(raw.user_id) @@ -1567,6 +1721,66 @@ class ConnectionState(Generic[ClientT]): self.dispatch('raw_typing', raw) + def parse_entitlement_create(self, data: gw.EntitlementCreateEvent) -> None: + entitlement = Entitlement(data=data, state=self) + self.dispatch('entitlement_create', entitlement) + + def parse_entitlement_update(self, data: gw.EntitlementUpdateEvent) -> None: + entitlement = Entitlement(data=data, state=self) + self.dispatch('entitlement_update', entitlement) + + def parse_entitlement_delete(self, data: gw.EntitlementDeleteEvent) -> None: + entitlement = Entitlement(data=data, state=self) + self.dispatch('entitlement_delete', entitlement) + + def parse_message_poll_vote_add(self, data: gw.PollVoteActionEvent) -> None: + raw = RawPollVoteActionEvent(data) + + self.dispatch('raw_poll_vote_add', raw) + + message = self._get_message(raw.message_id) + guild = self._get_guild(raw.guild_id) + + if guild: + user = guild.get_member(raw.user_id) + else: + user = self.get_user(raw.user_id) + + if message and user: + poll = self._update_poll_counts(message, raw.answer_id, True, raw.user_id == self.self_id) + if poll: + self.dispatch('poll_vote_add', user, poll.get_answer(raw.answer_id)) + + def parse_message_poll_vote_remove(self, data: gw.PollVoteActionEvent) -> None: + raw = RawPollVoteActionEvent(data) + + self.dispatch('raw_poll_vote_remove', raw) + + message = self._get_message(raw.message_id) + guild = self._get_guild(raw.guild_id) + + if guild: + user = guild.get_member(raw.user_id) + else: + user = self.get_user(raw.user_id) + + if message and user: + poll = self._update_poll_counts(message, raw.answer_id, False, raw.user_id == self.self_id) + if poll: + self.dispatch('poll_vote_remove', user, poll.get_answer(raw.answer_id)) + + def parse_subscription_create(self, data: gw.SubscriptionCreateEvent) -> None: + subscription = Subscription(data=data, state=self) + self.dispatch('subscription_create', subscription) + + def parse_subscription_update(self, data: gw.SubscriptionUpdateEvent) -> None: + subscription = Subscription(data=data, state=self) + self.dispatch('subscription_update', subscription) + + def parse_subscription_delete(self, data: gw.SubscriptionDeleteEvent) -> None: + subscription = Subscription(data=data, state=self) + self.dispatch('subscription_delete', subscription) + def _get_reaction_user(self, channel: MessageableChannel, user_id: int) -> Optional[Union[User, Member]]: if isinstance(channel, (TextChannel, Thread, VoiceChannel)): return channel.guild.get_member(user_id) @@ -1611,6 +1825,15 @@ class ConnectionState(Generic[ClientT]): def create_message(self, *, channel: MessageableChannel, data: MessagePayload) -> Message: return Message(state=self, channel=channel, data=data) + def get_soundboard_sound(self, id: Optional[int]) -> Optional[SoundboardSound]: + if id is None: + return + + for guild in self.guilds: + sound = guild._resolve_soundboard_sound(id) + if sound is not None: + return sound + class AutoShardedConnectionState(ConnectionState[ClientT]): def __init__(self, *args: Any, **kwargs: Any) -> None: @@ -1721,6 +1944,7 @@ class AutoShardedConnectionState(ConnectionState[ClientT]): if shard_id in self._ready_tasks: self._ready_tasks[shard_id].cancel() + self.clear_chunk_requests(shard_id) if shard_id not in self._ready_states: self._ready_states[shard_id] = asyncio.Queue() diff --git a/discord/sticker.py b/discord/sticker.py index 2872f3663..bf90f8866 100644 --- a/discord/sticker.py +++ b/discord/sticker.py @@ -28,8 +28,7 @@ import unicodedata from .mixins import Hashable from .asset import Asset, AssetMixin -from .utils import cached_slot_property, find, snowflake_time, get, MISSING, _get_as_snowflake -from .errors import InvalidData +from .utils import cached_slot_property, snowflake_time, get, MISSING, _get_as_snowflake from .enums import StickerType, StickerFormatType, try_enum __all__ = ( @@ -51,7 +50,6 @@ if TYPE_CHECKING: Sticker as StickerPayload, StandardSticker as StandardStickerPayload, GuildSticker as GuildStickerPayload, - ListPremiumStickerPacks as ListPremiumStickerPacksPayload, ) @@ -203,7 +201,10 @@ class StickerItem(_StickerTag): self.name: str = data['name'] self.id: int = int(data['id']) self.format: StickerFormatType = try_enum(StickerFormatType, data['format_type']) - self.url: str = f'{Asset.BASE}/stickers/{self.id}.{self.format.file_extension}' + if self.format is StickerFormatType.gif: + self.url: str = f'https://media.discordapp.net/stickers/{self.id}.gif' + else: + self.url: str = f'{Asset.BASE}/stickers/{self.id}.{self.format.file_extension}' def __repr__(self) -> str: return f'' @@ -258,8 +259,6 @@ class Sticker(_StickerTag): The id of the sticker. description: :class:`str` The description of the sticker. - pack_id: :class:`int` - The id of the sticker's pack. format: :class:`StickerFormatType` The format for the sticker's image. url: :class:`str` @@ -277,7 +276,10 @@ class Sticker(_StickerTag): self.name: str = data['name'] self.description: str = data['description'] self.format: StickerFormatType = try_enum(StickerFormatType, data['format_type']) - self.url: str = f'{Asset.BASE}/stickers/{self.id}.{self.format.file_extension}' + if self.format is StickerFormatType.gif: + self.url: str = f'https://media.discordapp.net/stickers/{self.id}.gif' + else: + self.url: str = f'{Asset.BASE}/stickers/{self.id}.{self.format.file_extension}' def __repr__(self) -> str: return f'' @@ -349,9 +351,12 @@ class StandardSticker(Sticker): Retrieves the sticker pack that this sticker belongs to. + .. versionchanged:: 2.5 + Now raises ``NotFound`` instead of ``InvalidData``. + Raises -------- - InvalidData + NotFound The corresponding sticker pack was not found. HTTPException Retrieving the sticker pack failed. @@ -361,13 +366,8 @@ class StandardSticker(Sticker): :class:`StickerPack` The retrieved sticker pack. """ - data: ListPremiumStickerPacksPayload = await self._state.http.list_premium_sticker_packs() - packs = data['sticker_packs'] - pack = find(lambda d: int(d['id']) == self.pack_id, packs) - - if pack: - return StickerPack(state=self._state, data=pack) - raise InvalidData(f'Could not find corresponding sticker pack for {self!r}') + data = await self._state.http.get_sticker_pack(self.pack_id) + return StickerPack(state=self._state, data=data) class GuildSticker(Sticker): @@ -414,7 +414,7 @@ class GuildSticker(Sticker): def _from_data(self, data: GuildStickerPayload) -> None: super()._from_data(data) - self.available: bool = data['available'] + self.available: bool = data.get('available', True) self.guild_id: int = int(data['guild_id']) user = data.get('user') self.user: Optional[User] = self._state.store_user(user) if user else None diff --git a/discord/subscription.py b/discord/subscription.py new file mode 100644 index 000000000..ec6d7c3e5 --- /dev/null +++ b/discord/subscription.py @@ -0,0 +1,107 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-present Rapptz + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +""" + +from __future__ import annotations + +import datetime +from typing import List, Optional, TYPE_CHECKING + +from . import utils +from .mixins import Hashable +from .enums import try_enum, SubscriptionStatus + +if TYPE_CHECKING: + from .state import ConnectionState + from .types.subscription import Subscription as SubscriptionPayload + from .user import User + +__all__ = ('Subscription',) + + +class Subscription(Hashable): + """Represents a Discord subscription. + + .. versionadded:: 2.5 + + Attributes + ----------- + id: :class:`int` + The subscription's ID. + user_id: :class:`int` + The ID of the user that is subscribed. + sku_ids: List[:class:`int`] + The IDs of the SKUs that the user subscribed to. + entitlement_ids: List[:class:`int`] + The IDs of the entitlements granted for this subscription. + current_period_start: :class:`datetime.datetime` + When the current billing period started. + current_period_end: :class:`datetime.datetime` + When the current billing period ends. + status: :class:`SubscriptionStatus` + The status of the subscription. + canceled_at: Optional[:class:`datetime.datetime`] + When the subscription was canceled. + This is only available for subscriptions with a :attr:`status` of :attr:`SubscriptionStatus.inactive`. + renewal_sku_ids: List[:class:`int`] + The IDs of the SKUs that the user is going to be subscribed to when renewing. + """ + + __slots__ = ( + '_state', + 'id', + 'user_id', + 'sku_ids', + 'entitlement_ids', + 'current_period_start', + 'current_period_end', + 'status', + 'canceled_at', + 'renewal_sku_ids', + ) + + def __init__(self, *, state: ConnectionState, data: SubscriptionPayload): + self._state = state + + self.id: int = int(data['id']) + self.user_id: int = int(data['user_id']) + self.sku_ids: List[int] = list(map(int, data['sku_ids'])) + self.entitlement_ids: List[int] = list(map(int, data['entitlement_ids'])) + self.current_period_start: datetime.datetime = utils.parse_time(data['current_period_start']) + self.current_period_end: datetime.datetime = utils.parse_time(data['current_period_end']) + self.status: SubscriptionStatus = try_enum(SubscriptionStatus, data['status']) + self.canceled_at: Optional[datetime.datetime] = utils.parse_time(data['canceled_at']) + self.renewal_sku_ids: List[int] = list(map(int, data['renewal_sku_ids'] or [])) + + def __repr__(self) -> str: + return f'' + + @property + def created_at(self) -> datetime.datetime: + """:class:`datetime.datetime`: Returns the subscription's creation time in UTC.""" + return utils.snowflake_time(self.id) + + @property + def user(self) -> Optional[User]: + """Optional[:class:`User`]: The user that is subscribed.""" + return self._state.get_user(self.user_id) diff --git a/discord/team.py b/discord/team.py index eef2c2d81..cc2de039c 100644 --- a/discord/team.py +++ b/discord/team.py @@ -27,7 +27,7 @@ from __future__ import annotations from . import utils from .user import BaseUser from .asset import Asset -from .enums import TeamMembershipState, try_enum +from .enums import TeamMemberRole, TeamMembershipState, try_enum from typing import TYPE_CHECKING, Optional, List @@ -108,7 +108,7 @@ class TeamMember(BaseUser): .. describe:: str(x) - Returns the team member's name with discriminator. + Returns the team member's handle (e.g. ``name`` or ``name#discriminator``). .. versionadded:: 1.3 @@ -119,25 +119,34 @@ class TeamMember(BaseUser): id: :class:`int` The team member's unique ID. discriminator: :class:`str` - The team member's discriminator. This is given when the username has conflicts. + The team member's discriminator. This is a legacy concept that is no longer used. + global_name: Optional[:class:`str`] + The team member's global nickname, taking precedence over the username in display. + + .. versionadded:: 2.3 bot: :class:`bool` Specifies if the user is a bot account. team: :class:`Team` The team that the member is from. membership_state: :class:`TeamMembershipState` The membership state of the member (e.g. invited or accepted) + role: :class:`TeamMemberRole` + The role of the member within the team. + + .. versionadded:: 2.4 """ - __slots__ = ('team', 'membership_state', 'permissions') + __slots__ = ('team', 'membership_state', 'permissions', 'role') def __init__(self, team: Team, state: ConnectionState, data: TeamMemberPayload) -> None: self.team: Team = team self.membership_state: TeamMembershipState = try_enum(TeamMembershipState, data['membership_state']) - self.permissions: List[str] = data['permissions'] + self.permissions: List[str] = data.get('permissions', []) + self.role: TeamMemberRole = try_enum(TeamMemberRole, data['role']) super().__init__(state=state, data=data['user']) def __repr__(self) -> str: return ( f'<{self.__class__.__name__} id={self.id} name={self.name!r} ' - f'discriminator={self.discriminator!r} membership_state={self.membership_state!r}>' + f'global_name={self.global_name!r} membership_state={self.membership_state!r}>' ) diff --git a/discord/template.py b/discord/template.py index ab9e07ef6..691be2caf 100644 --- a/discord/template.py +++ b/discord/template.py @@ -25,7 +25,7 @@ DEALINGS IN THE SOFTWARE. from __future__ import annotations from typing import Any, Optional, TYPE_CHECKING, List -from .utils import parse_time, _bytes_to_base64_data, MISSING +from .utils import parse_time, _bytes_to_base64_data, MISSING, deprecated from .guild import Guild # fmt: off @@ -69,6 +69,10 @@ class _PartialTemplateState: def member_cache_flags(self): return self.__state.member_cache_flags + @property + def cache_guild_expressions(self): + return False + def store_emoji(self, guild, packet) -> None: return None @@ -146,18 +150,11 @@ class Template: self.created_at: Optional[datetime.datetime] = parse_time(data.get('created_at')) self.updated_at: Optional[datetime.datetime] = parse_time(data.get('updated_at')) - guild_id = int(data['source_guild_id']) - guild: Optional[Guild] = self._state._get_guild(guild_id) - - self.source_guild: Guild - if guild is None: - source_serialised = data['serialized_source_guild'] - source_serialised['id'] = guild_id - state = _PartialTemplateState(state=self._state) - # Guild expects a ConnectionState, we're passing a _PartialTemplateState - self.source_guild = Guild(data=source_serialised, state=state) # type: ignore - else: - self.source_guild = guild + source_serialised = data['serialized_source_guild'] + source_serialised['id'] = int(data['source_guild_id']) + state = _PartialTemplateState(state=self._state) + # Guild expects a ConnectionState, we're passing a _PartialTemplateState + self.source_guild = Guild(data=source_serialised, state=state) # type: ignore self.is_dirty: Optional[bool] = data.get('is_dirty', None) @@ -167,6 +164,7 @@ class Template: f' creator={self.creator!r} source_guild={self.source_guild!r} is_dirty={self.is_dirty}>' ) + @deprecated() async def create_guild(self, name: str, icon: bytes = MISSING) -> Guild: """|coro| @@ -181,6 +179,9 @@ class Template: This function will now raise :exc:`ValueError` instead of ``InvalidArgument``. + .. deprecated:: 2.6 + This function is deprecated and will be removed in a future version. + Parameters ---------- name: :class:`str` diff --git a/discord/threads.py b/discord/threads.py index c5551a55a..0c8060193 100644 --- a/discord/threads.py +++ b/discord/threads.py @@ -121,8 +121,12 @@ class Thread(Messageable, Hashable): This is always ``True`` for public threads. archiver_id: Optional[:class:`int`] The user's ID that archived this thread. + + .. note:: + Due to an API change, the ``archiver_id`` will always be ``None`` and can only be obtained via the audit log. + 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. @@ -188,7 +192,7 @@ class Thread(Messageable, Hashable): self.me: Optional[ThreadMember] try: - member = data['member'] + member = data['member'] # pyright: ignore[reportTypedDictNotRequiredAccess] except KeyError: self.me = None else: @@ -268,12 +272,12 @@ class Thread(Messageable, Hashable): .. versionadded:: 2.1 """ tags = [] - if self.parent is None or self.parent.type != ChannelType.forum: + if self.parent is None or self.parent.type not in (ChannelType.forum, ChannelType.media): return tags parent = self.parent for tag_id in self._applied_tags: - tag = parent.get_tag(tag_id) + tag = parent.get_tag(tag_id) # type: ignore # parent here will be ForumChannel instance if tag is not None: tags.append(tag) @@ -608,7 +612,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. @@ -846,13 +850,21 @@ class Thread(Messageable, Hashable): members = await self._state.http.get_thread_members(self.id) return [ThreadMember(parent=self, data=data) for data in members] - async def delete(self) -> None: + async def delete(self, *, reason: Optional[str] = None) -> None: """|coro| Deletes this thread. You must have :attr:`~Permissions.manage_threads` to delete threads. + Parameters + ----------- + reason: Optional[:class:`str`] + The reason for deleting this thread. + Shows up on the audit log. + + .. versionadded:: 2.4 + Raises ------- Forbidden @@ -860,7 +872,7 @@ class Thread(Messageable, Hashable): HTTPException Deleting the thread failed. """ - await self._state.http.delete_channel(self.id) + await self._state.http.delete_channel(self.id, reason=reason) def get_partial_message(self, message_id: int, /) -> PartialMessage: """Creates a :class:`PartialMessage` from the message ID. diff --git a/discord/types/activity.py b/discord/types/activity.py index 57fb86d4a..f57334936 100644 --- a/discord/types/activity.py +++ b/discord/types/activity.py @@ -93,6 +93,7 @@ class Activity(_BaseActivity, total=False): state: Optional[str] details: Optional[str] timestamps: ActivityTimestamps + platform: Optional[str] assets: ActivityAssets party: ActivityParty application_id: Snowflake diff --git a/discord/types/appinfo.py b/discord/types/appinfo.py index 640fe1be7..9452bbbb1 100644 --- a/discord/types/appinfo.py +++ b/discord/types/appinfo.py @@ -24,12 +24,13 @@ DEALINGS IN THE SOFTWARE. from __future__ import annotations -from typing import TypedDict, List, Optional +from typing import Literal, Dict, TypedDict, List, Optional from typing_extensions import NotRequired from .user import User from .team import Team from .snowflake import Snowflake +from .emoji import Emoji class InstallParams(TypedDict): @@ -37,6 +38,10 @@ class InstallParams(TypedDict): permissions: str +class AppIntegrationTypeConfig(TypedDict): + oauth2_install_params: NotRequired[InstallParams] + + class BaseAppInfo(TypedDict): id: Snowflake name: str @@ -44,10 +49,18 @@ class BaseAppInfo(TypedDict): icon: Optional[str] summary: str description: str + flags: int + approximate_user_install_count: NotRequired[int] + cover_image: NotRequired[str] + terms_of_service_url: NotRequired[str] + privacy_policy_url: NotRequired[str] + rpc_origins: NotRequired[List[str]] + interactions_endpoint_url: NotRequired[Optional[str]] + redirect_uris: NotRequired[List[str]] + role_connections_verification_url: NotRequired[Optional[str]] class AppInfo(BaseAppInfo): - rpc_origins: List[str] owner: User bot_public: bool bot_require_code_grant: bool @@ -55,26 +68,24 @@ class AppInfo(BaseAppInfo): guild_id: NotRequired[Snowflake] primary_sku_id: NotRequired[Snowflake] slug: NotRequired[str] - terms_of_service_url: NotRequired[str] - privacy_policy_url: NotRequired[str] hook: NotRequired[bool] max_participants: NotRequired[int] tags: NotRequired[List[str]] install_params: NotRequired[InstallParams] custom_install_url: NotRequired[str] - role_connections_verification_url: NotRequired[str] + integration_types_config: NotRequired[Dict[Literal['0', '1'], AppIntegrationTypeConfig]] class PartialAppInfo(BaseAppInfo, total=False): - rpc_origins: List[str] - cover_image: str hook: bool - terms_of_service_url: str - privacy_policy_url: str max_participants: int - flags: int + approximate_guild_count: int class GatewayAppInfo(TypedDict): id: Snowflake flags: int + + +class ListAppEmojis(TypedDict): + items: List[Emoji] diff --git a/discord/types/audit_log.py b/discord/types/audit_log.py index 4401bc784..2c37542fd 100644 --- a/discord/types/audit_log.py +++ b/discord/types/audit_log.py @@ -37,6 +37,7 @@ from .role import Role from .channel import ChannelType, DefaultReaction, PrivacyLevel, VideoQualityMode, PermissionOverwrite, ForumTag from .threads import Thread from .command import ApplicationCommand, ApplicationCommandPermissions +from .automod import AutoModerationTriggerMetadata AuditLogEvent = Literal[ 1, @@ -87,12 +88,17 @@ AuditLogEvent = Literal[ 111, 112, 121, + 130, + 131, + 132, 140, 141, 142, 143, 144, 145, + 150, + 151, ] @@ -109,6 +115,7 @@ class _AuditLogChange_Str(TypedDict): 'permissions', 'tags', 'unicode_emoji', + 'emoji_name', ] new_value: str old_value: str @@ -133,6 +140,8 @@ class _AuditLogChange_Snowflake(TypedDict): 'channel_id', 'inviter_id', 'guild_id', + 'user_id', + 'sound_id', ] new_value: Snowflake old_value: Snowflake @@ -180,6 +189,12 @@ class _AuditLogChange_Int(TypedDict): old_value: int +class _AuditLogChange_Float(TypedDict): + key: Literal['volume'] + new_value: float + old_value: float + + class _AuditLogChange_ListRole(TypedDict): key: Literal['$add', '$remove'] new_value: List[Role] @@ -276,11 +291,18 @@ class _AuditLogChange_DefaultReactionEmoji(TypedDict): old_value: Optional[DefaultReaction] +class _AuditLogChange_TriggerMetadata(TypedDict): + key: Literal['trigger_metadata'] + new_value: Optional[AutoModerationTriggerMetadata] + old_value: Optional[AutoModerationTriggerMetadata] + + AuditLogChange = Union[ _AuditLogChange_Str, _AuditLogChange_AssetHash, _AuditLogChange_Snowflake, _AuditLogChange_Int, + _AuditLogChange_Float, _AuditLogChange_Bool, _AuditLogChange_ListRole, _AuditLogChange_MFALevel, @@ -298,6 +320,7 @@ AuditLogChange = Union[ _AuditLogChange_AppliedTags, _AuditLogChange_AvailableTags, _AuditLogChange_DefaultReactionEmoji, + _AuditLogChange_TriggerMetadata, ] @@ -314,6 +337,7 @@ class AuditEntryInfo(TypedDict): guild_id: Snowflake auto_moderation_rule_name: str auto_moderation_rule_trigger_type: str + integration_type: str class AuditLogEntry(TypedDict): diff --git a/discord/types/automod.py b/discord/types/automod.py index 250d1e41e..246b7ee6a 100644 --- a/discord/types/automod.py +++ b/discord/types/automod.py @@ -45,9 +45,13 @@ class _AutoModerationActionMetadataTimeout(TypedDict): duration_seconds: int +class _AutoModerationActionMetadataCustomMessage(TypedDict): + custom_message: str + + class _AutoModerationActionBlockMessage(TypedDict): type: Literal[1] - metadata: NotRequired[Empty] + metadata: NotRequired[_AutoModerationActionMetadataCustomMessage] class _AutoModerationActionAlert(TypedDict): @@ -75,6 +79,7 @@ class _AutoModerationTriggerMetadataKeywordPreset(TypedDict): class _AutoModerationTriggerMetadataMentionLimit(TypedDict): mention_total_limit: int + mention_raid_protection_enabled: bool AutoModerationTriggerMetadata = Union[ diff --git a/discord/types/channel.py b/discord/types/channel.py index ad17af689..4b593e554 100644 --- a/discord/types/channel.py +++ b/discord/types/channel.py @@ -28,6 +28,7 @@ from typing_extensions import NotRequired from .user import PartialUser from .snowflake import Snowflake from .threads import ThreadMetadata, ThreadMember, ThreadArchiveDuration, ThreadType +from .emoji import PartialEmoji OverwriteType = Literal[0, 1] @@ -40,7 +41,7 @@ class PermissionOverwrite(TypedDict): deny: str -ChannelTypeWithoutThread = Literal[0, 1, 2, 3, 4, 5, 6, 13, 15] +ChannelTypeWithoutThread = Literal[0, 1, 2, 3, 4, 5, 6, 13, 15, 16] ChannelType = Union[ChannelTypeWithoutThread, ThreadType] @@ -89,6 +90,20 @@ class VoiceChannel(_BaseTextChannel): video_quality_mode: NotRequired[VideoQualityMode] +VoiceChannelEffectAnimationType = Literal[0, 1] + + +class VoiceChannelEffect(TypedDict): + guild_id: Snowflake + channel_id: Snowflake + user_id: Snowflake + emoji: NotRequired[Optional[PartialEmoji]] + animation_type: NotRequired[VoiceChannelEffectAnimationType] + animation_id: NotRequired[int] + sound_id: NotRequired[Union[int, str]] + sound_volume: NotRequired[float] + + class CategoryChannel(_BaseGuildChannel): type: Literal[4] @@ -134,30 +149,49 @@ class ForumTag(TypedDict): emoji_name: Optional[str] +ForumOrderType = Literal[0, 1] ForumLayoutType = Literal[0, 1, 2] -class ForumChannel(_BaseTextChannel): - type: Literal[15] +class _BaseForumChannel(_BaseTextChannel): available_tags: List[ForumTag] default_reaction_emoji: Optional[DefaultReaction] + default_sort_order: Optional[ForumOrderType] default_forum_layout: NotRequired[ForumLayoutType] flags: NotRequired[int] -GuildChannel = Union[TextChannel, NewsChannel, VoiceChannel, CategoryChannel, StageChannel, ThreadChannel, ForumChannel] +class ForumChannel(_BaseForumChannel): + type: Literal[15] + +class MediaChannel(_BaseForumChannel): + type: Literal[16] -class DMChannel(_BaseChannel): + +GuildChannel = Union[ + TextChannel, NewsChannel, VoiceChannel, CategoryChannel, StageChannel, ThreadChannel, ForumChannel, MediaChannel +] + + +class _BaseDMChannel(_BaseChannel): type: Literal[1] last_message_id: Optional[Snowflake] + + +class DMChannel(_BaseDMChannel): recipients: List[PartialUser] +class InteractionDMChannel(_BaseDMChannel): + recipients: NotRequired[List[PartialUser]] + + class GroupDMChannel(_BaseChannel): type: Literal[3] icon: Optional[str] owner_id: Snowflake + recipients: List[PartialUser] Channel = Union[GuildChannel, DMChannel, GroupDMChannel] diff --git a/discord/types/command.py b/discord/types/command.py index f4eb41ef8..7876ee6dd 100644 --- a/discord/types/command.py +++ b/discord/types/command.py @@ -29,9 +29,11 @@ from typing_extensions import NotRequired, Required from .channel import ChannelType from .snowflake import Snowflake +from .interactions import InteractionContextType ApplicationCommandType = Literal[1, 2, 3] ApplicationCommandOptionType = Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] +ApplicationIntegrationType = Literal[0, 1] class _BaseApplicationCommandOption(TypedDict): @@ -141,6 +143,8 @@ class _BaseApplicationCommand(TypedDict): id: Snowflake application_id: Snowflake name: str + contexts: List[InteractionContextType] + integration_types: List[ApplicationIntegrationType] dm_permission: NotRequired[Optional[bool]] default_member_permissions: NotRequired[Optional[str]] nsfw: NotRequired[bool] diff --git a/discord/types/components.py b/discord/types/components.py index f1790ff35..3b1295c13 100644 --- a/discord/types/components.py +++ b/discord/types/components.py @@ -31,8 +31,9 @@ from .emoji import PartialEmoji from .channel import ChannelType ComponentType = Literal[1, 2, 3, 4] -ButtonStyle = Literal[1, 2, 3, 4, 5] +ButtonStyle = Literal[1, 2, 3, 4, 5, 6] TextStyle = Literal[1, 2] +DefaultValueType = Literal['user', 'role', 'channel'] class ActionRow(TypedDict): @@ -48,6 +49,7 @@ class ButtonComponent(TypedDict): disabled: NotRequired[bool] emoji: NotRequired[PartialEmoji] label: NotRequired[str] + sku_id: NotRequired[str] class SelectOption(TypedDict): @@ -66,6 +68,11 @@ class SelectComponent(TypedDict): disabled: NotRequired[bool] +class SelectDefaultValues(TypedDict): + id: int + type: DefaultValueType + + class StringSelectComponent(SelectComponent): type: Literal[3] options: NotRequired[List[SelectOption]] @@ -73,19 +80,23 @@ class StringSelectComponent(SelectComponent): class UserSelectComponent(SelectComponent): type: Literal[5] + default_values: NotRequired[List[SelectDefaultValues]] class RoleSelectComponent(SelectComponent): type: Literal[6] + default_values: NotRequired[List[SelectDefaultValues]] class MentionableSelectComponent(SelectComponent): type: Literal[7] + default_values: NotRequired[List[SelectDefaultValues]] class ChannelSelectComponent(SelectComponent): type: Literal[8] channel_types: NotRequired[List[ChannelType]] + default_values: NotRequired[List[SelectDefaultValues]] class TextInput(TypedDict): @@ -104,6 +115,7 @@ class SelectMenu(SelectComponent): type: Literal[3, 5, 6, 7, 8] options: NotRequired[List[SelectOption]] channel_types: NotRequired[List[ChannelType]] + default_values: NotRequired[List[SelectDefaultValues]] ActionRowChildComponent = Union[ButtonComponent, SelectMenu, TextInput] diff --git a/discord/types/embed.py b/discord/types/embed.py index f2f1c5a9f..a18912f6e 100644 --- a/discord/types/embed.py +++ b/discord/types/embed.py @@ -38,25 +38,12 @@ class EmbedField(TypedDict): inline: NotRequired[bool] -class EmbedThumbnail(TypedDict, total=False): - url: Required[str] - proxy_url: str - height: int - width: int - - -class EmbedVideo(TypedDict, total=False): - url: str - proxy_url: str - height: int - width: int - - -class EmbedImage(TypedDict, total=False): +class EmbedMedia(TypedDict, total=False): url: Required[str] proxy_url: str height: int width: int + flags: int class EmbedProvider(TypedDict, total=False): @@ -71,7 +58,7 @@ class EmbedAuthor(TypedDict, total=False): proxy_icon_url: str -EmbedType = Literal['rich', 'image', 'video', 'gifv', 'article', 'link'] +EmbedType = Literal['rich', 'image', 'video', 'gifv', 'article', 'link', 'poll_result'] class Embed(TypedDict, total=False): @@ -82,9 +69,10 @@ class Embed(TypedDict, total=False): timestamp: str color: int footer: EmbedFooter - image: EmbedImage - thumbnail: EmbedThumbnail - video: EmbedVideo + image: EmbedMedia + thumbnail: EmbedMedia + video: EmbedMedia provider: EmbedProvider author: EmbedAuthor fields: List[EmbedField] + flags: int diff --git a/discord/types/emoji.py b/discord/types/emoji.py index d54690c14..85e709757 100644 --- a/discord/types/emoji.py +++ b/discord/types/emoji.py @@ -23,6 +23,7 @@ DEALINGS IN THE SOFTWARE. """ from typing import Optional, TypedDict +from typing_extensions import NotRequired from .snowflake import Snowflake, SnowflakeList from .user import User @@ -30,6 +31,7 @@ from .user import User class PartialEmoji(TypedDict): id: Optional[Snowflake] name: Optional[str] + animated: NotRequired[bool] class Emoji(PartialEmoji, total=False): diff --git a/discord/types/gateway.py b/discord/types/gateway.py index a87b101f0..7dca5badc 100644 --- a/discord/types/gateway.py +++ b/discord/types/gateway.py @@ -27,23 +27,26 @@ from typing_extensions import NotRequired, Required from .automod import AutoModerationAction, AutoModerationRuleTriggerType from .activity import PartialPresenceUpdate +from .sku import Entitlement from .voice import GuildVoiceState from .integration import BaseIntegration, IntegrationApplication from .role import Role -from .channel import ChannelType, StageInstance +from .channel import ChannelType, StageInstance, VoiceChannelEffect from .interactions import Interaction from .invite import InviteTargetType from .emoji import Emoji, PartialEmoji from .member import MemberWithUser from .snowflake import Snowflake -from .message import Message +from .message import Message, ReactionType from .sticker import GuildSticker from .appinfo import GatewayAppInfo, PartialAppInfo from .guild import Guild, UnavailableGuild -from .user import User +from .user import User, AvatarDecorationData from .threads import Thread, ThreadMember from .scheduled_event import GuildScheduledEvent from .audit_log import AuditLogEntry +from .soundboard import SoundboardSound +from .subscription import Subscription class SessionStartLimit(TypedDict): @@ -89,8 +92,7 @@ class MessageDeleteBulkEvent(TypedDict): guild_id: NotRequired[Snowflake] -class MessageUpdateEvent(Message): - channel_id: Snowflake +MessageUpdateEvent = MessageCreateEvent class MessageReactionAddEvent(TypedDict): @@ -100,6 +102,10 @@ class MessageReactionAddEvent(TypedDict): emoji: PartialEmoji member: NotRequired[MemberWithUser] guild_id: NotRequired[Snowflake] + message_author_id: NotRequired[Snowflake] + burst: bool + burst_colors: NotRequired[List[str]] + type: ReactionType class MessageReactionRemoveEvent(TypedDict): @@ -108,6 +114,8 @@ class MessageReactionRemoveEvent(TypedDict): message_id: Snowflake emoji: PartialEmoji guild_id: NotRequired[Snowflake] + burst: bool + type: ReactionType class MessageReactionRemoveAllEvent(TypedDict): @@ -223,6 +231,7 @@ class GuildMemberUpdateEvent(TypedDict): mute: NotRequired[bool] pending: NotRequired[bool] communication_disabled_until: NotRequired[str] + avatar_decoration_data: NotRequired[AvatarDecorationData] class GuildEmojisUpdateEvent(TypedDict): @@ -311,6 +320,19 @@ class _GuildScheduledEventUsersEvent(TypedDict): GuildScheduledEventUserAdd = GuildScheduledEventUserRemove = _GuildScheduledEventUsersEvent VoiceStateUpdateEvent = GuildVoiceState +VoiceChannelEffectSendEvent = VoiceChannelEffect + +GuildSoundBoardSoundCreateEvent = GuildSoundBoardSoundUpdateEvent = SoundboardSound + + +class GuildSoundBoardSoundsUpdateEvent(TypedDict): + guild_id: Snowflake + soundboard_sounds: List[SoundboardSound] + + +class GuildSoundBoardSoundDeleteEvent(TypedDict): + sound_id: Snowflake + guild_id: Snowflake class VoiceServerUpdateEvent(TypedDict): @@ -343,3 +365,17 @@ class AutoModerationActionExecution(TypedDict): class GuildAuditLogEntryCreate(AuditLogEntry): guild_id: Snowflake + + +EntitlementCreateEvent = EntitlementUpdateEvent = EntitlementDeleteEvent = Entitlement + + +class PollVoteActionEvent(TypedDict): + user_id: Snowflake + channel_id: Snowflake + message_id: Snowflake + guild_id: NotRequired[Snowflake] + answer_id: int + + +SubscriptionCreateEvent = SubscriptionUpdateEvent = SubscriptionDeleteEvent = Subscription diff --git a/discord/types/guild.py b/discord/types/guild.py index b6c2a1365..7ac90b89e 100644 --- a/discord/types/guild.py +++ b/discord/types/guild.py @@ -37,6 +37,7 @@ from .member import Member from .emoji import Emoji from .user import User from .threads import Thread +from .soundboard import SoundboardSound class Ban(TypedDict): @@ -49,6 +50,11 @@ class UnavailableGuild(TypedDict): unavailable: NotRequired[bool] +class IncidentData(TypedDict): + invites_disabled_until: NotRequired[Optional[str]] + dms_disabled_until: NotRequired[Optional[str]] + + DefaultMessageNotificationLevel = Literal[0, 1] ExplicitContentFilterLevel = Literal[0, 1, 2] MFALevel = Literal[0, 1] @@ -84,6 +90,9 @@ GuildFeature = Literal[ 'VERIFIED', 'VIP_REGIONS', 'WELCOME_SCREEN_ENABLED', + 'RAID_ALERTS_DISABLED', + 'SOUNDBOARD', + 'MORE_SOUNDBOARD', ] @@ -96,6 +105,7 @@ class _BaseGuildPreview(UnavailableGuild): stickers: List[GuildSticker] features: List[GuildFeature] description: Optional[str] + incidents_data: Optional[IncidentData] class _GuildPreviewUnique(TypedDict): @@ -147,6 +157,7 @@ class Guild(_BaseGuildPreview): max_members: NotRequired[int] premium_subscription_count: NotRequired[int] max_video_channel_users: NotRequired[int] + soundboard_sounds: NotRequired[List[SoundboardSound]] class InviteGuild(Guild, total=False): @@ -161,11 +172,15 @@ class GuildPrune(TypedDict): pruned: Optional[int] +class GuildMFALevel(TypedDict): + level: MFALevel + + class ChannelPositionUpdate(TypedDict): id: Snowflake position: Optional[int] - lock_permissions: Optional[bool] - parent_id: Optional[Snowflake] + lock_permissions: NotRequired[Optional[bool]] + parent_id: NotRequired[Optional[Snowflake]] class _RolePositionRequired(TypedDict): @@ -174,3 +189,8 @@ class _RolePositionRequired(TypedDict): class RolePositionUpdate(_RolePositionRequired, total=False): position: Optional[Snowflake] + + +class BulkBanUserResponse(TypedDict): + banned_users: Optional[List[Snowflake]] + failed_users: Optional[List[Snowflake]] diff --git a/discord/types/interactions.py b/discord/types/interactions.py index cfbaf310e..464f2445f 100644 --- a/discord/types/interactions.py +++ b/discord/types/interactions.py @@ -24,22 +24,36 @@ DEALINGS IN THE SOFTWARE. from __future__ import annotations -from typing import TYPE_CHECKING, Dict, List, Literal, TypedDict, Union +from typing import TYPE_CHECKING, Dict, List, Literal, TypedDict, Union, Optional from typing_extensions import NotRequired -from .channel import ChannelTypeWithoutThread, ThreadMetadata -from .threads import ThreadType +from .channel import ChannelTypeWithoutThread, GuildChannel, InteractionDMChannel, GroupDMChannel +from .sku import Entitlement +from .threads import ThreadType, ThreadMetadata from .member import Member from .message import Attachment from .role import Role from .snowflake import Snowflake from .user import User +from .guild import GuildFeature if TYPE_CHECKING: from .message import Message InteractionType = Literal[1, 2, 3, 4, 5] +InteractionResponseType = Literal[ + 1, + 4, + 5, + 6, + 7, + 8, + 9, + 10, +] +InteractionContextType = Literal[0, 1, 2] +InteractionInstallationType = Literal[0, 1] class _BasePartialChannel(TypedDict): @@ -50,6 +64,14 @@ class _BasePartialChannel(TypedDict): class PartialChannel(_BasePartialChannel): type: ChannelTypeWithoutThread + topic: NotRequired[str] + position: int + nsfw: bool + flags: int + rate_limit_per_user: int + parent_id: Optional[Snowflake] + last_message_id: Optional[Snowflake] + last_pin_timestamp: NotRequired[str] class PartialThread(_BasePartialChannel): @@ -67,6 +89,12 @@ class ResolvedData(TypedDict, total=False): attachments: Dict[str, Attachment] +class PartialInteractionGuild(TypedDict): + id: Snowflake + locale: str + features: List[GuildFeature] + + class _BaseApplicationCommandInteractionDataOption(TypedDict): name: str @@ -203,10 +231,17 @@ class _BaseInteraction(TypedDict): token: str version: Literal[1] guild_id: NotRequired[Snowflake] + guild: NotRequired[PartialInteractionGuild] channel_id: NotRequired[Snowflake] + channel: Union[GuildChannel, InteractionDMChannel, GroupDMChannel] app_permissions: NotRequired[str] locale: NotRequired[str] guild_locale: NotRequired[str] + entitlement_sku_ids: NotRequired[List[Snowflake]] + entitlements: NotRequired[List[Entitlement]] + authorizing_integration_owners: Dict[Literal['0', '1'], Snowflake] + context: NotRequired[InteractionContextType] + attachment_size_limit: int class PingInteraction(_BaseInteraction): @@ -237,3 +272,75 @@ class MessageInteraction(TypedDict): name: str user: User member: NotRequired[Member] + + +class _MessageInteractionMetadata(TypedDict): + id: Snowflake + user: User + authorizing_integration_owners: Dict[Literal['0', '1'], Snowflake] + original_response_message_id: NotRequired[Snowflake] + + +class _ApplicationCommandMessageInteractionMetadata(_MessageInteractionMetadata): + type: Literal[2] + # command_type: Literal[1, 2, 3, 4] + + +class UserApplicationCommandMessageInteractionMetadata(_ApplicationCommandMessageInteractionMetadata): + # command_type: Literal[2] + target_user: User + + +class MessageApplicationCommandMessageInteractionMetadata(_ApplicationCommandMessageInteractionMetadata): + # command_type: Literal[3] + target_message_id: Snowflake + + +ApplicationCommandMessageInteractionMetadata = Union[ + _ApplicationCommandMessageInteractionMetadata, + UserApplicationCommandMessageInteractionMetadata, + MessageApplicationCommandMessageInteractionMetadata, +] + + +class MessageComponentMessageInteractionMetadata(_MessageInteractionMetadata): + type: Literal[3] + interacted_message_id: Snowflake + + +class ModalSubmitMessageInteractionMetadata(_MessageInteractionMetadata): + type: Literal[5] + triggering_interaction_metadata: Union[ + ApplicationCommandMessageInteractionMetadata, MessageComponentMessageInteractionMetadata + ] + + +MessageInteractionMetadata = Union[ + ApplicationCommandMessageInteractionMetadata, + MessageComponentMessageInteractionMetadata, + ModalSubmitMessageInteractionMetadata, +] + + +class InteractionCallbackResponse(TypedDict): + id: Snowflake + type: InteractionType + activity_instance_id: NotRequired[str] + response_message_id: NotRequired[Snowflake] + response_message_loading: NotRequired[bool] + response_message_ephemeral: NotRequired[bool] + + +class InteractionCallbackActivity(TypedDict): + id: str + + +class InteractionCallbackResource(TypedDict): + type: InteractionResponseType + activity_instance: NotRequired[InteractionCallbackActivity] + message: NotRequired[Message] + + +class InteractionCallback(TypedDict): + interaction: InteractionCallbackResponse + resource: NotRequired[InteractionCallbackResource] diff --git a/discord/types/invite.py b/discord/types/invite.py index b53ca374c..47c972994 100644 --- a/discord/types/invite.py +++ b/discord/types/invite.py @@ -35,6 +35,7 @@ from .user import PartialUser from .appinfo import PartialAppInfo InviteTargetType = Literal[1, 2] +InviteType = Literal[0, 1, 2] class _InviteMetadata(TypedDict, total=False): @@ -63,6 +64,8 @@ class Invite(IncompleteInvite, total=False): target_type: InviteTargetType target_application: PartialAppInfo guild_scheduled_event: GuildScheduledEvent + type: InviteType + flags: NotRequired[int] class InviteWithCounts(Invite, _GuildPreviewUnique): @@ -82,6 +85,7 @@ class GatewayInviteCreate(TypedDict): target_type: NotRequired[InviteTargetType] target_user: NotRequired[PartialUser] target_application: NotRequired[PartialAppInfo] + flags: NotRequired[int] class GatewayInviteDelete(TypedDict): diff --git a/discord/types/member.py b/discord/types/member.py index ad9e49008..576ef421d 100644 --- a/discord/types/member.py +++ b/discord/types/member.py @@ -24,7 +24,8 @@ DEALINGS IN THE SOFTWARE. from typing import Optional, TypedDict from .snowflake import SnowflakeList -from .user import User +from .user import User, AvatarDecorationData +from typing_extensions import NotRequired class Nickname(TypedDict): @@ -33,7 +34,7 @@ class Nickname(TypedDict): class PartialMember(TypedDict): roles: SnowflakeList - joined_at: str + joined_at: Optional[str] # null if guest deaf: bool mute: bool flags: int @@ -47,6 +48,8 @@ class Member(PartialMember, total=False): pending: bool permissions: str communication_disabled_until: str + banner: NotRequired[Optional[str]] + avatar_decoration_data: NotRequired[AvatarDecorationData] class _OptionalMemberWithUser(PartialMember, total=False): @@ -56,6 +59,7 @@ class _OptionalMemberWithUser(PartialMember, total=False): pending: bool permissions: str communication_disabled_until: str + avatar_decoration_data: NotRequired[AvatarDecorationData] class MemberWithUser(_OptionalMemberWithUser): diff --git a/discord/types/message.py b/discord/types/message.py index 1319b2e65..ae38db46f 100644 --- a/discord/types/message.py +++ b/discord/types/message.py @@ -34,8 +34,10 @@ from .emoji import PartialEmoji from .embed import Embed from .channel import ChannelType from .components import Component -from .interactions import MessageInteraction +from .interactions import MessageInteraction, MessageInteractionMetadata from .sticker import StickerItem +from .threads import Thread +from .poll import Poll class PartialMessage(TypedDict): @@ -50,10 +52,21 @@ class ChannelMention(TypedDict): name: str +class ReactionCountDetails(TypedDict): + burst: int + normal: int + + +ReactionType = Literal[0, 1] + + class Reaction(TypedDict): count: int me: bool emoji: PartialEmoji + me_burst: bool + count_details: ReactionCountDetails + burst_colors: List[str] class Attachment(TypedDict): @@ -68,6 +81,9 @@ class Attachment(TypedDict): content_type: NotRequired[str] spoiler: NotRequired[bool] ephemeral: NotRequired[bool] + duration_secs: NotRequired[float] + waveform: NotRequired[str] + flags: NotRequired[int] MessageActivityType = Literal[1, 2, 3, 5] @@ -86,7 +102,11 @@ class MessageApplication(TypedDict): cover_image: NotRequired[str] +MessageReferenceType = Literal[0, 1] + + class MessageReference(TypedDict, total=False): + type: MessageReferenceType message_id: Snowflake channel_id: Required[Snowflake] guild_id: Snowflake @@ -100,11 +120,78 @@ class RoleSubscriptionData(TypedDict): is_renewal: bool +PurchaseNotificationResponseType = Literal[0] + + +class GuildProductPurchase(TypedDict): + listing_id: Snowflake + product_name: str + + +class PurchaseNotificationResponse(TypedDict): + type: PurchaseNotificationResponseType + guild_product_purchase: Optional[GuildProductPurchase] + + +class CallMessage(TypedDict): + participants: SnowflakeList + ended_timestamp: NotRequired[Optional[str]] + + MessageType = Literal[ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32 + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 14, + 15, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 36, + 37, + 38, + 39, + 44, + 46, ] +class MessageSnapshot(TypedDict): + type: MessageType + content: str + embeds: List[Embed] + attachments: List[Attachment] + timestamp: str + edited_timestamp: Optional[str] + flags: NotRequired[int] + mentions: List[UserWithMember] + mention_roles: SnowflakeList + sticker_items: NotRequired[List[StickerItem]] + components: NotRequired[List[Component]] + + class Message(PartialMessage): id: Snowflake author: User @@ -118,6 +205,7 @@ class Message(PartialMessage): attachments: List[Attachment] embeds: List[Embed] pinned: bool + poll: NotRequired[Poll] type: MessageType member: NotRequired[Member] mention_channels: NotRequired[List[ChannelMention]] @@ -131,10 +219,14 @@ class Message(PartialMessage): flags: NotRequired[int] sticker_items: NotRequired[List[StickerItem]] referenced_message: NotRequired[Optional[Message]] - interaction: NotRequired[MessageInteraction] + interaction: NotRequired[MessageInteraction] # deprecated, use interaction_metadata + interaction_metadata: NotRequired[MessageInteractionMetadata] components: NotRequired[List[Component]] position: NotRequired[int] role_subscription_data: NotRequired[RoleSubscriptionData] + thread: NotRequired[Thread] + call: NotRequired[CallMessage] + purchase_notification: NotRequired[PurchaseNotificationResponse] AllowedMentionType = Literal['roles', 'users', 'everyone'] diff --git a/discord/types/poll.py b/discord/types/poll.py new file mode 100644 index 000000000..fabdbd48f --- /dev/null +++ b/discord/types/poll.py @@ -0,0 +1,88 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-present Rapptz + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +""" + +from __future__ import annotations + + +from typing import List, TypedDict, Optional, Literal, TYPE_CHECKING +from typing_extensions import NotRequired + +from .snowflake import Snowflake + +if TYPE_CHECKING: + from .user import User + from .emoji import PartialEmoji + + +LayoutType = Literal[1] # 1 = Default + + +class PollMedia(TypedDict): + text: str + emoji: NotRequired[Optional[PartialEmoji]] + + +class PollAnswer(TypedDict): + poll_media: PollMedia + + +class PollAnswerWithID(PollAnswer): + answer_id: int + + +class PollAnswerCount(TypedDict): + id: Snowflake + count: int + me_voted: bool + + +class PollAnswerVoters(TypedDict): + users: List[User] + + +class PollResult(TypedDict): + is_finalized: bool + answer_counts: List[PollAnswerCount] + + +class PollCreate(TypedDict): + allow_multiselect: bool + answers: List[PollAnswer] + duration: float + layout_type: LayoutType + question: PollMedia + + +# We don't subclass Poll as it will +# still have the duration field, which +# is converted into expiry when poll is +# fetched from a message or returned +# by a `send` method in a Messageable +class Poll(TypedDict): + allow_multiselect: bool + answers: List[PollAnswerWithID] + expiry: str + layout_type: LayoutType + question: PollMedia + results: PollResult diff --git a/discord/types/role.py b/discord/types/role.py index 63e58fd0c..d32de8803 100644 --- a/discord/types/role.py +++ b/discord/types/role.py @@ -39,6 +39,7 @@ class Role(TypedDict): permissions: str managed: bool mentionable: bool + flags: int icon: NotRequired[Optional[str]] unicode_emoji: NotRequired[Optional[str]] tags: NotRequired[RoleTags] diff --git a/discord/types/sku.py b/discord/types/sku.py new file mode 100644 index 000000000..a49e0d659 --- /dev/null +++ b/discord/types/sku.py @@ -0,0 +1,53 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-present Rapptz + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +""" + +from __future__ import annotations + +from typing import TypedDict, Optional, Literal +from typing_extensions import NotRequired + + +class SKU(TypedDict): + id: str + type: int + application_id: str + name: str + slug: str + flags: int + + +class Entitlement(TypedDict): + id: str + sku_id: str + application_id: str + user_id: Optional[str] + type: int + deleted: bool + starts_at: NotRequired[str] + ends_at: NotRequired[str] + guild_id: NotRequired[str] + consumed: NotRequired[bool] + + +EntitlementOwnerType = Literal[1, 2] diff --git a/discord/types/soundboard.py b/discord/types/soundboard.py new file mode 100644 index 000000000..4910df808 --- /dev/null +++ b/discord/types/soundboard.py @@ -0,0 +1,49 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-present Rapptz + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +""" + +from typing import TypedDict, Optional, Union +from typing_extensions import NotRequired + +from .snowflake import Snowflake +from .user import User + + +class BaseSoundboardSound(TypedDict): + sound_id: Union[Snowflake, str] # basic string number when it's a default sound + volume: float + + +class SoundboardSound(BaseSoundboardSound): + name: str + emoji_name: Optional[str] + emoji_id: Optional[Snowflake] + user_id: NotRequired[Snowflake] + available: bool + guild_id: NotRequired[Snowflake] + user: NotRequired[User] + + +class SoundboardDefaultSound(BaseSoundboardSound): + name: str + emoji_name: str diff --git a/discord/types/sticker.py b/discord/types/sticker.py index 7dcd0ccba..77fc6a1ef 100644 --- a/discord/types/sticker.py +++ b/discord/types/sticker.py @@ -30,7 +30,7 @@ from typing_extensions import NotRequired from .snowflake import Snowflake from .user import User -StickerFormatType = Literal[1, 2, 3] +StickerFormatType = Literal[1, 2, 3, 4] class StickerItem(TypedDict): @@ -55,7 +55,7 @@ class StandardSticker(BaseSticker): class GuildSticker(BaseSticker): type: Literal[2] - available: bool + available: NotRequired[bool] guild_id: Snowflake user: NotRequired[User] diff --git a/discord/types/subscription.py b/discord/types/subscription.py new file mode 100644 index 000000000..8d4c02070 --- /dev/null +++ b/discord/types/subscription.py @@ -0,0 +1,43 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-present Rapptz + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +""" + +from __future__ import annotations + +from typing import List, Literal, Optional, TypedDict + +from .snowflake import Snowflake + +SubscriptionStatus = Literal[0, 1, 2] + + +class Subscription(TypedDict): + id: Snowflake + user_id: Snowflake + sku_ids: List[Snowflake] + entitlement_ids: List[Snowflake] + current_period_start: str + current_period_end: str + status: SubscriptionStatus + canceled_at: Optional[str] + renewal_sku_ids: Optional[List[Snowflake]] diff --git a/discord/types/team.py b/discord/types/team.py index 83ed08137..1f744a08c 100644 --- a/discord/types/team.py +++ b/discord/types/team.py @@ -24,7 +24,7 @@ DEALINGS IN THE SOFTWARE. from __future__ import annotations -from typing import TypedDict, List, Optional +from typing import Literal, TypedDict, List, Optional from .user import PartialUser from .snowflake import Snowflake @@ -35,6 +35,7 @@ class TeamMember(TypedDict): membership_state: int permissions: List[str] team_id: Snowflake + role: Literal['admin', 'developer', 'read_only'] class Team(TypedDict): diff --git a/discord/types/user.py b/discord/types/user.py index fba5aef5b..1f027ce9d 100644 --- a/discord/types/user.py +++ b/discord/types/user.py @@ -24,6 +24,12 @@ DEALINGS IN THE SOFTWARE. from .snowflake import Snowflake from typing import Literal, Optional, TypedDict +from typing_extensions import NotRequired + + +class AvatarDecorationData(TypedDict): + asset: str + sku_id: Snowflake class PartialUser(TypedDict): @@ -31,16 +37,18 @@ class PartialUser(TypedDict): username: str discriminator: str avatar: Optional[str] + global_name: Optional[str] + avatar_decoration_data: NotRequired[AvatarDecorationData] -PremiumType = Literal[0, 1, 2] +PremiumType = Literal[0, 1, 2, 3] class User(PartialUser, total=False): bot: bool system: bool mfa_enabled: bool - local: str + locale: str verified: bool email: Optional[str] flags: int diff --git a/discord/types/voice.py b/discord/types/voice.py index 8f4e2e03e..7e856ecdd 100644 --- a/discord/types/voice.py +++ b/discord/types/voice.py @@ -29,7 +29,12 @@ from .snowflake import Snowflake from .member import MemberWithUser -SupportedModes = Literal['xsalsa20_poly1305_lite', 'xsalsa20_poly1305_suffix', 'xsalsa20_poly1305'] +SupportedModes = Literal[ + 'aead_xchacha20_poly1305_rtpsize', + 'xsalsa20_poly1305_lite', + 'xsalsa20_poly1305_suffix', + 'xsalsa20_poly1305', +] class _VoiceState(TypedDict): diff --git a/discord/ui/__init__.py b/discord/ui/__init__.py index 0133be6f5..c5a51777c 100644 --- a/discord/ui/__init__.py +++ b/discord/ui/__init__.py @@ -15,3 +15,4 @@ from .item import * from .button import * from .select import * from .text_input import * +from .dynamic import * diff --git a/discord/ui/button.py b/discord/ui/button.py index 2c051d12c..43bd3a8b0 100644 --- a/discord/ui/button.py +++ b/discord/ui/button.py @@ -61,12 +61,14 @@ class Button(Item[V]): custom_id: Optional[:class:`str`] The ID of the button that gets received during an interaction. If this button is for a URL, it does not have a custom ID. + Can only be up to 100 characters. url: Optional[:class:`str`] The URL this button sends you to. disabled: :class:`bool` Whether the button is disabled or not. label: Optional[:class:`str`] The label of the button, if any. + Can only be up to 80 characters. emoji: Optional[Union[:class:`.PartialEmoji`, :class:`.Emoji`, :class:`str`]] The emoji of the button, if available. row: Optional[:class:`int`] @@ -75,6 +77,11 @@ class Button(Item[V]): like to control the relative positioning of the row then passing an index is advised. For example, row=1 will show up before row=2. Defaults to ``None``, which is automatic ordering. The row number must be between 0 and 4 (i.e. zero indexed). + sku_id: Optional[:class:`int`] + The SKU ID this button sends you to. Can't be combined with ``url``, ``label``, ``emoji`` + nor ``custom_id``. + + .. versionadded:: 2.4 """ __item_repr_attributes__: Tuple[str, ...] = ( @@ -84,6 +91,7 @@ class Button(Item[V]): 'label', 'emoji', 'row', + 'sku_id', ) def __init__( @@ -96,13 +104,18 @@ class Button(Item[V]): url: Optional[str] = None, emoji: Optional[Union[str, Emoji, PartialEmoji]] = None, row: Optional[int] = None, + sku_id: Optional[int] = None, ): super().__init__() - if custom_id is not None and url is not None: - raise TypeError('cannot mix both url and custom_id with Button') + if custom_id is not None and (url is not None or sku_id is not None): + raise TypeError('cannot mix both url or sku_id and custom_id with Button') + + if url is not None and sku_id is not None: + raise TypeError('cannot mix both url and sku_id') + requires_custom_id = url is None and sku_id is None self._provided_custom_id = custom_id is not None - if url is None and custom_id is None: + if requires_custom_id and custom_id is None: custom_id = os.urandom(16).hex() if custom_id is not None and not isinstance(custom_id, str): @@ -111,6 +124,9 @@ class Button(Item[V]): if url is not None: style = ButtonStyle.link + if sku_id is not None: + style = ButtonStyle.premium + if emoji is not None: if isinstance(emoji, str): emoji = PartialEmoji.from_str(emoji) @@ -126,6 +142,7 @@ class Button(Item[V]): label=label, style=style, emoji=emoji, + sku_id=sku_id, ) self.row = row @@ -200,6 +217,20 @@ class Button(Item[V]): else: self._underlying.emoji = None + @property + def sku_id(self) -> Optional[int]: + """Optional[:class:`int`]: The SKU ID this button sends you to. + + .. versionadded:: 2.4 + """ + return self._underlying.sku_id + + @sku_id.setter + def sku_id(self, value: Optional[int]) -> None: + if value is not None: + self.style = ButtonStyle.premium + self._underlying.sku_id = value + @classmethod def from_component(cls, button: ButtonComponent) -> Self: return cls( @@ -210,6 +241,7 @@ class Button(Item[V]): url=button.url, emoji=button.emoji, row=None, + sku_id=button.sku_id, ) @property @@ -248,19 +280,21 @@ def button( .. note:: - Buttons with a URL cannot be created with this function. + Buttons with a URL or an SKU cannot be created with this function. Consider creating a :class:`Button` manually instead. - This is because buttons with a URL do not have a callback + This is because these buttons cannot have a callback associated with them since Discord does not do any processing - with it. + with them. Parameters ------------ label: Optional[:class:`str`] The label of the button, if any. + Can only be up to 80 characters. custom_id: Optional[:class:`str`] The ID of the button that gets received during an interaction. It is recommended not to set this parameter to prevent conflicts. + Can only be up to 100 characters. style: :class:`.ButtonStyle` The style of the button. Defaults to :attr:`.ButtonStyle.grey`. disabled: :class:`bool` @@ -289,6 +323,7 @@ def button( 'label': label, 'emoji': emoji, 'row': row, + 'sku_id': None, } return func diff --git a/discord/ui/dynamic.py b/discord/ui/dynamic.py new file mode 100644 index 000000000..0b65e90f3 --- /dev/null +++ b/discord/ui/dynamic.py @@ -0,0 +1,216 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-present Rapptz + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +""" + +from __future__ import annotations +from typing import ClassVar, Dict, Generic, Optional, Tuple, Type, TypeVar, TYPE_CHECKING, Any, Union +import re + +from .item import Item +from .._types import ClientT + +__all__ = ('DynamicItem',) + +BaseT = TypeVar('BaseT', bound='Item[Any]', covariant=True) + +if TYPE_CHECKING: + from typing_extensions import TypeVar, Self + from ..interactions import Interaction + from ..components import Component + from ..enums import ComponentType + from .view import View + + V = TypeVar('V', bound='View', covariant=True, default=View) +else: + V = TypeVar('V', bound='View', covariant=True) + + +class DynamicItem(Generic[BaseT], Item['View']): + """Represents an item with a dynamic ``custom_id`` that can be used to store state within + that ``custom_id``. + + The ``custom_id`` parsing is done using the ``re`` module by passing a ``template`` + parameter to the class parameter list. + + This item is generated every time the component is dispatched. This means that + any variable that holds an instance of this class will eventually be out of date + and should not be used long term. Their only purpose is to act as a "template" + for the actual dispatched item. + + When this item is generated, :attr:`view` is set to a regular :class:`View` instance + from the original message given from the interaction. This means that custom view + subclasses cannot be accessed from this item. + + .. versionadded:: 2.4 + + Parameters + ------------ + item: :class:`Item` + The item to wrap with dynamic custom ID parsing. + template: Union[:class:`str`, ``re.Pattern``] + The template to use for parsing the ``custom_id``. This can be a string or a compiled + regular expression. This must be passed as a keyword argument to the class creation. + row: Optional[:class:`int`] + The relative row this button belongs to. A Discord component can only have 5 + rows. By default, items are arranged automatically into those 5 rows. If you'd + like to control the relative positioning of the row then passing an index is advised. + For example, row=1 will show up before row=2. Defaults to ``None``, which is automatic + ordering. The row number must be between 0 and 4 (i.e. zero indexed). + + Attributes + ----------- + item: :class:`Item` + The item that is wrapped with dynamic custom ID parsing. + """ + + __item_repr_attributes__: Tuple[str, ...] = ( + 'item', + 'template', + ) + + __discord_ui_compiled_template__: ClassVar[re.Pattern[str]] + + def __init_subclass__(cls, *, template: Union[str, re.Pattern[str]]) -> None: + super().__init_subclass__() + cls.__discord_ui_compiled_template__ = re.compile(template) if isinstance(template, str) else template + if not isinstance(cls.__discord_ui_compiled_template__, re.Pattern): + raise TypeError('template must be a str or a re.Pattern') + + def __init__( + self, + item: BaseT, + *, + row: Optional[int] = None, + ) -> None: + super().__init__() + self.item: BaseT = item + if row is not None: + self.row = row + + if not self.item.is_dispatchable(): + raise TypeError('item must be dispatchable, e.g. not a URL button') + + if not self.template.match(self.custom_id): + raise ValueError(f'item custom_id {self.custom_id!r} must match the template {self.template.pattern!r}') + + @property + def template(self) -> re.Pattern[str]: + """``re.Pattern``: The compiled regular expression that is used to parse the ``custom_id``.""" + return self.__class__.__discord_ui_compiled_template__ + + def to_component_dict(self) -> Dict[str, Any]: + return self.item.to_component_dict() + + def _refresh_component(self, component: Component) -> None: + self.item._refresh_component(component) + + def _refresh_state(self, interaction: Interaction, data: Dict[str, Any]) -> None: + self.item._refresh_state(interaction, data) + + @classmethod + def from_component(cls: Type[Self], component: Component) -> Self: + raise TypeError('Dynamic items cannot be created from components') + + @property + def type(self) -> ComponentType: + return self.item.type + + def is_dispatchable(self) -> bool: + return self.item.is_dispatchable() + + def is_persistent(self) -> bool: + return True + + @property + def custom_id(self) -> str: + """:class:`str`: The ID of the dynamic item that gets received during an interaction.""" + return self.item.custom_id # type: ignore # This attribute exists for dispatchable items + + @custom_id.setter + def custom_id(self, value: str) -> None: + if not isinstance(value, str): + raise TypeError('custom_id must be a str') + + if not self.template.match(value): + raise ValueError(f'custom_id must match the template {self.template.pattern!r}') + + self.item.custom_id = value # type: ignore # This attribute exists for dispatchable items + self._provided_custom_id = True + + @property + def row(self) -> Optional[int]: + return self.item._row + + @row.setter + def row(self, value: Optional[int]) -> None: + self.item.row = value + + @property + def width(self) -> int: + return self.item.width + + @classmethod + async def from_custom_id( + cls: Type[Self], interaction: Interaction[ClientT], item: Item[Any], match: re.Match[str], / + ) -> Self: + """|coro| + + A classmethod that is called when the ``custom_id`` of a component matches the + ``template`` of the class. This is called when the component is dispatched. + + It must return a new instance of the :class:`DynamicItem`. + + Subclasses *must* implement this method. + + Exceptions raised in this method are logged and ignored. + + .. warning:: + + This method is called before the callback is dispatched, therefore + it means that it is subject to the same timing restrictions as the callback. + Ergo, you must reply to an interaction within 3 seconds of it being + dispatched. + + Parameters + ------------ + interaction: :class:`~discord.Interaction` + The interaction that the component belongs to. + item: :class:`~discord.ui.Item` + The base item that is being dispatched. + match: ``re.Match`` + The match object that was created from the ``template`` + matching the ``custom_id``. + + Returns + -------- + :class:`DynamicItem` + The new instance of the :class:`DynamicItem` with information + from the ``match`` object. + """ + raise NotImplementedError + + async def callback(self, interaction: Interaction[ClientT]) -> Any: + return await self.item.callback(interaction) + + async def interaction_check(self, interaction: Interaction[ClientT], /) -> bool: + return await self.item.interaction_check(interaction) diff --git a/discord/ui/item.py b/discord/ui/item.py index 443876c1a..1ee549283 100644 --- a/discord/ui/item.py +++ b/discord/ui/item.py @@ -40,7 +40,7 @@ if TYPE_CHECKING: from .view import View from ..components import Component -I = TypeVar('I', bound='Item') +I = TypeVar('I', bound='Item[Any]') V = TypeVar('V', bound='View', covariant=True) ItemCallbackType = Callable[[V, Interaction[Any], I], Coroutine[Any, Any, Any]] @@ -133,3 +133,36 @@ class Item(Generic[V]): The interaction that triggered this UI item. """ pass + + async def interaction_check(self, interaction: Interaction[ClientT], /) -> bool: + """|coro| + + A callback that is called when an interaction happens within this item + that checks whether the callback should be processed. + + This is useful to override if, for example, you want to ensure that the + interaction author is a given user. + + The default implementation of this returns ``True``. + + .. note:: + + If an exception occurs within the body then the check + is considered a failure and :meth:`discord.ui.View.on_error` is called. + + For :class:`~discord.ui.DynamicItem` this does not call the ``on_error`` + handler. + + .. versionadded:: 2.4 + + Parameters + ----------- + interaction: :class:`~discord.Interaction` + The interaction that occurred. + + Returns + --------- + :class:`bool` + Whether the callback should be called. + """ + return True diff --git a/discord/ui/modal.py b/discord/ui/modal.py index 615930d5c..630fc20f0 100644 --- a/discord/ui/modal.py +++ b/discord/ui/modal.py @@ -64,6 +64,7 @@ class Modal(View): .. code-block:: python3 + import discord from discord import ui class Questionnaire(ui.Modal, title='Questionnaire Response'): @@ -76,7 +77,8 @@ class Modal(View): Parameters ----------- title: :class:`str` - The title of the modal. Can only be up to 45 characters. + The title of the modal. + Can only be up to 45 characters. timeout: Optional[:class:`float`] Timeout in seconds from last interaction with the UI before no longer accepting input. If ``None`` then there is no timeout. diff --git a/discord/ui/select.py b/discord/ui/select.py index 11555c7f7..1ef085cc5 100644 --- a/discord/ui/select.py +++ b/discord/ui/select.py @@ -21,22 +21,44 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ + from __future__ import annotations -from typing import List, Literal, Optional, TYPE_CHECKING, Tuple, Type, TypeVar, Callable, Union, Dict, overload +from typing import ( + Any, + List, + Literal, + Optional, + TYPE_CHECKING, + Tuple, + Type, + TypeVar, + Callable, + Union, + Dict, + overload, + Sequence, +) from contextvars import ContextVar import inspect import os from .item import Item, ItemCallbackType -from ..enums import ChannelType, ComponentType +from ..enums import ChannelType, ComponentType, SelectDefaultValueType from ..partial_emoji import PartialEmoji from ..emoji import Emoji -from ..utils import MISSING +from ..utils import MISSING, _human_join from ..components import ( SelectOption, SelectMenu, + SelectDefaultValue, ) from ..app_commands.namespace import Namespace +from ..member import Member +from ..object import Object +from ..role import Role +from ..user import User, ClientUser +from ..abc import GuildChannel +from ..threads import Thread __all__ = ( 'Select', @@ -48,15 +70,12 @@ __all__ = ( ) if TYPE_CHECKING: - from typing_extensions import TypeAlias, Self + from typing_extensions import TypeAlias, TypeGuard from .view import View from ..types.components import SelectMenu as SelectMenuPayload from ..types.interactions import SelectMessageComponentInteractionData from ..app_commands import AppCommandChannel, AppCommandThread - from ..member import Member - from ..role import Role - from ..user import User from ..interactions import Interaction ValidSelectType: TypeAlias = Literal[ @@ -69,19 +88,102 @@ if TYPE_CHECKING: PossibleValue: TypeAlias = Union[ str, User, Member, Role, AppCommandChannel, AppCommandThread, Union[Role, Member], Union[Role, User] ] + ValidDefaultValues: TypeAlias = Union[ + SelectDefaultValue, + Object, + Role, + Member, + ClientUser, + User, + GuildChannel, + AppCommandChannel, + AppCommandThread, + Thread, + ] V = TypeVar('V', bound='View', covariant=True) -BaseSelectT = TypeVar('BaseSelectT', bound='BaseSelect') -SelectT = TypeVar('SelectT', bound='Select') -UserSelectT = TypeVar('UserSelectT', bound='UserSelect') -RoleSelectT = TypeVar('RoleSelectT', bound='RoleSelect') -ChannelSelectT = TypeVar('ChannelSelectT', bound='ChannelSelect') -MentionableSelectT = TypeVar('MentionableSelectT', bound='MentionableSelect') +BaseSelectT = TypeVar('BaseSelectT', bound='BaseSelect[Any]') +SelectT = TypeVar('SelectT', bound='Select[Any]') +UserSelectT = TypeVar('UserSelectT', bound='UserSelect[Any]') +RoleSelectT = TypeVar('RoleSelectT', bound='RoleSelect[Any]') +ChannelSelectT = TypeVar('ChannelSelectT', bound='ChannelSelect[Any]') +MentionableSelectT = TypeVar('MentionableSelectT', bound='MentionableSelect[Any]') SelectCallbackDecorator: TypeAlias = Callable[[ItemCallbackType[V, BaseSelectT]], BaseSelectT] +DefaultSelectComponentTypes = Literal[ + ComponentType.user_select, + ComponentType.role_select, + ComponentType.channel_select, + ComponentType.mentionable_select, +] selected_values: ContextVar[Dict[str, List[PossibleValue]]] = ContextVar('selected_values') +def _is_valid_object_type( + obj: Any, + component_type: DefaultSelectComponentTypes, + type_to_supported_classes: Dict[ValidSelectType, Tuple[Type[ValidDefaultValues], ...]], +) -> TypeGuard[Type[ValidDefaultValues]]: + return issubclass(obj, type_to_supported_classes[component_type]) + + +def _handle_select_defaults( + defaults: Sequence[ValidDefaultValues], component_type: DefaultSelectComponentTypes +) -> List[SelectDefaultValue]: + if not defaults or defaults is MISSING: + return [] + + from ..app_commands import AppCommandChannel, AppCommandThread + + cls_to_type: Dict[Type[ValidDefaultValues], SelectDefaultValueType] = { + User: SelectDefaultValueType.user, + Member: SelectDefaultValueType.user, + ClientUser: SelectDefaultValueType.user, + Role: SelectDefaultValueType.role, + GuildChannel: SelectDefaultValueType.channel, + AppCommandChannel: SelectDefaultValueType.channel, + AppCommandThread: SelectDefaultValueType.channel, + Thread: SelectDefaultValueType.channel, + } + type_to_supported_classes: Dict[ValidSelectType, Tuple[Type[ValidDefaultValues], ...]] = { + ComponentType.user_select: (User, ClientUser, Member, Object), + ComponentType.role_select: (Role, Object), + ComponentType.channel_select: (GuildChannel, AppCommandChannel, AppCommandThread, Thread, Object), + ComponentType.mentionable_select: (User, ClientUser, Member, Role, Object), + } + + values: List[SelectDefaultValue] = [] + for obj in defaults: + if isinstance(obj, SelectDefaultValue): + values.append(obj) + continue + + object_type = obj.__class__ if not isinstance(obj, Object) else obj.type + + if not _is_valid_object_type(object_type, component_type, type_to_supported_classes): + supported_classes = _human_join([c.__name__ for c in type_to_supported_classes[component_type]]) + raise TypeError(f'Expected an instance of {supported_classes} not {object_type.__name__}') + + if object_type is Object: + if component_type is ComponentType.mentionable_select: + raise ValueError( + 'Object must have a type specified for the chosen select type. Please pass one using the `type`` kwarg.' + ) + elif component_type is ComponentType.user_select: + object_type = User + elif component_type is ComponentType.role_select: + object_type = Role + elif component_type is ComponentType.channel_select: + object_type = GuildChannel + + if issubclass(object_type, GuildChannel): + object_type = GuildChannel + + values.append(SelectDefaultValue(id=obj.id, type=cls_to_type[object_type])) + + return values + + class BaseSelect(Item[V]): """The base Select model that all other Select models inherit from. @@ -115,6 +217,13 @@ class BaseSelect(Item[V]): 'max_values', 'disabled', ) + __component_attributes__: Tuple[str, ...] = ( + 'custom_id', + 'placeholder', + 'min_values', + 'max_values', + 'disabled', + ) def __init__( self, @@ -128,6 +237,7 @@ class BaseSelect(Item[V]): disabled: bool = False, options: List[SelectOption] = MISSING, channel_types: List[ChannelType] = MISSING, + default_values: Sequence[SelectDefaultValue] = MISSING, ) -> None: super().__init__() self._provided_custom_id = custom_id is not MISSING @@ -144,6 +254,7 @@ class BaseSelect(Item[V]): disabled=disabled, channel_types=[] if channel_types is MISSING else channel_types, options=[] if options is MISSING else options, + default_values=[] if default_values is MISSING else default_values, ) self.row = row @@ -162,10 +273,10 @@ class BaseSelect(Item[V]): @custom_id.setter def custom_id(self, value: str) -> None: if not isinstance(value, str): - raise TypeError('custom_id must be None or str') + raise TypeError('custom_id must be a str') self._underlying.custom_id = value - self._provided_custom_id = value is not None + self._provided_custom_id = True @property def placeholder(self) -> Optional[str]: @@ -220,7 +331,9 @@ class BaseSelect(Item[V]): values = selected_values.get({}) payload: List[PossibleValue] try: - resolved = Namespace._get_resolved_items(interaction, data['resolved']) + resolved = Namespace._get_resolved_items( + interaction, data['resolved'] # pyright: ignore[reportTypedDictNotRequiredAccess] + ) payload = list(resolved.values()) except KeyError: payload = data.get("values", []) # type: ignore @@ -232,11 +345,17 @@ class BaseSelect(Item[V]): return True @classmethod - def from_component(cls, component: SelectMenu) -> Self: - return cls( - **{k: getattr(component, k) for k in cls.__item_repr_attributes__}, - row=None, - ) + def from_component(cls, component: SelectMenu) -> BaseSelect[V]: + type_to_cls: Dict[ComponentType, Type[BaseSelect[Any]]] = { + ComponentType.string_select: Select, + ComponentType.user_select: UserSelect, + ComponentType.role_select: RoleSelect, + ComponentType.channel_select: ChannelSelect, + ComponentType.mentionable_select: MentionableSelect, + } + constructor = type_to_cls.get(component.type, Select) + kwrgs = {key: getattr(component, key) for key in constructor.__component_attributes__} + return constructor(**kwrgs) class Select(BaseSelect[V]): @@ -250,8 +369,10 @@ class Select(BaseSelect[V]): custom_id: :class:`str` The ID of the select menu that gets received during an interaction. If not given then one is generated for you. + Can only be up to 100 characters. placeholder: Optional[:class:`str`] The placeholder text that is shown if nothing is selected, if any. + Can only be up to 150 characters. min_values: :class:`int` The minimum number of items that must be chosen for this select menu. Defaults to 1 and must be between 0 and 25. @@ -260,6 +381,7 @@ class Select(BaseSelect[V]): Defaults to 1 and must be between 1 and 25. options: List[:class:`discord.SelectOption`] A list of options that can be selected in this menu. + Can only contain up to 25 items. disabled: :class:`bool` Whether the select is disabled or not. row: Optional[:class:`int`] @@ -270,7 +392,7 @@ class Select(BaseSelect[V]): ordering. The row number must be between 0 and 4 (i.e. zero indexed). """ - __item_repr_attributes__ = BaseSelect.__item_repr_attributes__ + ('options',) + __component_attributes__ = BaseSelect.__component_attributes__ + ('options',) def __init__( self, @@ -339,7 +461,8 @@ class Select(BaseSelect[V]): Can only be up to 100 characters. value: :class:`str` The value of the option. This is not displayed to users. - If not given, defaults to the label. Can only be up to 100 characters. + If not given, defaults to the label. + Can only be up to 100 characters. description: Optional[:class:`str`] An additional description of the option, if any. Can only be up to 100 characters. @@ -379,7 +502,7 @@ class Select(BaseSelect[V]): The number of options exceeds 25. """ - if len(self._underlying.options) > 25: + if len(self._underlying.options) >= 25: raise ValueError('maximum number of options already provided') self._underlying.options.append(option) @@ -399,8 +522,10 @@ class UserSelect(BaseSelect[V]): custom_id: :class:`str` The ID of the select menu that gets received during an interaction. If not given then one is generated for you. + Can only be up to 100 characters. placeholder: Optional[:class:`str`] The placeholder text that is shown if nothing is selected, if any. + Can only be up to 150 characters. min_values: :class:`int` The minimum number of items that must be chosen for this select menu. Defaults to 1 and must be between 0 and 25. @@ -409,6 +534,11 @@ class UserSelect(BaseSelect[V]): Defaults to 1 and must be between 1 and 25. disabled: :class:`bool` Whether the select is disabled or not. + default_values: Sequence[:class:`~discord.abc.Snowflake`] + A list of objects representing the users that should be selected by default. + Number of items must be in range of ``min_values`` and ``max_values``. + + .. versionadded:: 2.4 row: Optional[:class:`int`] The relative row this select menu belongs to. A Discord component can only have 5 rows. By default, items are arranged automatically into those 5 rows. If you'd @@ -417,6 +547,8 @@ class UserSelect(BaseSelect[V]): ordering. The row number must be between 0 and 4 (i.e. zero indexed). """ + __component_attributes__ = BaseSelect.__component_attributes__ + ('default_values',) + def __init__( self, *, @@ -426,6 +558,7 @@ class UserSelect(BaseSelect[V]): max_values: int = 1, disabled: bool = False, row: Optional[int] = None, + default_values: Sequence[ValidDefaultValues] = MISSING, ) -> None: super().__init__( self.type, @@ -435,6 +568,7 @@ class UserSelect(BaseSelect[V]): max_values=max_values, disabled=disabled, row=row, + default_values=_handle_select_defaults(default_values, self.type), ) @property @@ -455,6 +589,18 @@ class UserSelect(BaseSelect[V]): """ return super().values # type: ignore + @property + def default_values(self) -> List[SelectDefaultValue]: + """List[:class:`discord.SelectDefaultValue`]: A list of default values for the select menu. + + .. versionadded:: 2.4 + """ + return self._underlying.default_values + + @default_values.setter + def default_values(self, value: Sequence[ValidDefaultValues]) -> None: + self._underlying.default_values = _handle_select_defaults(value, self.type) + class RoleSelect(BaseSelect[V]): """Represents a UI select menu with a list of predefined options with the current roles of the guild. @@ -468,8 +614,10 @@ class RoleSelect(BaseSelect[V]): custom_id: :class:`str` The ID of the select menu that gets received during an interaction. If not given then one is generated for you. + Can only be up to 100 characters. placeholder: Optional[:class:`str`] The placeholder text that is shown if nothing is selected, if any. + Can only be up to 150 characters. min_values: :class:`int` The minimum number of items that must be chosen for this select menu. Defaults to 1 and must be between 0 and 25. @@ -478,6 +626,11 @@ class RoleSelect(BaseSelect[V]): Defaults to 1 and must be between 1 and 25. disabled: :class:`bool` Whether the select is disabled or not. + default_values: Sequence[:class:`~discord.abc.Snowflake`] + A list of objects representing the roles that should be selected by default. + Number of items must be in range of ``min_values`` and ``max_values``. + + .. versionadded:: 2.4 row: Optional[:class:`int`] The relative row this select menu belongs to. A Discord component can only have 5 rows. By default, items are arranged automatically into those 5 rows. If you'd @@ -486,6 +639,8 @@ class RoleSelect(BaseSelect[V]): ordering. The row number must be between 0 and 4 (i.e. zero indexed). """ + __component_attributes__ = BaseSelect.__component_attributes__ + ('default_values',) + def __init__( self, *, @@ -495,6 +650,7 @@ class RoleSelect(BaseSelect[V]): max_values: int = 1, disabled: bool = False, row: Optional[int] = None, + default_values: Sequence[ValidDefaultValues] = MISSING, ) -> None: super().__init__( self.type, @@ -504,6 +660,7 @@ class RoleSelect(BaseSelect[V]): max_values=max_values, disabled=disabled, row=row, + default_values=_handle_select_defaults(default_values, self.type), ) @property @@ -516,6 +673,18 @@ class RoleSelect(BaseSelect[V]): """List[:class:`discord.Role`]: A list of roles that have been selected by the user.""" return super().values # type: ignore + @property + def default_values(self) -> List[SelectDefaultValue]: + """List[:class:`discord.SelectDefaultValue`]: A list of default values for the select menu. + + .. versionadded:: 2.4 + """ + return self._underlying.default_values + + @default_values.setter + def default_values(self, value: Sequence[ValidDefaultValues]) -> None: + self._underlying.default_values = _handle_select_defaults(value, self.type) + class MentionableSelect(BaseSelect[V]): """Represents a UI select menu with a list of predefined options with the current members and roles in the guild. @@ -532,8 +701,10 @@ class MentionableSelect(BaseSelect[V]): custom_id: :class:`str` The ID of the select menu that gets received during an interaction. If not given then one is generated for you. + Can only be up to 100 characters. placeholder: Optional[:class:`str`] The placeholder text that is shown if nothing is selected, if any. + Can only be up to 150 characters. min_values: :class:`int` The minimum number of items that must be chosen for this select menu. Defaults to 1 and must be between 0 and 25. @@ -542,6 +713,12 @@ class MentionableSelect(BaseSelect[V]): Defaults to 1 and must be between 1 and 25. disabled: :class:`bool` Whether the select is disabled or not. + default_values: Sequence[:class:`~discord.abc.Snowflake`] + A list of objects representing the users/roles that should be selected by default. + if :class:`.Object` is passed, then the type must be specified in the constructor. + Number of items must be in range of ``min_values`` and ``max_values``. + + .. versionadded:: 2.4 row: Optional[:class:`int`] The relative row this select menu belongs to. A Discord component can only have 5 rows. By default, items are arranged automatically into those 5 rows. If you'd @@ -550,6 +727,8 @@ class MentionableSelect(BaseSelect[V]): ordering. The row number must be between 0 and 4 (i.e. zero indexed). """ + __component_attributes__ = BaseSelect.__component_attributes__ + ('default_values',) + def __init__( self, *, @@ -559,6 +738,7 @@ class MentionableSelect(BaseSelect[V]): max_values: int = 1, disabled: bool = False, row: Optional[int] = None, + default_values: Sequence[ValidDefaultValues] = MISSING, ) -> None: super().__init__( self.type, @@ -568,6 +748,7 @@ class MentionableSelect(BaseSelect[V]): max_values=max_values, disabled=disabled, row=row, + default_values=_handle_select_defaults(default_values, self.type), ) @property @@ -588,6 +769,18 @@ class MentionableSelect(BaseSelect[V]): """ return super().values # type: ignore + @property + def default_values(self) -> List[SelectDefaultValue]: + """List[:class:`discord.SelectDefaultValue`]: A list of default values for the select menu. + + .. versionadded:: 2.4 + """ + return self._underlying.default_values + + @default_values.setter + def default_values(self, value: Sequence[ValidDefaultValues]) -> None: + self._underlying.default_values = _handle_select_defaults(value, self.type) + class ChannelSelect(BaseSelect[V]): """Represents a UI select menu with a list of predefined options with the current channels in the guild. @@ -601,10 +794,12 @@ class ChannelSelect(BaseSelect[V]): custom_id: :class:`str` The ID of the select menu that gets received during an interaction. If not given then one is generated for you. + Can only be up to 100 characters. channel_types: List[:class:`~discord.ChannelType`] The types of channels to show in the select menu. Defaults to all channels. placeholder: Optional[:class:`str`] The placeholder text that is shown if nothing is selected, if any. + Can only be up to 150 characters. min_values: :class:`int` The minimum number of items that must be chosen for this select menu. Defaults to 1 and must be between 0 and 25. @@ -613,6 +808,11 @@ class ChannelSelect(BaseSelect[V]): Defaults to 1 and must be between 1 and 25. disabled: :class:`bool` Whether the select is disabled or not. + default_values: Sequence[:class:`~discord.abc.Snowflake`] + A list of objects representing the channels that should be selected by default. + Number of items must be in range of ``min_values`` and ``max_values``. + + .. versionadded:: 2.4 row: Optional[:class:`int`] The relative row this select menu belongs to. A Discord component can only have 5 rows. By default, items are arranged automatically into those 5 rows. If you'd @@ -621,7 +821,10 @@ class ChannelSelect(BaseSelect[V]): ordering. The row number must be between 0 and 4 (i.e. zero indexed). """ - __item_repr_attributes__ = BaseSelect.__item_repr_attributes__ + ('channel_types',) + __component_attributes__ = BaseSelect.__component_attributes__ + ( + 'channel_types', + 'default_values', + ) def __init__( self, @@ -633,6 +836,7 @@ class ChannelSelect(BaseSelect[V]): max_values: int = 1, disabled: bool = False, row: Optional[int] = None, + default_values: Sequence[ValidDefaultValues] = MISSING, ) -> None: super().__init__( self.type, @@ -643,6 +847,7 @@ class ChannelSelect(BaseSelect[V]): disabled=disabled, row=row, channel_types=channel_types, + default_values=_handle_select_defaults(default_values, self.type), ) @property @@ -669,11 +874,23 @@ class ChannelSelect(BaseSelect[V]): """List[Union[:class:`~discord.app_commands.AppCommandChannel`, :class:`~discord.app_commands.AppCommandThread`]]: A list of channels selected by the user.""" return super().values # type: ignore + @property + def default_values(self) -> List[SelectDefaultValue]: + """List[:class:`discord.SelectDefaultValue`]: A list of default values for the select menu. + + .. versionadded:: 2.4 + """ + return self._underlying.default_values + + @default_values.setter + def default_values(self, value: Sequence[ValidDefaultValues]) -> None: + self._underlying.default_values = _handle_select_defaults(value, self.type) + @overload def select( *, - cls: Type[SelectT] = Select[V], + cls: Type[SelectT] = Select[Any], options: List[SelectOption] = MISSING, channel_types: List[ChannelType] = ..., placeholder: Optional[str] = ..., @@ -689,7 +906,7 @@ def select( @overload def select( *, - cls: Type[UserSelectT] = UserSelect[V], + cls: Type[UserSelectT] = UserSelect[Any], options: List[SelectOption] = MISSING, channel_types: List[ChannelType] = ..., placeholder: Optional[str] = ..., @@ -697,6 +914,7 @@ def select( min_values: int = ..., max_values: int = ..., disabled: bool = ..., + default_values: Sequence[ValidDefaultValues] = ..., row: Optional[int] = ..., ) -> SelectCallbackDecorator[V, UserSelectT]: ... @@ -705,7 +923,7 @@ def select( @overload def select( *, - cls: Type[RoleSelectT] = RoleSelect[V], + cls: Type[RoleSelectT] = RoleSelect[Any], options: List[SelectOption] = MISSING, channel_types: List[ChannelType] = ..., placeholder: Optional[str] = ..., @@ -713,6 +931,7 @@ def select( min_values: int = ..., max_values: int = ..., disabled: bool = ..., + default_values: Sequence[ValidDefaultValues] = ..., row: Optional[int] = ..., ) -> SelectCallbackDecorator[V, RoleSelectT]: ... @@ -721,7 +940,7 @@ def select( @overload def select( *, - cls: Type[ChannelSelectT] = ChannelSelect[V], + cls: Type[ChannelSelectT] = ChannelSelect[Any], options: List[SelectOption] = MISSING, channel_types: List[ChannelType] = ..., placeholder: Optional[str] = ..., @@ -729,6 +948,7 @@ def select( min_values: int = ..., max_values: int = ..., disabled: bool = ..., + default_values: Sequence[ValidDefaultValues] = ..., row: Optional[int] = ..., ) -> SelectCallbackDecorator[V, ChannelSelectT]: ... @@ -737,7 +957,7 @@ def select( @overload def select( *, - cls: Type[MentionableSelectT] = MentionableSelect[V], + cls: Type[MentionableSelectT] = MentionableSelect[Any], options: List[SelectOption] = MISSING, channel_types: List[ChannelType] = MISSING, placeholder: Optional[str] = ..., @@ -745,6 +965,7 @@ def select( min_values: int = ..., max_values: int = ..., disabled: bool = ..., + default_values: Sequence[ValidDefaultValues] = ..., row: Optional[int] = ..., ) -> SelectCallbackDecorator[V, MentionableSelectT]: ... @@ -752,7 +973,7 @@ def select( def select( *, - cls: Type[BaseSelectT] = Select[V], + cls: Type[BaseSelectT] = Select[Any], options: List[SelectOption] = MISSING, channel_types: List[ChannelType] = MISSING, placeholder: Optional[str] = None, @@ -760,6 +981,7 @@ def select( min_values: int = 1, max_values: int = 1, disabled: bool = False, + default_values: Sequence[ValidDefaultValues] = MISSING, row: Optional[int] = None, ) -> SelectCallbackDecorator[V, BaseSelectT]: """A decorator that attaches a select menu to a component. @@ -808,9 +1030,11 @@ def select( get overridden. placeholder: Optional[:class:`str`] The placeholder text that is shown if nothing is selected, if any. + Can only be up to 150 characters. custom_id: :class:`str` The ID of the select menu that gets received during an interaction. It is recommended not to set this parameter to prevent conflicts. + Can only be up to 100 characters. row: Optional[:class:`int`] The relative row this select menu belongs to. A Discord component can only have 5 rows. By default, items are arranged automatically into those 5 rows. If you'd @@ -826,11 +1050,18 @@ def select( options: List[:class:`discord.SelectOption`] A list of options that can be selected in this menu. This can only be used with :class:`Select` instances. + Can only contain up to 25 items. channel_types: List[:class:`~discord.ChannelType`] The types of channels to show in the select menu. Defaults to all channels. This can only be used with :class:`ChannelSelect` instances. disabled: :class:`bool` Whether the select is disabled or not. Defaults to ``False``. + default_values: Sequence[:class:`~discord.abc.Snowflake`] + A list of objects representing the default values for the select menu. This cannot be used with regular :class:`Select` instances. + If ``cls`` is :class:`MentionableSelect` and :class:`.Object` is passed, then the type must be specified in the constructor. + Number of items must be in range of ``min_values`` and ``max_values``. + + .. versionadded:: 2.4 """ def decorator(func: ItemCallbackType[V, BaseSelectT]) -> ItemCallbackType[V, BaseSelectT]: @@ -838,8 +1069,8 @@ def select( raise TypeError('select function must be a coroutine function') callback_cls = getattr(cls, '__origin__', cls) if not issubclass(callback_cls, BaseSelect): - supported_classes = ", ".join(["ChannelSelect", "MentionableSelect", "RoleSelect", "Select", "UserSelect"]) - raise TypeError(f'cls must be one of {supported_classes} or a subclass of one of them, not {cls!r}.') + supported_classes = ', '.join(['ChannelSelect', 'MentionableSelect', 'RoleSelect', 'Select', 'UserSelect']) + raise TypeError(f'cls must be one of {supported_classes} or a subclass of one of them, not {cls.__name__}.') func.__discord_ui_model_type__ = callback_cls func.__discord_ui_model_kwargs__ = { @@ -854,6 +1085,24 @@ def select( func.__discord_ui_model_kwargs__['options'] = options if issubclass(callback_cls, ChannelSelect): func.__discord_ui_model_kwargs__['channel_types'] = channel_types + if not issubclass(callback_cls, Select): + cls_to_type: Dict[ + Type[BaseSelect], + Literal[ + ComponentType.user_select, + ComponentType.channel_select, + ComponentType.role_select, + ComponentType.mentionable_select, + ], + ] = { + UserSelect: ComponentType.user_select, + RoleSelect: ComponentType.role_select, + MentionableSelect: ComponentType.mentionable_select, + ChannelSelect: ComponentType.channel_select, + } + func.__discord_ui_model_kwargs__['default_values'] = ( + MISSING if default_values is MISSING else _handle_select_defaults(default_values, cls_to_type[callback_cls]) + ) return func diff --git a/discord/ui/text_input.py b/discord/ui/text_input.py index 79ac652b9..96b4581f4 100644 --- a/discord/ui/text_input.py +++ b/discord/ui/text_input.py @@ -65,21 +65,27 @@ class TextInput(Item[V]): ------------ label: :class:`str` The label to display above the text input. + Can only be up to 45 characters. custom_id: :class:`str` The ID of the text input that gets received during an interaction. If not given then one is generated for you. + Can only be up to 100 characters. style: :class:`discord.TextStyle` The style of the text input. placeholder: Optional[:class:`str`] The placeholder text to display when the text input is empty. + Can only be up to 100 characters. default: Optional[:class:`str`] The default value of the text input. + Can only be up to 4000 characters. required: :class:`bool` Whether the text input is required. min_length: Optional[:class:`int`] The minimum length of the text input. + Must be between 0 and 4000. max_length: Optional[:class:`int`] The maximum length of the text input. + Must be between 1 and 4000. row: Optional[:class:`int`] The relative row this text input belongs to. A Discord component can only have 5 rows. By default, items are arranged automatically into those 5 rows. If you'd @@ -137,9 +143,10 @@ class TextInput(Item[V]): @custom_id.setter def custom_id(self, value: str) -> None: if not isinstance(value, str): - raise TypeError('custom_id must be None or str') + raise TypeError('custom_id must be a str') self._underlying.custom_id = value + self._provided_custom_id = True @property def width(self) -> int: diff --git a/discord/ui/view.py b/discord/ui/view.py index 19354478f..f27b71eeb 100644 --- a/discord/ui/view.py +++ b/discord/ui/view.py @@ -23,7 +23,7 @@ DEALINGS IN THE SOFTWARE. """ from __future__ import annotations -from typing import Any, Callable, ClassVar, Coroutine, Dict, Iterator, List, Optional, Sequence, TYPE_CHECKING, Tuple +from typing import Any, Callable, ClassVar, Coroutine, Dict, Iterator, List, Optional, Sequence, TYPE_CHECKING, Tuple, Type from functools import partial from itertools import groupby @@ -33,6 +33,8 @@ import sys import time import os from .item import Item, ItemCallbackType +from .select import Select +from .dynamic import DynamicItem from ..components import ( Component, ActionRow as ActionRowComponent, @@ -50,6 +52,7 @@ __all__ = ( if TYPE_CHECKING: from typing_extensions import Self + import re from ..interactions import Interaction from ..message import Message @@ -76,9 +79,10 @@ def _component_to_item(component: Component) -> Item: return Button.from_component(component) if isinstance(component, SelectComponent): - from .select import Select + from .select import BaseSelect + + return BaseSelect.from_component(component) - return Select.from_component(component) return Item.from_component(component) @@ -174,8 +178,10 @@ class View: children = [] for func in self.__view_children_items__: item: Item = func.__discord_ui_model_type__(**func.__discord_ui_model_kwargs__) - item.callback = _ViewCallback(func, self, item) + item.callback = _ViewCallback(func, self, item) # type: ignore item._view = self + if isinstance(item, Select): + item.options = [option.copy() for option in item.options] setattr(self, func.__name__, item) children.append(item) return children @@ -211,6 +217,11 @@ class View: # Wait N seconds to see if timeout data has been refreshed await asyncio.sleep(self.__timeout_expiry - now) + def is_dispatchable(self) -> bool: + # this is used by webhooks to check whether a view requires a state attached + # or not, this simply is, whether a view has a component other than a url button + return any(item.is_dispatchable() for item in self.children) + def to_components(self) -> List[Dict[str, Any]]: def key(item: Item) -> int: return item._rendered_row or 0 @@ -317,7 +328,7 @@ class View: or the row the item is trying to be added to is full. """ - if len(self._children) > 25: + if len(self._children) >= 25: raise ValueError('maximum number of children exceeded') if not isinstance(item, Item): @@ -417,7 +428,7 @@ class View: try: item._refresh_state(interaction, interaction.data) # type: ignore - allow = await self.interaction_check(interaction) + allow = await item.interaction_check(interaction) and await self.interaction_check(interaction) if not allow: return @@ -534,6 +545,8 @@ class ViewStore: self._synced_message_views: Dict[int, View] = {} # custom_id: Modal self._modals: Dict[str, Modal] = {} + # component_type is the key + self._dynamic_items: Dict[re.Pattern[str], Type[DynamicItem[Item[Any]]]] = {} self._state: ConnectionState = state @property @@ -548,6 +561,16 @@ class ViewStore: # fmt: on return list(views.values()) + def add_dynamic_items(self, *items: Type[DynamicItem[Item[Any]]]) -> None: + for item in items: + pattern = item.__discord_ui_compiled_template__ + self._dynamic_items[pattern] = item + + def remove_dynamic_items(self, *items: Type[DynamicItem[Item[Any]]]) -> None: + for item in items: + pattern = item.__discord_ui_compiled_template__ + self._dynamic_items.pop(pattern, None) + def add_view(self, view: View, message_id: Optional[int] = None) -> None: view._start_listening_from_store(self) if view.__discord_ui_modal__: @@ -555,12 +578,17 @@ class ViewStore: return dispatch_info = self._views.setdefault(message_id, {}) + is_fully_dynamic = True for item in view._children: - if item.is_dispatchable(): + if isinstance(item, DynamicItem): + pattern = item.__discord_ui_compiled_template__ + self._dynamic_items[pattern] = item.__class__ + elif item.is_dispatchable(): dispatch_info[(item.type.value, item.custom_id)] = item # type: ignore + is_fully_dynamic = False view._cache_key = message_id - if message_id is not None: + if message_id is not None and not is_fully_dynamic: self._synced_message_views[message_id] = view def remove_view(self, view: View) -> None: @@ -571,7 +599,10 @@ class ViewStore: dispatch_info = self._views.get(view._cache_key) if dispatch_info: for item in view._children: - if item.is_dispatchable(): + if isinstance(item, DynamicItem): + pattern = item.__discord_ui_compiled_template__ + self._dynamic_items.pop(pattern, None) + elif item.is_dispatchable(): dispatch_info.pop((item.type.value, item.custom_id), None) # type: ignore if len(dispatch_info) == 0: @@ -579,7 +610,64 @@ class ViewStore: self._synced_message_views.pop(view._cache_key, None) # type: ignore + async def schedule_dynamic_item_call( + self, + component_type: int, + factory: Type[DynamicItem[Item[Any]]], + interaction: Interaction, + custom_id: str, + match: re.Match[str], + ) -> None: + if interaction.message is None: + return + + view = View.from_message(interaction.message, timeout=None) + + try: + base_item_index, base_item = next( + (index, child) + for index, child in enumerate(view._children) + if child.type.value == component_type and getattr(child, 'custom_id', None) == custom_id + ) + except StopIteration: + return + + try: + item = await factory.from_custom_id(interaction, base_item, match) + except Exception: + _log.exception('Ignoring exception in dynamic item creation for %r', factory) + return + + # Swap the item in the view with our new dynamic item + view._children[base_item_index] = item + item._view = view + item._rendered_row = base_item._rendered_row + item._refresh_state(interaction, interaction.data) # type: ignore + + try: + allow = await item.interaction_check(interaction) + except Exception: + allow = False + + if not allow: + return + + try: + await item.callback(interaction) + except Exception: + _log.exception('Ignoring exception in dynamic item callback for %r', item) + + def dispatch_dynamic_items(self, component_type: int, custom_id: str, interaction: Interaction) -> None: + for pattern, item in self._dynamic_items.items(): + match = pattern.fullmatch(custom_id) + if match is not None: + asyncio.create_task( + self.schedule_dynamic_item_call(component_type, item, interaction, custom_id, match), + name=f'discord-ui-dynamic-item-{item.__name__}-{custom_id}', + ) + def dispatch_view(self, component_type: int, custom_id: str, interaction: Interaction) -> None: + self.dispatch_dynamic_items(component_type, custom_id, interaction) interaction_id: Optional[int] = None message_id: Optional[int] = None # Realistically, in a component based interaction the Interaction.message will never be None @@ -587,8 +675,8 @@ class ViewStore: msg = interaction.message if msg is not None: message_id = msg.id - if msg.interaction: - interaction_id = msg.interaction.id + if msg.interaction_metadata: + interaction_id = msg.interaction_metadata.id key = (component_type, custom_id) diff --git a/discord/user.py b/discord/user.py index 23868712c..c5391372a 100644 --- a/discord/user.py +++ b/discord/user.py @@ -31,7 +31,7 @@ from .asset import Asset from .colour import Colour from .enums import DefaultAvatar from .flags import PublicUserFlags -from .utils import snowflake_time, _bytes_to_base64_data, MISSING +from .utils import snowflake_time, _bytes_to_base64_data, MISSING, _get_as_snowflake if TYPE_CHECKING: from typing_extensions import Self @@ -43,10 +43,7 @@ if TYPE_CHECKING: from .message import Message from .state import ConnectionState from .types.channel import DMChannel as DMChannelPayload - from .types.user import ( - PartialUser as PartialUserPayload, - User as UserPayload, - ) + from .types.user import PartialUser as PartialUserPayload, User as UserPayload, AvatarDecorationData __all__ = ( @@ -65,6 +62,7 @@ class BaseUser(_UserTag): 'name', 'id', 'discriminator', + 'global_name', '_avatar', '_banner', '_accent_colour', @@ -72,12 +70,14 @@ class BaseUser(_UserTag): 'system', '_public_flags', '_state', + '_avatar_decoration_data', ) if TYPE_CHECKING: name: str id: int discriminator: str + global_name: Optional[str] bot: bool system: bool _state: ConnectionState @@ -85,6 +85,7 @@ class BaseUser(_UserTag): _banner: Optional[str] _accent_colour: Optional[int] _public_flags: int + _avatar_decoration_data: Optional[AvatarDecorationData] def __init__(self, *, state: ConnectionState, data: Union[UserPayload, PartialUserPayload]) -> None: self._state = state @@ -92,11 +93,13 @@ class BaseUser(_UserTag): def __repr__(self) -> str: return ( - f"" ) def __str__(self) -> str: + if self.discriminator == '0': + return self.name return f'{self.name}#{self.discriminator}' def __eq__(self, other: object) -> bool: @@ -112,12 +115,14 @@ class BaseUser(_UserTag): self.name = data['username'] self.id = int(data['id']) self.discriminator = data['discriminator'] + self.global_name = data.get('global_name') self._avatar = data['avatar'] self._banner = data.get('banner', None) self._accent_colour = data.get('accent_color', None) self._public_flags = data.get('public_flags', 0) self.bot = data.get('bot', False) self.system = data.get('system', False) + self._avatar_decoration_data = data.get('avatar_decoration_data') @classmethod def _copy(cls, user: Self) -> Self: @@ -126,12 +131,14 @@ class BaseUser(_UserTag): self.name = user.name self.id = user.id self.discriminator = user.discriminator + self.global_name = user.global_name self._avatar = user._avatar self._banner = user._banner self._accent_colour = user._accent_colour self.bot = user.bot self._state = user._state self._public_flags = user._public_flags + self._avatar_decoration_data = user._avatar_decoration_data return self @@ -141,6 +148,7 @@ class BaseUser(_UserTag): 'id': self.id, 'avatar': self._avatar, 'discriminator': self.discriminator, + 'global_name': self.global_name, 'bot': self.bot, } @@ -162,8 +170,13 @@ class BaseUser(_UserTag): @property def default_avatar(self) -> Asset: - """:class:`Asset`: Returns the default avatar for a given user. This is calculated by the user's discriminator.""" - return Asset._from_default_avatar(self._state, int(self.discriminator) % len(DefaultAvatar)) + """:class:`Asset`: Returns the default avatar for a given user.""" + if self.discriminator in ('0', '0000'): + avatar_id = (self.id >> 22) % len(DefaultAvatar) + else: + avatar_id = int(self.discriminator) % 5 + + return Asset._from_default_avatar(self._state, avatar_id) @property def display_avatar(self) -> Asset: @@ -175,6 +188,30 @@ class BaseUser(_UserTag): """ return self.avatar or self.default_avatar + @property + def avatar_decoration(self) -> Optional[Asset]: + """Optional[:class:`Asset`]: Returns an :class:`Asset` for the avatar decoration the user has. + + If the user has not set an avatar decoration, ``None`` is returned. + + .. versionadded:: 2.4 + """ + if self._avatar_decoration_data is not None: + return Asset._from_avatar_decoration(self._state, self._avatar_decoration_data['asset']) + return None + + @property + def avatar_decoration_sku_id(self) -> Optional[int]: + """Optional[:class:`int`]: Returns the SKU ID of the avatar decoration the user has. + + If the user has not set an avatar decoration, ``None`` is returned. + + .. versionadded:: 2.4 + """ + if self._avatar_decoration_data is not None: + return _get_as_snowflake(self._avatar_decoration_data, 'sku_id') + return None + @property def banner(self) -> Optional[Asset]: """Optional[:class:`Asset`]: Returns the user's banner asset, if available. @@ -260,10 +297,12 @@ class BaseUser(_UserTag): def display_name(self) -> str: """:class:`str`: Returns the user's display name. - For regular users this is just their username, but - if they have a guild specific nickname then that + For regular users this is just their global name or their username, + but if they have a guild specific nickname then that is returned instead. """ + if self.global_name: + return self.global_name return self.name def mentioned_in(self, message: Message) -> bool: @@ -305,7 +344,7 @@ class ClientUser(BaseUser): .. describe:: str(x) - Returns the user's name with discriminator. + Returns the user's handle (e.g. ``name`` or ``name#discriminator``). Attributes ----------- @@ -314,7 +353,11 @@ class ClientUser(BaseUser): id: :class:`int` The user's unique ID. discriminator: :class:`str` - The user's discriminator. This is given when the username has conflicts. + The user's discriminator. This is a legacy concept that is no longer used. + global_name: Optional[:class:`str`] + The user's global nickname, taking precedence over the username in display. + + .. versionadded:: 2.3 bot: :class:`bool` Specifies if the user is a bot account. system: :class:`bool` @@ -343,7 +386,7 @@ class ClientUser(BaseUser): def __repr__(self) -> str: return ( - f'' ) @@ -355,7 +398,9 @@ class ClientUser(BaseUser): self._flags = data.get('flags', 0) self.mfa_enabled = data.get('mfa_enabled', False) - async def edit(self, *, username: str = MISSING, avatar: Optional[bytes] = MISSING) -> ClientUser: + async def edit( + self, *, username: str = MISSING, avatar: Optional[bytes] = MISSING, banner: Optional[bytes] = MISSING + ) -> ClientUser: """|coro| Edits the current profile of the client. @@ -367,7 +412,6 @@ class ClientUser(BaseUser): then the file must be opened via ``open('some_filename', 'rb')`` and the :term:`py:bytes-like object` is given through the use of ``fp.read()``. - The only image formats supported for uploading is JPEG and PNG. .. versionchanged:: 2.0 The edit is no longer in-place, instead the newly edited client user is returned. @@ -383,6 +427,13 @@ class ClientUser(BaseUser): avatar: Optional[:class:`bytes`] A :term:`py:bytes-like object` representing the image to upload. Could be ``None`` to denote no avatar. + Only image formats supported for uploading are JPEG, PNG, GIF, and WEBP. + banner: Optional[:class:`bytes`] + A :term:`py:bytes-like object` representing the image to upload. + Could be ``None`` to denote no banner. + Only image formats supported for uploading are JPEG, PNG, GIF and WEBP. + + .. versionadded:: 2.4 Raises ------ @@ -406,9 +457,27 @@ class ClientUser(BaseUser): else: payload['avatar'] = None + if banner is not MISSING: + if banner is not None: + payload['banner'] = _bytes_to_base64_data(banner) + else: + payload['banner'] = None + data: UserPayload = await self._state.http.edit_profile(payload) return ClientUser(state=self._state, data=data) + @property + def mutual_guilds(self) -> List[Guild]: + """List[:class:`Guild`]: The guilds that the user shares with the client. + + .. note:: + + This will only return mutual guilds within the client's internal cache. + + .. versionadded:: 1.7 + """ + return list(self._state.guilds) + class User(BaseUser, discord.abc.Messageable): """Represents a Discord user. @@ -429,7 +498,7 @@ class User(BaseUser, discord.abc.Messageable): .. describe:: str(x) - Returns the user's name with discriminator. + Returns the user's handle (e.g. ``name`` or ``name#discriminator``). Attributes ----------- @@ -438,7 +507,11 @@ class User(BaseUser, discord.abc.Messageable): id: :class:`int` The user's unique ID. discriminator: :class:`str` - The user's discriminator. This is given when the username has conflicts. + The user's discriminator. This is a legacy concept that is no longer used. + global_name: Optional[:class:`str`] + The user's global nickname, taking precedence over the username in display. + + .. versionadded:: 2.3 bot: :class:`bool` Specifies if the user is a bot account. system: :class:`bool` @@ -448,7 +521,7 @@ class User(BaseUser, discord.abc.Messageable): __slots__ = ('__weakref__',) def __repr__(self) -> str: - return f'' + return f'' async def _get_channel(self) -> DMChannel: ch = await self.create_dm() diff --git a/discord/utils.py b/discord/utils.py index 6b4e14347..bcdf922b4 100644 --- a/discord/utils.py +++ b/discord/utils.py @@ -21,6 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ + from __future__ import annotations import array @@ -41,7 +42,6 @@ from typing import ( Iterator, List, Literal, - Mapping, NamedTuple, Optional, Protocol, @@ -56,7 +56,7 @@ from typing import ( TYPE_CHECKING, ) import unicodedata -from base64 import b64encode +from base64 import b64encode, b64decode from bisect import bisect_left import datetime import functools @@ -68,8 +68,10 @@ import re import os import sys import types +import typing import warnings import logging +import zlib import yarl @@ -80,6 +82,12 @@ except ModuleNotFoundError: else: HAS_ORJSON = True +try: + import zstandard # type: ignore +except ImportError: + _HAS_ZSTD = False +else: + _HAS_ZSTD = True __all__ = ( 'oauth_url', @@ -100,6 +108,7 @@ __all__ = ( ) DISCORD_EPOCH = 1420070400000 +DEFAULT_FILE_SIZE_LIMIT_BYTES = 10485760 class _MissingSentinel: @@ -146,8 +155,11 @@ if TYPE_CHECKING: from .invite import Invite from .template import Template - class _RequestLike(Protocol): - headers: Mapping[str, Any] + class _DecompressionContext(Protocol): + COMPRESSION_TYPE: str + + def decompress(self, data: bytes, /) -> str | None: + ... P = ParamSpec('P') @@ -300,7 +312,7 @@ def deprecated(instead: Optional[str] = None) -> Callable[[Callable[P, T]], Call else: fmt = '{0.__name__} is deprecated.' - warnings.warn(fmt.format(func, instead), stacklevel=3, category=DeprecationWarning) + warnings.warn(fmt.format(func, instead), stacklevel=2, category=DeprecationWarning) warnings.simplefilter('default', DeprecationWarning) # reset filter return func(*args, **kwargs) @@ -315,7 +327,7 @@ def oauth_url( permissions: Permissions = MISSING, guild: Snowflake = MISSING, redirect_uri: str = MISSING, - scopes: Iterable[str] = MISSING, + scopes: Optional[Iterable[str]] = MISSING, disable_guild_select: bool = False, state: str = MISSING, ) -> str: @@ -357,7 +369,8 @@ def oauth_url( The OAuth2 URL for inviting the bot into guilds. """ url = f'https://discord.com/oauth2/authorize?client_id={client_id}' - url += '&scope=' + '+'.join(scopes or ('bot', 'applications.commands')) + if scopes is not None: + url += '&scope=' + '+'.join(scopes or ('bot', 'applications.commands')) if permissions is not MISSING: url += f'&permissions={permissions.value}' if guild is not MISSING: @@ -621,13 +634,27 @@ def _get_mime_type_for_image(data: bytes): raise ValueError('Unsupported image type given') -def _bytes_to_base64_data(data: bytes) -> str: +def _get_mime_type_for_audio(data: bytes): + if data.startswith(b'\x49\x44\x33') or data.startswith(b'\xff\xfb'): + return 'audio/mpeg' + else: + raise ValueError('Unsupported audio type given') + + +def _bytes_to_base64_data(data: bytes, *, audio: bool = False) -> str: fmt = 'data:{mime};base64,{data}' - mime = _get_mime_type_for_image(data) + if audio: + mime = _get_mime_type_for_audio(data) + else: + mime = _get_mime_type_for_image(data) b64 = b64encode(data).decode('ascii') return fmt.format(mime=mime, data=b64) +def _base64_to_bytes(data: str) -> bytes: + return b64decode(data.encode('ascii')) + + def _is_submodule(parent: str, child: str) -> bool: return parent == child or child.startswith(parent + '.') @@ -687,13 +714,13 @@ async def maybe_coroutine(f: MaybeAwaitableFunc[P, T], *args: P.args, **kwargs: if _isawaitable(value): return await value else: - return value # type: ignore + return value async def async_all( gen: Iterable[Union[T, Awaitable[T]]], *, - check: Callable[[Union[T, Awaitable[T]]], TypeGuard[Awaitable[T]]] = _isawaitable, + check: Callable[[Union[T, Awaitable[T]]], TypeGuard[Awaitable[T]]] = _isawaitable, # type: ignore ) -> bool: for elem in gen: if check(elem): @@ -716,7 +743,7 @@ async def sane_wait_for(futures: Iterable[Awaitable[T]], *, timeout: Optional[fl def get_slots(cls: Type[Any]) -> Iterator[str]: for mro in reversed(cls.__mro__): try: - yield from mro.__slots__ # type: ignore + yield from mro.__slots__ except AttributeError: continue @@ -842,6 +869,12 @@ def resolve_invite(invite: Union[Invite, str]) -> ResolvedInvite: invite: Union[:class:`~discord.Invite`, :class:`str`] The invite. + Raises + ------- + ValueError + The invite is not a valid Discord invite, e.g. is not a URL + or does not contain alphanumeric characters. + Returns -------- :class:`.ResolvedInvite` @@ -861,7 +894,12 @@ def resolve_invite(invite: Union[Invite, str]) -> ResolvedInvite: event_id = url.query.get('event') return ResolvedInvite(code, int(event_id) if event_id else None) - return ResolvedInvite(invite, None) + + allowed_characters = r'[a-zA-Z0-9\-_]+' + if not re.fullmatch(allowed_characters, invite): + raise ValueError('Invite contains characters that are not allowed') + + return ResolvedInvite(invite, None) def resolve_template(code: Union[Template, str]) -> str: @@ -894,7 +932,7 @@ def resolve_template(code: Union[Template, str]) -> str: _MARKDOWN_ESCAPE_SUBREGEX = '|'.join(r'\{0}(?=([\s\S]*((?(?:>>)?\s|\[.+\]\(.+\)' +_MARKDOWN_ESCAPE_COMMON = r'^>(?:>>)?\s|\[.+\]\(.+\)|^#{1,3}|^\s*-' _MARKDOWN_ESCAPE_REGEX = re.compile(fr'(?P{_MARKDOWN_ESCAPE_SUBREGEX}|{_MARKDOWN_ESCAPE_COMMON})', re.MULTILINE) @@ -927,7 +965,7 @@ def remove_markdown(text: str, *, ignore_links: bool = True) -> str: The text with the markdown special characters removed. """ - def replacement(match): + def replacement(match: re.Match[str]) -> str: groupdict = match.groupdict() return groupdict.get('url', '') @@ -1075,6 +1113,7 @@ def as_chunks(iterator: _Iter[T], max_size: int) -> _Iter[List[T]]: PY_310 = sys.version_info >= (3, 10) +PY_312 = sys.version_info >= (3, 12) def flatten_literal_params(parameters: Iterable[Any]) -> Tuple[Any, ...]: @@ -1082,7 +1121,7 @@ def flatten_literal_params(parameters: Iterable[Any]) -> Tuple[Any, ...]: literal_cls = type(Literal[0]) for p in parameters: if isinstance(p, literal_cls): - params.extend(p.__args__) + params.extend(p.__args__) # type: ignore else: params.append(p) return tuple(params) @@ -1113,6 +1152,16 @@ def evaluate_annotation( cache[tp] = evaluated return evaluated + if PY_312 and getattr(tp.__repr__, '__objclass__', None) is typing.TypeAliasType: # type: ignore + temp_locals = dict(**locals, **{t.__name__: t for t in tp.__type_params__}) + annotation = evaluate_annotation(tp.__value__, globals, temp_locals, cache.copy()) + if hasattr(tp, '__args__'): + annotation = annotation[tp.__args__] + return annotation + + if hasattr(tp, '__supertype__'): + return evaluate_annotation(tp.__supertype__, globals, locals, cache) + if hasattr(tp, '__metadata__'): # Annotated[X, Y] can access Y via __metadata__ metadata = tp.__metadata__[0] @@ -1239,11 +1288,12 @@ def is_docker() -> bool: def stream_supports_colour(stream: Any) -> bool: + is_a_tty = hasattr(stream, 'isatty') and stream.isatty() + # Pycharm and Vscode support colour in their inbuilt editors if 'PYCHARM_HOSTED' in os.environ or os.environ.get('TERM_PROGRAM') == 'vscode': - return True + return is_a_tty - is_a_tty = hasattr(stream, 'isatty') and stream.isatty() if sys.platform != 'win32': # Docker does not consistently have a tty attached to it return is_a_tty or is_docker() @@ -1374,3 +1424,119 @@ CAMEL_CASE_REGEX = re.compile(r'(? str: return CAMEL_CASE_REGEX.sub('-', text).lower() + + +def _human_join(seq: Sequence[str], /, *, delimiter: str = ', ', final: str = 'or') -> str: + size = len(seq) + if size == 0: + return '' + + if size == 1: + return seq[0] + + if size == 2: + return f'{seq[0]} {final} {seq[1]}' + + return delimiter.join(seq[:-1]) + f' {final} {seq[-1]}' + + +if _HAS_ZSTD: + + class _ZstdDecompressionContext: + __slots__ = ('context',) + + COMPRESSION_TYPE: str = 'zstd-stream' + + def __init__(self) -> None: + decompressor = zstandard.ZstdDecompressor() + self.context = decompressor.decompressobj() + + def decompress(self, data: bytes, /) -> str | None: + # Each WS message is a complete gateway message + return self.context.decompress(data).decode('utf-8') + + _ActiveDecompressionContext: Type[_DecompressionContext] = _ZstdDecompressionContext +else: + + class _ZlibDecompressionContext: + __slots__ = ('context', 'buffer') + + COMPRESSION_TYPE: str = 'zlib-stream' + + def __init__(self) -> None: + self.buffer: bytearray = bytearray() + self.context = zlib.decompressobj() + + def decompress(self, data: bytes, /) -> str | None: + self.buffer.extend(data) + + # Check whether ending is Z_SYNC_FLUSH + if len(data) < 4 or data[-4:] != b'\x00\x00\xff\xff': + return + + msg = self.context.decompress(self.buffer) + self.buffer = bytearray() + + return msg.decode('utf-8') + + _ActiveDecompressionContext: Type[_DecompressionContext] = _ZlibDecompressionContext + + +def _format_call_duration(duration: datetime.timedelta) -> str: + seconds = duration.total_seconds() + + minutes_s = 60 + hours_s = minutes_s * 60 + days_s = hours_s * 24 + # Discord uses approx. 1/12 of 365.25 days (avg. days per year) + months_s = days_s * 30.4375 + years_s = months_s * 12 + + threshold_s = 45 + threshold_m = 45 + threshold_h = 21.5 + threshold_d = 25.5 + threshold_M = 10.5 + + if seconds < threshold_s: + formatted = "a few seconds" + elif seconds < (threshold_m * minutes_s): + minutes = round(seconds / minutes_s) + if minutes == 1: + formatted = "a minute" + else: + formatted = f"{minutes} minutes" + elif seconds < (threshold_h * hours_s): + hours = round(seconds / hours_s) + if hours == 1: + formatted = "an hour" + else: + formatted = f"{hours} hours" + elif seconds < (threshold_d * days_s): + days = round(seconds / days_s) + if days == 1: + formatted = "a day" + else: + formatted = f"{days} days" + elif seconds < (threshold_M * months_s): + months = round(seconds / months_s) + if months == 1: + formatted = "a month" + else: + formatted = f"{months} months" + else: + years = round(seconds / years_s) + if years == 1: + formatted = "a year" + else: + formatted = f"{years} years" + + return formatted + + +class _RawReprMixin: + __slots__: Tuple[str, ...] = () + + def __repr__(self) -> str: + value = ' '.join(f'{attr}={getattr(self, attr)!r}' for attr in self.__slots__) + return f'<{self.__class__.__name__} {value}>' diff --git a/discord/voice_client.py b/discord/voice_client.py index c438b5c0e..795434e1e 100644 --- a/discord/voice_client.py +++ b/discord/voice_client.py @@ -20,45 +20,29 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - -Some documentation to refer to: - -- Our main web socket (mWS) sends opcode 4 with a guild ID and channel ID. -- The mWS receives VOICE_STATE_UPDATE and VOICE_SERVER_UPDATE. -- We pull the session_id from VOICE_STATE_UPDATE. -- We pull the token, endpoint and server_id from VOICE_SERVER_UPDATE. -- Then we initiate the voice web socket (vWS) pointing to the endpoint. -- We send opcode 0 with the user_id, server_id, session_id and token using the vWS. -- The vWS sends back opcode 2 with an ssrc, port, modes(array) and heartbeat_interval. -- We send a UDP discovery packet to endpoint:port and receive our IP and our port in LE. -- Then we send our IP and port via vWS with opcode 1. -- When that's all done, we receive opcode 4 from the vWS. -- Finally we can transmit data to endpoint:port. """ from __future__ import annotations import asyncio -import socket import logging import struct -import threading from typing import Any, Callable, List, Optional, TYPE_CHECKING, Tuple, Union -from . import opus, utils -from .backoff import ExponentialBackoff +from . import opus from .gateway import * -from .errors import ClientException, ConnectionClosed +from .errors import ClientException from .player import AudioPlayer, AudioSource from .utils import MISSING +from .voice_state import VoiceConnectionState if TYPE_CHECKING: + from .gateway import DiscordVoiceWebSocket from .client import Client from .guild import Guild from .state import ConnectionState from .user import ClientUser - from .opus import Encoder + from .opus import Encoder, APPLICATION_CTL, BAND_CTL, SIGNAL_CTL from .channel import StageChannel, VoiceChannel from . import abc @@ -121,6 +105,10 @@ class VoiceProtocol: An abstract method that is called when the client's voice state has changed. This corresponds to ``VOICE_STATE_UPDATE``. + .. warning:: + + This method is not the same as the event. See: :func:`on_voice_state_update` + Parameters ------------ data: :class:`dict` @@ -137,7 +125,7 @@ class VoiceProtocol: Parameters ------------ data: :class:`dict` - The raw :ddocs:`voice server update payload `. + The raw :ddocs:`voice server update payload `. """ raise NotImplementedError @@ -226,12 +214,6 @@ class VoiceClient(VoiceProtocol): """ channel: VocalGuildChannel - endpoint_ip: str - voice_port: int - ip: str - port: int - secret_key: List[int] - ssrc: int def __init__(self, client: Client, channel: abc.Connectable) -> None: if not has_nacl: @@ -239,32 +221,22 @@ class VoiceClient(VoiceProtocol): super().__init__(client, channel) state = client._connection - self.token: str = MISSING self.server_id: int = MISSING self.socket = MISSING self.loop: asyncio.AbstractEventLoop = state.loop self._state: ConnectionState = state - # this will be used in the AudioPlayer thread - self._connected: threading.Event = threading.Event() - - self._handshaking: bool = False - self._potentially_reconnecting: bool = False - self._voice_state_complete: asyncio.Event = asyncio.Event() - self._voice_server_complete: asyncio.Event = asyncio.Event() - self.mode: str = MISSING - self._connections: int = 0 self.sequence: int = 0 self.timestamp: int = 0 - self.timeout: float = 0 - self._runner: asyncio.Task = MISSING self._player: Optional[AudioPlayer] = None self.encoder: Encoder = MISSING - self._lite_nonce: int = 0 - self.ws: DiscordVoiceWebSocket = MISSING + self._incr_nonce: int = 0 + + self._connection: VoiceConnectionState = self.create_connection_state() warn_nacl: bool = not has_nacl supported_modes: Tuple[SupportedModes, ...] = ( + 'aead_xchacha20_poly1305_rtpsize', 'xsalsa20_poly1305_lite', 'xsalsa20_poly1305_suffix', 'xsalsa20_poly1305', @@ -280,6 +252,38 @@ class VoiceClient(VoiceProtocol): """:class:`ClientUser`: The user connected to voice (i.e. ourselves).""" return self._state.user # type: ignore + @property + def session_id(self) -> Optional[str]: + return self._connection.session_id + + @property + def token(self) -> Optional[str]: + return self._connection.token + + @property + def endpoint(self) -> Optional[str]: + return self._connection.endpoint + + @property + def ssrc(self) -> int: + return self._connection.ssrc + + @property + def mode(self) -> SupportedModes: + return self._connection.mode + + @property + def secret_key(self) -> List[int]: + return self._connection.secret_key + + @property + def ws(self) -> DiscordVoiceWebSocket: + return self._connection.ws + + @property + def timeout(self) -> float: + return self._connection.timeout + def checked_add(self, attr: str, value: int, limit: int) -> None: val = getattr(self, attr) if val + value > limit: @@ -289,144 +293,23 @@ class VoiceClient(VoiceProtocol): # connection related + def create_connection_state(self) -> VoiceConnectionState: + return VoiceConnectionState(self) + async def on_voice_state_update(self, data: GuildVoiceStatePayload) -> None: - self.session_id: str = data['session_id'] - channel_id = data['channel_id'] - - if not self._handshaking or self._potentially_reconnecting: - # If we're done handshaking then we just need to update ourselves - # If we're potentially reconnecting due to a 4014, then we need to differentiate - # a channel move and an actual force disconnect - if channel_id is None: - # We're being disconnected so cleanup - await self.disconnect() - else: - self.channel = channel_id and self.guild.get_channel(int(channel_id)) # type: ignore - else: - self._voice_state_complete.set() + await self._connection.voice_state_update(data) async def on_voice_server_update(self, data: VoiceServerUpdatePayload) -> None: - if self._voice_server_complete.is_set(): - _log.warning('Ignoring extraneous voice server update.') - return - - self.token = data['token'] - self.server_id = int(data['guild_id']) - endpoint = data.get('endpoint') - - if endpoint is None or self.token is None: - _log.warning( - 'Awaiting endpoint... This requires waiting. ' - 'If timeout occurred considering raising the timeout and reconnecting.' - ) - return - - self.endpoint, _, _ = endpoint.rpartition(':') - if self.endpoint.startswith('wss://'): - # Just in case, strip it off since we're going to add it later - self.endpoint: str = self.endpoint[6:] - - # This gets set later - self.endpoint_ip = MISSING - - self.socket: socket.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - self.socket.setblocking(False) - - if not self._handshaking: - # If we're not handshaking then we need to terminate our previous connection in the websocket - await self.ws.close(4000) - return - - self._voice_server_complete.set() - - async def voice_connect(self, self_deaf: bool = False, self_mute: bool = False) -> None: - await self.channel.guild.change_voice_state(channel=self.channel, self_deaf=self_deaf, self_mute=self_mute) - - async def voice_disconnect(self) -> None: - _log.info('The voice handshake is being terminated for Channel ID %s (Guild ID %s)', self.channel.id, self.guild.id) - await self.channel.guild.change_voice_state(channel=None) - - def prepare_handshake(self) -> None: - self._voice_state_complete.clear() - self._voice_server_complete.clear() - self._handshaking = True - _log.info('Starting voice handshake... (connection attempt %d)', self._connections + 1) - self._connections += 1 - - def finish_handshake(self) -> None: - _log.info('Voice handshake complete. Endpoint found %s', self.endpoint) - self._handshaking = False - self._voice_server_complete.clear() - self._voice_state_complete.clear() - - async def connect_websocket(self) -> DiscordVoiceWebSocket: - ws = await DiscordVoiceWebSocket.from_client(self) - self._connected.clear() - while ws.secret_key is None: - await ws.poll_event() - self._connected.set() - return ws + await self._connection.voice_server_update(data) async def connect(self, *, reconnect: bool, timeout: float, self_deaf: bool = False, self_mute: bool = False) -> None: - _log.info('Connecting to voice...') - self.timeout = timeout - - for i in range(5): - self.prepare_handshake() - - # This has to be created before we start the flow. - futures = [ - self._voice_state_complete.wait(), - self._voice_server_complete.wait(), - ] - - # Start the connection flow - await self.voice_connect(self_deaf=self_deaf, self_mute=self_mute) - - try: - await utils.sane_wait_for(futures, timeout=timeout) - except asyncio.TimeoutError: - await self.disconnect(force=True) - raise - - self.finish_handshake() - - try: - self.ws = await self.connect_websocket() - break - except (ConnectionClosed, asyncio.TimeoutError): - if reconnect: - _log.exception('Failed to connect to voice... Retrying...') - await asyncio.sleep(1 + i * 2.0) - await self.voice_disconnect() - continue - else: - raise - - if self._runner is MISSING: - self._runner = self.client.loop.create_task(self.poll_voice_ws(reconnect)) - - async def potential_reconnect(self) -> bool: - # Attempt to stop the player thread from playing early - self._connected.clear() - self.prepare_handshake() - self._potentially_reconnecting = True - try: - # We only care about VOICE_SERVER_UPDATE since VOICE_STATE_UPDATE can come before we get disconnected - await asyncio.wait_for(self._voice_server_complete.wait(), timeout=self.timeout) - except asyncio.TimeoutError: - self._potentially_reconnecting = False - await self.disconnect(force=True) - return False - - self.finish_handshake() - self._potentially_reconnecting = False - try: - self.ws = await self.connect_websocket() - except (ConnectionClosed, asyncio.TimeoutError): - return False - else: - return True + await self._connection.connect( + reconnect=reconnect, timeout=timeout, self_deaf=self_deaf, self_mute=self_mute, resume=False + ) + + def wait_until_connected(self, timeout: Optional[float] = 30.0) -> bool: + self._connection.wait(timeout) + return self._connection.is_connected() @property def latency(self) -> float: @@ -437,7 +320,7 @@ class VoiceClient(VoiceProtocol): .. versionadded:: 1.4 """ - ws = self.ws + ws = self._connection.ws return float("inf") if not ws else ws.latency @property @@ -446,72 +329,19 @@ class VoiceClient(VoiceProtocol): .. versionadded:: 1.4 """ - ws = self.ws + ws = self._connection.ws return float("inf") if not ws else ws.average_latency - async def poll_voice_ws(self, reconnect: bool) -> None: - backoff = ExponentialBackoff() - while True: - try: - await self.ws.poll_event() - except (ConnectionClosed, asyncio.TimeoutError) as exc: - if isinstance(exc, ConnectionClosed): - # The following close codes are undocumented so I will document them here. - # 1000 - normal closure (obviously) - # 4014 - voice channel has been deleted. - # 4015 - voice server has crashed - if exc.code in (1000, 4015): - _log.info('Disconnecting from voice normally, close code %d.', exc.code) - await self.disconnect() - break - if exc.code == 4014: - _log.info('Disconnected from voice by force... potentially reconnecting.') - successful = await self.potential_reconnect() - if not successful: - _log.info('Reconnect was unsuccessful, disconnecting from voice normally...') - await self.disconnect() - break - else: - continue - - if not reconnect: - await self.disconnect() - raise - - retry = backoff.delay() - _log.exception('Disconnected from voice... Reconnecting in %.2fs.', retry) - self._connected.clear() - await asyncio.sleep(retry) - await self.voice_disconnect() - try: - await self.connect(reconnect=True, timeout=self.timeout) - except asyncio.TimeoutError: - # at this point we've retried 5 times... let's continue the loop. - _log.warning('Could not connect to voice... Retrying...') - continue - async def disconnect(self, *, force: bool = False) -> None: """|coro| Disconnects this voice client from voice. """ - if not force and not self.is_connected(): - return - self.stop() - self._connected.clear() - - try: - if self.ws: - await self.ws.close() - - await self.voice_disconnect() - finally: - self.cleanup() - if self.socket: - self.socket.close() + await self._connection.disconnect(force=force, wait=True) + self.cleanup() - async def move_to(self, channel: Optional[abc.Snowflake]) -> None: + async def move_to(self, channel: Optional[abc.Snowflake], *, timeout: Optional[float] = 30.0) -> None: """|coro| Moves you to a different voice channel. @@ -520,12 +350,21 @@ class VoiceClient(VoiceProtocol): ----------- channel: Optional[:class:`abc.Snowflake`] The channel to move to. Must be a voice channel. + timeout: Optional[:class:`float`] + How long to wait for the move to complete. + + .. versionadded:: 2.4 + + Raises + ------- + asyncio.TimeoutError + The move did not complete in time, but may still be ongoing. """ - await self.channel.guild.change_voice_state(channel=channel) + await self._connection.move_to(channel, timeout) def is_connected(self) -> bool: """Indicates if the voice client is connected to voice.""" - return self._connected.is_set() + return self._connection.is_connected() # audio related @@ -542,7 +381,21 @@ class VoiceClient(VoiceProtocol): encrypt_packet = getattr(self, '_encrypt_' + self.mode) return encrypt_packet(header, data) + def _encrypt_aead_xchacha20_poly1305_rtpsize(self, header: bytes, data) -> bytes: + # Esentially the same as _lite + # Uses an incrementing 32-bit integer which is appended to the payload + # The only other difference is we require AEAD with Additional Authenticated Data (the header) + box = nacl.secret.Aead(bytes(self.secret_key)) + nonce = bytearray(24) + + nonce[:4] = struct.pack('>I', self._incr_nonce) + self.checked_add('_incr_nonce', 1, 4294967295) + + return header + box.encrypt(bytes(data), bytes(header), bytes(nonce)).ciphertext + nonce[:4] + def _encrypt_xsalsa20_poly1305(self, header: bytes, data) -> bytes: + # Deprecated. Removal: 18th Nov 2024. See: + # https://discord.com/developers/docs/topics/voice-connections#transport-encryption-modes box = nacl.secret.SecretBox(bytes(self.secret_key)) nonce = bytearray(24) nonce[:12] = header @@ -550,21 +403,36 @@ class VoiceClient(VoiceProtocol): return header + box.encrypt(bytes(data), bytes(nonce)).ciphertext def _encrypt_xsalsa20_poly1305_suffix(self, header: bytes, data) -> bytes: + # Deprecated. Removal: 18th Nov 2024. See: + # https://discord.com/developers/docs/topics/voice-connections#transport-encryption-modes box = nacl.secret.SecretBox(bytes(self.secret_key)) nonce = nacl.utils.random(nacl.secret.SecretBox.NONCE_SIZE) return header + box.encrypt(bytes(data), nonce).ciphertext + nonce def _encrypt_xsalsa20_poly1305_lite(self, header: bytes, data) -> bytes: + # Deprecated. Removal: 18th Nov 2024. See: + # https://discord.com/developers/docs/topics/voice-connections#transport-encryption-modes box = nacl.secret.SecretBox(bytes(self.secret_key)) nonce = bytearray(24) - nonce[:4] = struct.pack('>I', self._lite_nonce) - self.checked_add('_lite_nonce', 1, 4294967295) + nonce[:4] = struct.pack('>I', self._incr_nonce) + self.checked_add('_incr_nonce', 1, 4294967295) return header + box.encrypt(bytes(data), bytes(nonce)).ciphertext + nonce[:4] - def play(self, source: AudioSource, *, after: Optional[Callable[[Optional[Exception]], Any]] = None) -> None: + def play( + self, + source: AudioSource, + *, + after: Optional[Callable[[Optional[Exception]], Any]] = None, + application: APPLICATION_CTL = 'audio', + bitrate: int = 128, + fec: bool = True, + expected_packet_loss: float = 0.15, + bandwidth: BAND_CTL = 'full', + signal_type: SIGNAL_CTL = 'auto', + ) -> None: """Plays an :class:`AudioSource`. The finalizer, ``after`` is called after the source has been exhausted @@ -574,9 +442,15 @@ class VoiceClient(VoiceProtocol): caught and the audio player is then stopped. If no after callback is passed, any caught exception will be logged using the library logger. + Extra parameters may be passed to the internal opus encoder if a PCM based + source is used. Otherwise, they are ignored. + .. versionchanged:: 2.0 Instead of writing to ``sys.stderr``, the library's logger is used. + .. versionchanged:: 2.4 + Added encoder parameters as keyword arguments. + Parameters ----------- source: :class:`AudioSource` @@ -585,6 +459,27 @@ class VoiceClient(VoiceProtocol): The finalizer that is called after the stream is exhausted. This function must have a single parameter, ``error``, that denotes an optional exception that was raised during playing. + application: :class:`str` + Configures the encoder's intended application. Can be one of: + ``'audio'``, ``'voip'``, ``'lowdelay'``. + Defaults to ``'audio'``. + bitrate: :class:`int` + Configures the bitrate in the encoder. Can be between ``16`` and ``512``. + Defaults to ``128``. + fec: :class:`bool` + Configures the encoder's use of inband forward error correction. + Defaults to ``True``. + expected_packet_loss: :class:`float` + Configures the encoder's expected packet loss percentage. Requires FEC. + Defaults to ``0.15``. + bandwidth: :class:`str` + Configures the encoder's bandpass. Can be one of: + ``'narrow'``, ``'medium'``, ``'wide'``, ``'superwide'``, ``'full'``. + Defaults to ``'full'``. + signal_type: :class:`str` + Configures the type of signal being encoded. Can be one of: + ``'auto'``, ``'voice'``, ``'music'``. + Defaults to ``'auto'``. Raises ------- @@ -594,6 +489,8 @@ class VoiceClient(VoiceProtocol): Source is not a :class:`AudioSource` or after is not a callable. OpusNotLoaded Source is not opus encoded and opus is not loaded. + ValueError + An improper value was passed as an encoder parameter. """ if not self.is_connected(): @@ -605,8 +502,15 @@ class VoiceClient(VoiceProtocol): if not isinstance(source, AudioSource): raise TypeError(f'source must be an AudioSource not {source.__class__.__name__}') - if not self.encoder and not source.is_opus(): - self.encoder = opus.Encoder() + if not source.is_opus(): + self.encoder = opus.Encoder( + application=application, + bitrate=bitrate, + fec=fec, + expected_packet_loss=expected_packet_loss, + bandwidth=bandwidth, + signal_type=signal_type, + ) self._player = AudioPlayer(source, self, after=after) self._player.start() @@ -651,7 +555,7 @@ class VoiceClient(VoiceProtocol): if self._player is None: raise ValueError('Not playing anything.') - self._player._set_source(value) + self._player.set_source(value) def send_audio_packet(self, data: bytes, *, encode: bool = True) -> None: """Sends an audio packet composed of the data. @@ -680,8 +584,8 @@ class VoiceClient(VoiceProtocol): encoded_data = data packet = self._get_voice_packet(encoded_data) try: - self.socket.sendto(packet, (self.endpoint_ip, self.voice_port)) - except BlockingIOError: - _log.warning('A packet has been dropped (seq: %s, timestamp: %s)', self.sequence, self.timestamp) + self._connection.send_packet(packet) + except OSError: + _log.debug('A packet has been dropped (seq: %s, timestamp: %s)', self.sequence, self.timestamp) self.checked_add('timestamp', opus.Encoder.SAMPLES_PER_FRAME, 4294967295) diff --git a/discord/voice_state.py b/discord/voice_state.py new file mode 100644 index 000000000..d2cc0ebc1 --- /dev/null +++ b/discord/voice_state.py @@ -0,0 +1,718 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-present Rapptz + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +Some documentation to refer to: + +- Our main web socket (mWS) sends opcode 4 with a guild ID and channel ID. +- The mWS receives VOICE_STATE_UPDATE and VOICE_SERVER_UPDATE. +- We pull the session_id from VOICE_STATE_UPDATE. +- We pull the token, endpoint and server_id from VOICE_SERVER_UPDATE. +- Then we initiate the voice web socket (vWS) pointing to the endpoint. +- We send opcode 0 with the user_id, server_id, session_id and token using the vWS. +- The vWS sends back opcode 2 with an ssrc, port, modes(array) and heartbeat_interval. +- We send a UDP discovery packet to endpoint:port and receive our IP and our port in LE. +- Then we send our IP and port via vWS with opcode 1. +- When that's all done, we receive opcode 4 from the vWS. +- Finally we can transmit data to endpoint:port. +""" + +from __future__ import annotations + +import select +import socket +import asyncio +import logging +import threading + +from typing import TYPE_CHECKING, Optional, Dict, List, Callable, Coroutine, Any, Tuple + +from .enums import Enum +from .utils import MISSING, sane_wait_for +from .errors import ConnectionClosed +from .backoff import ExponentialBackoff +from .gateway import DiscordVoiceWebSocket + +if TYPE_CHECKING: + from . import abc + from .guild import Guild + from .user import ClientUser + from .member import VoiceState + from .voice_client import VoiceClient + + from .types.voice import ( + GuildVoiceState as GuildVoiceStatePayload, + VoiceServerUpdate as VoiceServerUpdatePayload, + SupportedModes, + ) + + WebsocketHook = Optional[Callable[[DiscordVoiceWebSocket, Dict[str, Any]], Coroutine[Any, Any, Any]]] + SocketReaderCallback = Callable[[bytes], Any] + + +__all__ = ('VoiceConnectionState',) + +_log = logging.getLogger(__name__) + + +class SocketReader(threading.Thread): + def __init__(self, state: VoiceConnectionState, *, start_paused: bool = True) -> None: + super().__init__(daemon=True, name=f'voice-socket-reader:{id(self):#x}') + self.state: VoiceConnectionState = state + self.start_paused = start_paused + self._callbacks: List[SocketReaderCallback] = [] + self._running = threading.Event() + self._end = threading.Event() + # If we have paused reading due to having no callbacks + self._idle_paused: bool = True + + def register(self, callback: SocketReaderCallback) -> None: + self._callbacks.append(callback) + if self._idle_paused: + self._idle_paused = False + self._running.set() + + def unregister(self, callback: SocketReaderCallback) -> None: + try: + self._callbacks.remove(callback) + except ValueError: + pass + else: + if not self._callbacks and self._running.is_set(): + # If running is not set, we are either explicitly paused and + # should be explicitly resumed, or we are already idle paused + self._idle_paused = True + self._running.clear() + + def pause(self) -> None: + self._idle_paused = False + self._running.clear() + + def resume(self, *, force: bool = False) -> None: + if self._running.is_set(): + return + # Don't resume if there are no callbacks registered + if not force and not self._callbacks: + # We tried to resume but there was nothing to do, so resume when ready + self._idle_paused = True + return + self._idle_paused = False + self._running.set() + + def stop(self) -> None: + self._end.set() + self._running.set() + + def run(self) -> None: + self._end.clear() + self._running.set() + if self.start_paused: + self.pause() + try: + self._do_run() + except Exception: + _log.exception('Error in %s', self) + finally: + self.stop() + self._running.clear() + self._callbacks.clear() + + def _do_run(self) -> None: + while not self._end.is_set(): + if not self._running.is_set(): + self._running.wait() + continue + + # Since this socket is a non blocking socket, select has to be used to wait on it for reading. + try: + readable, _, _ = select.select([self.state.socket], [], [], 30) + except (ValueError, TypeError, OSError) as e: + _log.debug( + "Select error handling socket in reader, this should be safe to ignore: %s: %s", e.__class__.__name__, e + ) + # The socket is either closed or doesn't exist at the moment + continue + + if not readable: + continue + + try: + data = self.state.socket.recv(2048) + except OSError: + _log.debug('Error reading from socket in %s, this should be safe to ignore', self, exc_info=True) + else: + for cb in self._callbacks: + try: + cb(data) + except Exception: + _log.exception('Error calling %s in %s', cb, self) + + +class ConnectionFlowState(Enum): + """Enum representing voice connection flow state.""" + + # fmt: off + disconnected = 0 + set_guild_voice_state = 1 + got_voice_state_update = 2 + got_voice_server_update = 3 + got_both_voice_updates = 4 + websocket_connected = 5 + got_websocket_ready = 6 + got_ip_discovery = 7 + connected = 8 + # fmt: on + + +class VoiceConnectionState: + """Represents the internal state of a voice connection.""" + + def __init__(self, voice_client: VoiceClient, *, hook: Optional[WebsocketHook] = None) -> None: + self.voice_client = voice_client + self.hook = hook + + self.timeout: float = 30.0 + self.reconnect: bool = True + self.self_deaf: bool = False + self.self_mute: bool = False + self.token: Optional[str] = None + self.session_id: Optional[str] = None + self.endpoint: Optional[str] = None + self.endpoint_ip: Optional[str] = None + self.server_id: Optional[int] = None + self.ip: Optional[str] = None + self.port: Optional[int] = None + self.voice_port: Optional[int] = None + self.secret_key: List[int] = MISSING + self.ssrc: int = MISSING + self.mode: SupportedModes = MISSING + self.socket: socket.socket = MISSING + self.ws: DiscordVoiceWebSocket = MISSING + + self._state: ConnectionFlowState = ConnectionFlowState.disconnected + self._expecting_disconnect: bool = False + self._connected = threading.Event() + self._state_event = asyncio.Event() + self._disconnected = asyncio.Event() + self._runner: Optional[asyncio.Task] = None + self._connector: Optional[asyncio.Task] = None + self._socket_reader = SocketReader(self) + self._socket_reader.start() + + @property + def state(self) -> ConnectionFlowState: + return self._state + + @state.setter + def state(self, state: ConnectionFlowState) -> None: + if state is not self._state: + _log.debug('Connection state changed to %s', state.name) + self._state = state + self._state_event.set() + self._state_event.clear() + + if state is ConnectionFlowState.connected: + self._connected.set() + else: + self._connected.clear() + + @property + def guild(self) -> Guild: + return self.voice_client.guild + + @property + def user(self) -> ClientUser: + return self.voice_client.user + + @property + def supported_modes(self) -> Tuple[SupportedModes, ...]: + return self.voice_client.supported_modes + + @property + def self_voice_state(self) -> Optional[VoiceState]: + return self.guild.me.voice + + async def voice_state_update(self, data: GuildVoiceStatePayload) -> None: + channel_id = data['channel_id'] + + if channel_id is None: + self._disconnected.set() + + # If we know we're going to get a voice_state_update where we have no channel due to + # being in the reconnect or disconnect flow, we ignore it. Otherwise, it probably wasn't from us. + if self._expecting_disconnect: + self._expecting_disconnect = False + else: + _log.debug('We were externally disconnected from voice.') + await self.disconnect() + + return + + channel_id = int(channel_id) + self.session_id = data['session_id'] + + # we got the event while connecting + if self.state in (ConnectionFlowState.set_guild_voice_state, ConnectionFlowState.got_voice_server_update): + if self.state is ConnectionFlowState.set_guild_voice_state: + self.state = ConnectionFlowState.got_voice_state_update + + # we moved ourselves + if channel_id != self.voice_client.channel.id: + self._update_voice_channel(channel_id) + + else: + self.state = ConnectionFlowState.got_both_voice_updates + return + + if self.state is ConnectionFlowState.connected: + self._update_voice_channel(channel_id) + + elif self.state is not ConnectionFlowState.disconnected: + if channel_id != self.voice_client.channel.id: + # For some unfortunate reason we were moved during the connection flow + _log.info('Handling channel move while connecting...') + + self._update_voice_channel(channel_id) + await self.soft_disconnect(with_state=ConnectionFlowState.got_voice_state_update) + await self.connect( + reconnect=self.reconnect, + timeout=self.timeout, + self_deaf=(self.self_voice_state or self).self_deaf, + self_mute=(self.self_voice_state or self).self_mute, + resume=False, + wait=False, + ) + else: + _log.debug('Ignoring unexpected voice_state_update event') + + async def voice_server_update(self, data: VoiceServerUpdatePayload) -> None: + previous_token = self.token + previous_server_id = self.server_id + previous_endpoint = self.endpoint + + self.token = data['token'] + self.server_id = int(data['guild_id']) + endpoint = data.get('endpoint') + + if self.token is None or endpoint is None: + _log.warning( + 'Awaiting endpoint... This requires waiting. ' + 'If timeout occurred considering raising the timeout and reconnecting.' + ) + return + + self.endpoint = endpoint + if self.endpoint.startswith('wss://'): + # Just in case, strip it off since we're going to add it later + self.endpoint = self.endpoint[6:] + + # we got the event while connecting + if self.state in (ConnectionFlowState.set_guild_voice_state, ConnectionFlowState.got_voice_state_update): + # This gets set after READY is received + self.endpoint_ip = MISSING + self._create_socket() + + if self.state is ConnectionFlowState.set_guild_voice_state: + self.state = ConnectionFlowState.got_voice_server_update + else: + self.state = ConnectionFlowState.got_both_voice_updates + + elif self.state is ConnectionFlowState.connected: + _log.debug('Voice server update, closing old voice websocket') + await self.ws.close(4014) + self.state = ConnectionFlowState.got_voice_server_update + + elif self.state is not ConnectionFlowState.disconnected: + # eventual consistency + if previous_token == self.token and previous_server_id == self.server_id and previous_endpoint == self.endpoint: + return + + _log.debug('Unexpected server update event, attempting to handle') + + await self.soft_disconnect(with_state=ConnectionFlowState.got_voice_server_update) + await self.connect( + reconnect=self.reconnect, + timeout=self.timeout, + self_deaf=(self.self_voice_state or self).self_deaf, + self_mute=(self.self_voice_state or self).self_mute, + resume=False, + wait=False, + ) + self._create_socket() + + async def connect( + self, *, reconnect: bool, timeout: float, self_deaf: bool, self_mute: bool, resume: bool, wait: bool = True + ) -> None: + if self._connector: + self._connector.cancel() + self._connector = None + + if self._runner: + self._runner.cancel() + self._runner = None + + self.timeout = timeout + self.reconnect = reconnect + self._connector = self.voice_client.loop.create_task( + self._wrap_connect(reconnect, timeout, self_deaf, self_mute, resume), name='Voice connector' + ) + if wait: + await self._connector + + async def _wrap_connect(self, *args: Any) -> None: + try: + await self._connect(*args) + except asyncio.CancelledError: + _log.debug('Cancelling voice connection') + await self.soft_disconnect() + raise + except asyncio.TimeoutError: + _log.info('Timed out connecting to voice') + await self.disconnect() + raise + except Exception: + _log.exception('Error connecting to voice... disconnecting') + await self.disconnect() + raise + + async def _inner_connect(self, reconnect: bool, self_deaf: bool, self_mute: bool, resume: bool) -> None: + for i in range(5): + _log.info('Starting voice handshake... (connection attempt %d)', i + 1) + + await self._voice_connect(self_deaf=self_deaf, self_mute=self_mute) + # Setting this unnecessarily will break reconnecting + if self.state is ConnectionFlowState.disconnected: + self.state = ConnectionFlowState.set_guild_voice_state + + await self._wait_for_state(ConnectionFlowState.got_both_voice_updates) + + _log.info('Voice handshake complete. Endpoint found: %s', self.endpoint) + + try: + self.ws = await self._connect_websocket(resume) + await self._handshake_websocket() + break + except ConnectionClosed: + if reconnect: + wait = 1 + i * 2.0 + _log.exception('Failed to connect to voice... Retrying in %ss...', wait) + await self.disconnect(cleanup=False) + await asyncio.sleep(wait) + continue + else: + await self.disconnect() + raise + + async def _connect(self, reconnect: bool, timeout: float, self_deaf: bool, self_mute: bool, resume: bool) -> None: + _log.info('Connecting to voice...') + + await asyncio.wait_for( + self._inner_connect(reconnect=reconnect, self_deaf=self_deaf, self_mute=self_mute, resume=resume), + timeout=timeout, + ) + _log.info('Voice connection complete.') + + if not self._runner: + self._runner = self.voice_client.loop.create_task(self._poll_voice_ws(reconnect), name='Voice websocket poller') + + async def disconnect(self, *, force: bool = True, cleanup: bool = True, wait: bool = False) -> None: + if not force and not self.is_connected(): + return + + try: + await self._voice_disconnect() + if self.ws: + await self.ws.close() + except Exception: + _log.debug('Ignoring exception disconnecting from voice', exc_info=True) + finally: + self.state = ConnectionFlowState.disconnected + self._socket_reader.pause() + + # Stop threads before we unlock waiters so they end properly + if cleanup: + self._socket_reader.stop() + self.voice_client.stop() + + # Flip the connected event to unlock any waiters + self._connected.set() + self._connected.clear() + + if self.socket: + self.socket.close() + + self.ip = MISSING + self.port = MISSING + + # Skip this part if disconnect was called from the poll loop task + if wait and not self._inside_runner(): + # Wait for the voice_state_update event confirming the bot left the voice channel. + # This prevents a race condition caused by disconnecting and immediately connecting again. + # The new VoiceConnectionState object receives the voice_state_update event containing channel=None while still + # connecting leaving it in a bad state. Since there's no nice way to transfer state to the new one, we have to do this. + try: + await asyncio.wait_for(self._disconnected.wait(), timeout=self.timeout) + except TimeoutError: + _log.debug('Timed out waiting for voice disconnection confirmation') + except asyncio.CancelledError: + pass + + if cleanup: + self.voice_client.cleanup() + + async def soft_disconnect(self, *, with_state: ConnectionFlowState = ConnectionFlowState.got_both_voice_updates) -> None: + _log.debug('Soft disconnecting from voice') + # Stop the websocket reader because closing the websocket will trigger an unwanted reconnect + if self._runner: + self._runner.cancel() + self._runner = None + + try: + if self.ws: + await self.ws.close() + except Exception: + _log.debug('Ignoring exception soft disconnecting from voice', exc_info=True) + finally: + self.state = with_state + self._socket_reader.pause() + + if self.socket: + self.socket.close() + + self.ip = MISSING + self.port = MISSING + + async def move_to(self, channel: Optional[abc.Snowflake], timeout: Optional[float]) -> None: + if channel is None: + # This function should only be called externally so its ok to wait for the disconnect. + await self.disconnect(wait=True) + return + + if self.voice_client.channel and channel.id == self.voice_client.channel.id: + return + + previous_state = self.state + + # this is only an outgoing ws request + # if it fails, nothing happens and nothing changes (besides self.state) + await self._move_to(channel) + + last_state = self.state + try: + await self.wait_async(timeout) + except asyncio.TimeoutError: + _log.warning('Timed out trying to move to channel %s in guild %s', channel.id, self.guild.id) + if self.state is last_state: + _log.debug('Reverting to previous state %s', previous_state.name) + self.state = previous_state + + def wait(self, timeout: Optional[float] = None) -> bool: + return self._connected.wait(timeout) + + async def wait_async(self, timeout: Optional[float] = None) -> None: + await self._wait_for_state(ConnectionFlowState.connected, timeout=timeout) + + def is_connected(self) -> bool: + return self.state is ConnectionFlowState.connected + + def send_packet(self, packet: bytes) -> None: + self.socket.sendall(packet) + + def add_socket_listener(self, callback: SocketReaderCallback) -> None: + _log.debug('Registering socket listener callback %s', callback) + self._socket_reader.register(callback) + + def remove_socket_listener(self, callback: SocketReaderCallback) -> None: + _log.debug('Unregistering socket listener callback %s', callback) + self._socket_reader.unregister(callback) + + def _inside_runner(self) -> bool: + return self._runner is not None and asyncio.current_task() == self._runner + + async def _wait_for_state( + self, state: ConnectionFlowState, *other_states: ConnectionFlowState, timeout: Optional[float] = None + ) -> None: + states = (state, *other_states) + while True: + if self.state in states: + return + await sane_wait_for([self._state_event.wait()], timeout=timeout) + + async def _voice_connect(self, *, self_deaf: bool = False, self_mute: bool = False) -> None: + channel = self.voice_client.channel + await channel.guild.change_voice_state(channel=channel, self_deaf=self_deaf, self_mute=self_mute) + + async def _voice_disconnect(self) -> None: + _log.info( + 'The voice handshake is being terminated for Channel ID %s (Guild ID %s)', + self.voice_client.channel.id, + self.voice_client.guild.id, + ) + self.state = ConnectionFlowState.disconnected + await self.voice_client.channel.guild.change_voice_state(channel=None) + self._expecting_disconnect = True + self._disconnected.clear() + + async def _connect_websocket(self, resume: bool) -> DiscordVoiceWebSocket: + seq_ack = -1 + if self.ws is not MISSING: + seq_ack = self.ws.seq_ack + ws = await DiscordVoiceWebSocket.from_connection_state(self, resume=resume, hook=self.hook, seq_ack=seq_ack) + self.state = ConnectionFlowState.websocket_connected + return ws + + async def _handshake_websocket(self) -> None: + while not self.ip: + await self.ws.poll_event() + self.state = ConnectionFlowState.got_ip_discovery + while self.ws.secret_key is None: + await self.ws.poll_event() + self.state = ConnectionFlowState.connected + + def _create_socket(self) -> None: + self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.socket.setblocking(False) + self._socket_reader.resume() + + async def _poll_voice_ws(self, reconnect: bool) -> None: + backoff = ExponentialBackoff() + while True: + try: + await self.ws.poll_event() + except asyncio.CancelledError: + return + except (ConnectionClosed, asyncio.TimeoutError) as exc: + if isinstance(exc, ConnectionClosed): + # The following close codes are undocumented so I will document them here. + # 1000 - normal closure (obviously) + # 4014 - we were externally disconnected (voice channel deleted, we were moved, etc) + # 4015 - voice server has crashed, we should resume + # 4021 - rate limited, we should not reconnect + # 4022 - call terminated, similar to 4014 + if exc.code == 1000: + # Don't call disconnect a second time if the websocket closed from a disconnect call + if not self._expecting_disconnect: + _log.info('Disconnecting from voice normally, close code %d.', exc.code) + await self.disconnect() + break + + if exc.code in (4014, 4022): + # We were disconnected by discord + # This condition is a race between the main ws event and the voice ws closing + if self._disconnected.is_set(): + _log.info('Disconnected from voice by discord, close code %d.', exc.code) + await self.disconnect() + break + + # We may have been moved to a different channel + _log.info('Disconnected from voice by force... potentially reconnecting.') + successful = await self._potential_reconnect() + if not successful: + _log.info('Reconnect was unsuccessful, disconnecting from voice normally...') + # Don't bother to disconnect if already disconnected + if self.state is not ConnectionFlowState.disconnected: + await self.disconnect() + break + else: + continue + + if exc.code == 4021: + _log.warning('We are being ratelimited while trying to connect to voice. Disconnecting...') + if self.state is not ConnectionFlowState.disconnected: + await self.disconnect() + break + + if exc.code == 4015: + _log.info('Disconnected from voice, attempting a resume...') + try: + await self._connect( + reconnect=reconnect, + timeout=self.timeout, + self_deaf=(self.self_voice_state or self).self_deaf, + self_mute=(self.self_voice_state or self).self_mute, + resume=True, + ) + except asyncio.TimeoutError: + _log.info('Could not resume the voice connection... Disconnecting...') + if self.state is not ConnectionFlowState.disconnected: + await self.disconnect() + break + else: + _log.info('Successfully resumed voice connection') + continue + + _log.debug('Not handling close code %s (%s)', exc.code, exc.reason or 'no reason') + + if not reconnect: + await self.disconnect() + raise + + retry = backoff.delay() + _log.exception('Disconnected from voice... Reconnecting in %.2fs.', retry) + await asyncio.sleep(retry) + await self.disconnect(cleanup=False) + + try: + await self._connect( + reconnect=reconnect, + timeout=self.timeout, + self_deaf=(self.self_voice_state or self).self_deaf, + self_mute=(self.self_voice_state or self).self_mute, + resume=False, + ) + except asyncio.TimeoutError: + # at this point we've retried 5 times... let's continue the loop. + _log.warning('Could not connect to voice... Retrying...') + continue + + async def _potential_reconnect(self) -> bool: + try: + await self._wait_for_state( + ConnectionFlowState.got_voice_server_update, + ConnectionFlowState.got_both_voice_updates, + ConnectionFlowState.disconnected, + timeout=self.timeout, + ) + except asyncio.TimeoutError: + return False + else: + if self.state is ConnectionFlowState.disconnected: + return False + + previous_ws = self.ws + try: + self.ws = await self._connect_websocket(False) + await self._handshake_websocket() + except (ConnectionClosed, asyncio.TimeoutError): + return False + else: + return True + finally: + await previous_ws.close() + + async def _move_to(self, channel: abc.Snowflake) -> None: + await self.voice_client.channel.guild.change_voice_state(channel=channel) + self.state = ConnectionFlowState.set_guild_voice_state + + def _update_voice_channel(self, channel_id: Optional[int]) -> None: + self.voice_client.channel = channel_id and self.guild.get_channel(channel_id) # type: ignore diff --git a/discord/webhook/async_.py b/discord/webhook/async_.py index f9b03193a..3b62b10fa 100644 --- a/discord/webhook/async_.py +++ b/discord/webhook/async_.py @@ -38,14 +38,14 @@ import aiohttp from .. import utils from ..errors import HTTPException, Forbidden, NotFound, DiscordServerError from ..message import Message -from ..enums import try_enum, WebhookType, ChannelType +from ..enums import try_enum, WebhookType, ChannelType, DefaultAvatar from ..user import BaseUser, User from ..flags import MessageFlags from ..asset import Asset from ..partial_emoji import PartialEmoji from ..http import Route, handle_message_parameters, MultipartParameters, HTTPClient, json_or_text from ..mixins import Hashable -from ..channel import TextChannel, ForumChannel, PartialMessageable +from ..channel import TextChannel, ForumChannel, PartialMessageable, ForumTag from ..file import File __all__ = ( @@ -72,6 +72,7 @@ if TYPE_CHECKING: from ..channel import VoiceChannel from ..abc import Snowflake from ..ui.view import View + from ..poll import Poll import datetime from ..types.webhook import ( Webhook as WebhookPayload, @@ -88,6 +89,10 @@ if TYPE_CHECKING: PartialChannel as PartialChannelPayload, ) from ..types.emoji import PartialEmoji as PartialEmojiPayload + from ..types.snowflake import SnowflakeList + from ..types.interactions import ( + InteractionCallback as InteractionCallbackResponsePayload, + ) BE = TypeVar('BE', bound=BaseException) _State = Union[ConnectionState, '_WebhookState'] @@ -308,8 +313,9 @@ class AsyncWebhookAdapter: files: Optional[Sequence[File]] = None, thread_id: Optional[int] = None, wait: bool = False, + with_components: bool = False, ) -> Response[Optional[MessagePayload]]: - params = {'wait': int(wait)} + params = {'wait': int(wait), 'with_components': int(with_components)} if thread_id: params['thread_id'] = thread_id route = Route('POST', '/webhooks/{webhook_id}/{webhook_token}', webhook_id=webhook_id, webhook_token=token) @@ -358,7 +364,7 @@ class AsyncWebhookAdapter: multipart: Optional[List[Dict[str, Any]]] = None, files: Optional[Sequence[File]] = None, thread_id: Optional[int] = None, - ) -> Response[Message]: + ) -> Response[MessagePayload]: route = Route( 'PATCH', '/webhooks/{webhook_id}/{webhook_token}/messages/{message_id}', @@ -432,13 +438,14 @@ class AsyncWebhookAdapter: proxy: Optional[str] = None, proxy_auth: Optional[aiohttp.BasicAuth] = None, params: MultipartParameters, - ) -> Response[None]: + ) -> Response[InteractionCallbackResponsePayload]: route = Route( 'POST', '/interactions/{webhook_id}/{webhook_token}/callback', webhook_id=interaction_id, webhook_token=token, ) + request_params = {'with_response': '1'} if params.files: return self.request( @@ -448,9 +455,17 @@ class AsyncWebhookAdapter: proxy_auth=proxy_auth, files=params.files, multipart=params.multipart, + params=request_params, ) else: - return self.request(route, session=session, proxy=proxy, proxy_auth=proxy_auth, payload=params.payload) + return self.request( + route, + session=session, + proxy=proxy, + proxy_auth=proxy_auth, + payload=params.payload, + params=request_params, + ) def get_original_interaction_response( self, @@ -540,6 +555,7 @@ def interaction_message_response_params( view: Optional[View] = MISSING, allowed_mentions: Optional[AllowedMentions] = MISSING, previous_allowed_mentions: Optional[AllowedMentions] = None, + poll: Poll = MISSING, ) -> MultipartParameters: if files is not MISSING and file is not MISSING: raise TypeError('Cannot mix file and files keyword arguments.') @@ -607,6 +623,9 @@ def interaction_message_response_params( data['attachments'] = attachments_payload + if poll is not MISSING: + data['poll'] = poll._to_dict() + multipart = [] if files: data = {'type': type, 'data': data} @@ -654,6 +673,11 @@ class PartialWebhookChannel(Hashable): def __repr__(self) -> str: return f'' + @property + def mention(self) -> str: + """:class:`str`: The string that allows you to mention the channel that the webhook is following.""" + return f'<#{self.id}>' + class PartialWebhookGuild(Hashable): """Represents a partial guild for webhooks. @@ -715,9 +739,9 @@ class _WebhookState: return self._parent._get_guild(guild_id) return None - def store_user(self, data: Union[UserPayload, PartialUserPayload]) -> BaseUser: + def store_user(self, data: Union[UserPayload, PartialUserPayload], *, cache: bool = True) -> BaseUser: if self._parent is not None: - return self._parent.store_user(data) + return self._parent.store_user(data, cache=cache) # state parameter is artificial return BaseUser(state=self, data=data) # type: ignore @@ -1038,12 +1062,11 @@ class BaseWebhook(Hashable): @property def default_avatar(self) -> Asset: """ - :class:`Asset`: Returns the default avatar. This is always the blurple avatar. + :class:`Asset`: Returns the default avatar. .. versionadded:: 2.0 """ - # Default is always blurple apparently - return Asset._from_default_avatar(self._state, 0) + return Asset._from_default_avatar(self._state, (self.id >> 22) % len(DefaultAvatar)) @property def display_avatar(self) -> Asset: @@ -1154,7 +1177,7 @@ class Webhook(BaseWebhook): self.proxy_auth: Optional[aiohttp.BasicAuth] = proxy_auth def __repr__(self) -> str: - return f'' + return f'' @property def url(self) -> str: @@ -1275,7 +1298,7 @@ class Webhook(BaseWebhook): A partial :class:`Webhook`. A partial webhook is just a webhook object with an ID and a token. """ - m = re.search(r'discord(?:app)?\.com/api/webhooks/(?P[0-9]{17,20})/(?P[A-Za-z0-9\.\-\_]{60,68})', url) + m = re.search(r'discord(?:app)?\.com/api/webhooks/(?P[0-9]{17,20})/(?P[A-Za-z0-9\.\-\_]{60,})', url) if m is None: raise ValueError('Invalid webhook URL given.') @@ -1301,7 +1324,14 @@ class Webhook(BaseWebhook): 'name': name, 'channel_id': channel.id, 'guild_id': channel.guild.id, - 'user': {'username': user.name, 'discriminator': user.discriminator, 'id': user.id, 'avatar': user._avatar}, + 'user': { + 'username': user.name, + 'discriminator': user.discriminator, + 'id': user.id, + 'avatar': user._avatar, + 'avatar_decoration_data': user._avatar_decoration_data, + 'global_name': user.global_name, + }, } state = channel._state @@ -1511,8 +1541,7 @@ class Webhook(BaseWebhook): proxy_auth=self.proxy_auth, reason=reason, ) - - if prefer_auth and self.auth_token: + elif prefer_auth and self.auth_token: data = await adapter.edit_webhook( self.id, self.auth_token, @@ -1589,6 +1618,8 @@ class Webhook(BaseWebhook): wait: Literal[True], suppress_embeds: bool = MISSING, silent: bool = MISSING, + applied_tags: List[ForumTag] = MISSING, + poll: Poll = MISSING, ) -> WebhookMessage: ... @@ -1612,6 +1643,8 @@ class Webhook(BaseWebhook): wait: Literal[False] = ..., suppress_embeds: bool = MISSING, silent: bool = MISSING, + applied_tags: List[ForumTag] = MISSING, + poll: Poll = MISSING, ) -> None: ... @@ -1634,6 +1667,8 @@ class Webhook(BaseWebhook): wait: bool = False, suppress_embeds: bool = False, silent: bool = False, + applied_tags: List[ForumTag] = MISSING, + poll: Poll = MISSING, ) -> Optional[WebhookMessage]: """|coro| @@ -1693,10 +1728,9 @@ class Webhook(BaseWebhook): .. versionadded:: 1.4 view: :class:`discord.ui.View` - The view to send with the message. You can only send a view - if this webhook is not partial and has state attached. A - webhook has state attached if the webhook is managed by the - library. + The view to send with the message. If the webhook is partial or + is not managed by the library, then you can only send URL buttons. + Otherwise, you can send views with any type of components. .. versionadded:: 2.0 thread: :class:`~discord.abc.Snowflake` @@ -1719,6 +1753,19 @@ class Webhook(BaseWebhook): in the UI, but will not actually send a notification. .. versionadded:: 2.2 + applied_tags: List[:class:`ForumTag`] + Tags to apply to the thread if the webhook belongs to a :class:`~discord.ForumChannel`. + + .. versionadded:: 2.4 + + poll: :class:`Poll` + The poll to send with this message. + + .. warning:: + + When sending a Poll via webhook, you cannot manually end it. + + .. versionadded:: 2.4 Raises -------- @@ -1735,7 +1782,8 @@ class Webhook(BaseWebhook): The length of ``embeds`` was invalid, there was no token associated with this webhook or ``ephemeral`` was passed with the improper webhook type or there was no state - attached with this webhook when giving it a view. + attached with this webhook when giving it a view that had + components other than URL buttons. Returns --------- @@ -1765,18 +1813,25 @@ class Webhook(BaseWebhook): wait = True if view is not MISSING: - if isinstance(self._state, _WebhookState): - raise ValueError('Webhook views require an associated state with the webhook') - if not hasattr(view, '__discord_ui_view__'): raise TypeError(f'expected view parameter to be of type View not {view.__class__.__name__}') - if ephemeral is True and view.timeout is None: + if isinstance(self._state, _WebhookState) and view.is_dispatchable(): + raise ValueError( + 'Webhook views with any component other than URL buttons require an associated state with the webhook' + ) + + if ephemeral is True and view.timeout is None and view.is_dispatchable(): view.timeout = 15 * 60.0 if thread_name is not MISSING and thread is not MISSING: raise TypeError('Cannot mix thread_name and thread keyword arguments.') + if applied_tags is MISSING: + applied_tag_ids = MISSING + else: + applied_tag_ids: SnowflakeList = [tag.id for tag in applied_tags] + with handle_message_parameters( content=content, username=username, @@ -1791,6 +1846,8 @@ class Webhook(BaseWebhook): thread_name=thread_name, allowed_mentions=allowed_mentions, previous_allowed_mentions=previous_mentions, + applied_tags=applied_tag_ids, + poll=poll, ) as params: adapter = async_context.get() thread_id: Optional[int] = None @@ -1808,6 +1865,7 @@ class Webhook(BaseWebhook): files=params.files, thread_id=thread_id, wait=wait, + with_components=view is not MISSING, ) msg = None @@ -1818,6 +1876,9 @@ class Webhook(BaseWebhook): message_id = None if msg is None else msg.id self._state.store_view(view, message_id) + if poll is not MISSING and msg: + poll._update(msg) + return msg async def fetch_message(self, id: int, /, *, thread: Snowflake = MISSING) -> WebhookMessage: diff --git a/discord/webhook/sync.py b/discord/webhook/sync.py index 3128246f2..171931b12 100644 --- a/discord/webhook/sync.py +++ b/discord/webhook/sync.py @@ -44,7 +44,7 @@ from .. import utils from ..errors import HTTPException, Forbidden, NotFound, DiscordServerError from ..message import Message, MessageFlags from ..http import Route, handle_message_parameters -from ..channel import PartialMessageable +from ..channel import PartialMessageable, ForumTag from .async_ import BaseWebhook, _WebhookState @@ -61,16 +61,19 @@ if TYPE_CHECKING: from ..file import File from ..embeds import Embed + from ..poll import Poll from ..mentions import AllowedMentions from ..message import Attachment from ..abc import Snowflake from ..state import ConnectionState + from ..ui import View from ..types.webhook import ( Webhook as WebhookPayload, ) from ..types.message import ( Message as MessagePayload, ) + from ..types.snowflake import SnowflakeList BE = TypeVar('BE', bound=BaseException) @@ -288,8 +291,9 @@ class WebhookAdapter: files: Optional[Sequence[File]] = None, thread_id: Optional[int] = None, wait: bool = False, + with_components: bool = False, ) -> MessagePayload: - params = {'wait': int(wait)} + params = {'wait': int(wait), 'with_components': int(with_components)} if thread_id: params['thread_id'] = thread_id route = Route('POST', '/webhooks/{webhook_id}/{webhook_token}', webhook_id=webhook_id, webhook_token=token) @@ -608,7 +612,7 @@ class SyncWebhook(BaseWebhook): self.session: Session = session def __repr__(self) -> str: - return f'' + return f'' @property def url(self) -> str: @@ -636,9 +640,9 @@ class SyncWebhook(BaseWebhook): Returns -------- - :class:`Webhook` - A partial :class:`Webhook`. - A partial webhook is just a webhook object with an ID and a token. + :class:`SyncWebhook` + A partial :class:`SyncWebhook`. + A partial :class:`SyncWebhook` is just a :class:`SyncWebhook` object with an ID and a token. """ data: WebhookPayload = { 'id': id, @@ -678,11 +682,11 @@ class SyncWebhook(BaseWebhook): Returns -------- - :class:`Webhook` - A partial :class:`Webhook`. - A partial webhook is just a webhook object with an ID and a token. + :class:`SyncWebhook` + A partial :class:`SyncWebhook`. + A partial :class:`SyncWebhook` is just a :class:`SyncWebhook` object with an ID and a token. """ - m = re.search(r'discord(?:app)?\.com/api/webhooks/(?P[0-9]{17,20})/(?P[A-Za-z0-9\.\-\_]{60,68})', url) + m = re.search(r'discord(?:app)?\.com/api/webhooks/(?P[0-9]{17,20})/(?P[A-Za-z0-9\.\-\_]{60,})', url) if m is None: raise ValueError('Invalid webhook URL given.') @@ -835,8 +839,7 @@ class SyncWebhook(BaseWebhook): payload['channel_id'] = channel.id data = adapter.edit_webhook(self.id, self.auth_token, payload=payload, session=self.session, reason=reason) - - if prefer_auth and self.auth_token: + elif prefer_auth and self.auth_token: data = adapter.edit_webhook(self.id, self.auth_token, payload=payload, session=self.session, reason=reason) elif self.token: data = adapter.edit_webhook_with_token(self.id, self.token, payload=payload, session=self.session, reason=reason) @@ -871,6 +874,8 @@ class SyncWebhook(BaseWebhook): wait: Literal[True], suppress_embeds: bool = MISSING, silent: bool = MISSING, + applied_tags: List[ForumTag] = MISSING, + poll: Poll = MISSING, ) -> SyncWebhookMessage: ... @@ -892,6 +897,8 @@ class SyncWebhook(BaseWebhook): wait: Literal[False] = ..., suppress_embeds: bool = MISSING, silent: bool = MISSING, + applied_tags: List[ForumTag] = MISSING, + poll: Poll = MISSING, ) -> None: ... @@ -912,6 +919,9 @@ class SyncWebhook(BaseWebhook): wait: bool = False, suppress_embeds: bool = False, silent: bool = False, + applied_tags: List[ForumTag] = MISSING, + poll: Poll = MISSING, + view: View = MISSING, ) -> Optional[SyncWebhookMessage]: """Sends a message using the webhook. @@ -976,6 +986,21 @@ class SyncWebhook(BaseWebhook): in the UI, but will not actually send a notification. .. versionadded:: 2.2 + poll: :class:`Poll` + The poll to send with this message. + + .. warning:: + + When sending a Poll via webhook, you cannot manually end it. + + .. versionadded:: 2.4 + view: :class:`~discord.ui.View` + The view to send with the message. This can only have URL buttons, which donnot + require a state to be attached to it. + + If you want to send a view with any component attached to it, check :meth:`Webhook.send`. + + .. versionadded:: 2.5 Raises -------- @@ -989,8 +1014,9 @@ class SyncWebhook(BaseWebhook): You specified both ``embed`` and ``embeds`` or ``file`` and ``files`` or ``thread`` and ``thread_name``. ValueError - The length of ``embeds`` was invalid or - there was no token associated with this webhook. + The length of ``embeds`` was invalid, there was no token + associated with this webhook or you tried to send a view + with components other than URL buttons. Returns --------- @@ -1012,9 +1038,21 @@ class SyncWebhook(BaseWebhook): else: flags = MISSING + if view is not MISSING: + if not hasattr(view, '__discord_ui_view__'): + raise TypeError(f'expected view parameter to be of type View not {view.__class__.__name__}') + + if view.is_dispatchable(): + raise ValueError('SyncWebhook views can only contain URL buttons') + if thread_name is not MISSING and thread is not MISSING: raise TypeError('Cannot mix thread_name and thread keyword arguments.') + if applied_tags is MISSING: + applied_tag_ids = MISSING + else: + applied_tag_ids: SnowflakeList = [tag.id for tag in applied_tags] + with handle_message_parameters( content=content, username=username, @@ -1028,6 +1066,9 @@ class SyncWebhook(BaseWebhook): allowed_mentions=allowed_mentions, previous_allowed_mentions=previous_mentions, flags=flags, + applied_tags=applied_tag_ids, + poll=poll, + view=view, ) as params: adapter: WebhookAdapter = _get_webhook_adapter() thread_id: Optional[int] = None @@ -1043,10 +1084,18 @@ class SyncWebhook(BaseWebhook): files=params.files, thread_id=thread_id, wait=wait, + with_components=view is not MISSING, ) + msg = None + if wait: - return self._create_message(data, thread=thread) + msg = self._create_message(data, thread=thread) + + if poll is not MISSING and msg: + poll._update(msg) + + return msg def fetch_message(self, id: int, /, *, thread: Snowflake = MISSING) -> SyncWebhookMessage: """Retrieves a single :class:`~discord.SyncWebhookMessage` owned by this webhook. diff --git a/discord/welcome_screen.py b/discord/welcome_screen.py index d23392a54..1ca487c91 100644 --- a/discord/welcome_screen.py +++ b/discord/welcome_screen.py @@ -214,4 +214,4 @@ class WelcomeScreen: fields['enabled'] = enabled data = await self._state.http.edit_welcome_screen(self._guild.id, reason=reason, **fields) - return WelcomeScreen(data=data, guild=self._guild) + return self.__class__(data=data, guild=self._guild) diff --git a/discord/widget.py b/discord/widget.py index 2c46d49ba..cdb883fd9 100644 --- a/discord/widget.py +++ b/discord/widget.py @@ -121,7 +121,7 @@ class WidgetMember(BaseUser): .. describe:: str(x) - Returns the widget member's ``name#discriminator``. + Returns the widget member's handle (e.g. ``name`` or ``name#discriminator``). Attributes ----------- @@ -130,13 +130,17 @@ class WidgetMember(BaseUser): name: :class:`str` The member's username. discriminator: :class:`str` - The member's discriminator. + The member's discriminator. This is a legacy concept that is no longer used. + global_name: Optional[:class:`str`] + The member's global nickname, taking precedence over the username in display. + + .. versionadded:: 2.3 bot: :class:`bool` Whether the member is a bot. status: :class:`Status` The member's status. nick: Optional[:class:`str`] - The member's nickname. + The member's guild-specific nickname. Takes precedence over the global name. avatar: Optional[:class:`str`] The member's avatar hash. activity: Optional[Union[:class:`BaseActivity`, :class:`Spotify`]] @@ -180,7 +184,7 @@ class WidgetMember(BaseUser): self.suppress: Optional[bool] = data.get('suppress', False) try: - game = data['game'] + game = data['game'] # pyright: ignore[reportTypedDictNotRequiredAccess] except KeyError: activity = None else: @@ -191,9 +195,7 @@ class WidgetMember(BaseUser): self.connected_channel: Optional[WidgetChannel] = connected_channel def __repr__(self) -> str: - return ( - f"" - ) + return f"" @property def display_name(self) -> str: @@ -226,7 +228,7 @@ class Widget: The guild's name. channels: List[:class:`WidgetChannel`] The accessible voice channels in the guild. - members: List[:class:`Member`] + members: List[:class:`WidgetMember`] The online members in the guild. Offline members do not appear in the widget. diff --git a/docs/_static/style.css b/docs/_static/style.css index 9f211a447..4354344ec 100644 --- a/docs/_static/style.css +++ b/docs/_static/style.css @@ -16,6 +16,10 @@ Historically however, thanks to: box-sizing: border-box; } +section { + word-break: break-word; +} + /* CSS variables would go here */ :root { --font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; @@ -109,6 +113,12 @@ Historically however, thanks to: --attribute-table-entry-hover-text: var(--blue-2); --attribute-table-badge: var(--grey-7); --highlighted-text: rgb(252, 233, 103); + --tabs--label-text: var(--main-text); + --tabs--label-text--hover: var(--main-text); + --tabs--label-text--active: var(--blue-1); + --tabs--label-text--active--hover: var(--blue-1); + --tabs--label-border--active: var(--blue-1); + --tabs--label-border--active--hover: var(--blue-1); } :root[data-font="serif"] { @@ -216,6 +226,7 @@ body { display: grid; min-height: 100%; grid-auto-rows: min-content auto min-content; + grid-template-columns: minmax(0, 1fr); grid-template-areas: "s" "h" @@ -1046,6 +1057,7 @@ code.xref, a code { span.pre { padding: 0 2px; + white-space: pre-wrap !important; } dl.class { @@ -1211,12 +1223,13 @@ div.code-block-caption { /* desktop stuff */ -@media screen and (min-width: 600px) { +@media screen and (min-width: 768px) { .grid-item { max-width: unset; } .main-grid { + grid-template-columns: repeat(6, 1fr); grid-template-areas: "h h h h h h" "n n n n n n" @@ -1273,6 +1286,7 @@ div.code-block-caption { position: sticky; top: 1em; max-height: calc(100vh - 2em); + max-width: 100%; overflow-y: auto; margin: 1em; } @@ -1322,6 +1336,10 @@ div.code-block-caption { "s s s f f f f f f f f f f f f f" } + #sidebar { + max-width: unset; + } + header > nav { margin-left: 18.75%; margin-right: 18.75%; diff --git a/docs/api.rst b/docs/api.rst index 253620e06..dc6775ec6 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -80,6 +80,14 @@ AppInstallParams .. autoclass:: AppInstallParams() :members: +IntegrationTypeConfig +~~~~~~~~~~~~~~~~~~~~~~ + +.. attributetable:: IntegrationTypeConfig + +.. autoclass:: IntegrationTypeConfig() + :members: + Team ~~~~~ @@ -493,6 +501,48 @@ Debug :param payload: The message that is about to be passed on to the WebSocket library. It can be :class:`bytes` to denote a binary message or :class:`str` to denote a regular text message. + :type payload: Union[:class:`bytes`, :class:`str`] + + +Entitlements +~~~~~~~~~~~~ + +.. function:: on_entitlement_create(entitlement) + + Called when a user subscribes to a SKU. + + .. versionadded:: 2.4 + + :param entitlement: The entitlement that was created. + :type entitlement: :class:`Entitlement` + +.. function:: on_entitlement_update(entitlement) + + Called when a user updates their subscription to a SKU. This is usually called when + the user renews or cancels their subscription. + + .. versionadded:: 2.4 + + :param entitlement: The entitlement that was updated. + :type entitlement: :class:`Entitlement` + +.. function:: on_entitlement_delete(entitlement) + + Called when a users subscription to a SKU is cancelled. This is typically only called when: + + - Discord issues a refund for the subscription. + - Discord removes an entitlement from a user. + + .. warning:: + + This event won't be called if the user cancels their subscription manually, instead + :func:`on_entitlement_update` will be called with :attr:`Entitlement.ends_at` set to the end of the + current billing period. + + .. versionadded:: 2.4 + + :param entitlement: The entitlement that was deleted. + :type entitlement: :class:`Entitlement` Gateway @@ -834,7 +884,7 @@ Members .. function:: on_member_ban(guild, user) - Called when user gets banned from a :class:`Guild`. + Called when a user gets banned from a :class:`Guild`. This requires :attr:`Intents.moderation` to be enabled. @@ -874,6 +924,29 @@ Members :param after: The updated member's updated info. :type after: :class:`Member` +.. function:: on_raw_presence_update(payload) + + Called when a :class:`Member` updates their presence. + + This requires :attr:`Intents.presences` to be enabled. + + Unlike :func:`on_presence_update`, when enabled, this is called regardless of the state of internal guild + and member caches, and **does not** provide a comparison between the previous and updated states of the :class:`Member`. + + .. important:: + + By default, this event is only dispatched when :attr:`Intents.presences` is enabled **and** :attr:`Intents.members` + is disabled. + + You can manually override this behaviour by setting the **enable_raw_presences** flag in the :class:`Client`, + however :attr:`Intents.presences` is always required for this event to work. + + .. versionadded:: 2.5 + + :param payload: The raw presence update event model. + :type payload: :class:`RawPresenceUpdateEvent` + + Messages ~~~~~~~~~ @@ -966,7 +1039,7 @@ Messages will return a :class:`Message` object that represents the message before the content was modified. Due to the inherently raw nature of this event, the data parameter coincides with - the raw data given by the :ddocs:`gateway `. + the raw data given by the :ddocs:`gateway `. Since the data payload can be partial, care must be taken when accessing stuff in the dictionary. One example of a common case of partial data is when the ``'content'`` key is inaccessible. This @@ -1005,6 +1078,47 @@ Messages :param payload: The raw event payload data. :type payload: :class:`RawBulkMessageDeleteEvent` +Polls +~~~~~~ + +.. function:: on_poll_vote_add(user, answer) + on_poll_vote_remove(user, answer) + + Called when a :class:`Poll` gains or loses a vote. If the ``user`` or ``answer``'s poll + parent message are not cached then this event will not be called. + + This requires :attr:`Intents.message_content` and :attr:`Intents.polls` to be enabled. + + .. note:: + + If the poll allows multiple answers and the user removes or adds multiple votes, this + event will be called as many times as votes that are added or removed. + + .. versionadded:: 2.4 + + :param user: The user that performed the action. + :type user: Union[:class:`User`, :class:`Member`] + :param answer: The answer the user voted or removed their vote from. + :type answer: :class:`PollAnswer` + +.. function:: on_raw_poll_vote_add(payload) + on_raw_poll_vote_remove(payload) + + Called when a :class:`Poll` gains or loses a vote. Unlike :func:`on_poll_vote_add` and :func:`on_poll_vote_remove` + this is called regardless of the state of the internal user and message cache. + + This requires :attr:`Intents.message_content` and :attr:`Intents.polls` to be enabled. + + .. note:: + + If the poll allows multiple answers and the user removes or adds multiple votes, this + event will be called as many times as votes that are added or removed. + + .. versionadded:: 2.4 + + :param payload: The raw event payload data. + :type payload: :class:`RawPollVoteActionEvent` + Reactions ~~~~~~~~~~ @@ -1028,6 +1142,12 @@ Reactions Consider using :func:`on_raw_reaction_add` if you need this and do not otherwise want to enable the members intent. + .. warning:: + + This event does not have a way of differentiating whether a reaction is a + burst reaction (also known as "super reaction") or not. If you need this, + consider using :func:`on_raw_reaction_add` instead. + :param reaction: The current state of the reaction. :type reaction: :class:`Reaction` :param user: The user who added the reaction. @@ -1050,6 +1170,12 @@ Reactions Consider using :func:`on_raw_reaction_remove` if you need this and do not want to enable the members intent. + .. warning:: + + This event does not have a way of differentiating whether a reaction is a + burst reaction (also known as "super reaction") or not. If you need this, + consider using :func:`on_raw_reaction_remove` instead. + :param reaction: The current state of the reaction. :type reaction: :class:`Reaction` :param user: The user whose reaction was removed. @@ -1203,6 +1329,37 @@ Scheduled Events :type user: :class:`User` +Soundboard +~~~~~~~~~~~ + +.. function:: on_soundboard_sound_create(sound) + on_soundboard_sound_delete(sound) + + Called when a :class:`SoundboardSound` is created or deleted. + + .. versionadded:: 2.5 + + :param sound: The soundboard sound that was created or deleted. + :type sound: :class:`SoundboardSound` + +.. function:: on_soundboard_sound_update(before, after) + + Called when a :class:`SoundboardSound` is updated. + + The following examples illustrate when this event is called: + + - The name is changed. + - The emoji is changed. + - The volume is changed. + + .. versionadded:: 2.5 + + :param before: The soundboard sound before the update. + :type before: :class:`SoundboardSound` + :param after: The soundboard sound after the update. + :type after: :class:`SoundboardSound` + + Stages ~~~~~~~ @@ -1232,6 +1389,37 @@ Stages :param after: The stage instance after the update. :type after: :class:`StageInstance` + +Subscriptions +~~~~~~~~~~~~~ + +.. function:: on_subscription_create(subscription) + + Called when a subscription is created. + + .. versionadded:: 2.5 + + :param subscription: The subscription that was created. + :type subscription: :class:`Subscription` + +.. function:: on_subscription_update(subscription) + + Called when a subscription is updated. + + .. versionadded:: 2.5 + + :param subscription: The subscription that was updated. + :type subscription: :class:`Subscription` + +.. function:: on_subscription_delete(subscription) + + Called when a subscription is deleted. + + .. versionadded:: 2.5 + + :param subscription: The subscription that was deleted. + :type subscription: :class:`Subscription` + Threads ~~~~~~~~ @@ -1363,7 +1551,7 @@ Threads .. versionadded:: 2.0 :param payload: The raw event payload data. - :type member: :class:`RawThreadMembersUpdate` + :type payload: :class:`RawThreadMembersUpdate` Voice ~~~~~~ @@ -1388,6 +1576,17 @@ Voice :param after: The voice state after the changes. :type after: :class:`VoiceState` +.. function:: on_voice_channel_effect(effect) + + Called when a :class:`Member` sends a :class:`VoiceChannelEffect` in a voice channel the bot is in. + + This requires :attr:`Intents.voice_states` to be enabled. + + .. versionadded:: 2.5 + + :param effect: The effect that is sent. + :type effect: :class:`VoiceChannelEffect` + .. _discord-api-utils: Utility Functions @@ -1512,6 +1711,12 @@ of :class:`enum.Enum`. .. versionadded:: 2.0 + .. attribute:: media + + A media channel. + + .. versionadded:: 2.4 + .. class:: MessageType Specifies the type of :class:`Message`. This is used to denote if a message @@ -1653,6 +1858,31 @@ of :class:`enum.Enum`. The system message sent when a user is given an advertisement to purchase a premium tier for an application during an interaction. + .. versionadded:: 2.2 + .. attribute:: stage_start + + The system message sent when the stage starts. + + .. versionadded:: 2.2 + .. attribute:: stage_end + + The system message sent when the stage ends. + + .. versionadded:: 2.2 + .. attribute:: stage_speaker + + The system message sent when the stage speaker changes. + + .. versionadded:: 2.2 + .. attribute:: stage_raise_hand + + The system message sent when a user is requesting to speak by raising their hands. + + .. versionadded:: 2.2 + .. attribute:: stage_topic + + The system message sent when the stage topic changes. + .. versionadded:: 2.2 .. attribute:: guild_application_premium_subscription @@ -1660,6 +1890,40 @@ of :class:`enum.Enum`. .. versionadded:: 2.2 + .. attribute:: guild_incident_alert_mode_enabled + + The system message sent when security actions is enabled. + + .. versionadded:: 2.4 + + .. attribute:: guild_incident_alert_mode_disabled + + The system message sent when security actions is disabled. + + .. versionadded:: 2.4 + + .. attribute:: guild_incident_report_raid + + The system message sent when a raid is reported. + + .. versionadded:: 2.4 + + .. attribute:: guild_incident_report_false_alarm + + The system message sent when a false alarm is reported. + + .. versionadded:: 2.4 + + .. attribute:: purchase_notification + + The system message sent when a purchase is made in the guild. + + .. versionadded:: 2.5 + + .. attribute:: poll_result + + The system message sent when a poll has closed. + .. class:: UserFlags Represents Discord User flags. @@ -1950,6 +2214,8 @@ of :class:`enum.Enum`. - :attr:`~AuditLogDiff.verification_level` - :attr:`~AuditLogDiff.widget_channel` - :attr:`~AuditLogDiff.widget_enabled` + - :attr:`~AuditLogDiff.premium_progress_bar_enabled` + - :attr:`~AuditLogDiff.system_channel_flags` .. attribute:: channel_create @@ -1991,6 +2257,9 @@ of :class:`enum.Enum`. - :attr:`~AuditLogDiff.rtc_region` - :attr:`~AuditLogDiff.video_quality_mode` - :attr:`~AuditLogDiff.default_auto_archive_duration` + - :attr:`~AuditLogDiff.nsfw` + - :attr:`~AuditLogDiff.slowmode_delay` + - :attr:`~AuditLogDiff.user_limit` .. attribute:: channel_delete @@ -2007,6 +2276,9 @@ of :class:`enum.Enum`. - :attr:`~AuditLogDiff.name` - :attr:`~AuditLogDiff.type` - :attr:`~AuditLogDiff.overwrites` + - :attr:`~AuditLogDiff.flags` + - :attr:`~AuditLogDiff.nsfw` + - :attr:`~AuditLogDiff.slowmode_delay` .. attribute:: overwrite_create @@ -2066,6 +2338,11 @@ of :class:`enum.Enum`. When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` or :class:`Object` who got kicked. + When this is the action, the type of :attr:`~AuditLogEntry.extra` is + set to an unspecified proxy object with one attribute: + + - ``integration_type``: An optional string that denotes the type of integration that did the action. + When this is the action, :attr:`~AuditLogEntry.changes` is empty. .. attribute:: member_prune @@ -2078,7 +2355,7 @@ of :class:`enum.Enum`. When this is the action, the type of :attr:`~AuditLogEntry.extra` is set to an unspecified proxy object with two attributes: - - ``delete_members_days``: An integer specifying how far the prune was. + - ``delete_member_days``: An integer specifying how far the prune was. - ``members_removed``: An integer specifying how many members were removed. When this is the action, :attr:`~AuditLogEntry.changes` is empty. @@ -2126,6 +2403,11 @@ of :class:`enum.Enum`. When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member`, :class:`User`, or :class:`Object` who got the role. + When this is the action, the type of :attr:`~AuditLogEntry.extra` is + set to an unspecified proxy object with one attribute: + + - ``integration_type``: An optional string that denotes the type of integration that did the action. + Possible attributes for :class:`AuditLogDiff`: - :attr:`~AuditLogDiff.roles` @@ -2138,7 +2420,7 @@ of :class:`enum.Enum`. When this is the action, the type of :attr:`~AuditLogEntry.extra` is set to an unspecified proxy object with two attributes: - - ``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the members were moved. + - ``channel``: An :class:`abc.Connectable` or :class:`Object` with the channel ID where the members were moved. - ``count``: An integer specifying how many members were moved. .. versionadded:: 1.3 @@ -2235,6 +2517,7 @@ of :class:`enum.Enum`. - :attr:`~AuditLogDiff.channel` - :attr:`~AuditLogDiff.uses` - :attr:`~AuditLogDiff.max_uses` + - :attr:`~AuditLogDiff.flags` .. attribute:: invite_update @@ -2259,6 +2542,7 @@ of :class:`enum.Enum`. - :attr:`~AuditLogDiff.channel` - :attr:`~AuditLogDiff.uses` - :attr:`~AuditLogDiff.max_uses` + - :attr:`~AuditLogDiff.flags` .. attribute:: webhook_create @@ -2722,7 +3006,7 @@ of :class:`enum.Enum`. set to an unspecified proxy object with 3 attributes: - ``automod_rule_name``: The name of the automod rule that was triggered. - - ``automod_rule_trigger``: A :class:`AutoModRuleTriggerType` representation of the rule type that was triggered. + - ``automod_rule_trigger_type``: A :class:`AutoModRuleTriggerType` representation of the rule type that was triggered. - ``channel``: The channel in which the automod rule was triggered. When this is the action, :attr:`AuditLogEntry.changes` is empty. @@ -2740,7 +3024,7 @@ of :class:`enum.Enum`. set to an unspecified proxy object with 3 attributes: - ``automod_rule_name``: The name of the automod rule that was triggered. - - ``automod_rule_trigger``: A :class:`AutoModRuleTriggerType` representation of the rule type that was triggered. + - ``automod_rule_trigger_type``: A :class:`AutoModRuleTriggerType` representation of the rule type that was triggered. - ``channel``: The channel in which the automod rule was triggered. When this is the action, :attr:`AuditLogEntry.changes` is empty. @@ -2758,13 +3042,61 @@ of :class:`enum.Enum`. set to an unspecified proxy object with 3 attributes: - ``automod_rule_name``: The name of the automod rule that was triggered. - - ``automod_rule_trigger``: A :class:`AutoModRuleTriggerType` representation of the rule type that was triggered. + - ``automod_rule_trigger_type``: A :class:`AutoModRuleTriggerType` representation of the rule type that was triggered. - ``channel``: The channel in which the automod rule was triggered. When this is the action, :attr:`AuditLogEntry.changes` is empty. .. versionadded:: 2.1 + .. attribute:: creator_monetization_request_created + + A request to monetize the server was created. + + .. versionadded:: 2.4 + + .. attribute:: creator_monetization_terms_accepted + + The terms and conditions for creator monetization were accepted. + + .. versionadded:: 2.4 + + .. attribute:: soundboard_sound_create + + A soundboard sound was created. + + Possible attributes for :class:`AuditLogDiff`: + + - :attr:`~AuditLogDiff.name` + - :attr:`~AuditLogDiff.emoji` + - :attr:`~AuditLogDiff.volume` + + .. versionadded:: 2.5 + + .. attribute:: soundboard_sound_update + + A soundboard sound was updated. + + Possible attributes for :class:`AuditLogDiff`: + + - :attr:`~AuditLogDiff.name` + - :attr:`~AuditLogDiff.emoji` + - :attr:`~AuditLogDiff.volume` + + .. versionadded:: 2.5 + + .. attribute:: soundboard_sound_delete + + A soundboard sound was deleted. + + Possible attributes for :class:`AuditLogDiff`: + + - :attr:`~AuditLogDiff.name` + - :attr:`~AuditLogDiff.emoji` + - :attr:`~AuditLogDiff.volume` + + .. versionadded:: 2.5 + .. class:: AuditLogActionCategory Represents the category that the :class:`AuditLogAction` belongs to. @@ -2797,6 +3129,27 @@ of :class:`enum.Enum`. Represents a member currently in the team. +.. class:: TeamMemberRole + + Represents the type of role of a team member retrieved through :func:`Client.application_info`. + + .. versionadded:: 2.4 + + .. attribute:: admin + + The team member is an admin. This allows them to invite members to the team, access credentials, edit the application, + and do most things the owner can do. However they cannot do destructive actions. + + .. attribute:: developer + + The team member is a developer. This allows them to access information, like the client secret or public key. + They can also configure interaction endpoints or reset the bot token. Developers cannot invite anyone to the team + nor can they do destructive actions. + + .. attribute:: read_only + + The team member is a read-only member. This allows them to access information, but not edit anything. + .. class:: WebhookType Represents the type of webhook that can be received. @@ -2841,27 +3194,33 @@ of :class:`enum.Enum`. .. attribute:: blurple - Represents the default avatar with the color blurple. + Represents the default avatar with the colour blurple. See also :attr:`Colour.blurple` .. attribute:: grey - Represents the default avatar with the color grey. + Represents the default avatar with the colour grey. See also :attr:`Colour.greyple` .. attribute:: gray An alias for :attr:`grey`. .. attribute:: green - Represents the default avatar with the color green. + Represents the default avatar with the colour green. See also :attr:`Colour.green` .. attribute:: orange - Represents the default avatar with the color orange. + Represents the default avatar with the colour orange. See also :attr:`Colour.orange` .. attribute:: red - Represents the default avatar with the color red. + Represents the default avatar with the colour red. See also :attr:`Colour.red` + .. attribute:: pink + + Represents the default avatar with the colour pink. + See also :attr:`Colour.pink` + + .. versionadded:: 2.3 .. class:: StickerType @@ -3070,6 +3429,12 @@ of :class:`enum.Enum`. The ``ko`` locale. + .. attribute:: latin_american_spanish + + The ``es-419`` locale. + + .. versionadded:: 2.4 + .. attribute:: lithuanian The ``lt`` locale. @@ -3229,6 +3594,12 @@ of :class:`enum.Enum`. The rule will trigger when combined number of role and user mentions is greater than the set limit. + .. attribute:: member_profile + + The rule will trigger when a user's profile contains a keyword. + + .. versionadded:: 2.4 + .. class:: AutoModRuleEventType Represents the event type of an automod rule. @@ -3239,6 +3610,12 @@ of :class:`enum.Enum`. The rule will trigger when a message is sent. + .. attribute:: member_update + + The rule will trigger when a member's profile is updated. + + .. versionadded:: 2.4 + .. class:: AutoModRuleActionType Represents the action type of an automod rule. @@ -3257,6 +3634,12 @@ of :class:`enum.Enum`. The rule will timeout a user. + .. attribute:: block_member_interactions + + Similar to :attr:`timeout`, except the user will be timed out indefinitely. + This will request the user to edit it's profile. + + .. versionadded:: 2.4 .. class:: ForumLayoutType @@ -3276,20 +3659,221 @@ of :class:`enum.Enum`. Displays posts as a collection of tiles. -.. class:: OnboardingPromptType - Represents the type of a guild onboarding prompt. +.. class:: ForumOrderType - .. versionadded:: 2.2 + Represents how a forum's posts are sorted in the client. + + .. versionadded:: 2.3 + + .. attribute:: latest_activity + + Sort forum posts by activity. + + .. attribute:: creation_date + + Sort forum posts by creation time (from most recent to oldest). + +.. class:: SelectDefaultValueType + + Represents the default value of a select menu. + + .. versionadded:: 2.4 + + .. attribute:: user + + The underlying type of the ID is a user. + + .. attribute:: role + + The underlying type of the ID is a role. + + .. attribute:: channel + + The underlying type of the ID is a channel or thread. + + +.. class:: SKUType + + Represents the type of a SKU. + + .. versionadded:: 2.4 + + .. attribute:: durable + + The SKU is a durable one-time purchase. + + .. attribute:: consumable + + The SKU is a consumable one-time purchase. + + .. attribute:: subscription + + The SKU is a recurring subscription. + + .. attribute:: subscription_group + + The SKU is a system-generated group which is created for each :attr:`SKUType.subscription`. + + +.. class:: EntitlementType + + Represents the type of an entitlement. + + .. versionadded:: 2.4 + + .. attribute:: purchase + + The entitlement was purchased by the user. + + .. attribute:: premium_subscription + + The entitlement is for a nitro subscription. + + .. attribute:: developer_gift + + The entitlement was gifted by the developer. + + .. attribute:: test_mode_purchase + + The entitlement was purchased by a developer in application test mode. + + .. attribute:: free_purchase + + The entitlement was granted, when the SKU was free. + + .. attribute:: user_gift + + The entitlement was gifted by a another user. + + .. attribute:: premium_purchase + + The entitlement was claimed for free by a nitro subscriber. + + .. attribute:: application_subscription + + The entitlement was purchased as an app subscription. + + +.. class:: EntitlementOwnerType + + Represents the type of an entitlement owner. + + .. versionadded:: 2.4 + + .. attribute:: guild + + The entitlement owner is a guild. + + .. attribute:: user - .. attribute:: multiple_choice + The entitlement owner is a user. - The prompt will be a multiple choice. - .. attribute:: dropdown +.. class:: PollLayoutType - The prompt will be a dropdown. + Represents how a poll answers are shown. + .. versionadded:: 2.4 + + .. attribute:: default + + The default layout. + + +.. class:: InviteType + + Represents the type of an invite. + + .. versionadded:: 2.4 + + .. attribute:: guild + + The invite is a guild invite. + + .. attribute:: group_dm + + The invite is a group DM invite. + + .. attribute:: friend + + The invite is a friend invite. + + +.. class:: ReactionType + + Represents the type of a reaction. + + .. versionadded:: 2.4 + + .. attribute:: normal + + A normal reaction. + + .. attribute:: burst + + A burst reaction, also known as a "super reaction". + + +.. class:: VoiceChannelEffectAnimationType + + Represents the animation type of a voice channel effect. + + .. versionadded:: 2.5 + + .. attribute:: premium + + A fun animation, sent by a Nitro subscriber. + + .. attribute:: basic + + The standard animation. + + +.. class:: SubscriptionStatus + + Represents the status of an subscription. + + .. versionadded:: 2.5 + + .. attribute:: active + + The subscription is active. + + .. attribute:: ending + + The subscription is active but will not renew. + + .. attribute:: inactive + + The subscription is inactive and not being charged. + + +.. class:: MessageReferenceType + + Represents the type of a message reference. + + .. versionadded:: 2.5 + + .. attribute:: default + + A standard reference used by message replies (:attr:`MessageType.reply`), + crossposted messaged created by a followed channel integration, and messages of type: + + - :attr:`MessageType.pins_add` + - :attr:`MessageType.channel_follow_add` + - :attr:`MessageType.thread_created` + - :attr:`MessageType.thread_starter_message` + - :attr:`MessageType.poll_result` + - :attr:`MessageType.context_menu_command` + + .. attribute:: forward + + A forwarded message. + + .. attribute:: reply + + An alias for :attr:`.default`. .. _discord-api-audit-logs: @@ -3388,6 +3972,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`. @@ -3750,11 +4340,12 @@ AuditLogDiff .. attribute:: emoji - The name of the emoji that represents a sticker being changed. + The emoji which represents one of the following: - See also :attr:`GuildSticker.emoji`. + * :attr:`GuildSticker.emoji` + * :attr:`SoundboardSound.emoji` - :type: :class:`str` + :type: Union[:class:`str`, :class:`PartialEmoji`] .. attribute:: unicode_emoji @@ -3775,9 +4366,10 @@ AuditLogDiff .. attribute:: available - The availability of a sticker being changed. + The availability of one of the following being changed: - See also :attr:`GuildSticker.available` + * :attr:`GuildSticker.available` + * :attr:`SoundboardSound.available` :type: :class:`bool` @@ -3906,6 +4498,12 @@ AuditLogDiff The trigger for the automod rule. + .. note :: + + The :attr:`~AutoModTrigger.type` of the trigger may be incorrect. + Some attributes such as :attr:`~AutoModTrigger.keyword_filter`, :attr:`~AutoModTrigger.regex_patterns`, + and :attr:`~AutoModTrigger.allow_list` will only have the added or removed values. + :type: :class:`AutoModTrigger` .. attribute:: actions @@ -3926,6 +4524,90 @@ AuditLogDiff :type: List[:class:`abc.GuildChannel`, :class:`Thread`, :class:`Object`] + .. attribute:: premium_progress_bar_enabled + + The guild’s display setting to show boost progress bar. + + :type: :class:`bool` + + .. attribute:: system_channel_flags + + The guild’s system channel settings. + + See also :attr:`Guild.system_channel_flags` + + :type: :class:`SystemChannelFlags` + + .. attribute:: nsfw + + Whether the channel is marked as “not safe for work” or “age restricted”. + + :type: :class:`bool` + + .. attribute:: user_limit + + The channel’s limit for number of members that can be in a voice or stage channel. + + See also :attr:`VoiceChannel.user_limit` and :attr:`StageChannel.user_limit` + + :type: :class:`int` + + .. attribute:: flags + + The flags associated with this thread, forum post or invite. + + See also :attr:`ForumChannel.flags`, :attr:`Thread.flags` and :attr:`Invite.flags` + + :type: Union[:class:`ChannelFlags`, :class:`InviteFlags`] + + .. 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: Optional[:class:`PartialEmoji`] + + .. attribute:: user + + The user that represents the uploader of a soundboard sound. + + See also :attr:`SoundboardSound.user` + + :type: Union[:class:`Member`, :class:`User`] + + .. attribute:: volume + + The volume of a soundboard sound. + + See also :attr:`SoundboardSound.volume` + + :type: :class:`float` + .. this is currently missing the following keys: reason and application_id I'm not sure how to port these @@ -4161,6 +4843,32 @@ Guild :type: :class:`User` +.. class:: BulkBanResult + + A namedtuple which represents the result returned from :meth:`~Guild.bulk_ban`. + + .. versionadded:: 2.4 + + .. attribute:: banned + + The list of users that were banned. The inner :class:`Object` of the list + has the :attr:`Object.type` set to :class:`User`. + + :type: List[:class:`Object`] + .. attribute:: failed + + The list of users that could not be banned. The inner :class:`Object` of the list + has the :attr:`Object.type` set to :class:`User`. + + :type: List[:class:`Object`] + +GuildPreview +~~~~~~~~~~~~ + +.. attributetable:: GuildPreview + +.. autoclass:: GuildPreview + :members: ScheduledEvent ~~~~~~~~~~~~~~ @@ -4328,6 +5036,35 @@ VoiceChannel :members: :inherited-members: +.. attributetable:: VoiceChannelEffect + +.. autoclass:: VoiceChannelEffect() + :members: + :inherited-members: + +.. class:: VoiceChannelEffectAnimation + + A namedtuple which represents a voice channel effect animation. + + .. versionadded:: 2.5 + + .. attribute:: id + + The ID of the animation. + + :type: :class:`int` + .. attribute:: type + + The type of the animation. + + :type: :class:`VoiceChannelEffectAnimationType` + +.. attributetable:: VoiceChannelSoundEffect + +.. autoclass:: VoiceChannelSoundEffect() + :members: + :inherited-members: + StageChannel ~~~~~~~~~~~~~ @@ -4494,6 +5231,30 @@ GuildSticker .. autoclass:: GuildSticker() :members: +BaseSoundboardSound +~~~~~~~~~~~~~~~~~~~~~~~ + +.. attributetable:: BaseSoundboardSound + +.. autoclass:: BaseSoundboardSound() + :members: + +SoundboardDefaultSound +~~~~~~~~~~~~~~~~~~~~~~~ + +.. attributetable:: SoundboardDefaultSound + +.. autoclass:: SoundboardDefaultSound() + :members: + +SoundboardSound +~~~~~~~~~~~~~~~~~~~~~~~ + +.. attributetable:: SoundboardSound + +.. autoclass:: SoundboardSound() + :members: + ShardInfo ~~~~~~~~~~~ @@ -4502,6 +5263,38 @@ ShardInfo .. autoclass:: ShardInfo() :members: +SessionStartLimits +~~~~~~~~~~~~~~~~~~~~ + +.. attributetable:: SessionStartLimits + +.. autoclass:: SessionStartLimits() + :members: + +SKU +~~~~~~~~~~~ + +.. attributetable:: SKU + +.. autoclass:: SKU() + :members: + +Entitlement +~~~~~~~~~~~ + +.. attributetable:: Entitlement + +.. autoclass:: Entitlement() + :members: + +Subscription +~~~~~~~~~~~~ + +.. attributetable:: Subscription + +.. autoclass:: Subscription() + :members: + RawMessageDeleteEvent ~~~~~~~~~~~~~~~~~~~~~~~ @@ -4606,6 +5399,22 @@ RawAppCommandPermissionsUpdateEvent .. autoclass:: RawAppCommandPermissionsUpdateEvent() :members: +RawPollVoteActionEvent +~~~~~~~~~~~~~~~~~~~~~~ + +.. attributetable:: RawPollVoteActionEvent + +.. autoclass:: RawPollVoteActionEvent() + :members: + +RawPresenceUpdateEvent +~~~~~~~~~~~~~~~~~~~~~~ + +.. attributetable:: RawPresenceUpdateEvent + +.. autoclass:: RawPresenceUpdateEvent() + :members: + PartialWebhookGuild ~~~~~~~~~~~~~~~~~~~~ @@ -4622,33 +5431,32 @@ PartialWebhookChannel .. autoclass:: PartialWebhookChannel() :members: -Onboarding -~~~~~~~~~~~~ +PollAnswer +~~~~~~~~~~ -.. attributetable:: Onboarding +.. attributetable:: PollAnswer -.. autoclass:: Onboarding() +.. autoclass:: PollAnswer() :members: -OnboardingPrompt -~~~~~~~~~~~~~~~~~~ +.. _discord_api_data: + +MessageSnapshot +~~~~~~~~~~~~~~~~~ -.. attributetable:: OnboardingPrompt +.. attributetable:: MessageSnapshot -.. autoclass:: OnboardingPrompt() +.. autoclass:: MessageSnapshot :members: -OnboardingPromptOption -~~~~~~~~~~~~~~~~~~~~~~~ +ClientStatus +~~~~~~~~~~~~ -.. attributetable:: OnboardingPromptOption +.. attributetable:: ClientStatus -.. autoclass:: OnboardingPromptOption() +.. autoclass:: ClientStatus() :members: - -.. _discord_api_data: - Data Classes -------------- @@ -4720,6 +5528,22 @@ RoleSubscriptionInfo .. autoclass:: RoleSubscriptionInfo :members: +PurchaseNotification +~~~~~~~~~~~~~~~~~~~~~ + +.. attributetable:: PurchaseNotification + +.. autoclass:: PurchaseNotification() + :members: + +GuildProductPurchase ++++++++++++++++++++++ + +.. attributetable:: GuildProductPurchase + +.. autoclass:: GuildProductPurchase() + :members: + Intents ~~~~~~~~~~ @@ -4880,6 +5704,46 @@ MemberFlags .. autoclass:: MemberFlags :members: +AttachmentFlags +~~~~~~~~~~~~~~~~ + +.. attributetable:: AttachmentFlags + +.. autoclass:: AttachmentFlags + :members: + +RoleFlags +~~~~~~~~~~ + +.. attributetable:: RoleFlags + +.. autoclass:: RoleFlags + :members: + +SKUFlags +~~~~~~~~~~~ + +.. attributetable:: SKUFlags + +.. autoclass:: SKUFlags() + :members: + +EmbedFlags +~~~~~~~~~~ + +.. attributetable:: EmbedFlags + +.. autoclass:: EmbedFlags() + :members: + +InviteFlags +~~~~~~~~~~~~~~~~ + +.. attributetable:: InviteFlags + +.. autoclass:: InviteFlags() + :members: + ForumTag ~~~~~~~~~ @@ -4888,6 +5752,30 @@ ForumTag .. autoclass:: ForumTag :members: +Poll +~~~~ + +.. attributetable:: Poll + +.. autoclass:: Poll + :members: + +PollMedia +~~~~~~~~~ + +.. attributetable:: PollMedia + +.. autoclass:: PollMedia + :members: + +CallMessage +~~~~~~~~~~~~~~~~~~~ + +.. attributetable:: CallMessage + +.. autoclass:: CallMessage() + :members: + Exceptions ------------ @@ -4922,6 +5810,8 @@ The following exceptions are thrown by the library. .. autoexception:: InteractionResponded +.. autoexception:: MissingApplicationID + .. autoexception:: discord.opus.OpusError .. autoexception:: discord.opus.OpusNotLoaded @@ -4939,6 +5829,7 @@ Exception Hierarchy - :exc:`ConnectionClosed` - :exc:`PrivilegedIntentsRequired` - :exc:`InteractionResponded` + - :exc:`MissingApplicationID` - :exc:`GatewayNotFound` - :exc:`HTTPException` - :exc:`Forbidden` diff --git a/docs/conf.py b/docs/conf.py index 8fee2cbfa..28b39452c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -37,6 +37,7 @@ extensions = [ 'sphinx.ext.intersphinx', 'sphinx.ext.napoleon', 'sphinxcontrib_trio', + 'sphinx_inline_tabs', 'details', 'exception_hierarchy', 'attributetable', @@ -51,7 +52,7 @@ autodoc_typehints = 'none' # napoleon_attr_annotations = False extlinks = { - 'issue': ('https://github.com/Rapptz/discord.py/issues/%s', 'GH-'), + 'issue': ('https://github.com/Rapptz/discord.py/issues/%s', 'GH-%s'), 'ddocs': ('https://discord.com/developers/docs/%s', None), } diff --git a/docs/discord.rst b/docs/discord.rst index ac12417f0..d58ca8fb0 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 @@ -66,7 +61,7 @@ If you want to invite your bot you must create an invite URL for it. 1. Make sure you're logged on to the `Discord website `_. 2. Navigate to the `application page `_ 3. Click on your bot's page. -4. Go to the "OAuth2" tab. +4. Go to the "OAuth2 > URL Generator" tab. .. image:: /images/discord_oauth2.png :alt: How the OAuth2 page should look like. diff --git a/docs/ext/commands/api.rst b/docs/ext/commands/api.rst index 6420d2b0d..3da5cae16 100644 --- a/docs/ext/commands/api.rst +++ b/docs/ext/commands/api.rst @@ -256,7 +256,7 @@ GroupCog .. attributetable:: discord.ext.commands.GroupCog .. autoclass:: discord.ext.commands.GroupCog - :members: + :members: interaction_check CogMeta @@ -531,6 +531,11 @@ Converters .. autoclass:: discord.ext.commands.ScheduledEventConverter :members: +.. attributetable:: discord.ext.commands.SoundboardSoundConverter + +.. autoclass:: discord.ext.commands.SoundboardSoundConverter + :members: + .. attributetable:: discord.ext.commands.clean_content .. autoclass:: discord.ext.commands.clean_content @@ -708,6 +713,9 @@ Exceptions .. autoexception:: discord.ext.commands.ScheduledEventNotFound :members: +.. autoexception:: discord.ext.commands.SoundboardSoundNotFound + :members: + .. autoexception:: discord.ext.commands.BadBoolArgument :members: @@ -800,6 +808,7 @@ Exception Hierarchy - :exc:`~.commands.EmojiNotFound` - :exc:`~.commands.GuildStickerNotFound` - :exc:`~.commands.ScheduledEventNotFound` + - :exc:`~.commands.SoundboardSoundNotFound` - :exc:`~.commands.PartialEmojiConversionFailure` - :exc:`~.commands.BadBoolArgument` - :exc:`~.commands.RangeError` diff --git a/docs/ext/commands/commands.rst b/docs/ext/commands/commands.rst index e479c4b08..52e57ff4d 100644 --- a/docs/ext/commands/commands.rst +++ b/docs/ext/commands/commands.rst @@ -778,6 +778,19 @@ This tells the parser that the ``members`` attribute is mapped to a flag named ` the default value is an empty list. For greater customisability, the default can either be a value or a callable that takes the :class:`~ext.commands.Context` as a sole parameter. This callable can either be a function or a coroutine. +A positional flag can be defined by setting the :attr:`~ext.commands.Flag.positional` attribute to ``True``. This +tells the parser that the content provided before the parsing occurs is part of the flag. This is useful for commands that +require a parameter to be used first and the flags are optional, such as the following: + +.. code-block:: python3 + + class BanFlags(commands.FlagConverter): + members: List[discord.Member] = commands.flag(name='member', positional=True, default=lambda ctx: []) + reason: Optional[str] = None + +.. note:: + Only one positional flag is allowed in a flag converter. + In order to customise the flag syntax we also have a few options that can be passed to the class parameter list: .. code-block:: python3 @@ -796,12 +809,17 @@ In order to customise the flag syntax we also have a few options that can be pas topic: Optional[str] nsfw: Optional[bool] slowmode: Optional[int] + + # Hello there --bold True + class Greeting(commands.FlagConverter): + text: str = commands.flag(positional=True) + bold: bool = False .. note:: Despite the similarities in these examples to command like arguments, the syntax and parser is not a command line parser. The syntax is mainly inspired by Discord's search bar input and as a result - all flags need a corresponding value. + all flags need a corresponding value unless part of a positional flag. Flag converters will only raise :exc:`~ext.commands.FlagError` derived exceptions. If an error is raised while converting a flag, :exc:`~ext.commands.BadFlagArgument` is raised instead and the original exception @@ -1147,6 +1165,7 @@ If you want a more robust error system, you can derive from the exception and ra return True return commands.check(predicate) + @bot.command() @guild_only() async def test(ctx): await ctx.send('Hey this is not a DM! Nice.') diff --git a/docs/ext/commands/extensions.rst b/docs/ext/commands/extensions.rst index 9351c9702..d03607845 100644 --- a/docs/ext/commands/extensions.rst +++ b/docs/ext/commands/extensions.rst @@ -54,6 +54,8 @@ Cleaning Up Although rare, sometimes an extension needs to clean-up or know when it's being unloaded. For cases like these, there is another entry point named ``teardown`` which is similar to ``setup`` except called when the extension is unloaded. +Exceptions raised in the ``teardown`` function are ignored, and the extension is still unloaded. + .. code-block:: python3 :caption: basic_ext.py diff --git a/docs/faq.rst b/docs/faq.rst index 0cd8b8ad6..16d03362a 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -439,7 +439,7 @@ How can I disable all items on timeout? This requires three steps. -1. Attach a message to the :class:`~discord.ui.View` using either the return type of :meth:`~abc.Messageable.send` or retrieving it via :meth:`Interaction.original_response`. +1. Attach a message to the :class:`~discord.ui.View` using either the return type of :meth:`~abc.Messageable.send` or retrieving it via :attr:`InteractionCallbackResponse.resource`. 2. Inside :meth:`~ui.View.on_timeout`, loop over all items inside the view and mark them disabled. 3. Edit the message we retrieved in step 1 with the newly modified view. @@ -467,7 +467,7 @@ Putting it all together, we can do this in a text command: # Step 1 view.message = await ctx.send('Press me!', view=view) -Application commands do not return a message when you respond with :meth:`InteractionResponse.send_message`, therefore in order to reliably do this we should retrieve the message using :meth:`Interaction.original_response`. +Application commands, when you respond with :meth:`InteractionResponse.send_message`, return an instance of :class:`InteractionCallbackResponse` which contains the message you sent. This is the message you should attach to the view. Putting it all together, using the previous view definition: @@ -477,10 +477,13 @@ Putting it all together, using the previous view definition: async def more_timeout_example(interaction): """Another example to showcase disabling buttons on timing out""" view = MyView() - await interaction.response.send_message('Press me!', view=view) + callback = await interaction.response.send_message('Press me!', view=view) # Step 1 - view.message = await interaction.original_response() + resource = callback.resource + # making sure it's an interaction response message + if isinstance(resource, discord.InteractionMessage): + view.message = resource Application Commands 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 diff --git a/docs/intents.rst b/docs/intents.rst index e805c5ff7..ca85ab8dd 100644 --- a/docs/intents.rst +++ b/docs/intents.rst @@ -114,6 +114,7 @@ Message Content - Whether you use :attr:`Message.attachments` to check message attachments. - Whether you use :attr:`Message.embeds` to check message embeds. - Whether you use :attr:`Message.components` to check message components. +- Whether you use :attr:`Message.poll` to check the message polls. - Whether you use the commands extension with a non-mentioning prefix. .. _intents_member_cache: diff --git a/docs/interactions/api.rst b/docs/interactions/api.rst index 25b08bf26..294a3b13a 100644 --- a/docs/interactions/api.rst +++ b/docs/interactions/api.rst @@ -28,6 +28,22 @@ InteractionResponse .. autoclass:: InteractionResponse() :members: +InteractionCallbackResponse +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. attributetable:: InteractionCallbackResponse + +.. autoclass:: InteractionCallbackResponse() + :members: + +InteractionCallbackActivityInstance +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. attributetable:: InteractionCallbackActivityInstance + +.. autoclass:: InteractionCallbackActivityInstance() + :members: + InteractionMessage ~~~~~~~~~~~~~~~~~~~ @@ -45,6 +61,14 @@ MessageInteraction .. autoclass:: MessageInteraction() :members: +MessageInteractionMetadata +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. attributetable:: MessageInteractionMetadata + +.. autoclass:: MessageInteractionMetadata() + :members: + Component ~~~~~~~~~~ @@ -129,6 +153,22 @@ AppCommandPermissions .. autoclass:: discord.app_commands.AppCommandPermissions() :members: +AppCommandContext +~~~~~~~~~~~~~~~~~ + +.. attributetable:: discord.app_commands.AppCommandContext + +.. autoclass:: discord.app_commands.AppCommandContext + :members: + +AppInstallationType +~~~~~~~~~~~~~~~~~~~~ + +.. attributetable:: discord.app_commands.AppInstallationType + +.. autoclass:: discord.app_commands.AppInstallationType + :members: + GuildAppCommandPermissions ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -166,6 +206,14 @@ SelectOption .. autoclass:: SelectOption :members: +SelectDefaultValue +~~~~~~~~~~~~~~~~~~~ + +.. attributetable:: SelectDefaultValue + +.. autoclass:: SelectDefaultValue + :members: + Choice ~~~~~~~ @@ -281,6 +329,10 @@ Enumerations Represents a select in which both users and roles can be selected. + .. attribute:: channel_select + + Represents a channel select component. + .. class:: ButtonStyle Represents the style of the button component. @@ -302,7 +354,12 @@ Enumerations .. attribute:: link Represents a link button. + .. attribute:: premium + Represents a button denoting that buying a SKU is + required to perform this action. + + .. versionadded:: 2.4 .. attribute:: blurple An alias for :attr:`primary`. @@ -443,6 +500,15 @@ Item .. autoclass:: discord.ui.Item :members: +DynamicItem +~~~~~~~~~~~~ + +.. attributetable:: discord.ui.DynamicItem + +.. autoclass:: discord.ui.DynamicItem + :members: + :inherited-members: + Button ~~~~~~~ @@ -625,6 +691,24 @@ Decorators .. autofunction:: discord.app_commands.guild_only :decorator: +.. autofunction:: discord.app_commands.dm_only + :decorator: + +.. autofunction:: discord.app_commands.private_channel_only + :decorator: + +.. autofunction:: discord.app_commands.allowed_contexts + :decorator: + +.. autofunction:: discord.app_commands.user_install + :decorator: + +.. autofunction:: discord.app_commands.guild_install + :decorator: + +.. autofunction:: discord.app_commands.allowed_installs + :decorator: + .. autofunction:: discord.app_commands.default_permissions :decorator: @@ -808,9 +892,6 @@ Exceptions .. autoexception:: discord.app_commands.CommandNotFound :members: -.. autoexception:: discord.app_commands.MissingApplicationID - :members: - .. autoexception:: discord.app_commands.CommandSyncFailure :members: @@ -835,7 +916,7 @@ Exception Hierarchy - :exc:`~discord.app_commands.CommandAlreadyRegistered` - :exc:`~discord.app_commands.CommandSignatureMismatch` - :exc:`~discord.app_commands.CommandNotFound` - - :exc:`~discord.app_commands.MissingApplicationID` + - :exc:`~discord.MissingApplicationID` - :exc:`~discord.app_commands.CommandSyncFailure` - :exc:`~discord.HTTPException` - :exc:`~discord.app_commands.CommandSyncFailure` diff --git a/docs/locale/ja/LC_MESSAGES/api.po b/docs/locale/ja/LC_MESSAGES/api.po index a240547dd..96a912b31 100644 --- a/docs/locale/ja/LC_MESSAGES/api.po +++ b/docs/locale/ja/LC_MESSAGES/api.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: discordpy\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-01-29 20:45+0000\n" -"PO-Revision-Date: 2023-01-30 13:48\n" +"POT-Creation-Date: 2024-03-26 03:41+0000\n" +"PO-Revision-Date: 2024-04-17 02:43\n" "Last-Translator: \n" "Language-Team: Japanese\n" "MIME-Version: 1.0\n" @@ -309,8 +309,8 @@ msgstr "これが ``__init__`` で渡されなかった場合、データを含 #: ../../../discord/client.py:docstring of discord.Client.application_id:10 #: ../../../discord/appinfo.py:docstring of discord.appinfo.AppInfo:72 #: ../../../discord/appinfo.py:docstring of discord.appinfo.AppInfo:82 -#: ../../api.rst:1430 -#: ../../../discord/audit_logs.py:docstring of discord.audit_logs.AuditLogEntry:41 +#: ../../../discord/team.py:docstring of discord.TeamMember.avatar_decoration_sku_id:7 +#: ../../api.rst:1484 msgid "Optional[:class:`int`]" msgstr "Optional[:class:`int`]" @@ -659,6 +659,7 @@ msgid "Optional[:class:`~discord.User`]" msgstr "Optional[:class:`~discord.User`]" #: ../../../discord/client.py:docstring of discord.client.Client.get_emoji:1 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.get_emoji:1 msgid "Returns an emoji with the given ID." msgstr "与えられたIDの絵文字を返します。" @@ -702,8 +703,8 @@ msgstr ":class:`.abc.GuildChannel` を受け取ったからと言って、その #: ../../../discord/client.py:docstring of discord.client.Client.get_all_channels:0 #: ../../../discord/client.py:docstring of discord.client.Client.get_all_members:0 #: ../../../discord/client.py:docstring of discord.client.Client.fetch_guilds:0 +#: ../../../discord/client.py:docstring of discord.client.Client.entitlements:0 #: ../../../discord/abc.py:docstring of discord.abc.Messageable.history:0 -#: ../../../discord/user.py:docstring of discord.abc.Messageable.history:0 msgid "Yields" msgstr "Yieldする値" @@ -748,10 +749,10 @@ msgid "This function returns the **first event that meets the requirements**." msgstr "この関数は **条件を満たす最初のイベント** を返します。" #: ../../../discord/client.py:docstring of discord.client.Client.wait_for:22 -#: ../../../discord/client.py:docstring of discord.client.Client.fetch_guilds:13 +#: ../../../discord/client.py:docstring of discord.client.Client.fetch_guilds:14 +#: ../../../discord/client.py:docstring of discord.client.Client.entitlements:6 #: ../../../discord/player.py:docstring of discord.player.FFmpegOpusAudio.from_probe:7 #: ../../../discord/utils.py:docstring of discord.utils.get:24 -#: ../../../discord/abc.py:docstring of discord.abc.GuildChannel.set_permissions:25 msgid "Examples" msgstr "例" @@ -823,55 +824,60 @@ msgid "Retrieves an :term:`asynchronous iterator` that enables receiving your gu msgstr "Botが所属するGuildを取得できる、 :term:`asynchronous iterator` を取得します。" #: ../../../discord/client.py:docstring of discord.client.Client.fetch_guilds:5 -msgid "Using this, you will only receive :attr:`.Guild.owner`, :attr:`.Guild.icon`, :attr:`.Guild.id`, and :attr:`.Guild.name` per :class:`.Guild`." -msgstr "これを使った場合、各 :class:`Guild` の :attr:`Guild.owner` 、 :attr:`Guild.icon` 、 :attr:`Guild.id` 、 :attr:`Guild.name` のみ取得できます。" +msgid "Using this, you will only receive :attr:`.Guild.owner`, :attr:`.Guild.icon`, :attr:`.Guild.id`, :attr:`.Guild.name`, :attr:`.Guild.approximate_member_count`, and :attr:`.Guild.approximate_presence_count` per :class:`.Guild`." +msgstr "これを使用した場合、各 :class:`.Guild` の :attr:`.Guild.owner` 、 :attr:`.Guild.icon` 、 :attr:`.Guild.id` 、 :attr:`.Guild.name` 、 :attr:`.Guild.approximate_member_count` 、および :attr:`.Guild.approximate_presence_count` のみ受け取ります。" -#: ../../../discord/client.py:docstring of discord.client.Client.fetch_guilds:10 +#: ../../../discord/client.py:docstring of discord.client.Client.fetch_guilds:11 msgid "This method is an API call. For general usage, consider :attr:`guilds` instead." msgstr "これはAPIを呼び出します。通常は :attr:`guilds` を代わりに使用してください。" -#: ../../../discord/client.py:docstring of discord.client.Client.fetch_guilds:14 +#: ../../../discord/client.py:docstring of discord.client.Client.fetch_guilds:15 +#: ../../../discord/client.py:docstring of discord.client.Client.entitlements:7 #: ../../../discord/abc.py:docstring of discord.abc.Messageable.history:7 #: ../../../discord/user.py:docstring of discord.abc.Messageable.history:7 #: ../../../discord/reaction.py:docstring of discord.reaction.Reaction.users:12 -#: ../../../discord/guild.py:docstring of discord.guild.Guild.fetch_members:27 msgid "Usage ::" msgstr "使い方 ::" -#: ../../../discord/client.py:docstring of discord.client.Client.fetch_guilds:19 +#: ../../../discord/client.py:docstring of discord.client.Client.fetch_guilds:20 +#: ../../../discord/client.py:docstring of discord.client.Client.entitlements:12 #: ../../../discord/guild.py:docstring of discord.guild.Guild.bans:15 msgid "Flattening into a list ::" msgstr "リストへフラット化 ::" -#: ../../../discord/client.py:docstring of discord.client.Client.fetch_guilds:24 +#: ../../../discord/client.py:docstring of discord.client.Client.fetch_guilds:25 +#: ../../../discord/client.py:docstring of discord.client.Client.entitlements:17 #: ../../../discord/abc.py:docstring of discord.abc.Messageable.history:19 #: ../../../discord/user.py:docstring of discord.abc.Messageable.history:19 #: ../../../discord/guild.py:docstring of discord.guild.Guild.fetch_members:10 -#: ../../../discord/guild.py:docstring of discord.guild.Guild.bans:20 msgid "All parameters are optional." msgstr "すべてのパラメータがオプションです。" -#: ../../../discord/client.py:docstring of discord.client.Client.fetch_guilds:26 +#: ../../../discord/client.py:docstring of discord.client.Client.fetch_guilds:27 msgid "The number of guilds to retrieve. If ``None``, it retrieves every guild you have access to. Note, however, that this would make it a slow operation. Defaults to ``200``." msgstr "取得するギルドの数。 ``None`` の場合、Botがアクセスできるギルドすべてを取得します。ただし、これには時間が掛かることに注意してください。デフォルトは200です。" -#: ../../../discord/client.py:docstring of discord.client.Client.fetch_guilds:33 +#: ../../../discord/client.py:docstring of discord.client.Client.fetch_guilds:34 msgid "The default has been changed to 200." msgstr "デフォルトが200に変更されました。" -#: ../../../discord/client.py:docstring of discord.client.Client.fetch_guilds:35 +#: ../../../discord/client.py:docstring of discord.client.Client.fetch_guilds:36 msgid "Retrieves guilds before this date or object. If a datetime is provided, it is recommended to use a UTC aware datetime. If the datetime is naive, it is assumed to be local time." msgstr "渡された日付、またはギルドより前のギルドを取得します。日付を指定する場合、UTC aware datetimeを利用することを推奨します。naive datetimeである場合、これはローカル時間であるとみなされます。" -#: ../../../discord/client.py:docstring of discord.client.Client.fetch_guilds:39 +#: ../../../discord/client.py:docstring of discord.client.Client.fetch_guilds:40 msgid "Retrieve guilds after this date or object. If a datetime is provided, it is recommended to use a UTC aware datetime. If the datetime is naive, it is assumed to be local time." msgstr "渡された日付、またはギルドより後のギルドを取得します。日付を指定する場合、UTC aware datetimeを利用することを推奨します。naive datetimeである場合、これはローカル時間であるとみなされます。" #: ../../../discord/client.py:docstring of discord.client.Client.fetch_guilds:44 +msgid "Whether to include count information in the guilds. This fills the :attr:`.Guild.approximate_member_count` and :attr:`.Guild.approximate_presence_count` attributes without needing any privileged intents. Defaults to ``True``." +msgstr "ギルドにカウント情報を含めるかどうか。これを使うことで特権インテントがなくても :attr:`.Guild.approximate_member_count` と :attr:`.Guild.approximate_presence_count` 属性が設定されます。デフォルトは ``True`` です。" + +#: ../../../discord/client.py:docstring of discord.client.Client.fetch_guilds:51 msgid "Getting the guilds failed." msgstr "ギルドの取得に失敗した場合。" -#: ../../../discord/client.py:docstring of discord.client.Client.fetch_guilds:46 +#: ../../../discord/client.py:docstring of discord.client.Client.fetch_guilds:53 msgid ":class:`.Guild` -- The guild with the guild data parsed." msgstr ":class:`.Guild` -- データを解析したGuild。" @@ -925,9 +931,8 @@ msgid "Whether to include count information in the guild. This fills the :attr:` msgstr "ギルドにカウント情報を含めるかどうか。これを使うことで特権インテントがなくても :attr:`.Guild.approximate_member_count` と :attr:`.Guild.approximate_presence_count` 属性が設定されます。デフォルトは ``True`` です。" #: ../../../discord/client.py:docstring of discord.client.Client.fetch_guild:28 -#: ../../../discord/guild.py:docstring of discord.guild.Guild.fetch_member:16 -msgid "You do not have access to the guild." -msgstr "ギルドにアクセスする権限がない場合。" +msgid "The guild doesn't exist or you got no access to it." +msgstr "" #: ../../../discord/client.py:docstring of discord.client.Client.fetch_guild:29 msgid "Getting the guild failed." @@ -1178,7 +1183,7 @@ msgstr "あなたがリクエストしたユーザー。" #: ../../../discord/client.py:docstring of discord.client.Client.fetch_user:22 #: ../../../discord/abc.py:docstring of discord.abc.User:5 -#: ../../../discord/abc.py:docstring of discord.abc.Messageable:10 +#: ../../../discord/abc.py:docstring of discord.abc.Messageable:11 msgid ":class:`~discord.User`" msgstr ":class:`~discord.User`" @@ -1211,7 +1216,10 @@ msgid "Invalid Channel ID." msgstr "引数が無効なチャンネル IDである場合。" #: ../../../discord/client.py:docstring of discord.client.Client.fetch_channel:18 +#: ../../../discord/webhook/async_.py:docstring of discord.message.PartialMessage.fetch_thread:14 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage.fetch_thread:14 #: ../../../discord/guild.py:docstring of discord.guild.Guild.fetch_channel:14 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage.fetch_thread:14 msgid "You do not have permission to fetch this channel." msgstr "このチャンネルからメッセージを取得する権限がない場合。" @@ -1273,6 +1281,123 @@ msgstr "要求されたスタンプ。" msgid "Union[:class:`.StandardSticker`, :class:`.GuildSticker`]" msgstr "Union[:class:`.StandardSticker`, :class:`.GuildSticker`]" +#: ../../../discord/client.py:docstring of discord.client.Client.fetch_skus:3 +msgid "Retrieves the bot's available SKUs." +msgstr "" + +#: ../../../discord/client.py:docstring of discord.client.Client.fetch_skus:7 +#: ../../../discord/client.py:docstring of discord.client.Client.fetch_entitlement:11 +#: ../../../discord/client.py:docstring of discord.client.Client.entitlements:39 +#: ../../../discord/client.py:docstring of discord.client.Client.create_entitlement:14 +#: ../../../discord/sku.py:docstring of discord.sku.Entitlement.delete:5 +msgid "The application ID could not be found." +msgstr "" + +#: ../../../discord/client.py:docstring of discord.client.Client.fetch_skus:8 +msgid "Retrieving the SKUs failed." +msgstr "" + +#: ../../../discord/client.py:docstring of discord.client.Client.fetch_skus:10 +msgid "The bot's available SKUs." +msgstr "" + +#: ../../../discord/client.py:docstring of discord.client.Client.fetch_skus:11 +msgid "List[:class:`.SKU`]" +msgstr "" + +#: ../../../discord/client.py:docstring of discord.client.Client.fetch_entitlement:3 +msgid "Retrieves a :class:`.Entitlement` with the specified ID." +msgstr "" + +#: ../../../discord/client.py:docstring of discord.client.Client.fetch_entitlement:7 +msgid "The entitlement's ID to fetch from." +msgstr "" + +#: ../../../discord/client.py:docstring of discord.client.Client.fetch_entitlement:10 +msgid "An entitlement with this ID does not exist." +msgstr "" + +#: ../../../discord/client.py:docstring of discord.client.Client.fetch_entitlement:12 +msgid "Fetching the entitlement failed." +msgstr "" + +#: ../../../discord/client.py:docstring of discord.client.Client.fetch_entitlement:14 +msgid "The entitlement you requested." +msgstr "" + +#: ../../../discord/client.py:docstring of discord.client.Client.fetch_entitlement:15 +msgid ":class:`.Entitlement`" +msgstr "" + +#: ../../../discord/client.py:docstring of discord.client.Client.entitlements:1 +msgid "Retrieves an :term:`asynchronous iterator` of the :class:`.Entitlement` that applications has." +msgstr "" + +#: ../../../discord/client.py:docstring of discord.client.Client.entitlements:19 +msgid "The number of entitlements to retrieve. If ``None``, it retrieves every entitlement for this application. Note, however, that this would make it a slow operation. Defaults to ``100``." +msgstr "" + +#: ../../../discord/client.py:docstring of discord.client.Client.entitlements:22 +msgid "Retrieve entitlements before this date or entitlement. If a datetime is provided, it is recommended to use a UTC aware datetime. If the datetime is naive, it is assumed to be local time." +msgstr "" + +#: ../../../discord/client.py:docstring of discord.client.Client.entitlements:26 +msgid "Retrieve entitlements after this date or entitlement. If a datetime is provided, it is recommended to use a UTC aware datetime. If the datetime is naive, it is assumed to be local time." +msgstr "" + +#: ../../../discord/client.py:docstring of discord.client.Client.entitlements:30 +msgid "A list of SKUs to filter by." +msgstr "" + +#: ../../../discord/client.py:docstring of discord.client.Client.entitlements:32 +msgid "The user to filter by." +msgstr "" + +#: ../../../discord/client.py:docstring of discord.client.Client.entitlements:34 +msgid "The guild to filter by." +msgstr "" + +#: ../../../discord/client.py:docstring of discord.client.Client.entitlements:36 +msgid "Whether to exclude ended entitlements. Defaults to ``False``." +msgstr "" + +#: ../../../discord/client.py:docstring of discord.client.Client.entitlements:40 +msgid "Fetching the entitlements failed." +msgstr "" + +#: ../../../discord/client.py:docstring of discord.client.Client.entitlements:41 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.bans:33 +msgid "Both ``after`` and ``before`` were provided, as Discord does not support this type of pagination." +msgstr "``after`` と ``before`` の両方が渡された場合。Discordはこのタイプのページネーションをサポートしていません。" + +#: ../../../discord/client.py:docstring of discord.client.Client.entitlements:43 +msgid ":class:`.Entitlement` -- The entitlement with the application." +msgstr "" + +#: ../../../discord/client.py:docstring of discord.client.Client.create_entitlement:3 +msgid "Creates a test :class:`.Entitlement` for the application." +msgstr "" + +#: ../../../discord/client.py:docstring of discord.client.Client.create_entitlement:7 +msgid "The SKU to create the entitlement for." +msgstr "" + +#: ../../../discord/client.py:docstring of discord.client.Client.create_entitlement:9 +msgid "The ID of the owner." +msgstr "" + +#: ../../../discord/client.py:docstring of discord.client.Client.create_entitlement:11 +msgid "The type of the owner." +msgstr "" + +#: ../../../discord/client.py:docstring of discord.client.Client.create_entitlement:15 +msgid "The SKU or owner could not be found." +msgstr "" + +#: ../../../discord/client.py:docstring of discord.client.Client.create_entitlement:16 +msgid "Creating the entitlement failed." +msgstr "" + #: ../../../discord/client.py:docstring of discord.client.Client.fetch_premium_sticker_packs:3 msgid "Retrieves all available premium sticker packs." msgstr "利用可能なプレミアムスタンプパックをすべて取得します。" @@ -1317,6 +1442,32 @@ msgstr "作成されたチャンネル。" msgid ":class:`.DMChannel`" msgstr ":class:`.DMChannel`" +#: ../../../discord/client.py:docstring of discord.client.Client.add_dynamic_items:1 +msgid "Registers :class:`~discord.ui.DynamicItem` classes for persistent listening." +msgstr "" + +#: ../../../discord/client.py:docstring of discord.client.Client.add_dynamic_items:3 +#: ../../../discord/client.py:docstring of discord.client.Client.remove_dynamic_items:3 +msgid "This method accepts *class types* rather than instances." +msgstr "" + +#: ../../../discord/client.py:docstring of discord.client.Client.add_dynamic_items:7 +msgid "The classes of dynamic items to add." +msgstr "" + +#: ../../../discord/client.py:docstring of discord.client.Client.add_dynamic_items:10 +#: ../../../discord/client.py:docstring of discord.client.Client.remove_dynamic_items:10 +msgid "A class is not a subclass of :class:`~discord.ui.DynamicItem`." +msgstr "" + +#: ../../../discord/client.py:docstring of discord.client.Client.remove_dynamic_items:1 +msgid "Removes :class:`~discord.ui.DynamicItem` classes from persistent listening." +msgstr "" + +#: ../../../discord/client.py:docstring of discord.client.Client.remove_dynamic_items:7 +msgid "The classes of dynamic items to remove." +msgstr "" + #: ../../../discord/client.py:docstring of discord.client.Client.add_view:1 msgid "Registers a :class:`~discord.ui.View` for persistent listening." msgstr ":class:`~discord.ui.View` を永続的にインタラクションを受け取るために登録します。" @@ -1338,8 +1489,8 @@ msgid "A view was not passed." msgstr "ビューが渡されない場合。" #: ../../../discord/client.py:docstring of discord.client.Client.add_view:16 -msgid "The view is not persistent. A persistent view has no timeout and all their components have an explicitly provided custom_id." -msgstr "ビューが永続しない場合。永続するビューにはタイムアウトがなく、すべてのコンポーネントに明示的に渡された custom_id があります。" +msgid "The view is not persistent or is already finished. A persistent view has no timeout and all their components have an explicitly provided custom_id." +msgstr "ビューが永続しないか、すでに終了している場合。永続するビューにはタイムアウトがなく、すべてのコンポーネントに明示的に渡された custom_id があります。" #: ../../../discord/client.py:docstring of discord.Client.persistent_views:1 msgid "A sequence of persistent views added to the client." @@ -1381,6 +1532,15 @@ msgstr "シャードの起動時に利用するshard_idsのオプショナルな msgid "Optional[List[:class:`int`]]" msgstr "Optional[List[:class:`int`]]" +#: ../../../discord/shard.py:docstring of discord.shard.AutoShardedClient:37 +msgid "The maximum number of seconds to wait before timing out when launching a shard. Defaults to 180 seconds." +msgstr "" + +#: ../../../discord/shard.py:docstring of discord.shard.AutoShardedClient:42 +#: ../../../discord/message.py:docstring of discord.message.Attachment:99 +msgid "Optional[:class:`float`]" +msgstr "Optional[:class:`float`]" + #: ../../../discord/shard.py:docstring of discord.AutoShardedClient.latency:3 msgid "This operates similarly to :meth:`Client.latency` except it uses the average latency of every shard's latency. To get a list of shard latency, check the :attr:`latencies` property. Returns ``nan`` if there are no shards ready." msgstr "これは :meth:`Client.latency` と同様に機能しますが、すべてのシャードの平均待ち時間を使用する点が異なります。シャードの待ち時間のリストを取得するには :attr:`latencies` プロパティを参照してください。準備ができていない場合は ``nan`` を返します。" @@ -1461,9 +1621,9 @@ msgstr "アプリケーションID。" #: ../../../discord/appinfo.py:docstring of discord.appinfo.AppInfo:8 #: ../../../discord/appinfo.py:docstring of discord.appinfo.PartialAppInfo:9 +#: ../../../discord/appinfo.py:docstring of discord.appinfo.PartialAppInfo:54 #: ../../../discord/team.py:docstring of discord.team.Team:7 #: ../../../discord/team.py:docstring of discord.team.Team:19 -#: ../../../discord/team.py:docstring of discord.team.TeamMember:33 msgid ":class:`int`" msgstr ":class:`int`" @@ -1485,7 +1645,7 @@ msgid "The application owner." msgstr "アプリケーションの所有者。" #: ../../../discord/appinfo.py:docstring of discord.appinfo.AppInfo:20 -#: ../../api.rst:4148 +#: ../../api.rst:4492 #: ../../../discord/integrations.py:docstring of discord.integrations.Integration:45 #: ../../../discord/integrations.py:docstring of discord.integrations.BotIntegration:39 #: ../../../discord/integrations.py:docstring of discord.integrations.StreamIntegration:63 @@ -1512,7 +1672,7 @@ msgstr "ボットの招待がアプリケーション所有者に限定されて #: ../../../discord/appinfo.py:docstring of discord.appinfo.AppInfo:41 #: ../../../discord/appinfo.py:docstring of discord.appinfo.AppInfo:48 -#: ../../../discord/team.py:docstring of discord.team.TeamMember:45 +#: ../../../discord/team.py:docstring of discord.team.TeamMember:53 #: ../../../discord/team.py:docstring of discord.user.BaseUser.mentioned_in:7 #: ../../../discord/opus.py:docstring of discord.opus.is_loaded:7 msgid ":class:`bool`" @@ -1553,7 +1713,7 @@ msgstr "このアプリケーションがDiscord上で販売されているゲ #: ../../../discord/appinfo.py:docstring of discord.appinfo.AppInfo:99 #: ../../../discord/appinfo.py:docstring of discord.appinfo.AppInfo:107 #: ../../../discord/appinfo.py:docstring of discord.appinfo.AppInfo:140 -#: ../../../discord/appinfo.py:docstring of discord.appinfo.PartialAppInfo:40 +#: ../../../discord/appinfo.py:docstring of discord.appinfo.AppInfo:149 msgid "Optional[:class:`str`]" msgstr "Optional[:class:`str`]" @@ -1573,9 +1733,9 @@ msgstr "アプリケーションの機能を説明するタグのリスト。" #: ../../../discord/appinfo.py:docstring of discord.appinfo.AppInfo:115 #: ../../../discord/appinfo.py:docstring of discord.appinfo.AppInfo:123 +#: ../../../discord/appinfo.py:docstring of discord.appinfo.AppInfo:157 +#: ../../../discord/appinfo.py:docstring of discord.appinfo.PartialAppInfo:62 #: ../../../discord/appinfo.py:docstring of discord.appinfo.AppInstallParams:10 -#: ../../../discord/guild.py:docstring of discord.guild.Guild:139 -#: ../../../discord/activity.py:docstring of discord.Spotify.artists:3 msgid "List[:class:`str`]" msgstr "List[:class:`str`]" @@ -1592,9 +1752,20 @@ msgid "Optional[:class:`AppInstallParams`]" msgstr "Optional[:class:`AppInstallParams`]" #: ../../../discord/appinfo.py:docstring of discord.appinfo.AppInfo:135 +#: ../../../discord/appinfo.py:docstring of discord.appinfo.PartialAppInfo:75 msgid "The application's connection verification URL which will render the application as a verification method in the guild's role verification configuration." msgstr "アプリケーションをギルドのロール紐づけ設定にて紐づけ方法として扱うようにするための、アプリケーションの接続確認URL。" +#: ../../../discord/appinfo.py:docstring of discord.appinfo.AppInfo:144 +#: ../../../discord/appinfo.py:docstring of discord.appinfo.PartialAppInfo:66 +msgid "The interactions endpoint url of the application to receive interactions over this endpoint rather than over the gateway, if configured." +msgstr "設定されている場合、ゲートウェイではなくエンドポイントからインタラクションを受け取るアプリケーションの、インタラクションエンドポイントのURI。" + +#: ../../../discord/appinfo.py:docstring of discord.appinfo.AppInfo:153 +#: ../../../discord/appinfo.py:docstring of discord.appinfo.PartialAppInfo:58 +msgid "A list of authentication redirect URIs." +msgstr "認証リダイレクトURIのリスト。" + #: ../../../discord/appinfo.py:docstring of discord.AppInfo.icon:1 #: ../../../discord/appinfo.py:docstring of discord.PartialAppInfo.icon:1 msgid "Retrieves the application's icon asset, if any." @@ -1603,8 +1774,8 @@ msgstr "存在する場合は、アプリケーションのアイコンアセッ #: ../../../discord/appinfo.py:docstring of discord.AppInfo.icon:3 #: ../../../discord/appinfo.py:docstring of discord.AppInfo.cover_image:5 #: ../../../discord/appinfo.py:docstring of discord.PartialAppInfo.icon:3 +#: ../../../discord/appinfo.py:docstring of discord.PartialAppInfo.cover_image:7 #: ../../../discord/team.py:docstring of discord.Team.icon:3 -#: ../../../discord/role.py:docstring of discord.Role.icon:11 msgid "Optional[:class:`.Asset`]" msgstr "Optional[:class:`.Asset`]" @@ -1613,6 +1784,7 @@ msgid "Retrieves the cover image on a store embed, if any." msgstr "存在する場合は、ストアの埋め込みのカバー画像を取得します。" #: ../../../discord/appinfo.py:docstring of discord.AppInfo.cover_image:3 +#: ../../../discord/appinfo.py:docstring of discord.PartialAppInfo.cover_image:3 msgid "This is only available if the application is a game sold on Discord." msgstr "これはアプリケーションがDiscordで販売されているゲームの場合にのみ利用できます。" @@ -1638,6 +1810,74 @@ msgstr "アプリケーションのフラグ。" msgid ":class:`ApplicationFlags`" msgstr ":class:`ApplicationFlags`" +#: ../../../discord/appinfo.py:docstring of discord.appinfo.AppInfo.edit:3 +msgid "Edits the application info." +msgstr "" + +#: ../../../discord/appinfo.py:docstring of discord.appinfo.AppInfo.edit:7 +msgid "The new custom authorization URL for the application. Can be ``None`` to remove the URL." +msgstr "" + +#: ../../../discord/appinfo.py:docstring of discord.appinfo.AppInfo.edit:9 +msgid "The new application description. Can be ``None`` to remove the description." +msgstr "" + +#: ../../../discord/appinfo.py:docstring of discord.appinfo.AppInfo.edit:11 +msgid "The new application’s connection verification URL which will render the application as a verification method in the guild’s role verification configuration. Can be ``None`` to remove the URL." +msgstr "" + +#: ../../../discord/appinfo.py:docstring of discord.appinfo.AppInfo.edit:14 +msgid "The new list of :ddocs:`OAuth2 scopes ` of the :attr:`~install_params`. Can be ``None`` to remove the scopes." +msgstr "" + +#: ../../../discord/appinfo.py:docstring of discord.appinfo.AppInfo.edit:17 +msgid "The new permissions of the :attr:`~install_params`. Can be ``None`` to remove the permissions." +msgstr "" + +#: ../../../discord/appinfo.py:docstring of discord.appinfo.AppInfo.edit:19 +msgid "The new application’s flags. Only limited intent flags (:attr:`~ApplicationFlags.gateway_presence_limited`, :attr:`~ApplicationFlags.gateway_guild_members_limited`, :attr:`~ApplicationFlags.gateway_message_content_limited`) can be edited. Can be ``None`` to remove the flags." +msgstr "" + +#: ../../../discord/appinfo.py:docstring of discord.appinfo.AppInfo.edit:25 +msgid "Editing the limited intent flags leads to the termination of the bot." +msgstr "" + +#: ../../../discord/appinfo.py:docstring of discord.appinfo.AppInfo.edit:27 +msgid "The new application’s icon as a :term:`py:bytes-like object`. Can be ``None`` to remove the icon." +msgstr "" + +#: ../../../discord/appinfo.py:docstring of discord.appinfo.AppInfo.edit:29 +msgid "The new application’s cover image as a :term:`py:bytes-like object` on a store embed. The cover image is only available if the application is a game sold on Discord. Can be ``None`` to remove the image." +msgstr "" + +#: ../../../discord/appinfo.py:docstring of discord.appinfo.AppInfo.edit:33 +msgid "The new interactions endpoint url of the application to receive interactions over this endpoint rather than over the gateway. Can be ``None`` to remove the URL." +msgstr "" + +#: ../../../discord/appinfo.py:docstring of discord.appinfo.AppInfo.edit:36 +msgid "The new list of tags describing the functionality of the application. Can be ``None`` to remove the tags." +msgstr "" + +#: ../../../discord/appinfo.py:docstring of discord.appinfo.AppInfo.edit:38 +msgid "The reason for editing the application. Shows up on the audit log." +msgstr "" + +#: ../../../discord/appinfo.py:docstring of discord.appinfo.AppInfo.edit:41 +msgid "Editing the application failed" +msgstr "" + +#: ../../../discord/appinfo.py:docstring of discord.appinfo.AppInfo.edit:42 +msgid "The image format passed in to ``icon`` or ``cover_image`` is invalid. This is also raised when ``install_params_scopes`` and ``install_params_permissions`` are incompatible with each other." +msgstr "" + +#: ../../../discord/appinfo.py:docstring of discord.appinfo.AppInfo.edit:44 +msgid "The newly updated application info." +msgstr "" + +#: ../../../discord/appinfo.py:docstring of discord.appinfo.AppInfo.edit:45 +msgid ":class:`AppInfo`" +msgstr "" + #: ../../api.rst:68 msgid "PartialAppInfo" msgstr "PartialAppInfo" @@ -1646,6 +1886,14 @@ msgstr "PartialAppInfo" msgid "Represents a partial AppInfo given by :func:`~discord.abc.GuildChannel.create_invite`" msgstr ":func:`~discord.abc.GuildChannel.create_invite` により与えられた部分的なAppInfo。" +#: ../../../discord/appinfo.py:docstring of discord.appinfo.PartialAppInfo:50 +msgid "The approximate count of the guilds the bot was added to." +msgstr "ボットが追加されたギルドのおおよその数。" + +#: ../../../discord/appinfo.py:docstring of discord.PartialAppInfo.cover_image:1 +msgid "Retrieves the cover image of the application's default rich presence." +msgstr "存在する場合は、アプリケーションの既定のリッチプレゼンスのカバー画像を取得します。" + #: ../../api.rst:76 msgid "AppInstallParams" msgstr "AppInstallParams" @@ -1663,8 +1911,8 @@ msgid "The permissions to give to application in the guild." msgstr "ギルドに追加するアプリケーションに与える権限。" #: ../../../discord/appinfo.py:docstring of discord.appinfo.AppInstallParams:16 -#: ../../api.rst:3598 -#: ../../api.rst:3687 +#: ../../api.rst:3868 +#: ../../api.rst:3957 #: ../../../discord/member.py:docstring of discord.Member.guild_permissions:14 #: ../../../discord/role.py:docstring of discord.Role.permissions:3 msgid ":class:`Permissions`" @@ -1731,8 +1979,8 @@ msgid "Return the team member's hash." msgstr "チームメンバーのハッシュ値を返します。" #: ../../../discord/team.py:docstring of discord.team.TeamMember:19 -msgid "Returns the team member's name with discriminator." -msgstr "チームメンバーの名前とタグを返します。" +msgid "Returns the team member's handle (e.g. ``name`` or ``name#discriminator``)." +msgstr "チームメンバーのハンドル(例えば ``name`` や ``name#discriminator`` など)を返します。" #: ../../../discord/team.py:docstring of discord.team.TeamMember:25 msgid "The team member's username." @@ -1743,31 +1991,43 @@ msgid "The team member's unique ID." msgstr "チームメンバーの一意のID。" #: ../../../discord/team.py:docstring of discord.team.TeamMember:37 -msgid "The team member's discriminator. This is given when the username has conflicts." -msgstr "チームメンバーのタグ。これはユーザー名が重複している場合に付与されます。" +msgid "The team member's discriminator. This is a legacy concept that is no longer used." +msgstr "チームメンバーのタグ。これは、現在は使用されていない、過去の遺物です。" #: ../../../discord/team.py:docstring of discord.team.TeamMember:43 -#: ../../../discord/user.py:docstring of discord.user.ClientUser:41 -#: ../../../discord/user.py:docstring of discord.user.User:41 +msgid "The team member's global nickname, taking precedence over the username in display." +msgstr "チームメンバーのグローバルの表示名。ユーザー名より優先して表示されます。" + +#: ../../../discord/team.py:docstring of discord.team.TeamMember:51 +#: ../../../discord/user.py:docstring of discord.user.ClientUser:49 +#: ../../../discord/user.py:docstring of discord.user.User:49 msgid "Specifies if the user is a bot account." msgstr "ユーザーがBotアカウントであるかを表します。" -#: ../../../discord/team.py:docstring of discord.team.TeamMember:49 +#: ../../../discord/team.py:docstring of discord.team.TeamMember:57 msgid "The team that the member is from." msgstr "メンバーの出身チーム。" -#: ../../../discord/team.py:docstring of discord.team.TeamMember:51 +#: ../../../discord/team.py:docstring of discord.team.TeamMember:59 msgid ":class:`Team`" msgstr ":class:`Team`" -#: ../../../discord/team.py:docstring of discord.team.TeamMember:55 +#: ../../../discord/team.py:docstring of discord.team.TeamMember:63 msgid "The membership state of the member (e.g. invited or accepted)" -msgstr "メンバーの参加状態((例:招待されたか、承認されたか)" +msgstr "メンバーの参加状態 (例:招待されたか、承認されたか)" -#: ../../../discord/team.py:docstring of discord.team.TeamMember:57 +#: ../../../discord/team.py:docstring of discord.team.TeamMember:65 msgid ":class:`TeamMembershipState`" msgstr ":class:`TeamMembershipState`" +#: ../../../discord/team.py:docstring of discord.team.TeamMember:69 +msgid "The role of the member within the team." +msgstr "" + +#: ../../../discord/team.py:docstring of discord.team.TeamMember:73 +msgid ":class:`TeamMemberRole`" +msgstr "" + #: ../../../discord/team.py:docstring of discord.TeamMember.accent_color:1 #: ../../../discord/user.py:docstring of discord.ClientUser.accent_color:1 #: ../../../discord/user.py:docstring of discord.User.accent_color:1 @@ -1839,13 +2099,35 @@ msgid "If the user has not uploaded a global avatar, ``None`` is returned. If yo msgstr "ユーザーがグローバルのアバターをアップロードしていない場合は、 ``None`` が返されます。ユーザーが表示しているアバターを取得したい場合は、 :attr:`display_avatar` を検討してください。" #: ../../../discord/team.py:docstring of discord.TeamMember.avatar:6 +#: ../../../discord/team.py:docstring of discord.TeamMember.avatar_decoration:7 #: ../../../discord/team.py:docstring of discord.TeamMember.banner:9 #: ../../../discord/webhook/async_.py:docstring of discord.Webhook.avatar:6 #: ../../../discord/webhook/sync.py:docstring of discord.SyncWebhook.avatar:6 -#: ../../../discord/user.py:docstring of discord.ClientUser.avatar:6 msgid "Optional[:class:`Asset`]" msgstr "Optional[:class:`Asset`]" +#: ../../../discord/team.py:docstring of discord.TeamMember.avatar_decoration:1 +#: ../../../discord/user.py:docstring of discord.ClientUser.avatar_decoration:1 +#: ../../../discord/user.py:docstring of discord.User.avatar_decoration:1 +#: ../../../discord/widget.py:docstring of discord.WidgetMember.avatar_decoration:1 +msgid "Returns an :class:`Asset` for the avatar decoration the user has." +msgstr "" + +#: ../../../discord/team.py:docstring of discord.TeamMember.avatar_decoration:3 +#: ../../../discord/team.py:docstring of discord.TeamMember.avatar_decoration_sku_id:3 +#: ../../../discord/user.py:docstring of discord.ClientUser.avatar_decoration:3 +#: ../../../discord/user.py:docstring of discord.ClientUser.avatar_decoration_sku_id:3 +#: ../../../discord/user.py:docstring of discord.User.avatar_decoration:3 +msgid "If the user has not set an avatar decoration, ``None`` is returned." +msgstr "" + +#: ../../../discord/team.py:docstring of discord.TeamMember.avatar_decoration_sku_id:1 +#: ../../../discord/user.py:docstring of discord.ClientUser.avatar_decoration_sku_id:1 +#: ../../../discord/user.py:docstring of discord.User.avatar_decoration_sku_id:1 +#: ../../../discord/widget.py:docstring of discord.WidgetMember.avatar_decoration_sku_id:1 +msgid "Returns the SKU ID of the avatar decoration the user has." +msgstr "" + #: ../../../discord/team.py:docstring of discord.TeamMember.banner:1 #: ../../../discord/user.py:docstring of discord.ClientUser.banner:1 #: ../../../discord/user.py:docstring of discord.User.banner:1 @@ -1870,7 +2152,7 @@ msgstr ":attr:`colour` という名前のエイリアスが存在します。" #: ../../../discord/team.py:docstring of discord.TeamMember.color:6 #: ../../../discord/team.py:docstring of discord.TeamMember.colour:6 -#: ../../api.rst:3607 +#: ../../api.rst:3877 #: ../../../discord/user.py:docstring of discord.ClientUser.color:6 #: ../../../discord/user.py:docstring of discord.ClientUser.colour:6 msgid ":class:`Colour`" @@ -1918,14 +2200,14 @@ msgstr ":class:`datetime.datetime`" #: ../../../discord/user.py:docstring of discord.ClientUser.default_avatar:1 #: ../../../discord/user.py:docstring of discord.User.default_avatar:1 #: ../../../discord/widget.py:docstring of discord.WidgetMember.default_avatar:1 -msgid "Returns the default avatar for a given user. This is calculated by the user's discriminator." -msgstr "ユーザーのデフォルトのアバターを返します。これはユーザーのタグから算出されます。" +msgid "Returns the default avatar for a given user." +msgstr "ユーザーの既定のアバターを返します。" #: ../../../discord/team.py:docstring of discord.TeamMember.default_avatar:3 #: ../../../discord/team.py:docstring of discord.TeamMember.display_avatar:7 -#: ../../api.rst:3381 -#: ../../api.rst:3387 -#: ../../api.rst:3393 +#: ../../api.rst:3651 +#: ../../api.rst:3657 +#: ../../api.rst:3663 msgid ":class:`Asset`" msgstr ":class:`Asset`" @@ -1957,8 +2239,8 @@ msgstr "ユーザーの表示名を返します。" #: ../../../discord/user.py:docstring of discord.ClientUser.display_name:3 #: ../../../discord/user.py:docstring of discord.User.display_name:3 #: ../../../discord/member.py:docstring of discord.Member.display_name:3 -msgid "For regular users this is just their username, but if they have a guild specific nickname then that is returned instead." -msgstr "通常であれば、これはユーザー名がそのまま返りますが、ギルドにてニックネームを設定している場合は、代替としてニックネームが返ります。" +msgid "For regular users this is just their global name or their username, but if they have a guild specific nickname then that is returned instead." +msgstr "通常であれば、これはグローバル表示名またはユーザー名がそのまま返りますが、ギルドにてニックネームを設定している場合は、代替としてニックネームが返ります。" #: ../../../discord/team.py:docstring of discord.TeamMember.mention:1 #: ../../../discord/abc.py:docstring of discord.abc.User.mention:1 @@ -2052,9 +2334,9 @@ msgstr "接続しているギルド。" #: ../../../discord/voice_client.py:docstring of discord.VoiceClient.guild:3 #: ../../../discord/audit_logs.py:docstring of discord.audit_logs.AuditLogEntry:53 +#: ../../api.rst:3645 #: ../../../discord/automod.py:docstring of discord.automod.AutoModRule:15 #: ../../../discord/automod.py:docstring of discord.AutoModAction.guild:3 -#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:103 msgid ":class:`Guild`" msgstr ":class:`Guild`" @@ -2063,7 +2345,7 @@ msgid "The user connected to voice (i.e. ourselves)." msgstr "ボイスチャンネルに接続しているユーザー。(つまり、自分自身)" #: ../../../discord/voice_client.py:docstring of discord.VoiceClient.user:3 -#: ../../../discord/user.py:docstring of discord.user.ClientUser.edit:31 +#: ../../../discord/user.py:docstring of discord.user.ClientUser.edit:37 #: ../../../discord/channel.py:docstring of discord.channel.DMChannel:33 #: ../../../discord/channel.py:docstring of discord.channel.GroupChannel:31 msgid ":class:`ClientUser`" @@ -2093,6 +2375,14 @@ msgstr "別のボイスチャンネルへ移動させます。" msgid "The channel to move to. Must be a voice channel." msgstr "移動先のチャンネル。ボイスチャンネルである必要があります。" +#: ../../../discord/voice_client.py:docstring of discord.voice_client.VoiceClient.move_to:7 +msgid "How long to wait for the move to complete." +msgstr "" + +#: ../../../discord/voice_client.py:docstring of discord.voice_client.VoiceClient.move_to:12 +msgid "The move did not complete in time, but may still be ongoing." +msgstr "" + #: ../../../discord/voice_client.py:docstring of discord.voice_client.VoiceClient.is_connected:1 msgid "Indicates if the voice client is connected to voice." msgstr "ボイスチャンネルに接続しているかどうか。" @@ -2110,29 +2400,65 @@ msgid "If an error happens while the audio player is running, the exception is c msgstr "オーディオプレーヤーの実行中にエラーが発生した場合、例外が捕捉され、オーディオプレーヤーが停止します。 コールバックが渡されない場合、捕捉された例外はライブラリロガーを用いて記録されます。" #: ../../../discord/voice_client.py:docstring of discord.voice_client.VoiceClient.play:10 +msgid "Extra parameters may be passed to the internal opus encoder if a PCM based source is used. Otherwise, they are ignored." +msgstr "" + +#: ../../../discord/voice_client.py:docstring of discord.voice_client.VoiceClient.play:13 msgid "Instead of writing to ``sys.stderr``, the library's logger is used." msgstr "``sys.stderr`` に出力するのではなく、ライブラリロガーが使用されるようになりました。" -#: ../../../discord/voice_client.py:docstring of discord.voice_client.VoiceClient.play:13 +#: ../../../discord/voice_client.py:docstring of discord.voice_client.VoiceClient.play:16 +msgid "Added encoder parameters as keyword arguments." +msgstr "" + +#: ../../../discord/voice_client.py:docstring of discord.voice_client.VoiceClient.play:19 msgid "The audio source we're reading from." msgstr "読み込むオーディオソース。" -#: ../../../discord/voice_client.py:docstring of discord.voice_client.VoiceClient.play:15 +#: ../../../discord/voice_client.py:docstring of discord.voice_client.VoiceClient.play:21 msgid "The finalizer that is called after the stream is exhausted. This function must have a single parameter, ``error``, that denotes an optional exception that was raised during playing." msgstr "ファイナライザーはストリームが空になると呼び出されます。この関数には再生中に発生したオプションの例外を表す一つのパラメータ ``error`` が必要です。" -#: ../../../discord/voice_client.py:docstring of discord.voice_client.VoiceClient.play:20 +#: ../../../discord/voice_client.py:docstring of discord.voice_client.VoiceClient.play:25 +msgid "Configures the encoder's intended application. Can be one of: ``'audio'``, ``'voip'``, ``'lowdelay'``. Defaults to ``'audio'``." +msgstr "" + +#: ../../../discord/voice_client.py:docstring of discord.voice_client.VoiceClient.play:29 +msgid "Configures the bitrate in the encoder. Can be between ``16`` and ``512``. Defaults to ``128``." +msgstr "" + +#: ../../../discord/voice_client.py:docstring of discord.voice_client.VoiceClient.play:32 +msgid "Configures the encoder's use of inband forward error correction. Defaults to ``True``." +msgstr "" + +#: ../../../discord/voice_client.py:docstring of discord.voice_client.VoiceClient.play:35 +msgid "Configures the encoder's expected packet loss percentage. Requires FEC. Defaults to ``0.15``." +msgstr "" + +#: ../../../discord/voice_client.py:docstring of discord.voice_client.VoiceClient.play:38 +msgid "Configures the encoder's bandpass. Can be one of: ``'narrow'``, ``'medium'``, ``'wide'``, ``'superwide'``, ``'full'``. Defaults to ``'full'``." +msgstr "" + +#: ../../../discord/voice_client.py:docstring of discord.voice_client.VoiceClient.play:42 +msgid "Configures the type of signal being encoded. Can be one of: ``'auto'``, ``'voice'``, ``'music'``. Defaults to ``'auto'``." +msgstr "" + +#: ../../../discord/voice_client.py:docstring of discord.voice_client.VoiceClient.play:47 msgid "Already playing audio or not connected." msgstr "既にオーディオを再生しているか、接続されていない場合。" -#: ../../../discord/voice_client.py:docstring of discord.voice_client.VoiceClient.play:21 +#: ../../../discord/voice_client.py:docstring of discord.voice_client.VoiceClient.play:48 msgid "Source is not a :class:`AudioSource` or after is not a callable." msgstr "ソースが :class:`AudioSource` でないか、afterが呼び出し可能でない場合。" -#: ../../../discord/voice_client.py:docstring of discord.voice_client.VoiceClient.play:22 +#: ../../../discord/voice_client.py:docstring of discord.voice_client.VoiceClient.play:49 msgid "Source is not opus encoded and opus is not loaded." msgstr "ソースがopusエンコードされておらず、opusが読み込まれていない場合。" +#: ../../../discord/voice_client.py:docstring of discord.voice_client.VoiceClient.play:50 +msgid "An improper value was passed as an encoder parameter." +msgstr "" + #: ../../../discord/voice_client.py:docstring of discord.voice_client.VoiceClient.is_playing:1 msgid "Indicates if we're currently playing audio." msgstr "現在オーディオを再生しているか。" @@ -2221,7 +2547,11 @@ msgstr "接続されているボイスチャンネル。" msgid "An abstract method that is called when the client's voice state has changed. This corresponds to ``VOICE_STATE_UPDATE``." msgstr "クライアントの音声状態が変更された際に呼び出される抽象メソッドです。これは ``VOICE_STATE_UPDATE`` と対応しています。" -#: ../../../discord/voice_client.py:docstring of discord.voice_client.VoiceProtocol.on_voice_state_update:6 +#: ../../../discord/voice_client.py:docstring of discord.voice_client.VoiceProtocol.on_voice_state_update:8 +msgid "This method is not the same as the event. See: :func:`on_voice_state_update`" +msgstr "" + +#: ../../../discord/voice_client.py:docstring of discord.voice_client.VoiceProtocol.on_voice_state_update:10 msgid "The raw :ddocs:`voice state payload `." msgstr "生の :ddocs:`ボイスステートペイロード ` 。" @@ -2430,28 +2760,33 @@ msgstr "ffmpegが受け取り、PCMバイトへ変換する入力。 ``pipe`` msgid "The executable name (and path) to use. Defaults to ``ffmpeg``." msgstr "使用する実行可能ファイルの名前 (およびパス)。デフォルトでは ``ffmpeg`` です。" -#: ../../../discord/player.py:docstring of discord.player.FFmpegPCMAudio:16 -#: ../../../discord/player.py:docstring of discord.player.FFmpegOpusAudio:41 +#: ../../../discord/player.py:docstring of discord.player.FFmpegPCMAudio:18 +#: ../../../discord/player.py:docstring of discord.player.FFmpegOpusAudio:43 +msgid "Since this class spawns a subprocess, care should be taken to not pass in an arbitrary executable name when using this parameter." +msgstr "" + +#: ../../../discord/player.py:docstring of discord.player.FFmpegPCMAudio:21 +#: ../../../discord/player.py:docstring of discord.player.FFmpegOpusAudio:46 msgid "If ``True``, denotes that ``source`` parameter will be passed to the stdin of ffmpeg. Defaults to ``False``." msgstr "``True`` の場合、 ``source`` パラメータがffmpegの標準入力に渡されます。デフォルトでは ``False`` です。" -#: ../../../discord/player.py:docstring of discord.player.FFmpegPCMAudio:19 -#: ../../../discord/player.py:docstring of discord.player.FFmpegOpusAudio:44 -msgid "A file-like object to pass to the Popen constructor. Could also be an instance of ``subprocess.PIPE``." -msgstr "Popenのコンストラクタに渡すファイルライクオブジェクト。 ``subprocess.PIPE`` のようなインスタンスにすることも可能です。" +#: ../../../discord/player.py:docstring of discord.player.FFmpegPCMAudio:24 +#: ../../../discord/player.py:docstring of discord.player.FFmpegOpusAudio:49 +msgid "A file-like object to pass to the Popen constructor." +msgstr "" -#: ../../../discord/player.py:docstring of discord.player.FFmpegPCMAudio:22 -#: ../../../discord/player.py:docstring of discord.player.FFmpegOpusAudio:47 +#: ../../../discord/player.py:docstring of discord.player.FFmpegPCMAudio:26 +#: ../../../discord/player.py:docstring of discord.player.FFmpegOpusAudio:51 msgid "Extra command line arguments to pass to ffmpeg before the ``-i`` flag." msgstr "``-i`` フラグのまえにffmepgに渡す追加のコマンドライン引数。" -#: ../../../discord/player.py:docstring of discord.player.FFmpegPCMAudio:24 -#: ../../../discord/player.py:docstring of discord.player.FFmpegOpusAudio:49 +#: ../../../discord/player.py:docstring of discord.player.FFmpegPCMAudio:28 +#: ../../../discord/player.py:docstring of discord.player.FFmpegOpusAudio:53 msgid "Extra command line arguments to pass to ffmpeg after the ``-i`` flag." msgstr "``-i`` フラグのあとにffmepgに渡す追加のコマンドライン引数。" -#: ../../../discord/player.py:docstring of discord.player.FFmpegPCMAudio:27 -#: ../../../discord/player.py:docstring of discord.player.FFmpegOpusAudio:52 +#: ../../../discord/player.py:docstring of discord.player.FFmpegPCMAudio:31 +#: ../../../discord/player.py:docstring of discord.player.FFmpegOpusAudio:56 msgid "The subprocess failed to be created." msgstr "サブプロセスを作成できなかった場合。" @@ -2663,9 +2998,9 @@ msgstr "アプリケーションコマンドの権限が更新されたときに #: ../../api.rst:215 #: ../../api.rst:370 -#: ../../api.rst:735 -#: ../../api.rst:793 -#: ../../api.rst:978 +#: ../../api.rst:777 +#: ../../api.rst:835 +#: ../../api.rst:1020 msgid "The raw event payload data." msgstr "生のイベントペイロードデータ。" @@ -2682,7 +3017,7 @@ msgid "The command that completed successfully" msgstr "正常に実行されたコマンド。" #: ../../api.rst:231 -#: ../../api.rst:4072 +#: ../../api.rst:4416 msgid "AutoMod" msgstr "AutoMod" @@ -2743,8 +3078,8 @@ msgstr "ギルドは :attr:`~abc.GuildChannel.guild` で取得できます。" #: ../../api.rst:291 #: ../../api.rst:300 #: ../../api.rst:311 -#: ../../api.rst:545 -#: ../../api.rst:554 +#: ../../api.rst:587 +#: ../../api.rst:596 msgid "This requires :attr:`Intents.guilds` to be enabled." msgstr ":attr:`Intents.guilds` を有効にする必要があります。" @@ -2782,10 +3117,10 @@ msgid "Called whenever a private group DM is updated. e.g. changed name or topic msgstr "プライベートグループDMが更新されたとき呼び出されます。 例: 名前やトピックの変更。" #: ../../api.rst:322 -#: ../../api.rst:884 -#: ../../api.rst:918 -#: ../../api.rst:935 -#: ../../api.rst:952 +#: ../../api.rst:926 +#: ../../api.rst:960 +#: ../../api.rst:977 +#: ../../api.rst:994 msgid "This requires :attr:`Intents.messages` to be enabled." msgstr ":attr:`Intents.messages` を有効にする必要があります。" @@ -2961,192 +3296,232 @@ msgstr "これはクライアントのWebSocketから受信したメッセージ msgid "The message that is about to be passed on to the WebSocket library. It can be :class:`bytes` to denote a binary message or :class:`str` to denote a regular text message." msgstr "WebSocketライブラリから渡されるメッセージ。バイナリメッセージの場合は :class:`bytes` 、通常のメッセージの場合は :class:`str` です。" -#: ../../api.rst:499 +#: ../../api.rst:500 +msgid "Entitlements" +msgstr "" + +#: ../../api.rst:504 +msgid "Called when a user subscribes to a SKU." +msgstr "" + +#: ../../api.rst:508 +msgid "The entitlement that was created." +msgstr "" + +#: ../../api.rst:513 +msgid "Called when a user updates their subscription to a SKU. This is usually called when the user renews or cancels their subscription." +msgstr "" + +#: ../../api.rst:518 +msgid "The entitlement that was updated." +msgstr "" + +#: ../../api.rst:523 +msgid "Called when a users subscription to a SKU is cancelled. This is typically only called when:" +msgstr "" + +#: ../../api.rst:525 +msgid "Discord issues a refund for the subscription." +msgstr "" + +#: ../../api.rst:526 +msgid "Discord removes an entitlement from a user." +msgstr "" + +#: ../../api.rst:530 +msgid "This event won't be called if the user cancels their subscription manually, instead :func:`on_entitlement_update` will be called with :attr:`Entitlement.ends_at` set to the end of the current billing period." +msgstr "" + +#: ../../api.rst:536 +msgid "The entitlement that was deleted." +msgstr "" + +#: ../../api.rst:541 msgid "Gateway" msgstr "Gateway" -#: ../../api.rst:503 +#: ../../api.rst:545 msgid "Called when the client is done preparing the data received from Discord. Usually after login is successful and the :attr:`Client.guilds` and co. are filled up." msgstr "クライアントがDiscordから受信したデータの準備を完了した際に呼び出されます。通常はログインが成功したあと、 :attr:`Client.guilds` とそれに関連するものの準備が完了したときです。" -#: ../../api.rst:508 +#: ../../api.rst:550 msgid "This function is not guaranteed to be the first event called. Likewise, this function is **not** guaranteed to only be called once. This library implements reconnection logic and thus will end up calling this event whenever a RESUME request fails." msgstr "このイベントは、最初に呼び出されるイベントとは限りません。同時に、このイベントは **一度だけ呼ばれるという保証もできません** 。このライブラリは、再接続ロジックを実装しているためリジューム要求が失敗するたびにこのイベントが呼び出されることになります。" -#: ../../api.rst:515 +#: ../../api.rst:557 msgid "Called when the client has resumed a session." msgstr "クライアントがセッションを再開したときに呼び出されます。" -#: ../../api.rst:519 +#: ../../api.rst:561 msgid "Similar to :func:`on_ready` except used by :class:`AutoShardedClient` to denote when a particular shard ID has become ready." msgstr "特定の Shard IDが準備完了になったかを確認するために :class:`AutoShardedClient` で使用される以外は :func:`on_ready` とほとんど同じです。" -#: ../../api.rst:522 +#: ../../api.rst:564 msgid "The shard ID that is ready." msgstr "準備が完了したShard ID。" -#: ../../api.rst:528 +#: ../../api.rst:570 msgid "Similar to :func:`on_resumed` except used by :class:`AutoShardedClient` to denote when a particular shard ID has resumed a session." msgstr "特定のシャードIDを持つシャードがセッションを再開したかどうかを確認するために :class:`AutoShardedClient` で使用されることを除けば :func:`on_resumed` とほとんど同じです。" -#: ../../api.rst:533 +#: ../../api.rst:575 msgid "The shard ID that has resumed." msgstr "セッションが再開したシャードのID。" -#: ../../api.rst:537 +#: ../../api.rst:579 msgid "Guilds" msgstr "Guilds" -#: ../../api.rst:542 +#: ../../api.rst:584 msgid "Called when a guild becomes available or unavailable. The guild must have existed in the :attr:`Client.guilds` cache." msgstr "ギルドが利用可能・不可能になったときに呼び出されます。ギルドは :attr:`Client.guilds` キャッシュに存在していないといけません。" -#: ../../api.rst:547 +#: ../../api.rst:589 msgid "The :class:`Guild` that has changed availability." msgstr "利用状況が変わった :class:`Guild` 。" -#: ../../api.rst:551 +#: ../../api.rst:593 msgid "Called when a :class:`Guild` is either created by the :class:`Client` or when the :class:`Client` joins a guild." msgstr ":class:`Client` によって :class:`Guild` が作成された。または :class:`Client` がギルドに参加したときに呼び出されます。" -#: ../../api.rst:556 +#: ../../api.rst:598 msgid "The guild that was joined." msgstr "参加したギルド。" -#: ../../api.rst:561 +#: ../../api.rst:603 msgid "Called when a :class:`Guild` is removed from the :class:`Client`." msgstr ":class:`Client` が :class:`Guild` から削除されたときに呼び出されます。" -#: ../../api.rst:563 +#: ../../api.rst:605 msgid "This happens through, but not limited to, these circumstances:" msgstr "これは以下の状況時に呼び出されますが、これに限ったものではありません:" -#: ../../api.rst:565 +#: ../../api.rst:607 msgid "The client got banned." msgstr "クライアントがBANされた。" -#: ../../api.rst:566 +#: ../../api.rst:608 msgid "The client got kicked." msgstr "クライアントがキックされた。" -#: ../../api.rst:567 +#: ../../api.rst:609 msgid "The client left the guild." msgstr "クライアントがギルドから脱退した。" -#: ../../api.rst:568 +#: ../../api.rst:610 msgid "The client or the guild owner deleted the guild." msgstr "クライアント、またはギルドオーナーがギルドを削除した。" -#: ../../api.rst:570 +#: ../../api.rst:612 msgid "In order for this event to be invoked then the :class:`Client` must have been part of the guild to begin with. (i.e. it is part of :attr:`Client.guilds`)" msgstr "このイベントが呼び出されるためには、 :class:`Client` がギルドに参加している必要があります。(つまり、 :attr:`Client.guilds` にギルドが存在しなければならない)" -#: ../../api.rst:575 +#: ../../api.rst:617 msgid "The guild that got removed." msgstr "削除されたギルド。" -#: ../../api.rst:580 +#: ../../api.rst:622 msgid "Called when a :class:`Guild` updates, for example:" msgstr ":class:`Guild` が更新されたときに呼び出されます。例えば:" -#: ../../api.rst:582 +#: ../../api.rst:624 msgid "Changed name" msgstr "名前が変更された" -#: ../../api.rst:583 +#: ../../api.rst:625 msgid "Changed AFK channel" msgstr "AFKチャンネルが変更された" -#: ../../api.rst:584 +#: ../../api.rst:626 msgid "Changed AFK timeout" msgstr "AFKのタイムアウト時間が変更された" -#: ../../api.rst:585 +#: ../../api.rst:627 msgid "etc" msgstr "その他" -#: ../../api.rst:589 +#: ../../api.rst:631 msgid "The guild prior to being updated." msgstr "更新される前のギルド。" -#: ../../api.rst:591 +#: ../../api.rst:633 msgid "The guild after being updated." msgstr "更新された後のギルド。" -#: ../../api.rst:596 +#: ../../api.rst:638 msgid "Called when a :class:`Guild` adds or removes :class:`Emoji`." msgstr ":class:`Guild` に :class:`Emoji` が追加、または削除されたときに呼び出されます。" -#: ../../api.rst:598 -#: ../../api.rst:611 +#: ../../api.rst:640 +#: ../../api.rst:653 msgid "This requires :attr:`Intents.emojis_and_stickers` to be enabled." msgstr ":attr:`Intents.emojis_and_stickers` を有効にする必要があります。" -#: ../../api.rst:600 +#: ../../api.rst:642 msgid "The guild who got their emojis updated." msgstr "絵文字が更新されたギルド。" -#: ../../api.rst:602 +#: ../../api.rst:644 msgid "A list of emojis before the update." msgstr "更新前の絵文字のリスト。" -#: ../../api.rst:604 +#: ../../api.rst:646 msgid "A list of emojis after the update." msgstr "更新後の絵文字のリスト。" -#: ../../api.rst:609 +#: ../../api.rst:651 msgid "Called when a :class:`Guild` updates its stickers." msgstr ":class:`Guild` のスタンプが更新されたときに呼び出されます。" -#: ../../api.rst:615 +#: ../../api.rst:657 msgid "The guild who got their stickers updated." msgstr "スタンプが更新されたギルド。" -#: ../../api.rst:617 +#: ../../api.rst:659 msgid "A list of stickers before the update." msgstr "更新前のスタンプのリスト。" -#: ../../api.rst:619 +#: ../../api.rst:661 msgid "A list of stickers after the update." msgstr "更新後のスタンプのリスト。" -#: ../../api.rst:624 +#: ../../api.rst:666 msgid "Called when a :class:`Guild` gets a new audit log entry. You must have :attr:`~Permissions.view_audit_log` to receive this." msgstr ":class:`Guild` に新しい監査ログ項目が追加されたときに呼び出されます。これを受け取るには :attr:`~Permissions.view_audit_log` が必要です。" -#: ../../api.rst:627 -#: ../../api.rst:839 -#: ../../api.rst:852 +#: ../../api.rst:669 +#: ../../api.rst:881 +#: ../../api.rst:894 msgid "This requires :attr:`Intents.moderation` to be enabled." msgstr ":attr:`Intents.moderation` を有効にする必要があります。" -#: ../../api.rst:633 +#: ../../api.rst:675 msgid "Audit log entries received through the gateway are subject to data retrieval from cache rather than REST. This means that some data might not be present when you expect it to be. For example, the :attr:`AuditLogEntry.target` attribute will usually be a :class:`discord.Object` and the :attr:`AuditLogEntry.user` attribute will depend on user and member cache." msgstr "ゲートウェイ経由で取得した監査ログ項目はデータをRESTではなくキャッシュから取得します。このため、一部のデータが不足している場合があります。例えば、 :attr:`AuditLogEntry.target` 属性は多くの場合 :class:`discord.Object` になり、 :attr:`AuditLogEntry.user` 属性はユーザーとメンバーキャッシュに依存します。" -#: ../../api.rst:639 +#: ../../api.rst:681 msgid "To get the user ID of entry, :attr:`AuditLogEntry.user_id` can be used instead." msgstr "項目のユーザーIDを取得するには、代わりに :attr:`AuditLogEntry.user_id` を使用できます。" -#: ../../api.rst:641 +#: ../../api.rst:683 msgid "The audit log entry that was created." msgstr "作成された監査ログの項目。" -#: ../../api.rst:646 +#: ../../api.rst:688 msgid "Called when an :class:`Invite` is created. You must have :attr:`~Permissions.manage_channels` to receive this." msgstr ":class:`Invite` が作成されたときに呼び出されます。 受け取るには :attr:`~Permissions.manage_channels` が必要です。" -#: ../../api.rst:653 -#: ../../api.rst:670 +#: ../../api.rst:695 +#: ../../api.rst:712 msgid "There is a rare possibility that the :attr:`Invite.guild` and :attr:`Invite.channel` attributes will be of :class:`Object` rather than the respective models." msgstr "まれに :attr:`Invite.guild` と :attr:`Invite.channel` 属性がそれぞれの本来のモデルではなく :class:`Object` になることがあります。" -#: ../../api.rst:656 -#: ../../api.rst:676 +#: ../../api.rst:698 +#: ../../api.rst:718 msgid "This requires :attr:`Intents.invites` to be enabled." msgstr ":attr:`Intents.invites` を有効にする必要があります。" -#: ../../api.rst:658 +#: ../../api.rst:700 #: ../../../discord/abc.py:docstring of discord.abc.GuildChannel.create_invite:37 #: ../../../discord/channel.py:docstring of discord.abc.GuildChannel.create_invite:37 #: ../../../discord/channel.py:docstring of discord.abc.GuildChannel.create_invite:37 @@ -3154,704 +3529,712 @@ msgstr ":attr:`Intents.invites` を有効にする必要があります。" msgid "The invite that was created." msgstr "作成された招待。" -#: ../../api.rst:663 +#: ../../api.rst:705 msgid "Called when an :class:`Invite` is deleted. You must have :attr:`~Permissions.manage_channels` to receive this." msgstr ":class:`Invite` が削除されたときに呼び出されます。 受け取るには :attr:`~Permissions.manage_channels` が必要です。" -#: ../../api.rst:673 +#: ../../api.rst:715 msgid "Outside of those two attributes, the only other attribute guaranteed to be filled by the Discord gateway for this event is :attr:`Invite.code`." msgstr "これらの属性以外では、Discordゲートウェイによってこのイベントに与えられているのが保証されている属性は :attr:`Invite.code` のみです。" -#: ../../api.rst:678 +#: ../../api.rst:720 msgid "The invite that was deleted." msgstr "削除された招待。" -#: ../../api.rst:683 +#: ../../api.rst:725 msgid "Integrations" msgstr "Integrations" -#: ../../api.rst:687 +#: ../../api.rst:729 msgid "Called when an integration is created." msgstr "連携サービスが作成されたときに呼び出されます。" -#: ../../api.rst:689 -#: ../../api.rst:700 -#: ../../api.rst:711 #: ../../api.rst:731 +#: ../../api.rst:742 +#: ../../api.rst:753 +#: ../../api.rst:773 msgid "This requires :attr:`Intents.integrations` to be enabled." msgstr ":attr:`Intents.integrations` を有効にする必要があります。" -#: ../../api.rst:693 +#: ../../api.rst:735 msgid "The integration that was created." msgstr "作成された連携サービス。" -#: ../../api.rst:698 +#: ../../api.rst:740 msgid "Called when an integration is updated." msgstr "連携サービスが更新されたときに呼び出されます。" -#: ../../api.rst:704 +#: ../../api.rst:746 msgid "The integration that was updated." msgstr "更新された連携サービス。" -#: ../../api.rst:709 +#: ../../api.rst:751 msgid "Called whenever an integration is created, modified, or removed from a guild." msgstr "ギルドの連携サービスが作成、更新、削除されるたびに呼び出されます。" -#: ../../api.rst:715 +#: ../../api.rst:757 msgid "The guild that had its integrations updated." msgstr "連携サービスが更新されたギルド。" -#: ../../api.rst:720 +#: ../../api.rst:762 msgid "Called whenever a webhook is created, modified, or removed from a guild channel." msgstr "ギルドチャンネルのWebhookが作成、更新、削除されたときに呼び出されます。" -#: ../../api.rst:722 +#: ../../api.rst:764 msgid "This requires :attr:`Intents.webhooks` to be enabled." msgstr ":attr:`Intents.webhooks` を有効にする必要があります。" -#: ../../api.rst:724 +#: ../../api.rst:766 msgid "The channel that had its webhooks updated." msgstr "Webhookが更新されたチャンネル。" -#: ../../api.rst:729 +#: ../../api.rst:771 msgid "Called when an integration is deleted." msgstr "連携サービスが削除されたときに呼び出されます。" -#: ../../api.rst:739 +#: ../../api.rst:781 msgid "Interactions" msgstr "Interactions" -#: ../../api.rst:743 +#: ../../api.rst:785 msgid "Called when an interaction happened." msgstr "インタラクションが発生したときに呼び出されます。" -#: ../../api.rst:745 +#: ../../api.rst:787 msgid "This currently happens due to slash command invocations or components being used." msgstr "これは、現在はスラッシュコマンドの呼び出しやコンポーネントの使用により起こります。" -#: ../../api.rst:749 +#: ../../api.rst:791 msgid "This is a low level function that is not generally meant to be used. If you are working with components, consider using the callbacks associated with the :class:`~discord.ui.View` instead as it provides a nicer user experience." msgstr "これは、一般的な使用を意図していない低レベル関数です。コンポーネントを使用している場合は、よりよいユーザーエクスペリエンスを提供する :class:`~discord.ui.View` のコールバックの使用を検討してください。" -#: ../../api.rst:755 +#: ../../api.rst:797 msgid "The interaction data." msgstr "インタラクションデータ。" -#: ../../api.rst:759 +#: ../../api.rst:801 msgid "Members" msgstr "Members" -#: ../../api.rst:763 +#: ../../api.rst:805 msgid "Called when a :class:`Member` joins a :class:`Guild`." msgstr ":class:`Member` が :class:`Guild` に参加したときに呼び出されます。" -#: ../../api.rst:765 -#: ../../api.rst:777 -#: ../../api.rst:789 -#: ../../api.rst:811 -#: ../../api.rst:828 +#: ../../api.rst:807 +#: ../../api.rst:819 +#: ../../api.rst:831 +#: ../../api.rst:853 +#: ../../api.rst:870 msgid "This requires :attr:`Intents.members` to be enabled." msgstr ":attr:`Intents.members` を有効にする必要があります。" -#: ../../api.rst:767 +#: ../../api.rst:809 msgid "The member who joined." msgstr "参加したメンバー。" -#: ../../api.rst:772 -#: ../../api.rst:784 +#: ../../api.rst:814 +#: ../../api.rst:826 msgid "Called when a :class:`Member` leaves a :class:`Guild`." msgstr ":class:`Member` が :class:`Guild` から脱退したときに呼び出されます。" -#: ../../api.rst:774 +#: ../../api.rst:816 msgid "If the guild or member could not be found in the internal cache this event will not be called, you may use :func:`on_raw_member_remove` instead." msgstr "ギルドまたはメンバーが内部キャッシュで見つからない場合、このイベントは呼び出されません。代わりに :func:`on_raw_member_remove` を使用してください。" -#: ../../api.rst:779 +#: ../../api.rst:821 msgid "The member who left." msgstr "脱退したメンバー。" -#: ../../api.rst:786 +#: ../../api.rst:828 msgid "Unlike :func:`on_member_remove` this is called regardless of the guild or member being in the internal cache." msgstr ":func:`on_member_remove` とは異なり、ギルドやメンバーが内部キャッシュに存在するかどうかに関係なく呼び出されます。" -#: ../../api.rst:798 +#: ../../api.rst:840 msgid "Called when a :class:`Member` updates their profile." msgstr ":class:`Member` のプロフィールが更新されたときに呼び出されます。" -#: ../../api.rst:800 -#: ../../api.rst:822 -#: ../../api.rst:863 +#: ../../api.rst:842 +#: ../../api.rst:864 +#: ../../api.rst:905 msgid "This is called when one or more of the following things change:" msgstr "これらのうちひとつ以上が変更されたとき呼び出されます:" -#: ../../api.rst:802 +#: ../../api.rst:844 msgid "nickname" msgstr "ニックネーム" -#: ../../api.rst:803 +#: ../../api.rst:845 #: ../../../discord/member.py:docstring of discord.member.Member.edit:16 msgid "roles" msgstr "roles" -#: ../../api.rst:804 +#: ../../api.rst:846 msgid "pending" msgstr "ペンディング状態" -#: ../../api.rst:805 +#: ../../api.rst:847 msgid "timeout" msgstr "タイムアウト" -#: ../../api.rst:806 +#: ../../api.rst:848 msgid "guild avatar" msgstr "ギルドアバター" -#: ../../api.rst:807 +#: ../../api.rst:849 msgid "flags" msgstr "flags" -#: ../../api.rst:809 +#: ../../api.rst:851 msgid "Due to a Discord limitation, this event is not dispatched when a member's timeout expires." msgstr "Discordの制限により、このイベントはメンバーのタイムアウト期間が満了した場合には発生しません。" -#: ../../api.rst:813 -#: ../../api.rst:872 +#: ../../api.rst:855 +#: ../../api.rst:914 msgid "The updated member's old info." msgstr "更新されたメンバーの更新前情報。" -#: ../../api.rst:815 -#: ../../api.rst:874 +#: ../../api.rst:857 +#: ../../api.rst:916 msgid "The updated member's updated info." msgstr "更新されたメンバーの更新後情報。" -#: ../../api.rst:820 +#: ../../api.rst:862 msgid "Called when a :class:`User` updates their profile." msgstr ":class:`User` がプロフィールを編集したとき呼び出されます。" -#: ../../api.rst:824 +#: ../../api.rst:866 msgid "avatar" msgstr "アバター" -#: ../../api.rst:825 +#: ../../api.rst:867 msgid "username" msgstr "ユーザー名" -#: ../../api.rst:826 +#: ../../api.rst:868 msgid "discriminator" msgstr "タグ" -#: ../../api.rst:830 +#: ../../api.rst:872 msgid "The updated user's old info." msgstr "更新されたユーザーの更新前情報。" -#: ../../api.rst:832 +#: ../../api.rst:874 msgid "The updated user's updated info." msgstr "更新されたユーザーの更新後情報。" -#: ../../api.rst:837 -msgid "Called when user gets banned from a :class:`Guild`." -msgstr "ユーザーが :class:`Guild` からBANされたとき呼び出されます。" +#: ../../api.rst:879 +msgid "Called when a user gets banned from a :class:`Guild`." +msgstr "" -#: ../../api.rst:841 +#: ../../api.rst:883 msgid "The guild the user got banned from." msgstr "ユーザーがBANされたギルド。" -#: ../../api.rst:843 +#: ../../api.rst:885 msgid "The user that got banned. Can be either :class:`User` or :class:`Member` depending if the user was in the guild or not at the time of removal." msgstr "BANされたユーザー。BAN時にユーザーがギルドにいたかによって、 :class:`User` か :class:`Member` になります。" -#: ../../api.rst:850 +#: ../../api.rst:892 msgid "Called when a :class:`User` gets unbanned from a :class:`Guild`." msgstr ":class:`User` が :class:`Guild` のBANを解除されたとき呼び出されます。" -#: ../../api.rst:854 +#: ../../api.rst:896 msgid "The guild the user got unbanned from." msgstr "ユーザーのBANが解除されたギルド。" -#: ../../api.rst:856 +#: ../../api.rst:898 msgid "The user that got unbanned." msgstr "Banが解除されたユーザー。" -#: ../../api.rst:861 +#: ../../api.rst:903 msgid "Called when a :class:`Member` updates their presence." msgstr ":class:`Member` がプレゼンスを変更したとき呼び出されます。" -#: ../../api.rst:865 +#: ../../api.rst:907 msgid "status" msgstr "ステータス" -#: ../../api.rst:866 +#: ../../api.rst:908 msgid "activity" msgstr "アクティビティ" -#: ../../api.rst:868 +#: ../../api.rst:910 msgid "This requires :attr:`Intents.presences` and :attr:`Intents.members` to be enabled." msgstr "これを使用するには :attr:`Intents.presences` と :attr:`Intents.members` を有効にしないといけません。" -#: ../../api.rst:878 +#: ../../api.rst:920 msgid "Messages" msgstr "Messages" -#: ../../api.rst:882 +#: ../../api.rst:924 msgid "Called when a :class:`Message` is created and sent." msgstr ":class:`Message` が作成され送信されたときに呼び出されます。" -#: ../../api.rst:888 +#: ../../api.rst:930 msgid "Your bot's own messages and private messages are sent through this event. This can lead cases of 'recursion' depending on how your bot was programmed. If you want the bot to not reply to itself, consider checking the user IDs. Note that :class:`~ext.commands.Bot` does not have this problem." msgstr "Botのメッセージとプライベートメッセージはこのイベントを通して送信されます。Botのプログラムによっては「再帰呼び出し」を続けることになります。Botが自分自身に返信しないようにするためにはユーザーIDを確認する方法が考えられます。 :class:`~ext.commands.Bot` にはこの問題は存在しません。" -#: ../../api.rst:894 +#: ../../api.rst:936 msgid "The current message." msgstr "現在のメッセージ。" -#: ../../api.rst:899 +#: ../../api.rst:941 msgid "Called when a :class:`Message` receives an update event. If the message is not found in the internal message cache, then these events will not be called. Messages might not be in cache if the message is too old or the client is participating in high traffic guilds." msgstr ":class:`Message` が更新イベントを受け取ったときに呼び出されます。メッセージが内部のメッセージキャッシュに見つからない場合、このイベントは呼び出されません。これはメッセージが古すぎるか、クライアントが通信の多いギルドに参加している場合に発生します。" -#: ../../api.rst:904 +#: ../../api.rst:946 msgid "If this occurs increase the :class:`max_messages ` parameter or use the :func:`on_raw_message_edit` event instead." msgstr "発生する場合は :class:`max_messages ` パラメータの値を増加させるか :func:`on_raw_message_edit` イベントを使用してください。" -#: ../../api.rst:907 +#: ../../api.rst:949 msgid "The following non-exhaustive cases trigger this event:" msgstr "以下の非網羅的ケースがこのイベントを発生させます:" -#: ../../api.rst:909 +#: ../../api.rst:951 msgid "A message has been pinned or unpinned." msgstr "メッセージをピン留め、または解除した。" -#: ../../api.rst:910 +#: ../../api.rst:952 msgid "The message content has been changed." msgstr "メッセージの内容を変更した。" -#: ../../api.rst:911 +#: ../../api.rst:953 msgid "The message has received an embed." msgstr "メッセージが埋め込みを受け取った。" -#: ../../api.rst:913 +#: ../../api.rst:955 msgid "For performance reasons, the embed server does not do this in a \"consistent\" manner." msgstr "パフォーマンス上の理由から、埋め込みのサーバーはこれを「一貫した」方法では行いません。" -#: ../../api.rst:915 +#: ../../api.rst:957 msgid "The message's embeds were suppressed or unsuppressed." msgstr "メッセージの埋め込みが削除されたり、復元されたりした。" -#: ../../api.rst:916 +#: ../../api.rst:958 msgid "A call message has received an update to its participants or ending time." msgstr "通話呼び出しメッセージの参加者や終了時刻が変わった。" -#: ../../api.rst:920 +#: ../../api.rst:962 msgid "The previous version of the message." msgstr "更新前のメッセージ。" -#: ../../api.rst:922 +#: ../../api.rst:964 msgid "The current version of the message." msgstr "更新後のメッセージ。" -#: ../../api.rst:927 +#: ../../api.rst:969 msgid "Called when a message is deleted. If the message is not found in the internal message cache, then this event will not be called. Messages might not be in cache if the message is too old or the client is participating in high traffic guilds." msgstr "メッセージが削除された際に呼び出されます。メッセージが内部のメッセージキャッシュに見つからない場合、このイベントは呼び出されません。これはメッセージが古すぎるか、クライアントが通信の多いギルドに参加している場合に発生します。" -#: ../../api.rst:932 +#: ../../api.rst:974 msgid "If this occurs increase the :class:`max_messages ` parameter or use the :func:`on_raw_message_delete` event instead." msgstr "発生する場合は :class:`max_messages ` パラメータの値を増加させるか :func:`on_raw_message_delete` イベントを使用してください。" -#: ../../api.rst:937 +#: ../../api.rst:979 msgid "The deleted message." msgstr "削除されたメッセージ。" -#: ../../api.rst:942 +#: ../../api.rst:984 msgid "Called when messages are bulk deleted. If none of the messages deleted are found in the internal message cache, then this event will not be called. If individual messages were not found in the internal message cache, this event will still be called, but the messages not found will not be included in the messages list. Messages might not be in cache if the message is too old or the client is participating in high traffic guilds." msgstr "メッセージが一括削除されたときに呼び出されます。メッセージが内部のメッセージキャッシュに見つからない場合、このイベントは呼び出されません。個々のメッセージが見つからない場合でも、このイベントは呼び出されますが、見つからなかったメッセージはメッセージのリストに含まれません。これはメッセージが古すぎるか、クライアントが通信の多いギルドに参加している場合に発生します。" -#: ../../api.rst:949 +#: ../../api.rst:991 msgid "If this occurs increase the :class:`max_messages ` parameter or use the :func:`on_raw_bulk_message_delete` event instead." msgstr "発生する場合は :class:`max_messages ` パラメータの値を増加させるか :func:`on_raw_bulk_message_delete` イベントを使用してください。" -#: ../../api.rst:954 +#: ../../api.rst:996 msgid "The messages that have been deleted." msgstr "削除されたメッセージのリスト。" -#: ../../api.rst:959 +#: ../../api.rst:1001 msgid "Called when a message is edited. Unlike :func:`on_message_edit`, this is called regardless of the state of the internal message cache." msgstr "メッセージが編集されたときに呼び出されます。 :func:`on_message_edit` とは異なり、これは内部のメッセージキャッシュの状態に関係なく呼び出されます。" -#: ../../api.rst:962 +#: ../../api.rst:1004 msgid "If the message is found in the message cache, it can be accessed via :attr:`RawMessageUpdateEvent.cached_message`. The cached message represents the message before it has been edited. For example, if the content of a message is modified and triggers the :func:`on_raw_message_edit` coroutine, the :attr:`RawMessageUpdateEvent.cached_message` will return a :class:`Message` object that represents the message before the content was modified." msgstr "メッセージがメッセージキャッシュに存在した場合、 :attr:`RawMessageUpdateEvent.cached_message` を介してそのメッセージにアクセスすることができます。キャッシュされていたメッセージは編集前のメッセージです。たとえば、メッセージの内容が編集され、 :func:`on_raw_message_edit` が発火された場合、 :attr:`RawMessageUpdateEvent.cached_message` は内容が編集される前の情報を持つ :class:`Message` オブジェクトを返します。" -#: ../../api.rst:968 +#: ../../api.rst:1010 msgid "Due to the inherently raw nature of this event, the data parameter coincides with the raw data given by the :ddocs:`gateway `." msgstr "このイベントの性質は、本質的に生表現のため、データのパラメータは :ddocs:`ゲートウェイ ` によって与えられた生データと一致します。" -#: ../../api.rst:971 +#: ../../api.rst:1013 msgid "Since the data payload can be partial, care must be taken when accessing stuff in the dictionary. One example of a common case of partial data is when the ``'content'`` key is inaccessible. This denotes an \"embed\" only edit, which is an edit in which only the embeds are updated by the Discord embed server." msgstr "データのペイロードが部分的であるため、データにアクセスするときは気をつけてください。部分的なデータの主な場合のひとつは、``'content'`` にアクセスできない場合です。Discordの埋め込みサーバーによって、埋め込みが更新される、\"埋め込み\"しか変わっていない編集がそうです。" -#: ../../api.rst:984 +#: ../../api.rst:1026 msgid "Called when a message is deleted. Unlike :func:`on_message_delete`, this is called regardless of the message being in the internal message cache or not." msgstr "メッセージが削除されたときに呼び出されます。 :func:`on_message_delete` とは異なり、削除されたメッセージが内部キャッシュに存在するか否かにかかわらず呼び出されます。" -#: ../../api.rst:987 +#: ../../api.rst:1029 msgid "If the message is found in the message cache, it can be accessed via :attr:`RawMessageDeleteEvent.cached_message`" msgstr "メッセージがメッセージキャッシュ内に見つかった場合、 :attr:`RawMessageDeleteEvent.cached_message` を介してアクセスすることができます。" -#: ../../api.rst:997 +#: ../../api.rst:1039 msgid "Called when a bulk delete is triggered. Unlike :func:`on_bulk_message_delete`, this is called regardless of the messages being in the internal message cache or not." msgstr "メッセージが一括削除されたときに呼び出されます。 :func:`on_bulk_message_delete` とは異なり、削除されたメッセージが内部キャッシュに存在するか否かにかかわらず呼び出されます。" -#: ../../api.rst:1000 +#: ../../api.rst:1042 msgid "If the messages are found in the message cache, they can be accessed via :attr:`RawBulkMessageDeleteEvent.cached_messages`" msgstr "メッセージがメッセージキャッシュ内に見つかった場合、 :attr:`RawBulkMessageDeleteEvent.cached_messages` を介してアクセスすることができます。" -#: ../../api.rst:1009 +#: ../../api.rst:1051 msgid "Reactions" msgstr "Reactions" -#: ../../api.rst:1013 +#: ../../api.rst:1055 msgid "Called when a message has a reaction added to it. Similar to :func:`on_message_edit`, if the message is not found in the internal message cache, then this event will not be called. Consider using :func:`on_raw_reaction_add` instead." msgstr "メッセージにリアクションが追加されたときに呼び出されます。 :func:`on_message_edit` と同様に内部メッセージキャッシュにメッセージが見つからない場合は、このイベントは呼び出されません。代わりに :func:`on_raw_reaction_add` の利用を検討してください。" -#: ../../api.rst:1019 +#: ../../api.rst:1061 msgid "To get the :class:`Message` being reacted, access it via :attr:`Reaction.message`." msgstr "リアクションの付いた :class:`Message` を取得するには、 :attr:`Reaction.message` を使ってください。" -#: ../../api.rst:1021 -#: ../../api.rst:1064 -#: ../../api.rst:1077 -#: ../../api.rst:1090 -#: ../../api.rst:1100 +#: ../../api.rst:1063 +#: ../../api.rst:1118 +#: ../../api.rst:1131 +#: ../../api.rst:1144 +#: ../../api.rst:1154 msgid "This requires :attr:`Intents.reactions` to be enabled." msgstr ":attr:`Intents.reactions` を有効にする必要があります。" -#: ../../api.rst:1025 +#: ../../api.rst:1067 msgid "This doesn't require :attr:`Intents.members` within a guild context, but due to Discord not providing updated user information in a direct message it's required for direct messages to receive this event. Consider using :func:`on_raw_reaction_add` if you need this and do not otherwise want to enable the members intent." msgstr "ギルド内では :attr:`Intents.members` は有効にしなくてもよいですが、DiscordがDM内では更新されたユーザーの情報を提供しないため、DMではこのインテントが必要です。 このイベントが必要でメンバーインテントを有効化したくない場合は :func:`on_raw_reaction_add` の使用を検討してください。" -#: ../../api.rst:1031 -#: ../../api.rst:1053 +#: ../../api.rst:1075 +msgid "This event does not have a way of differentiating whether a reaction is a burst reaction (also known as \"super reaction\") or not. If you need this, consider using :func:`on_raw_reaction_add` instead." +msgstr "" + +#: ../../api.rst:1079 +#: ../../api.rst:1107 msgid "The current state of the reaction." msgstr "リアクションの現在の状態。" -#: ../../api.rst:1033 +#: ../../api.rst:1081 msgid "The user who added the reaction." msgstr "リアクションを追加したユーザー。" -#: ../../api.rst:1038 +#: ../../api.rst:1086 msgid "Called when a message has a reaction removed from it. Similar to on_message_edit, if the message is not found in the internal message cache, then this event will not be called." msgstr "メッセージのリアクションが取り除かれたときに呼び出されます。on_message_editのように、内部のメッセージキャッシュにメッセージがないときには、このイベントは呼び出されません。" -#: ../../api.rst:1044 +#: ../../api.rst:1092 msgid "To get the message being reacted, access it via :attr:`Reaction.message`." msgstr "リアクションの付いたメッセージを取得するには、 :attr:`Reaction.message` を使ってください。" -#: ../../api.rst:1046 +#: ../../api.rst:1094 msgid "This requires both :attr:`Intents.reactions` and :attr:`Intents.members` to be enabled." msgstr "これを使用するには :attr:`Intents.reactions` と :attr:`Intents.members` の両方を有効にしないといけません。" -#: ../../api.rst:1050 +#: ../../api.rst:1098 msgid "Consider using :func:`on_raw_reaction_remove` if you need this and do not want to enable the members intent." msgstr "このイベントが必要でメンバーインテントを有効化したくない場合は :func:`on_raw_reaction_remove` の使用を検討してください。" -#: ../../api.rst:1055 +#: ../../api.rst:1103 +msgid "This event does not have a way of differentiating whether a reaction is a burst reaction (also known as \"super reaction\") or not. If you need this, consider using :func:`on_raw_reaction_remove` instead." +msgstr "" + +#: ../../api.rst:1109 msgid "The user whose reaction was removed." msgstr "リアクションが除去されたユーザー。" -#: ../../api.rst:1060 +#: ../../api.rst:1114 msgid "Called when a message has all its reactions removed from it. Similar to :func:`on_message_edit`, if the message is not found in the internal message cache, then this event will not be called. Consider using :func:`on_raw_reaction_clear` instead." msgstr "メッセージからすべてのリアクションが削除されたときに呼び出されます。 :func:`on_message_edit` と同様に内部メッセージキャッシュにメッセージが見つからない場合は、このイベントは呼び出されません。代わりに :func:`on_raw_reaction_clear` の利用を検討してください。" -#: ../../api.rst:1066 +#: ../../api.rst:1120 msgid "The message that had its reactions cleared." msgstr "リアクションが削除されたメッセージ。" -#: ../../api.rst:1068 +#: ../../api.rst:1122 msgid "The reactions that were removed." msgstr "除去されたリアクション。" -#: ../../api.rst:1073 +#: ../../api.rst:1127 msgid "Called when a message has a specific reaction removed from it. Similar to :func:`on_message_edit`, if the message is not found in the internal message cache, then this event will not be called. Consider using :func:`on_raw_reaction_clear_emoji` instead." msgstr "メッセージから特定の絵文字のリアクションが除去されたときに呼び出されます。 :func:`on_message_edit` と同様に内部メッセージキャッシュにメッセージが見つからない場合は、このイベントは呼び出されません。代わりに :func:`on_raw_reaction_clear_emoji` の利用を検討してください。" -#: ../../api.rst:1081 +#: ../../api.rst:1135 msgid "The reaction that got cleared." msgstr "除去されたリアクション。" -#: ../../api.rst:1087 +#: ../../api.rst:1141 msgid "Called when a message has a reaction added. Unlike :func:`on_reaction_add`, this is called regardless of the state of the internal message cache." msgstr "メッセージにリアクションが追加されたときに呼び出されます。 :func:`on_reaction_add` とは異なり、これは内部のメッセージキャッシュの状態に関係なく呼び出されます。" -#: ../../api.rst:1097 +#: ../../api.rst:1151 msgid "Called when a message has a reaction removed. Unlike :func:`on_reaction_remove`, this is called regardless of the state of the internal message cache." msgstr "メッセージからリアクションが削除されたときに呼び出されます。 :func:`on_reaction_remove` とは異なり、これは内部メッセージキャッシュの状態に関係なく呼び出されます。" -#: ../../api.rst:1107 +#: ../../api.rst:1161 msgid "Called when a message has all its reactions removed. Unlike :func:`on_reaction_clear`, this is called regardless of the state of the internal message cache." msgstr "メッセージからリアクションがすべて削除されたときに呼び出されます。 :func:`on_reaction_clear` とは異なり、これは内部メッセージキャッシュの状態に関係なく呼び出されます。" -#: ../../api.rst:1117 +#: ../../api.rst:1171 msgid "Called when a message has a specific reaction removed from it. Unlike :func:`on_reaction_clear_emoji` this is called regardless of the state of the internal message cache." msgstr "メッセージから特定の絵文字のリアクションがすべて除去されたときに呼び出されます。 :func:`on_reaction_clear_emoji` とは異なり、これは内部メッセージキャッシュの状態に関係なく呼び出されます。" -#: ../../api.rst:1129 +#: ../../api.rst:1183 msgid "Roles" msgstr "Roles" -#: ../../api.rst:1134 +#: ../../api.rst:1188 msgid "Called when a :class:`Guild` creates or deletes a new :class:`Role`." msgstr ":class:`Guild` で :class:`Role` が新しく作成されたか、削除されたときに呼び出されます。" -#: ../../api.rst:1136 +#: ../../api.rst:1190 msgid "To get the guild it belongs to, use :attr:`Role.guild`." msgstr "ギルドを取得するには :attr:`Role.guild` を使用してください。" -#: ../../api.rst:1140 +#: ../../api.rst:1194 msgid "The role that was created or deleted." msgstr "作成、または削除されたロール。" -#: ../../api.rst:1145 +#: ../../api.rst:1199 msgid "Called when a :class:`Role` is changed guild-wide." msgstr ":class:`Role` がギルド全体で変更されたときに呼び出されます。" -#: ../../api.rst:1149 +#: ../../api.rst:1203 msgid "The updated role's old info." msgstr "更新されたロールの更新前情報。" -#: ../../api.rst:1151 +#: ../../api.rst:1205 msgid "The updated role's updated info." msgstr "更新されたロールの更新後情報。" -#: ../../api.rst:1156 +#: ../../api.rst:1210 msgid "Scheduled Events" msgstr "Scheduled Events" -#: ../../api.rst:1161 +#: ../../api.rst:1215 msgid "Called when a :class:`ScheduledEvent` is created or deleted." msgstr ":class:`ScheduledEvent` が作成または削除されたときに呼び出されます。" -#: ../../api.rst:1163 -#: ../../api.rst:1174 -#: ../../api.rst:1196 +#: ../../api.rst:1217 +#: ../../api.rst:1228 +#: ../../api.rst:1250 msgid "This requires :attr:`Intents.guild_scheduled_events` to be enabled." msgstr ":attr:`Intents.guild_scheduled_events` を有効にする必要があります。" -#: ../../api.rst:1167 +#: ../../api.rst:1221 msgid "The scheduled event that was created or deleted." msgstr "作成、または削除されたスケジュールイベント。" -#: ../../api.rst:1172 +#: ../../api.rst:1226 msgid "Called when a :class:`ScheduledEvent` is updated." msgstr ":class:`ScheduledEvent` が変更されたときに呼び出されます。" -#: ../../api.rst:1176 -#: ../../api.rst:1223 -#: ../../api.rst:1375 +#: ../../api.rst:1230 +#: ../../api.rst:1277 +#: ../../api.rst:1429 msgid "The following, but not limited to, examples illustrate when this event is called:" msgstr "これらの場合に限りませんが、例を挙げると、以下の場合に呼び出されます:" -#: ../../api.rst:1178 +#: ../../api.rst:1232 msgid "The scheduled start/end times are changed." msgstr "スケジュールされた開始・終了時刻が変更された。" -#: ../../api.rst:1179 +#: ../../api.rst:1233 msgid "The channel is changed." msgstr "チャンネルが変更された時。" -#: ../../api.rst:1180 +#: ../../api.rst:1234 msgid "The description is changed." msgstr "説明が変更された時。" -#: ../../api.rst:1181 +#: ../../api.rst:1235 msgid "The status is changed." msgstr "ステータスが変更された時。" -#: ../../api.rst:1182 +#: ../../api.rst:1236 msgid "The image is changed." msgstr "画像が変更された時。" -#: ../../api.rst:1186 +#: ../../api.rst:1240 msgid "The scheduled event before the update." msgstr "変更前のスケジュールイベント。" -#: ../../api.rst:1188 +#: ../../api.rst:1242 msgid "The scheduled event after the update." msgstr "変更後のスケジュールイベント。" -#: ../../api.rst:1194 +#: ../../api.rst:1248 msgid "Called when a user is added or removed from a :class:`ScheduledEvent`." msgstr ":class:`ScheduledEvent` からユーザーが追加または削除されたときに呼び出されます。" -#: ../../api.rst:1200 +#: ../../api.rst:1254 msgid "The scheduled event that the user was added or removed from." msgstr "ユーザーが追加または削除されたスケジュールイベント。" -#: ../../api.rst:1202 +#: ../../api.rst:1256 msgid "The user that was added or removed." msgstr "追加・削除されたユーザー。" -#: ../../api.rst:1207 +#: ../../api.rst:1261 msgid "Stages" msgstr "Stages" -#: ../../api.rst:1212 +#: ../../api.rst:1266 msgid "Called when a :class:`StageInstance` is created or deleted for a :class:`StageChannel`." msgstr ":class:`StageChannel` の :class:`StageInstance` が作成または削除されたときに呼び出されます。" -#: ../../api.rst:1216 +#: ../../api.rst:1270 msgid "The stage instance that was created or deleted." msgstr "作成、または削除されたステージインスタンス。" -#: ../../api.rst:1221 +#: ../../api.rst:1275 msgid "Called when a :class:`StageInstance` is updated." msgstr ":class:`StageInstance` が変更されたときに呼び出されます。" -#: ../../api.rst:1225 +#: ../../api.rst:1279 msgid "The topic is changed." msgstr "トピックが変更された時。" -#: ../../api.rst:1226 +#: ../../api.rst:1280 msgid "The privacy level is changed." msgstr "プライバシーレベルが変更された時。" -#: ../../api.rst:1230 +#: ../../api.rst:1284 msgid "The stage instance before the update." msgstr "更新前のステージインスタンス。" -#: ../../api.rst:1232 +#: ../../api.rst:1286 msgid "The stage instance after the update." msgstr "更新後のステージインスタンス。" -#: ../../api.rst:1236 +#: ../../api.rst:1290 msgid "Threads" msgstr "Threads" -#: ../../api.rst:1240 +#: ../../api.rst:1294 msgid "Called whenever a thread is created." msgstr "スレッドが作成されたときに発生します。" -#: ../../api.rst:1242 -#: ../../api.rst:1255 -#: ../../api.rst:1285 +#: ../../api.rst:1296 #: ../../api.rst:1309 +#: ../../api.rst:1339 +#: ../../api.rst:1363 msgid "Note that you can get the guild from :attr:`Thread.guild`." msgstr "ギルドは :attr:`Thread.guild` から取得できます。" -#: ../../api.rst:1248 +#: ../../api.rst:1302 msgid "The thread that was created." msgstr "作成されたスレッド。" -#: ../../api.rst:1253 +#: ../../api.rst:1307 msgid "Called whenever a thread is joined." msgstr "スレッドに参加したときに呼び出されます。" -#: ../../api.rst:1261 +#: ../../api.rst:1315 msgid "The thread that got joined." msgstr "参加したスレッド。" -#: ../../api.rst:1266 +#: ../../api.rst:1320 msgid "Called whenever a thread is updated. If the thread could not be found in the internal cache this event will not be called. Threads will not be in the cache if they are archived." msgstr "スレッドが更新されたときに呼び出されます。スレッドが内部キャッシュに見つからなかった場合、このイベントは呼び出されません。 スレッドがアーカイブされている場合、キャッシュには含まれません。" -#: ../../api.rst:1270 +#: ../../api.rst:1324 msgid "If you need this information use :func:`on_raw_thread_update` instead." msgstr "この情報が必要な場合は、代わりに :func:`on_raw_thread_update` を使用してください。" -#: ../../api.rst:1276 +#: ../../api.rst:1330 msgid "The updated thread's old info." msgstr "古いスレッドの情報。" -#: ../../api.rst:1278 +#: ../../api.rst:1332 msgid "The updated thread's new info." msgstr "新しいスレッドの情報。" -#: ../../api.rst:1283 +#: ../../api.rst:1337 msgid "Called whenever a thread is removed. This is different from a thread being deleted." msgstr "スレッドが除去されたときに呼び出されます。これはスレッドの削除とは異なります。" -#: ../../api.rst:1291 +#: ../../api.rst:1345 msgid "Due to technical limitations, this event might not be called as soon as one expects. Since the library tracks thread membership locally, the API only sends updated thread membership status upon being synced by joining a thread." msgstr "技術的な制約のためこのイベントは期待通りの早さで呼び出されない場合があります。ライブラリーがスレッドの参加をローカルで追跡するため、APIは更新されたスレッドの参加状態をスレッドの参加時にのみ同期します。" -#: ../../api.rst:1298 +#: ../../api.rst:1352 msgid "The thread that got removed." msgstr "削除されたスレッド。" -#: ../../api.rst:1303 +#: ../../api.rst:1357 msgid "Called whenever a thread is deleted. If the thread could not be found in the internal cache this event will not be called. Threads will not be in the cache if they are archived." msgstr "スレッドが削除されたときに呼び出されます。スレッドが内部キャッシュに見つからなかった場合、このイベントは呼び出されません。 スレッドがアーカイブされている場合、キャッシュには含まれません。" -#: ../../api.rst:1307 +#: ../../api.rst:1361 msgid "If you need this information use :func:`on_raw_thread_delete` instead." msgstr "この情報が必要な場合は、代わりに :func:`on_raw_thread_delete` を使用してください。" -#: ../../api.rst:1315 +#: ../../api.rst:1369 msgid "The thread that got deleted." msgstr "削除されたスレッド。" -#: ../../api.rst:1320 +#: ../../api.rst:1374 msgid "Called whenever a thread is updated. Unlike :func:`on_thread_update` this is called regardless of the thread being in the internal thread cache or not." msgstr "スレッドが更新されたときに呼び出されます。 :func:`on_thread_update` とは異なり、更新されたスレッドが内部キャッシュに存在するか否かにかかわらず呼び出されます。" -#: ../../api.rst:1332 +#: ../../api.rst:1386 msgid "Called whenever a thread is deleted. Unlike :func:`on_thread_delete` this is called regardless of the thread being in the internal thread cache or not." msgstr "スレッドが削除されたときに呼び出されます。 :func:`on_thread_delete` とは異なり、削除されたスレッドが内部キャッシュに存在するか否かにかかわらず呼び出されます。" -#: ../../api.rst:1345 +#: ../../api.rst:1399 msgid "Called when a :class:`ThreadMember` leaves or joins a :class:`Thread`." msgstr ":class:`ThreadMember` が :class:`Thread` に参加したり退出したりしたときに呼び出されます。" -#: ../../api.rst:1347 +#: ../../api.rst:1401 msgid "You can get the thread a member belongs in by accessing :attr:`ThreadMember.thread`." msgstr "メンバーが所属するスレッドは :attr:`ThreadMember.thread` から取得できます。" -#: ../../api.rst:1353 +#: ../../api.rst:1407 msgid "The member who joined or left." msgstr "参加、または脱退したメンバー。" -#: ../../api.rst:1358 +#: ../../api.rst:1412 msgid "Called when a :class:`ThreadMember` leaves a :class:`Thread`. Unlike :func:`on_thread_member_remove` this is called regardless of the member being in the internal thread's members cache or not." msgstr ":class:`ThreadMember` が :class:`Thread` を退出したときに呼び出されます。 :func:`on_thread_member_remove` とは異なり、メンバーが内部スレッドメンバーキャッシュに存在するかどうかに関係なく呼び出されます。" -#: ../../api.rst:1369 +#: ../../api.rst:1423 msgid "Voice" msgstr "Voice" -#: ../../api.rst:1373 +#: ../../api.rst:1427 msgid "Called when a :class:`Member` changes their :class:`VoiceState`." msgstr ":class:`Member` が :class:`VoiceState` を変更したとき呼び出されます。" -#: ../../api.rst:1377 +#: ../../api.rst:1431 msgid "A member joins a voice or stage channel." msgstr "メンバーがボイスチャンネルやステージチャンネルに参加したとき。" -#: ../../api.rst:1378 +#: ../../api.rst:1432 msgid "A member leaves a voice or stage channel." msgstr "メンバーがボイスチャンネルやステージチャンネルから退出したとき。" -#: ../../api.rst:1379 +#: ../../api.rst:1433 msgid "A member is muted or deafened by their own accord." msgstr "メンバーが自身でマイクやスピーカーをミュートしたとき。" -#: ../../api.rst:1380 +#: ../../api.rst:1434 msgid "A member is muted or deafened by a guild administrator." msgstr "メンバーがギルドの管理者によってマイクやスピーカーをミュートされたとき。" -#: ../../api.rst:1382 +#: ../../api.rst:1436 msgid "This requires :attr:`Intents.voice_states` to be enabled." msgstr ":attr:`Intents.voice_states` を有効にする必要があります。" -#: ../../api.rst:1384 +#: ../../api.rst:1438 msgid "The member whose voice states changed." msgstr "ボイスの状態が変わった `Member` 。" -#: ../../api.rst:1386 +#: ../../api.rst:1440 msgid "The voice state prior to the changes." msgstr "更新前のボイス状態。" -#: ../../api.rst:1388 +#: ../../api.rst:1442 msgid "The voice state after the changes." msgstr "更新後のボイス状態。" -#: ../../api.rst:1394 +#: ../../api.rst:1448 msgid "Utility Functions" msgstr "ユーティリティ関数" @@ -4125,15 +4508,15 @@ msgstr "メンションをエスケープするテキスト。" msgid "The text with the mentions removed." msgstr "メンションが削除されたテキスト。" -#: ../../api.rst:1418 +#: ../../api.rst:1472 msgid "A data class which represents a resolved invite returned from :func:`discord.utils.resolve_invite`." msgstr ":func:`discord.utils.resolve_invite` から返された解決済みの招待を表すデータクラス。" -#: ../../api.rst:1422 +#: ../../api.rst:1476 msgid "The invite code." msgstr "招待コード。" -#: ../../api.rst:1428 +#: ../../api.rst:1482 msgid "The id of the scheduled event that the invite refers to." msgstr "招待が参照するスケジュールイベントのID。" @@ -4215,8 +4598,8 @@ msgid "Example Output" msgstr "出力例" #: ../../../discord/utils.py:docstring of discord.utils.format_dt:6 -#: ../../api.rst:3315 -#: ../../api.rst:3335 +#: ../../api.rst:3579 +#: ../../api.rst:3599 msgid "Description" msgstr "説明" @@ -4344,1872 +4727,2049 @@ msgstr "指定されたサイズのチャンクを生成する新しいイテレ msgid "Union[:class:`Iterator`, :class:`AsyncIterator`]" msgstr "Union[:class:`Iterator`, :class:`AsyncIterator`]" -#: ../../api.rst:1447 +#: ../../api.rst:1501 msgid "A type safe sentinel used in the library to represent something as missing. Used to distinguish from ``None`` values." msgstr "ライブラリで見つからないものを表現するために使用されるタイプセーフなセンチネル型。 ``None`` 値と区別するために使用されます。" -#: ../../api.rst:1454 +#: ../../api.rst:1508 msgid "Enumerations" msgstr "列挙型" -#: ../../api.rst:1456 +#: ../../api.rst:1510 msgid "The API provides some enumerations for certain types of strings to avoid the API from being stringly typed in case the strings change in the future." msgstr "APIは、文字列が将来変わることに備え、文字列を直書きするのを防ぐために、いくらかの文字列の列挙型を提供します。" -#: ../../api.rst:1459 +#: ../../api.rst:1513 msgid "All enumerations are subclasses of an internal class which mimics the behaviour of :class:`enum.Enum`." msgstr "列挙型はすべて :class:`enum.Enum` の動作を模倣した内部クラスのサブクラスです。" -#: ../../api.rst:1464 +#: ../../api.rst:1518 msgid "Specifies the type of channel." msgstr "特定チャンネルのチャンネルタイプ。" -#: ../../api.rst:1468 +#: ../../api.rst:1522 msgid "A text channel." msgstr "テキストチャンネル。" -#: ../../api.rst:1471 +#: ../../api.rst:1525 msgid "A voice channel." msgstr "ボイスチャンネル。" -#: ../../api.rst:1474 +#: ../../api.rst:1528 msgid "A private text channel. Also called a direct message." msgstr "プライベートのテキストチャンネル。ダイレクトメッセージとも呼ばれています。" -#: ../../api.rst:1477 +#: ../../api.rst:1531 msgid "A private group text channel." msgstr "プライベートのグループDM。" -#: ../../api.rst:1480 +#: ../../api.rst:1534 msgid "A category channel." msgstr "カテゴリチャンネル。" -#: ../../api.rst:1483 +#: ../../api.rst:1537 msgid "A guild news channel." msgstr "ギルドのニュースチャンネル。" -#: ../../api.rst:1487 +#: ../../api.rst:1541 msgid "A guild stage voice channel." msgstr "ギルドのステージボイスチャンネル。" -#: ../../api.rst:1493 +#: ../../api.rst:1547 msgid "A news thread" msgstr "ニューススレッド。" -#: ../../api.rst:1499 +#: ../../api.rst:1553 msgid "A public thread" msgstr "パブリックスレッド。" -#: ../../api.rst:1505 +#: ../../api.rst:1559 msgid "A private thread" msgstr "プライベートスレッド。" -#: ../../api.rst:1511 +#: ../../api.rst:1565 msgid "A forum channel." msgstr "フォーラムチャンネル。" -#: ../../api.rst:1517 +#: ../../api.rst:1571 +msgid "A media channel." +msgstr "" + +#: ../../api.rst:1577 msgid "Specifies the type of :class:`Message`. This is used to denote if a message is to be interpreted as a system message or a regular message." msgstr ":class:`Message` のタイプを指定します。これは、メッセージが通常のものかシステムメッセージかを判断するのに使用できます。" -#: ../../api.rst:1524 +#: ../../api.rst:1584 #: ../../../discord/message.py:docstring of discord.message.Message:7 msgid "Checks if two messages are equal." msgstr "二つのメッセージが等しいかを比較します。" -#: ../../api.rst:1527 +#: ../../api.rst:1587 #: ../../../discord/message.py:docstring of discord.message.Message:11 msgid "Checks if two messages are not equal." msgstr "二つのメッセージが等しくないかを比較します。" -#: ../../api.rst:1531 +#: ../../api.rst:1591 msgid "The default message type. This is the same as regular messages." msgstr "デフォルトのメッセージ。これは通常のメッセージと同じです。" -#: ../../api.rst:1534 +#: ../../api.rst:1594 msgid "The system message when a user is added to a group private message or a thread." msgstr "ユーザーがグループプライベートメッセージまたはスレッドに追加されたときのシステムメッセージ。" -#: ../../api.rst:1538 +#: ../../api.rst:1598 msgid "The system message when a user is removed from a group private message or a thread." msgstr "ユーザーがグループプライベートメッセージまたはスレッドから削除されたときのシステムメッセージ。" -#: ../../api.rst:1542 +#: ../../api.rst:1602 msgid "The system message denoting call state, e.g. missed call, started call, etc." msgstr "通話の状態を示すシステムメッセージ。例: 不在着信、通話の開始、その他。" -#: ../../api.rst:1546 +#: ../../api.rst:1606 msgid "The system message denoting that a channel's name has been changed." msgstr "チャンネル名の変更を示すシステムメッセージ。" -#: ../../api.rst:1549 +#: ../../api.rst:1609 msgid "The system message denoting that a channel's icon has been changed." msgstr "チャンネルのアイコンの変更を示すシステムメッセージ。" -#: ../../api.rst:1552 +#: ../../api.rst:1612 msgid "The system message denoting that a pinned message has been added to a channel." msgstr "ピン留めの追加を示すシステムメッセージ。" -#: ../../api.rst:1555 +#: ../../api.rst:1615 msgid "The system message denoting that a new member has joined a Guild." msgstr "ギルドの新規メンバーの参加を示すシステムメッセージ。" -#: ../../api.rst:1559 +#: ../../api.rst:1619 msgid "The system message denoting that a member has \"nitro boosted\" a guild." msgstr "メンバーがギルドを「ニトロブースト」したことを表すシステムメッセージ。" -#: ../../api.rst:1562 +#: ../../api.rst:1622 msgid "The system message denoting that a member has \"nitro boosted\" a guild and it achieved level 1." msgstr "メンバーがギルドを「ニトロブースト」し、それによってギルドがレベル1に到達したことを表すシステムメッセージ。" -#: ../../api.rst:1566 +#: ../../api.rst:1626 msgid "The system message denoting that a member has \"nitro boosted\" a guild and it achieved level 2." msgstr "メンバーがギルドを「ニトロブースト」し、それによってギルドがレベル2に到達したことを表すシステムメッセージ。" -#: ../../api.rst:1570 +#: ../../api.rst:1630 msgid "The system message denoting that a member has \"nitro boosted\" a guild and it achieved level 3." msgstr "メンバーがギルドを「ニトロブースト」し、それによってギルドがレベル3に到達したことを表すシステムメッセージ。" -#: ../../api.rst:1574 +#: ../../api.rst:1634 msgid "The system message denoting that an announcement channel has been followed." msgstr "アナウンスチャンネルがフォローされたことを表すシステムメッセージ。" -#: ../../api.rst:1579 +#: ../../api.rst:1639 msgid "The system message denoting that a member is streaming in the guild." msgstr "メンバーがギルドでストリーミングしていることを表すシステムメッセージ。" -#: ../../api.rst:1584 +#: ../../api.rst:1644 msgid "The system message denoting that the guild is no longer eligible for Server Discovery." msgstr "ギルドがサーバー発見の資格を持たなくなったことを示すシステムメッセージ。" -#: ../../api.rst:1590 +#: ../../api.rst:1650 msgid "The system message denoting that the guild has become eligible again for Server Discovery." msgstr "ギルドがサーバー発見の資格を再び持ったことを示すシステムメッセージ。" -#: ../../api.rst:1596 +#: ../../api.rst:1656 msgid "The system message denoting that the guild has failed to meet the Server Discovery requirements for one week." msgstr "ギルドが1週間サーバー発見の要件を満たすことに失敗したことを示すシステムメッセージ。" -#: ../../api.rst:1602 +#: ../../api.rst:1662 msgid "The system message denoting that the guild has failed to meet the Server Discovery requirements for 3 weeks in a row." msgstr "ギルドが連続で3週間サーバー発見の要件を満たすことに失敗したというシステムメッセージ。" -#: ../../api.rst:1608 +#: ../../api.rst:1668 msgid "The system message denoting that a thread has been created. This is only sent if the thread has been created from an older message. The period of time required for a message to be considered old cannot be relied upon and is up to Discord." msgstr "スレッドが作成されたことを示すシステムメッセージ。これはスレッドが古いメッセージから作成されたときにのみ送信されます。メッセージが古いものとされる時間の閾値は信頼できずDiscord次第です。" -#: ../../api.rst:1616 +#: ../../api.rst:1676 msgid "The system message denoting that the author is replying to a message." msgstr "送信者がメッセージに返信したことを示すシステムメッセージ。" -#: ../../api.rst:1621 +#: ../../api.rst:1681 msgid "The system message denoting that a slash command was executed." msgstr "スラッシュコマンドを実行したことを示すシステムメッセージ。" -#: ../../api.rst:1626 +#: ../../api.rst:1686 msgid "The system message sent as a reminder to invite people to the guild." msgstr "人々をギルドに招待することのリマインダーとして送信されたシステムメッセージ。" -#: ../../api.rst:1631 +#: ../../api.rst:1691 msgid "The system message denoting the message in the thread that is the one that started the thread's conversation topic." msgstr "スレッドの会話を始めたメッセージであるスレッド内のメッセージを示すシステムメッセージ。" -#: ../../api.rst:1637 +#: ../../api.rst:1697 msgid "The system message denoting that a context menu command was executed." msgstr "コンテキストメニューコマンドを実行したことを示すシステムメッセージ。" -#: ../../api.rst:1642 +#: ../../api.rst:1702 msgid "The system message sent when an AutoMod rule is triggered. This is only sent if the rule is configured to sent an alert when triggered." msgstr "自動管理ルールが発動されたときに送信されるシステムメッセージ。ルールが発動されたときにアラートを送信するように設定されている場合にのみ送信されます。" -#: ../../api.rst:1648 +#: ../../api.rst:1708 msgid "The system message sent when a user purchases or renews a role subscription." msgstr "ユーザーがロールサブスクリプションを購入または更新したときに送信されるシステムメッセージ。" -#: ../../api.rst:1653 +#: ../../api.rst:1713 msgid "The system message sent when a user is given an advertisement to purchase a premium tier for an application during an interaction." msgstr "ユーザーに対し、インタラクション中にアプリケーションのプレミアム版を購入する広告を表示するときに送信されるシステムメッセージ。" -#: ../../api.rst:1659 +#: ../../api.rst:1719 +msgid "The system message sent when the stage starts." +msgstr "ステージ開始時に送信されるシステムメッセージ。" + +#: ../../api.rst:1724 +msgid "The system message sent when the stage ends." +msgstr "ステージ終了時に送信されるシステムメッセージ。" + +#: ../../api.rst:1729 +msgid "The system message sent when the stage speaker changes." +msgstr "ステージの発言者が変わるときに送信されるシステムメッセージ。" + +#: ../../api.rst:1734 +msgid "The system message sent when a user is requesting to speak by raising their hands." +msgstr "ユーザーが挙手して発言許可を求めているときに送信されるシステムメッセージ。" + +#: ../../api.rst:1739 +msgid "The system message sent when the stage topic changes." +msgstr "ステージのトピックが変わるときに送信されるシステムメッセージ。" + +#: ../../api.rst:1744 msgid "The system message sent when an application's premium subscription is purchased for the guild." msgstr "アプリケーションのプレミアムサブスクリプションがギルドで購入されたときに送信されるシステムメッセージ。" -#: ../../api.rst:1665 +#: ../../api.rst:1750 +msgid "The system message sent when security actions is enabled." +msgstr "" + +#: ../../api.rst:1756 +msgid "The system message sent when security actions is disabled." +msgstr "" + +#: ../../api.rst:1762 +msgid "The system message sent when a raid is reported." +msgstr "" + +#: ../../api.rst:1768 +msgid "The system message sent when a false alarm is reported." +msgstr "" + +#: ../../api.rst:1774 msgid "Represents Discord User flags." msgstr "Discordユーザーフラグを表します。" -#: ../../api.rst:1669 +#: ../../api.rst:1778 msgid "The user is a Discord Employee." msgstr "ユーザーはDiscordの従業員です。" -#: ../../api.rst:1672 +#: ../../api.rst:1781 msgid "The user is a Discord Partner." msgstr "ユーザーはDiscordパートナーです。" -#: ../../api.rst:1675 +#: ../../api.rst:1784 msgid "The user is a HypeSquad Events member." msgstr "ユーザーはHypeSquad Eventsメンバーです。" -#: ../../api.rst:1678 +#: ../../api.rst:1787 msgid "The user is a Bug Hunter." msgstr "ユーザーはバグハンターです。" -#: ../../api.rst:1681 +#: ../../api.rst:1790 msgid "The user has SMS recovery for Multi Factor Authentication enabled." msgstr "ユーザーの多要素認証のSMSリカバリーが有効になっています。" -#: ../../api.rst:1684 +#: ../../api.rst:1793 msgid "The user has dismissed the Discord Nitro promotion." msgstr "ユーザーはDiscord Nitroプロモーションを無視しました。" -#: ../../api.rst:1687 +#: ../../api.rst:1796 msgid "The user is a HypeSquad Bravery member." msgstr "ユーザーはHypeSquad Braveryのメンバーです。" -#: ../../api.rst:1690 +#: ../../api.rst:1799 msgid "The user is a HypeSquad Brilliance member." msgstr "ユーザーはHypeSquad Brillianceのメンバーです。" -#: ../../api.rst:1693 +#: ../../api.rst:1802 msgid "The user is a HypeSquad Balance member." msgstr "ユーザーはHypeSquad Balanceのメンバーです。" -#: ../../api.rst:1696 +#: ../../api.rst:1805 msgid "The user is an Early Supporter." msgstr "ユーザーは早期サポーターです。" -#: ../../api.rst:1699 +#: ../../api.rst:1808 msgid "The user is a Team User." msgstr "ユーザーはチームユーザーです。" -#: ../../api.rst:1702 +#: ../../api.rst:1811 msgid "The user is a system user (i.e. represents Discord officially)." msgstr "ユーザーはシステムユーザーです。(つまり、Discord公式を表しています)" -#: ../../api.rst:1705 +#: ../../api.rst:1814 msgid "The user has an unread system message." msgstr "ユーザーに未読のシステムメッセージがあります。" -#: ../../api.rst:1708 +#: ../../api.rst:1817 msgid "The user is a Bug Hunter Level 2." msgstr "ユーザーはバグハンターレベル2です。" -#: ../../api.rst:1711 +#: ../../api.rst:1820 msgid "The user is a Verified Bot." msgstr "ユーザーは認証済みボットです。" -#: ../../api.rst:1714 +#: ../../api.rst:1823 msgid "The user is an Early Verified Bot Developer." msgstr "ユーザーは早期認証Botデベロッパーです。" -#: ../../api.rst:1717 +#: ../../api.rst:1826 msgid "The user is a Moderator Programs Alumni." msgstr "ユーザーはモデレータープログラム卒業生です。" -#: ../../api.rst:1720 +#: ../../api.rst:1829 msgid "The user is a bot that only uses HTTP interactions and is shown in the online member list." msgstr "ユーザーはHTTPインタラクションのみを使用し、オンラインメンバーのリストに表示されるボットです。" -#: ../../api.rst:1725 +#: ../../api.rst:1834 msgid "The user is flagged as a spammer by Discord." msgstr "ユーザーはDiscordよりスパマーとフラグ付けされました。" -#: ../../api.rst:1731 +#: ../../api.rst:1840 msgid "The user is an active developer." msgstr "ユーザーはアクティブな開発者です。" -#: ../../api.rst:1737 +#: ../../api.rst:1846 msgid "Specifies the type of :class:`Activity`. This is used to check how to interpret the activity itself." msgstr ":class:`Activity` のタイプを指定します。これはアクティビティをどう解釈するか確認するために使われます。" -#: ../../api.rst:1742 +#: ../../api.rst:1851 msgid "An unknown activity type. This should generally not happen." msgstr "不明なアクティビティタイプ。これは通常起こらないはずです。" -#: ../../api.rst:1745 +#: ../../api.rst:1854 msgid "A \"Playing\" activity type." msgstr "プレイ中のアクティビティタイプ。" -#: ../../api.rst:1748 +#: ../../api.rst:1857 msgid "A \"Streaming\" activity type." msgstr "放送中のアクティビティタイプ。" -#: ../../api.rst:1751 +#: ../../api.rst:1860 msgid "A \"Listening\" activity type." msgstr "再生中のアクティビティタイプ。" -#: ../../api.rst:1754 +#: ../../api.rst:1863 msgid "A \"Watching\" activity type." msgstr "視聴中のアクティビティタイプ。" -#: ../../api.rst:1757 +#: ../../api.rst:1866 msgid "A custom activity type." msgstr "カスタムのアクティビティタイプ。" -#: ../../api.rst:1760 +#: ../../api.rst:1869 msgid "A competing activity type." msgstr "競争中のアクティビティタイプ。" -#: ../../api.rst:1766 +#: ../../api.rst:1875 msgid "Specifies a :class:`Guild`\\'s verification level, which is the criteria in which a member must meet before being able to send messages to the guild." msgstr ":class:`Guild` の認証レベルを指定します。これは、メンバーがギルドにメッセージを送信できるようになるまでの条件です。" -#: ../../api.rst:1775 +#: ../../api.rst:1884 msgid "Checks if two verification levels are equal." msgstr "認証レベルが等しいか確認します。" -#: ../../api.rst:1778 +#: ../../api.rst:1887 msgid "Checks if two verification levels are not equal." msgstr "認証レベルが等しくないか確認します。" -#: ../../api.rst:1781 +#: ../../api.rst:1890 msgid "Checks if a verification level is higher than another." msgstr "認証レベルがあるレベルより厳しいか確認します。" -#: ../../api.rst:1784 +#: ../../api.rst:1893 msgid "Checks if a verification level is lower than another." msgstr "認証レベルがあるレベルより緩いか確認します。" -#: ../../api.rst:1787 +#: ../../api.rst:1896 msgid "Checks if a verification level is higher or equal to another." msgstr "認証レベルがあるレベルと同じ、又は厳しいか確認します。" -#: ../../api.rst:1790 +#: ../../api.rst:1899 msgid "Checks if a verification level is lower or equal to another." msgstr "認証レベルがあるレベルと同じ、又は緩いか確認します。" -#: ../../api.rst:1794 +#: ../../api.rst:1903 msgid "No criteria set." msgstr "無制限。" -#: ../../api.rst:1797 +#: ../../api.rst:1906 msgid "Member must have a verified email on their Discord account." msgstr "メンバーはDiscordアカウントのメール認証を済ませないといけません。" -#: ../../api.rst:1800 +#: ../../api.rst:1909 msgid "Member must have a verified email and be registered on Discord for more than five minutes." msgstr "メンバーはメール認証をし、かつアカウント登録から5分経過しないといけません。" -#: ../../api.rst:1804 +#: ../../api.rst:1913 msgid "Member must have a verified email, be registered on Discord for more than five minutes, and be a member of the guild itself for more than ten minutes." msgstr "メンバーはメール認証をし、Discordのアカウント登録から5分経過し、かつ10分以上ギルドに所属していないといけません。" -#: ../../api.rst:1809 +#: ../../api.rst:1918 msgid "Member must have a verified phone on their Discord account." msgstr "メンバーはDiscordアカウントの電話番号認証を済ませないといけません。" -#: ../../api.rst:1813 +#: ../../api.rst:1922 msgid "Specifies whether a :class:`Guild` has notifications on for all messages or mentions only by default." msgstr ":class:`Guild` の通知対象のデフォルト設定をすべてのメッセージ、またはメンションのみに指定します。" -#: ../../api.rst:1821 +#: ../../api.rst:1930 msgid "Checks if two notification levels are equal." msgstr "通知レベルが等しいか確認します。" -#: ../../api.rst:1824 +#: ../../api.rst:1933 msgid "Checks if two notification levels are not equal." msgstr "通知レベルが等しくないか確認します。" -#: ../../api.rst:1827 +#: ../../api.rst:1936 msgid "Checks if a notification level is higher than another." msgstr "通知レベルがあるレベルより高いか確認します。" -#: ../../api.rst:1830 +#: ../../api.rst:1939 msgid "Checks if a notification level is lower than another." msgstr "通知レベルがあるレベルより低いか確認します。" -#: ../../api.rst:1833 +#: ../../api.rst:1942 msgid "Checks if a notification level is higher or equal to another." msgstr "通知レベルがあるレベルと同じ、又は高いか確認します。" -#: ../../api.rst:1836 +#: ../../api.rst:1945 msgid "Checks if a notification level is lower or equal to another." msgstr "通知レベルがあるレベルと同じ、又は低いか確認します。" -#: ../../api.rst:1840 +#: ../../api.rst:1949 msgid "Members receive notifications for every message regardless of them being mentioned." msgstr "メンバーは、メンションされているかどうかに関わらず、すべてのメッセージの通知を受け取ります。" -#: ../../api.rst:1843 +#: ../../api.rst:1952 msgid "Members receive notifications for messages they are mentioned in." msgstr "メンバーは自分がメンションされているメッセージの通知のみ受け取ります。" -#: ../../api.rst:1847 +#: ../../api.rst:1956 msgid "Specifies a :class:`Guild`\\'s explicit content filter, which is the machine learning algorithms that Discord uses to detect if an image contains pornography or otherwise explicit content." msgstr ":class:`Guild` の不適切な表現のフィルターを指定します。これはDiscordがポルノ画像や不適切な表現を検出するために使用している機械学習アルゴリズムです。" -#: ../../api.rst:1857 +#: ../../api.rst:1966 msgid "Checks if two content filter levels are equal." msgstr "表現のフィルターのレベルが等しいか確認します。" -#: ../../api.rst:1860 +#: ../../api.rst:1969 msgid "Checks if two content filter levels are not equal." msgstr "表現のフィルターのレベルが等しくないか確認します。" -#: ../../api.rst:1863 +#: ../../api.rst:1972 msgid "Checks if a content filter level is higher than another." msgstr "表現のフィルターのレベルが他のレベルより大きいか確認します。" -#: ../../api.rst:1866 +#: ../../api.rst:1975 msgid "Checks if a content filter level is lower than another." msgstr "表現のフィルターのレベルが他のレベルより小さいか確認します。" -#: ../../api.rst:1869 +#: ../../api.rst:1978 msgid "Checks if a content filter level is higher or equal to another." msgstr "表現のフィルターのレベルが他のレベルより大きい、または等しいか確認します。" -#: ../../api.rst:1872 +#: ../../api.rst:1981 msgid "Checks if a content filter level is lower or equal to another." msgstr "表現のフィルターのレベルが他のレベルより小さい、または等しいか確認します。" -#: ../../api.rst:1876 +#: ../../api.rst:1985 msgid "The guild does not have the content filter enabled." msgstr "ギルドで表現のフィルターが有効ではない。" -#: ../../api.rst:1879 +#: ../../api.rst:1988 msgid "The guild has the content filter enabled for members without a role." msgstr "ギルドでロールを持たないメンバーに対して表現のフィルターが有効化されている。" -#: ../../api.rst:1882 +#: ../../api.rst:1991 msgid "The guild has the content filter enabled for every member." msgstr "ギルドで、すべてのメンバーに対して表現のフィルターが有効化されている。" -#: ../../api.rst:1886 +#: ../../api.rst:1995 msgid "Specifies a :class:`Member` 's status." msgstr ":class:`Member` のステータスを指定します。" -#: ../../api.rst:1890 +#: ../../api.rst:1999 msgid "The member is online." msgstr "メンバーがオンライン。" -#: ../../api.rst:1893 +#: ../../api.rst:2002 msgid "The member is offline." msgstr "メンバーがオフライン。" -#: ../../api.rst:1896 +#: ../../api.rst:2005 msgid "The member is idle." msgstr "メンバーが退席中。" -#: ../../api.rst:1899 +#: ../../api.rst:2008 msgid "The member is \"Do Not Disturb\"." msgstr "メンバーが取り込み中。" -#: ../../api.rst:1902 +#: ../../api.rst:2011 msgid "An alias for :attr:`dnd`." msgstr ":attr:`dnd` のエイリアス。" -#: ../../api.rst:1905 +#: ../../api.rst:2014 msgid "The member is \"invisible\". In reality, this is only used when sending a presence a la :meth:`Client.change_presence`. When you receive a user's presence this will be :attr:`offline` instead." msgstr "メンバーがオンライン状態を隠す。実際には、これは :meth:`Client.change_presence` でプレゼンスを送信する時のみ使用します。ユーザーのプレゼンスを受け取った場合、これは :attr:`offline` に置き換えられます。" -#: ../../api.rst:1912 +#: ../../api.rst:2021 msgid "Represents the type of action being done for a :class:`AuditLogEntry`\\, which is retrievable via :meth:`Guild.audit_logs`." msgstr ":class:`AuditLogEntry` で行われた動作の種類を取得します。AuditLogEntryは :meth:`Guild.audit_logs` で取得可能です。" -#: ../../api.rst:1917 +#: ../../api.rst:2026 msgid "The guild has updated. Things that trigger this include:" msgstr "ギルドの更新。このトリガーとなるものは以下のとおりです。" -#: ../../api.rst:1919 +#: ../../api.rst:2028 msgid "Changing the guild vanity URL" msgstr "ギルドのvanity URLの変更" -#: ../../api.rst:1920 +#: ../../api.rst:2029 msgid "Changing the guild invite splash" msgstr "ギルドの招待時のスプラッシュ画像の変更" -#: ../../api.rst:1921 +#: ../../api.rst:2030 msgid "Changing the guild AFK channel or timeout" msgstr "ギルドのAFKチャンネル、またはタイムアウトの変更" -#: ../../api.rst:1922 +#: ../../api.rst:2031 msgid "Changing the guild voice server region" msgstr "ギルドの音声通話のサーバーリージョンの変更" -#: ../../api.rst:1923 +#: ../../api.rst:2032 msgid "Changing the guild icon, banner, or discovery splash" msgstr "ギルドのアイコン、バナー、ディスカバリースプラッシュの変更" -#: ../../api.rst:1924 +#: ../../api.rst:2033 msgid "Changing the guild moderation settings" msgstr "ギルドの管理設定の変更" -#: ../../api.rst:1925 +#: ../../api.rst:2034 msgid "Changing things related to the guild widget" msgstr "ギルドのウィジェットに関連するものの変更" -#: ../../api.rst:1927 +#: ../../api.rst:2036 msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Guild`." msgstr "これが上記のactionならば、:attr:`~AuditLogEntry.target` の型は :class:`Guild` になります。" -#: ../../api.rst:1930 -#: ../../api.rst:1964 -#: ../../api.rst:1983 -#: ../../api.rst:2005 -#: ../../api.rst:2024 +#: ../../api.rst:2039 +#: ../../api.rst:2075 +#: ../../api.rst:2094 +#: ../../api.rst:2119 +#: ../../api.rst:2141 msgid "Possible attributes for :class:`AuditLogDiff`:" msgstr ":class:`AuditLogDiff` から、以下の属性を参照できます:" -#: ../../api.rst:1932 +#: ../../api.rst:2041 msgid ":attr:`~AuditLogDiff.afk_channel`" msgstr ":attr:`~AuditLogDiff.afk_channel`" -#: ../../api.rst:1933 +#: ../../api.rst:2042 msgid ":attr:`~AuditLogDiff.system_channel`" msgstr ":attr:`~AuditLogDiff.system_channel`" -#: ../../api.rst:1934 +#: ../../api.rst:2043 msgid ":attr:`~AuditLogDiff.afk_timeout`" msgstr ":attr:`~AuditLogDiff.afk_timeout`" -#: ../../api.rst:1935 +#: ../../api.rst:2044 msgid ":attr:`~AuditLogDiff.default_notifications`" msgstr ":attr:`~AuditLogDiff.default_notifications`" -#: ../../api.rst:1936 +#: ../../api.rst:2045 msgid ":attr:`~AuditLogDiff.explicit_content_filter`" msgstr ":attr:`~AuditLogDiff.explicit_content_filter`" -#: ../../api.rst:1937 +#: ../../api.rst:2046 msgid ":attr:`~AuditLogDiff.mfa_level`" msgstr ":attr:`~AuditLogDiff.mfa_level`" -#: ../../api.rst:1938 -#: ../../api.rst:1966 -#: ../../api.rst:1985 -#: ../../api.rst:2007 -#: ../../api.rst:2181 +#: ../../api.rst:2047 +#: ../../api.rst:2077 +#: ../../api.rst:2096 +#: ../../api.rst:2121 +#: ../../api.rst:2308 msgid ":attr:`~AuditLogDiff.name`" msgstr ":attr:`~AuditLogDiff.name`" -#: ../../api.rst:1939 +#: ../../api.rst:2048 msgid ":attr:`~AuditLogDiff.owner`" msgstr ":attr:`~AuditLogDiff.owner`" -#: ../../api.rst:1940 +#: ../../api.rst:2049 msgid ":attr:`~AuditLogDiff.splash`" msgstr ":attr:`~AuditLogDiff.splash`" -#: ../../api.rst:1941 +#: ../../api.rst:2050 msgid ":attr:`~AuditLogDiff.discovery_splash`" msgstr ":attr:`~AuditLogDiff.discovery_splash`" -#: ../../api.rst:1942 -#: ../../api.rst:2179 -#: ../../api.rst:2202 +#: ../../api.rst:2051 +#: ../../api.rst:2306 +#: ../../api.rst:2329 msgid ":attr:`~AuditLogDiff.icon`" msgstr ":attr:`~AuditLogDiff.icon`" -#: ../../api.rst:1943 +#: ../../api.rst:2052 msgid ":attr:`~AuditLogDiff.banner`" msgstr ":attr:`~AuditLogDiff.banner`" -#: ../../api.rst:1944 +#: ../../api.rst:2053 msgid ":attr:`~AuditLogDiff.vanity_url_code`" msgstr ":attr:`~AuditLogDiff.vanity_url_code`" -#: ../../api.rst:1945 -#: ../../api.rst:2476 -#: ../../api.rst:2495 -#: ../../api.rst:2514 +#: ../../api.rst:2054 +#: ../../api.rst:2603 +#: ../../api.rst:2622 +#: ../../api.rst:2641 msgid ":attr:`~AuditLogDiff.description`" msgstr ":attr:`~AuditLogDiff.description`" -#: ../../api.rst:1946 +#: ../../api.rst:2055 msgid ":attr:`~AuditLogDiff.preferred_locale`" msgstr ":attr:`~AuditLogDiff.preferred_locale`" -#: ../../api.rst:1947 +#: ../../api.rst:2056 msgid ":attr:`~AuditLogDiff.prune_delete_days`" msgstr ":attr:`~AuditLogDiff.prune_delete_days`" -#: ../../api.rst:1948 +#: ../../api.rst:2057 msgid ":attr:`~AuditLogDiff.public_updates_channel`" msgstr ":attr:`~AuditLogDiff.public_updates_channel`" -#: ../../api.rst:1949 +#: ../../api.rst:2058 msgid ":attr:`~AuditLogDiff.rules_channel`" msgstr ":attr:`~AuditLogDiff.rules_channel`" -#: ../../api.rst:1950 +#: ../../api.rst:2059 msgid ":attr:`~AuditLogDiff.verification_level`" msgstr ":attr:`~AuditLogDiff.verification_level`" -#: ../../api.rst:1951 +#: ../../api.rst:2060 msgid ":attr:`~AuditLogDiff.widget_channel`" msgstr ":attr:`~AuditLogDiff.widget_channel`" -#: ../../api.rst:1952 +#: ../../api.rst:2061 msgid ":attr:`~AuditLogDiff.widget_enabled`" msgstr ":attr:`~AuditLogDiff.widget_enabled`" -#: ../../api.rst:1956 +#: ../../api.rst:2062 +msgid ":attr:`~AuditLogDiff.premium_progress_bar_enabled`" +msgstr ":attr:`~AuditLogDiff.premium_progress_bar_enabled`" + +#: ../../api.rst:2063 +msgid ":attr:`~AuditLogDiff.system_channel_flags`" +msgstr ":attr:`~AuditLogDiff.system_channel_flags`" + +#: ../../api.rst:2067 msgid "A new channel was created." msgstr "チャンネルの作成。" -#: ../../api.rst:1958 +#: ../../api.rst:2069 msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is either a :class:`abc.GuildChannel` or :class:`Object` with an ID." msgstr "これが上記のactionならば、 :attr:`~AuditLogEntry.target` の型は、IDが設定されている :class:`abc.GuildChannel` か、 :class:`Object` のいずれかになります。" -#: ../../api.rst:1961 +#: ../../api.rst:2072 msgid "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after`." msgstr ":class:`Object` の場合、 :attr:`~AuditLogEntry.after` を使用して、より詳細な情報を持つオブジェクトを見つけることができます。" -#: ../../api.rst:1967 -#: ../../api.rst:1986 -#: ../../api.rst:2008 -#: ../../api.rst:2029 -#: ../../api.rst:2045 +#: ../../api.rst:2078 +#: ../../api.rst:2097 +#: ../../api.rst:2122 +#: ../../api.rst:2146 +#: ../../api.rst:2162 msgid ":attr:`~AuditLogDiff.type`" msgstr ":attr:`~AuditLogDiff.type`" -#: ../../api.rst:1968 -#: ../../api.rst:1988 -#: ../../api.rst:2009 +#: ../../api.rst:2079 +#: ../../api.rst:2099 +#: ../../api.rst:2123 msgid ":attr:`~AuditLogDiff.overwrites`" msgstr ":attr:`~AuditLogDiff.overwrites`" -#: ../../api.rst:1972 +#: ../../api.rst:2083 msgid "A channel was updated. Things that trigger this include:" msgstr "チャンネルの更新。これのトリガーとなるものは以下の通りです。" -#: ../../api.rst:1974 +#: ../../api.rst:2085 msgid "The channel name or topic was changed" msgstr "チャンネルのチャンネルトピックの変更、または名前の変更。" -#: ../../api.rst:1975 +#: ../../api.rst:2086 msgid "The channel bitrate was changed" msgstr "チャンネルのビットレートの変更。" -#: ../../api.rst:1977 -#: ../../api.rst:2015 +#: ../../api.rst:2088 +#: ../../api.rst:2132 msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`abc.GuildChannel` or :class:`Object` with an ID." msgstr "これが上記のactionならば、 :attr:`~AuditLogEntry.target` の型は、IDが設定されている :class:`abc.GuildChannel` か、 :class:`Object` のいずれかになります。" -#: ../../api.rst:1980 +#: ../../api.rst:2091 msgid "A more filled out object in the :class:`Object` case can be found by using :attr:`~AuditLogEntry.after` or :attr:`~AuditLogEntry.before`." msgstr ":class:`Object` の場合、 :attr:`~AuditLogEntry.after` または :attr:`~AuditLogEntry.before` を使用して、より詳細な情報を持つオブジェクトを見つけることができます。" -#: ../../api.rst:1987 +#: ../../api.rst:2098 msgid ":attr:`~AuditLogDiff.position`" msgstr ":attr:`~AuditLogDiff.position`" -#: ../../api.rst:1989 -#: ../../api.rst:2436 -#: ../../api.rst:2451 +#: ../../api.rst:2100 +#: ../../api.rst:2563 +#: ../../api.rst:2578 msgid ":attr:`~AuditLogDiff.topic`" msgstr ":attr:`~AuditLogDiff.topic`" -#: ../../api.rst:1990 +#: ../../api.rst:2101 msgid ":attr:`~AuditLogDiff.bitrate`" msgstr ":attr:`~AuditLogDiff.bitrate`" -#: ../../api.rst:1991 +#: ../../api.rst:2102 msgid ":attr:`~AuditLogDiff.rtc_region`" msgstr ":attr:`~AuditLogDiff.rtc_region`" -#: ../../api.rst:1992 +#: ../../api.rst:2103 msgid ":attr:`~AuditLogDiff.video_quality_mode`" msgstr ":attr:`~AuditLogDiff.video_quality_mode`" -#: ../../api.rst:1993 +#: ../../api.rst:2104 msgid ":attr:`~AuditLogDiff.default_auto_archive_duration`" msgstr ":attr:`~AuditLogDiff.default_auto_archive_duration`" -#: ../../api.rst:1997 +#: ../../api.rst:2105 +#: ../../api.rst:2125 +msgid ":attr:`~AuditLogDiff.nsfw`" +msgstr ":attr:`~AuditLogDiff.nsfw`" + +#: ../../api.rst:2106 +#: ../../api.rst:2126 +msgid ":attr:`~AuditLogDiff.slowmode_delay`" +msgstr ":attr:`~AuditLogDiff.slowmode_delay`" + +#: ../../api.rst:2107 +msgid ":attr:`~AuditLogDiff.user_limit`" +msgstr ":attr:`~AuditLogDiff.user_limit`" + +#: ../../api.rst:2111 msgid "A channel was deleted." msgstr "チャンネルの削除。" -#: ../../api.rst:1999 +#: ../../api.rst:2113 msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is an :class:`Object` with an ID." msgstr "これが上記のactionならば、 :attr:`~AuditLogEntry.target` の型は、IDが設定されている :class:`Object` になります。" -#: ../../api.rst:2002 +#: ../../api.rst:2116 msgid "A more filled out object can be found by using the :attr:`~AuditLogEntry.before` object." msgstr ":attr:`~AuditLogEntry.before` オブジェクトを使用すると、より詳細な情報が見つかります。" -#: ../../api.rst:2013 +#: ../../api.rst:2124 +msgid ":attr:`~AuditLogDiff.flags`" +msgstr ":attr:`~AuditLogDiff.flags`" + +#: ../../api.rst:2130 msgid "A channel permission overwrite was created." msgstr "チャンネルにおける権限の上書き設定の作成。" -#: ../../api.rst:2018 +#: ../../api.rst:2135 msgid "When this is the action, the type of :attr:`~AuditLogEntry.extra` is either a :class:`Role` or :class:`Member`. If the object is not found then it is a :class:`Object` with an ID being filled, a name, and a ``type`` attribute set to either ``'role'`` or ``'member'`` to help dictate what type of ID it is." msgstr "この場合には、 :attr:`~AuditLogEntry.extra` は :class:`Role` か :class:`Member` です。もしオブジェクトが見つからない場合はid、name、 ``'role'`` か ``'member'`` である ``type`` 属性がある :class:`Object` です。" -#: ../../api.rst:2026 -#: ../../api.rst:2042 -#: ../../api.rst:2057 +#: ../../api.rst:2143 +#: ../../api.rst:2159 +#: ../../api.rst:2174 msgid ":attr:`~AuditLogDiff.deny`" msgstr ":attr:`~AuditLogDiff.deny`" -#: ../../api.rst:2027 -#: ../../api.rst:2043 -#: ../../api.rst:2058 +#: ../../api.rst:2144 +#: ../../api.rst:2160 +#: ../../api.rst:2175 msgid ":attr:`~AuditLogDiff.allow`" msgstr ":attr:`~AuditLogDiff.allow`" -#: ../../api.rst:2028 -#: ../../api.rst:2044 -#: ../../api.rst:2059 +#: ../../api.rst:2145 +#: ../../api.rst:2161 +#: ../../api.rst:2176 msgid ":attr:`~AuditLogDiff.id`" msgstr ":attr:`~AuditLogDiff.id`" -#: ../../api.rst:2033 +#: ../../api.rst:2150 msgid "A channel permission overwrite was changed, this is typically when the permission values change." msgstr "チャンネルの権限の上書きの変更。典型的な例は、権限が変更された場合です。" -#: ../../api.rst:2036 -#: ../../api.rst:2051 +#: ../../api.rst:2153 +#: ../../api.rst:2168 msgid "See :attr:`overwrite_create` for more information on how the :attr:`~AuditLogEntry.target` and :attr:`~AuditLogEntry.extra` fields are set." msgstr ":attr:`overwrite_create` に、 :attr:`~AuditLogEntry.target` と :attr:`~AuditLogEntry.extra` についての説明があります。" -#: ../../api.rst:2049 +#: ../../api.rst:2166 msgid "A channel permission overwrite was deleted." msgstr "チャンネルにおける権限の上書き設定の削除。" -#: ../../api.rst:2064 +#: ../../api.rst:2181 msgid "A member was kicked." msgstr "メンバーのキック。" -#: ../../api.rst:2066 +#: ../../api.rst:2183 msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` or :class:`Object` who got kicked." msgstr "これが上記のactionならば、 :attr:`~AuditLogEntry.target` の型は、キックされた :class:`User` あるいは :class:`Object` になります。" -#: ../../api.rst:2069 -#: ../../api.rst:2084 -#: ../../api.rst:2093 -#: ../../api.rst:2102 +#: ../../api.rst:2186 +#: ../../api.rst:2251 +#: ../../api.rst:2278 +#: ../../api.rst:2486 +msgid "When this is the action, the type of :attr:`~AuditLogEntry.extra` is set to an unspecified proxy object with one attribute:" +msgstr "これが上記のactionならば、 :attr:`~AuditLogEntry.extra` は以下の属性を持つプロキシオブジェクトになります:" + +#: ../../api.rst:2189 +#: ../../api.rst:2254 +msgid "``integration_type``: An optional string that denotes the type of integration that did the action." +msgstr "" + +#: ../../api.rst:2191 +#: ../../api.rst:2206 +#: ../../api.rst:2215 +#: ../../api.rst:2224 msgid "When this is the action, :attr:`~AuditLogEntry.changes` is empty." msgstr "これが上記のactionなら、:attr:`~AuditLogEntry.changes` は空になります。" -#: ../../api.rst:2073 +#: ../../api.rst:2195 msgid "A member prune was triggered." msgstr "非アクティブメンバーの一括キック。" -#: ../../api.rst:2075 +#: ../../api.rst:2197 msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is set to ``None``." msgstr "これが上記のactionならば、:attr:`~AuditLogEntry.target` の型は ``None`` に設定されます。" -#: ../../api.rst:2078 -#: ../../api.rst:2138 -#: ../../api.rst:2346 -#: ../../api.rst:2373 -#: ../../api.rst:2388 +#: ../../api.rst:2200 +#: ../../api.rst:2265 +#: ../../api.rst:2473 +#: ../../api.rst:2500 +#: ../../api.rst:2515 msgid "When this is the action, the type of :attr:`~AuditLogEntry.extra` is set to an unspecified proxy object with two attributes:" msgstr "これが上記のactionならば、 :attr:`~AuditLogEntry.extra` は以下の属性を持つプロキシオブジェクトになります:" -#: ../../api.rst:2081 -msgid "``delete_members_days``: An integer specifying how far the prune was." +#: ../../api.rst:2203 +msgid "``delete_member_days``: An integer specifying how far the prune was." msgstr "``delete_members_days`` : 一括キック対象の期間を示す整数。" -#: ../../api.rst:2082 +#: ../../api.rst:2204 msgid "``members_removed``: An integer specifying how many members were removed." msgstr "``members_removed`` : 除去されたメンバーの数を示す整数。" -#: ../../api.rst:2088 +#: ../../api.rst:2210 msgid "A member was banned." msgstr "メンバーのBAN。" -#: ../../api.rst:2090 +#: ../../api.rst:2212 msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` or :class:`Object` who got banned." msgstr "これが上記のactionならば、 :attr:`~AuditLogEntry.target` の型は、BANされた :class:`User` あるいは :class:`Object` になります。" -#: ../../api.rst:2097 +#: ../../api.rst:2219 msgid "A member was unbanned." msgstr "メンバーのBANの解除。" -#: ../../api.rst:2099 +#: ../../api.rst:2221 msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`User` or :class:`Object` who got unbanned." msgstr "これが上記のactionならば、 :attr:`~AuditLogEntry.target` の型は、BAN解除された :class:`User` あるいは :class:`Object` になります。" -#: ../../api.rst:2106 +#: ../../api.rst:2228 msgid "A member has updated. This triggers in the following situations:" msgstr "メンバーの何らかの更新。これのトリガーとなるのは以下の場合です:" -#: ../../api.rst:2108 +#: ../../api.rst:2230 msgid "A nickname was changed" msgstr "メンバーのニックネームの変更。" -#: ../../api.rst:2109 +#: ../../api.rst:2231 msgid "They were server muted or deafened (or it was undo'd)" msgstr "サーバー側でミュートやスピーカーミュートされた(あるいは解除された)場合。" -#: ../../api.rst:2111 +#: ../../api.rst:2233 msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member`, :class:`User`, or :class:`Object` who got updated." msgstr "これが上記のactionならば、 :attr:`~AuditLogEntry.target` の型は、更新の行われた :class:`Member` 、 :class:`User` 、または :class:`Object` になります。" -#: ../../api.rst:2116 +#: ../../api.rst:2238 msgid ":attr:`~AuditLogDiff.nick`" msgstr ":attr:`~AuditLogDiff.nick`" -#: ../../api.rst:2117 +#: ../../api.rst:2239 msgid ":attr:`~AuditLogDiff.mute`" msgstr ":attr:`~AuditLogDiff.mute`" -#: ../../api.rst:2118 +#: ../../api.rst:2240 msgid ":attr:`~AuditLogDiff.deaf`" msgstr ":attr:`~AuditLogDiff.deaf`" -#: ../../api.rst:2119 +#: ../../api.rst:2241 msgid ":attr:`~AuditLogDiff.timed_out_until`" msgstr ":attr:`~AuditLogDiff.timed_out_until`" -#: ../../api.rst:2123 +#: ../../api.rst:2245 msgid "A member's role has been updated. This triggers when a member either gains a role or loses a role." msgstr "メンバーのロールの更新。これは、メンバーがロールを得たり、失った場合に発生します。" -#: ../../api.rst:2126 +#: ../../api.rst:2248 msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member`, :class:`User`, or :class:`Object` who got the role." msgstr "これが上記のactionならば、 :attr:`~AuditLogEntry.target` の型は、ロールの更新が行われた :class:`Member` 、 :class:`User` 、または :class:`Object` になります。" -#: ../../api.rst:2131 +#: ../../api.rst:2258 msgid ":attr:`~AuditLogDiff.roles`" msgstr ":attr:`~AuditLogDiff.roles`" -#: ../../api.rst:2135 +#: ../../api.rst:2262 msgid "A member's voice channel has been updated. This triggers when a member is moved to a different voice channel." msgstr "メンバーのボイスチャンネルの更新。これは、メンバーが他のボイスチャンネルに移動させられた時に発生します。" -#: ../../api.rst:2141 +#: ../../api.rst:2268 msgid "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the members were moved." msgstr "``channel`` : メンバーの移動先の :class:`TextChannel` か チャンネルIDを持つ :class:`Object` 。" -#: ../../api.rst:2142 +#: ../../api.rst:2269 msgid "``count``: An integer specifying how many members were moved." msgstr "``count`` : 移動されたメンバーの数を示す整数。" -#: ../../api.rst:2148 +#: ../../api.rst:2275 msgid "A member's voice state has changed. This triggers when a member is force disconnected from voice." msgstr "メンバーのボイス状態の変更。これはメンバーがボイスから強制的に切断された場合に発生します。" -#: ../../api.rst:2151 -#: ../../api.rst:2359 -msgid "When this is the action, the type of :attr:`~AuditLogEntry.extra` is set to an unspecified proxy object with one attribute:" -msgstr "これが上記のactionならば、 :attr:`~AuditLogEntry.extra` は以下の属性を持つプロキシオブジェクトになります:" - -#: ../../api.rst:2154 +#: ../../api.rst:2281 msgid "``count``: An integer specifying how many members were disconnected." msgstr "``count`` : 切断されたメンバーの数を示す整数。" -#: ../../api.rst:2160 +#: ../../api.rst:2287 msgid "A bot was added to the guild." msgstr "ボットのギルドへの追加。" -#: ../../api.rst:2162 +#: ../../api.rst:2289 msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member`, :class:`User`, or :class:`Object` which was added to the guild." msgstr "これが上記のactionならば、 :attr:`~AuditLogEntry.target` の型は、ギルドに追加された :class:`Member` 、 :class:`User` 、または :class:`Object` になります。" -#: ../../api.rst:2169 +#: ../../api.rst:2296 msgid "A new role was created." msgstr "新しいロールの作成。" -#: ../../api.rst:2171 -#: ../../api.rst:2194 -#: ../../api.rst:2211 +#: ../../api.rst:2298 +#: ../../api.rst:2321 +#: ../../api.rst:2338 msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Role` or a :class:`Object` with the ID." msgstr "これが上記のactionならば、 :attr:`~AuditLogEntry.target` の型は、IDが設定されている :class:`Role` か、 :class:`Object` のいずれかになります。" -#: ../../api.rst:2176 -#: ../../api.rst:2199 -#: ../../api.rst:2216 +#: ../../api.rst:2303 +#: ../../api.rst:2326 +#: ../../api.rst:2343 msgid ":attr:`~AuditLogDiff.colour`" msgstr ":attr:`~AuditLogDiff.colour`" -#: ../../api.rst:2177 -#: ../../api.rst:2200 -#: ../../api.rst:2217 +#: ../../api.rst:2304 +#: ../../api.rst:2327 +#: ../../api.rst:2344 msgid ":attr:`~AuditLogDiff.mentionable`" msgstr ":attr:`~AuditLogDiff.mentionable`" -#: ../../api.rst:2178 -#: ../../api.rst:2201 -#: ../../api.rst:2218 +#: ../../api.rst:2305 +#: ../../api.rst:2328 +#: ../../api.rst:2345 msgid ":attr:`~AuditLogDiff.hoist`" msgstr ":attr:`~AuditLogDiff.hoist`" -#: ../../api.rst:2180 -#: ../../api.rst:2203 +#: ../../api.rst:2307 +#: ../../api.rst:2330 msgid ":attr:`~AuditLogDiff.unicode_emoji`" msgstr ":attr:`~AuditLogDiff.unicode_emoji`" -#: ../../api.rst:2182 -#: ../../api.rst:2205 -#: ../../api.rst:2220 +#: ../../api.rst:2309 +#: ../../api.rst:2332 +#: ../../api.rst:2347 msgid ":attr:`~AuditLogDiff.permissions`" msgstr ":attr:`~AuditLogDiff.permissions`" -#: ../../api.rst:2186 +#: ../../api.rst:2313 msgid "A role was updated. This triggers in the following situations:" msgstr "ロールの何らかの更新。これのトリガーとなるのは以下の場合です:" -#: ../../api.rst:2188 +#: ../../api.rst:2315 msgid "The name has changed" msgstr "名前の更新。" -#: ../../api.rst:2189 +#: ../../api.rst:2316 msgid "The permissions have changed" msgstr "権限の更新。" -#: ../../api.rst:2190 +#: ../../api.rst:2317 msgid "The colour has changed" msgstr "色の更新。" -#: ../../api.rst:2191 +#: ../../api.rst:2318 msgid "The role icon (or unicode emoji) has changed" msgstr "ロールアイコン (または Unicode 絵文字)の変更。" -#: ../../api.rst:2192 +#: ../../api.rst:2319 msgid "Its hoist/mentionable state has changed" msgstr "ロールメンバーのオンライン表示、ロールへのメンションの許可の変更。" -#: ../../api.rst:2209 +#: ../../api.rst:2336 msgid "A role was deleted." msgstr "ロールの削除。" -#: ../../api.rst:2224 +#: ../../api.rst:2351 msgid "An invite was created." msgstr "招待の作成。" -#: ../../api.rst:2226 +#: ../../api.rst:2353 msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was created." msgstr "これが上記のactionならば、:attr:`~AuditLogEntry.target` の型は作成された招待に該当する :class:`Invite` になります。" -#: ../../api.rst:2231 -#: ../../api.rst:2255 +#: ../../api.rst:2358 +#: ../../api.rst:2382 msgid ":attr:`~AuditLogDiff.max_age`" msgstr ":attr:`~AuditLogDiff.max_age`" -#: ../../api.rst:2232 -#: ../../api.rst:2256 +#: ../../api.rst:2359 +#: ../../api.rst:2383 msgid ":attr:`~AuditLogDiff.code`" msgstr ":attr:`~AuditLogDiff.code`" -#: ../../api.rst:2233 -#: ../../api.rst:2257 +#: ../../api.rst:2360 +#: ../../api.rst:2384 msgid ":attr:`~AuditLogDiff.temporary`" msgstr ":attr:`~AuditLogDiff.temporary`" -#: ../../api.rst:2234 -#: ../../api.rst:2258 +#: ../../api.rst:2361 +#: ../../api.rst:2385 msgid ":attr:`~AuditLogDiff.inviter`" msgstr ":attr:`~AuditLogDiff.inviter`" -#: ../../api.rst:2235 -#: ../../api.rst:2259 -#: ../../api.rst:2272 -#: ../../api.rst:2288 -#: ../../api.rst:2301 +#: ../../api.rst:2362 +#: ../../api.rst:2386 +#: ../../api.rst:2399 +#: ../../api.rst:2415 +#: ../../api.rst:2428 msgid ":attr:`~AuditLogDiff.channel`" msgstr ":attr:`~AuditLogDiff.channel`" -#: ../../api.rst:2236 -#: ../../api.rst:2260 +#: ../../api.rst:2363 +#: ../../api.rst:2387 msgid ":attr:`~AuditLogDiff.uses`" msgstr ":attr:`~AuditLogDiff.uses`" -#: ../../api.rst:2237 -#: ../../api.rst:2261 +#: ../../api.rst:2364 +#: ../../api.rst:2388 msgid ":attr:`~AuditLogDiff.max_uses`" msgstr ":attr:`~AuditLogDiff.max_uses`" -#: ../../api.rst:2241 +#: ../../api.rst:2368 msgid "An invite was updated." msgstr "招待の更新。" -#: ../../api.rst:2243 +#: ../../api.rst:2370 msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was updated." msgstr "これが上記のactionならば、:attr:`~AuditLogEntry.target` の型は更新された招待に該当する :class:`Invite` になります。" -#: ../../api.rst:2248 +#: ../../api.rst:2375 msgid "An invite was deleted." msgstr "招待の削除。" -#: ../../api.rst:2250 +#: ../../api.rst:2377 msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Invite` that was deleted." msgstr "これが上記のactionならば、:attr:`~AuditLogEntry.target` のタイプは削除された招待に該当する :class:`Invite` になります。" -#: ../../api.rst:2265 +#: ../../api.rst:2392 msgid "A webhook was created." msgstr "Webhookの作成。" -#: ../../api.rst:2267 -#: ../../api.rst:2283 -#: ../../api.rst:2296 +#: ../../api.rst:2394 +#: ../../api.rst:2410 +#: ../../api.rst:2423 msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the webhook ID." msgstr "これが上記のactionならば、 :attr:`~AuditLogEntry.target` のタイプは、webhook IDが設定されている :class:`Object` になります。" -#: ../../api.rst:2274 -#: ../../api.rst:2303 +#: ../../api.rst:2401 +#: ../../api.rst:2430 msgid ":attr:`~AuditLogDiff.type` (always set to ``1`` if so)" msgstr ":attr:`~AuditLogDiff.type` (その場合は常に ``1`` です。)" -#: ../../api.rst:2278 +#: ../../api.rst:2405 msgid "A webhook was updated. This trigger in the following situations:" msgstr "Webhookの更新。これのトリガーとなるのは以下の場合です。" -#: ../../api.rst:2280 +#: ../../api.rst:2407 msgid "The webhook name changed" msgstr "Webhook名が変更されたとき" -#: ../../api.rst:2281 +#: ../../api.rst:2408 msgid "The webhook channel changed" msgstr "Webhookチャンネルが変更されたとき" -#: ../../api.rst:2290 +#: ../../api.rst:2417 msgid ":attr:`~AuditLogDiff.avatar`" msgstr ":attr:`~AuditLogDiff.avatar`" -#: ../../api.rst:2294 +#: ../../api.rst:2421 msgid "A webhook was deleted." msgstr "Webhookの削除。" -#: ../../api.rst:2307 +#: ../../api.rst:2434 msgid "An emoji was created." msgstr "絵文字の作成。" -#: ../../api.rst:2309 -#: ../../api.rst:2320 +#: ../../api.rst:2436 +#: ../../api.rst:2447 msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Emoji` or :class:`Object` with the emoji ID." msgstr "これが上記のactionならば、 :attr:`~AuditLogEntry.target` の型は、 :class:`Emoji` または 絵文字IDが設定された :class:`Object` です。" -#: ../../api.rst:2318 +#: ../../api.rst:2445 msgid "An emoji was updated. This triggers when the name has changed." msgstr "絵文字に対する何らかの更新。これは名前が変更されたときに発生します。" -#: ../../api.rst:2329 +#: ../../api.rst:2456 msgid "An emoji was deleted." msgstr "絵文字の削除。" -#: ../../api.rst:2331 +#: ../../api.rst:2458 msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Object` with the emoji ID." msgstr "これが上記のactionならば、 :attr:`~AuditLogEntry.target` の型は、絵文字IDが設定されている :class:`Object` になります。" -#: ../../api.rst:2340 +#: ../../api.rst:2467 msgid "A message was deleted by a moderator. Note that this only triggers if the message was deleted by someone other than the author." msgstr "管理者によるメッセージの削除。なお、これのトリガーとなるのは、メッセージが投稿者以外によって削除された場合のみです。" -#: ../../api.rst:2343 +#: ../../api.rst:2470 msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member`, :class:`User`, or :class:`Object` who had their message deleted." msgstr "これが上記のactionならば、 :attr:`~AuditLogEntry.target` の型は、削除されたメッセージの送信者である :class:`Member` 、 :class:`User` 、または :class:`Object` になります。" -#: ../../api.rst:2349 -#: ../../api.rst:2362 +#: ../../api.rst:2476 +#: ../../api.rst:2489 msgid "``count``: An integer specifying how many messages were deleted." msgstr "``count`` : 削除されたメッセージの数を示す整数。" -#: ../../api.rst:2350 +#: ../../api.rst:2477 msgid "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message got deleted." msgstr "``channel`` : メッセージが削除された :class:`TextChannel` か チャンネルIDを持つ :class:`Object` 。" -#: ../../api.rst:2354 +#: ../../api.rst:2481 msgid "Messages were bulk deleted by a moderator." msgstr "管理者によるメッセージの一括削除。" -#: ../../api.rst:2356 +#: ../../api.rst:2483 msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`TextChannel` or :class:`Object` with the ID of the channel that was purged." msgstr "これが上記のactionならば、 :attr:`~AuditLogEntry.target` の型は、 :class:`TextChannel` またはメッセージが一括削除されたチャンネルIDが設定された :class:`Object` です。" -#: ../../api.rst:2368 +#: ../../api.rst:2495 msgid "A message was pinned in a channel." msgstr "チャンネルへのメッセージのピン留め。" -#: ../../api.rst:2370 +#: ../../api.rst:2497 msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member`, :class:`User`, or :class:`Object` who had their message pinned." msgstr "これが上記のactionならば、 :attr:`~AuditLogEntry.target` の型は、ピン留めされたメッセージの送信者である :class:`Member` 、 :class:`User` 、または :class:`Object` になります。" -#: ../../api.rst:2376 +#: ../../api.rst:2503 msgid "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was pinned." msgstr "``channel`` : メッセージがピン留めされた :class:`TextChannel` か チャンネルIDを持つ :class:`Object` 。" -#: ../../api.rst:2377 +#: ../../api.rst:2504 msgid "``message_id``: the ID of the message which was pinned." msgstr "``message_id`` : ピン留めされたメッセージのID。" -#: ../../api.rst:2383 +#: ../../api.rst:2510 msgid "A message was unpinned in a channel." msgstr "チャンネルからのメッセージのピン留め解除。" -#: ../../api.rst:2385 +#: ../../api.rst:2512 msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Member`, :class:`User`, or :class:`Object` who had their message unpinned." msgstr "これが上記のactionならば、 :attr:`~AuditLogEntry.target` の型は、ピン留めが外されたメッセージの送信者である :class:`Member` 、 :class:`User` 、または :class:`Object` になります。" -#: ../../api.rst:2391 +#: ../../api.rst:2518 msgid "``channel``: A :class:`TextChannel` or :class:`Object` with the channel ID where the message was unpinned." msgstr "``channel`` : メッセージのピン留めが外された :class:`TextChannel` か チャンネルIDを持つ :class:`Object` 。" -#: ../../api.rst:2392 +#: ../../api.rst:2519 msgid "``message_id``: the ID of the message which was unpinned." msgstr "``message_id`` : ピン留めが外されたメッセージのID。" -#: ../../api.rst:2398 +#: ../../api.rst:2525 msgid "A guild integration was created." msgstr "ギルドの連携サービスの作成。" -#: ../../api.rst:2400 +#: ../../api.rst:2527 msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is a :class:`PartialIntegration` or :class:`Object` with the integration ID of the integration which was created." msgstr "これが上記のactionならば、 :attr:`~AuditLogEntry.target` の型は、 :class:`PartialIntegration` または作成されたインテグレーションのIDを持つ :class:`Object` になります。" -#: ../../api.rst:2408 +#: ../../api.rst:2535 msgid "A guild integration was updated." msgstr "ギルド連携サービスの更新。" -#: ../../api.rst:2410 +#: ../../api.rst:2537 msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is a :class:`PartialIntegration` or :class:`Object` with the integration ID of the integration which was updated." msgstr "これが上記のactionならば、 :attr:`~AuditLogEntry.target` の型は、 :class:`PartialIntegration` または更新されたインテグレーションのIDを持つ :class:`Object` になります。" -#: ../../api.rst:2418 +#: ../../api.rst:2545 msgid "A guild integration was deleted." msgstr "ギルド連携サービスの削除。" -#: ../../api.rst:2420 +#: ../../api.rst:2547 msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is a :class:`PartialIntegration` or :class:`Object` with the integration ID of the integration which was deleted." msgstr "これが上記のactionならば、 :attr:`~AuditLogEntry.target` の型は、 :class:`PartialIntegration` または削除されたインテグレーションのIDを持つ :class:`Object` になります。" -#: ../../api.rst:2428 +#: ../../api.rst:2555 msgid "A stage instance was started." msgstr "ステージインスタンスの開始。" -#: ../../api.rst:2430 +#: ../../api.rst:2557 msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was created." msgstr "これが上記のactionならば、 :attr:`~AuditLogEntry.target` の型は、 :class:`StageInstance` または作成されたステージインスタンスのIDが設定された :class:`Object` です。" -#: ../../api.rst:2437 -#: ../../api.rst:2452 +#: ../../api.rst:2564 +#: ../../api.rst:2579 msgid ":attr:`~AuditLogDiff.privacy_level`" msgstr ":attr:`~AuditLogDiff.privacy_level`" -#: ../../api.rst:2443 +#: ../../api.rst:2570 msgid "A stage instance was updated." msgstr "ステージインスタンスの更新。" -#: ../../api.rst:2445 +#: ../../api.rst:2572 msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`StageInstance` or :class:`Object` with the ID of the stage instance which was updated." msgstr "これが上記のactionならば、 :attr:`~AuditLogEntry.target` の型は、 :class:`StageInstance` または更新されたステージインスタンスのIDが設定された :class:`Object` です。" -#: ../../api.rst:2458 +#: ../../api.rst:2585 msgid "A stage instance was ended." msgstr "ステージインスタンスの終了。" -#: ../../api.rst:2464 +#: ../../api.rst:2591 msgid "A sticker was created." msgstr "スタンプの作成。" -#: ../../api.rst:2466 +#: ../../api.rst:2593 msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`GuildSticker` or :class:`Object` with the ID of the sticker which was created." msgstr "これが上記のactionならば、 :attr:`~AuditLogEntry.target` の型は、 :class:`GuildSticker` または作成されたスタンプのIDが設定された :class:`Object` です。" -#: ../../api.rst:2473 -#: ../../api.rst:2492 -#: ../../api.rst:2511 +#: ../../api.rst:2600 +#: ../../api.rst:2619 +#: ../../api.rst:2638 msgid ":attr:`~AuditLogDiff.emoji`" msgstr ":attr:`~AuditLogDiff.emoji`" -#: ../../api.rst:2475 -#: ../../api.rst:2494 -#: ../../api.rst:2513 +#: ../../api.rst:2602 +#: ../../api.rst:2621 +#: ../../api.rst:2640 msgid ":attr:`~AuditLogDiff.format_type`" msgstr ":attr:`~AuditLogDiff.format_type`" -#: ../../api.rst:2477 -#: ../../api.rst:2496 -#: ../../api.rst:2515 +#: ../../api.rst:2604 +#: ../../api.rst:2623 +#: ../../api.rst:2642 msgid ":attr:`~AuditLogDiff.available`" msgstr ":attr:`~AuditLogDiff.available`" -#: ../../api.rst:2483 +#: ../../api.rst:2610 msgid "A sticker was updated." msgstr "スタンプの更新。" -#: ../../api.rst:2485 -#: ../../api.rst:2504 +#: ../../api.rst:2612 +#: ../../api.rst:2631 msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`GuildSticker` or :class:`Object` with the ID of the sticker which was updated." msgstr "これが上記のactionならば、 :attr:`~AuditLogEntry.target` の型は、 :class:`GuildSticker` または更新されたスタンプのIDが設定された :class:`Object` です。" -#: ../../api.rst:2502 +#: ../../api.rst:2629 msgid "A sticker was deleted." msgstr "スタンプの削除。" -#: ../../api.rst:2521 -#: ../../api.rst:2540 -#: ../../api.rst:2559 +#: ../../api.rst:2648 +#: ../../api.rst:2667 +#: ../../api.rst:2686 msgid "A scheduled event was created." msgstr "スケジュールイベントの作成。" -#: ../../api.rst:2523 +#: ../../api.rst:2650 msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the event which was created." msgstr "これが上記のactionならば、 :attr:`~AuditLogEntry.target` の型は、 :class:`ScheduledEvent` または作成されたスケジュールイベントのIDが設定された :class:`Object` です。" -#: ../../api.rst:2527 -#: ../../api.rst:2546 -#: ../../api.rst:2565 +#: ../../api.rst:2654 +#: ../../api.rst:2673 +#: ../../api.rst:2692 msgid "Possible attributes for :class:`AuditLogDiff`: - :attr:`~AuditLogDiff.name` - :attr:`~AuditLogDiff.channel` - :attr:`~AuditLogDiff.description` - :attr:`~AuditLogDiff.privacy_level` - :attr:`~AuditLogDiff.status` - :attr:`~AuditLogDiff.entity_type` - :attr:`~AuditLogDiff.cover_image`" msgstr ":class:`AuditLogDiff` の可能な属性: - :attr:`~AuditLogDiff.name` - :attr:`~AuditLogDiff.channel` - :attr:`~AuditLogDiff.description` - :attr:`~AuditLogDiff.privacy_level` - :attr:`~AuditLogDiff.status` - :attr:`~AuditLogDiff.entity_type` - :attr:`~AuditLogDiff.cover_image`" -#: ../../api.rst:2542 +#: ../../api.rst:2669 msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the event which was updated." msgstr "これが上記のactionならば、 :attr:`~AuditLogEntry.target` の型は、 :class:`ScheduledEvent` または更新されたスケジュールイベントのIDが設定された :class:`Object` です。" -#: ../../api.rst:2561 +#: ../../api.rst:2688 msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`ScheduledEvent` or :class:`Object` with the ID of the event which was deleted." msgstr "これが上記のactionならば、 :attr:`~AuditLogEntry.target` の型は、 :class:`ScheduledEvent` または削除されたスケジュールイベントのIDが設定された :class:`Object` です。" -#: ../../api.rst:2578 +#: ../../api.rst:2705 msgid "A thread was created." msgstr "スレッドの作成。" -#: ../../api.rst:2580 +#: ../../api.rst:2707 msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was created." msgstr "これが上記のactionならば、 :attr:`~AuditLogEntry.target` の型は、 :class:`Thread` または作成されたスレッドのIDが設定された :class:`Object` です。" -#: ../../api.rst:2587 -#: ../../api.rst:2605 -#: ../../api.rst:2623 +#: ../../api.rst:2714 +#: ../../api.rst:2732 +#: ../../api.rst:2750 msgid ":attr:`~AuditLogDiff.archived`" msgstr ":attr:`~AuditLogDiff.archived`" -#: ../../api.rst:2588 -#: ../../api.rst:2606 -#: ../../api.rst:2624 +#: ../../api.rst:2715 +#: ../../api.rst:2733 +#: ../../api.rst:2751 msgid ":attr:`~AuditLogDiff.locked`" msgstr ":attr:`~AuditLogDiff.locked`" -#: ../../api.rst:2589 -#: ../../api.rst:2607 -#: ../../api.rst:2625 +#: ../../api.rst:2716 +#: ../../api.rst:2734 +#: ../../api.rst:2752 msgid ":attr:`~AuditLogDiff.auto_archive_duration`" msgstr ":attr:`~AuditLogDiff.auto_archive_duration`" -#: ../../api.rst:2590 -#: ../../api.rst:2608 -#: ../../api.rst:2626 +#: ../../api.rst:2717 +#: ../../api.rst:2735 +#: ../../api.rst:2753 msgid ":attr:`~AuditLogDiff.invitable`" msgstr ":attr:`~AuditLogDiff.invitable`" -#: ../../api.rst:2596 +#: ../../api.rst:2723 msgid "A thread was updated." msgstr "スレッドの更新。" -#: ../../api.rst:2598 +#: ../../api.rst:2725 msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was updated." msgstr "これが上記のactionならば、 :attr:`~AuditLogEntry.target` の型は、 :class:`Thread` または更新されたスレッドのIDが設定された :class:`Object` です。" -#: ../../api.rst:2614 +#: ../../api.rst:2741 msgid "A thread was deleted." msgstr "スレッドの削除。" -#: ../../api.rst:2616 +#: ../../api.rst:2743 msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is the :class:`Thread` or :class:`Object` with the ID of the thread which was deleted." msgstr "これが上記のactionならば、 :attr:`~AuditLogEntry.target` の型は、 :class:`Thread` または削除されたスレッドのIDが設定された :class:`Object` です。" -#: ../../api.rst:2632 +#: ../../api.rst:2759 msgid "An application command or integrations application command permissions were updated." msgstr "アプリケーションコマンドまたはインテグレーションアプリケーションコマンドの権限の更新。" -#: ../../api.rst:2635 +#: ../../api.rst:2762 msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is a :class:`PartialIntegration` for an integrations general permissions, :class:`~discord.app_commands.AppCommand` for a specific commands permissions, or :class:`Object` with the ID of the command or integration which was updated." msgstr "これが上記のactionならば、 :attr:`~AuditLogEntry.target` の型は、 インテグレーション一般の権限の場合 :class:`PartialIntegration` 、特定のコマンドの権限の場合 :class:`~discord.app_commands.AppCommand` 、あるいは更新されたコマンドまたはインテグレーションのIDが設定された :class:`Object` です。" -#: ../../api.rst:2641 +#: ../../api.rst:2768 msgid "When this is the action, the type of :attr:`~AuditLogEntry.extra` is set to an :class:`PartialIntegration` or :class:`Object` with the ID of application that command or integration belongs to." msgstr "これが上記のactionならば、 :attr:`~AuditLogEntry.extra` の型は、 :class:`PartialIntegration` またはコマンドまたはインテグレーションが属するアプリケーションのIDを持つ :class:`Object` になります。" -#: ../../api.rst:2647 +#: ../../api.rst:2774 msgid ":attr:`~AuditLogDiff.app_command_permissions`" msgstr ":attr:`~AuditLogDiff.app_command_permissions`" -#: ../../api.rst:2653 +#: ../../api.rst:2780 msgid "An automod rule was created." msgstr "自動管理ルールの作成。" -#: ../../api.rst:2655 -#: ../../api.rst:2676 -#: ../../api.rst:2697 +#: ../../api.rst:2782 +#: ../../api.rst:2803 +#: ../../api.rst:2824 msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is a :class:`AutoModRule` or :class:`Object` with the ID of the automod rule that was created." msgstr "これが上記のactionならば、 :attr:`~AuditLogEntry.target` のtypeは、 :class:`AutoModRule` または作成された自動管理ルールのIDが設定された :class:`Object` です。" -#: ../../api.rst:2662 -#: ../../api.rst:2683 -#: ../../api.rst:2704 +#: ../../api.rst:2789 +#: ../../api.rst:2810 +#: ../../api.rst:2831 msgid ":attr:`~AuditLogDiff.enabled`" msgstr ":attr:`~AuditLogDiff.enabled`" -#: ../../api.rst:2663 -#: ../../api.rst:2684 -#: ../../api.rst:2705 +#: ../../api.rst:2790 +#: ../../api.rst:2811 +#: ../../api.rst:2832 msgid ":attr:`~AuditLogDiff.event_type`" msgstr ":attr:`~AuditLogDiff.event_type`" -#: ../../api.rst:2664 -#: ../../api.rst:2685 -#: ../../api.rst:2706 +#: ../../api.rst:2791 +#: ../../api.rst:2812 +#: ../../api.rst:2833 msgid ":attr:`~AuditLogDiff.trigger_type`" msgstr ":attr:`~AuditLogDiff.trigger_type`" -#: ../../api.rst:2665 -#: ../../api.rst:2686 -#: ../../api.rst:2707 +#: ../../api.rst:2792 +#: ../../api.rst:2813 +#: ../../api.rst:2834 msgid ":attr:`~AuditLogDiff.trigger`" msgstr ":attr:`~AuditLogDiff.trigger`" -#: ../../api.rst:2666 -#: ../../api.rst:2687 -#: ../../api.rst:2708 +#: ../../api.rst:2793 +#: ../../api.rst:2814 +#: ../../api.rst:2835 msgid ":attr:`~AuditLogDiff.actions`" msgstr ":attr:`~AuditLogDiff.actions`" -#: ../../api.rst:2667 -#: ../../api.rst:2688 -#: ../../api.rst:2709 +#: ../../api.rst:2794 +#: ../../api.rst:2815 +#: ../../api.rst:2836 msgid ":attr:`~AuditLogDiff.exempt_roles`" msgstr ":attr:`~AuditLogDiff.exempt_roles`" -#: ../../api.rst:2668 -#: ../../api.rst:2689 -#: ../../api.rst:2710 +#: ../../api.rst:2795 +#: ../../api.rst:2816 +#: ../../api.rst:2837 msgid ":attr:`~AuditLogDiff.exempt_channels`" msgstr ":attr:`~AuditLogDiff.exempt_channels`" -#: ../../api.rst:2674 +#: ../../api.rst:2801 msgid "An automod rule was updated." msgstr "自動管理ルールの更新。" -#: ../../api.rst:2695 +#: ../../api.rst:2822 msgid "An automod rule was deleted." msgstr "自動管理ルールの削除。" -#: ../../api.rst:2716 +#: ../../api.rst:2843 msgid "An automod rule blocked a message from being sent." msgstr "自動管理ルールによる送信されたメッセージのブロック。" -#: ../../api.rst:2718 -#: ../../api.rst:2736 -#: ../../api.rst:2754 +#: ../../api.rst:2845 +#: ../../api.rst:2863 +#: ../../api.rst:2881 msgid "When this is the action, the type of :attr:`~AuditLogEntry.target` is a :class:`Member` with the ID of the person who triggered the automod rule." msgstr "これが上記のactionならば、 :attr:`~AuditLogEntry.target` のtypeは、自動管理ルールを発動させた :class:`Member` になります。" -#: ../../api.rst:2721 -#: ../../api.rst:2739 -#: ../../api.rst:2757 +#: ../../api.rst:2848 +#: ../../api.rst:2866 +#: ../../api.rst:2884 msgid "When this is the action, the type of :attr:`~AuditLogEntry.extra` is set to an unspecified proxy object with 3 attributes:" msgstr "これが上記のactionならば、 :attr:`~AuditLogEntry.extra` は以下の3つの属性を持つプロキシオブジェクトになります:" -#: ../../api.rst:2724 -#: ../../api.rst:2742 -#: ../../api.rst:2760 +#: ../../api.rst:2851 +#: ../../api.rst:2869 +#: ../../api.rst:2887 msgid "``automod_rule_name``: The name of the automod rule that was triggered." msgstr "``automod_rule_name`` : 発動した自動管理ルールの名前。" -#: ../../api.rst:2725 -#: ../../api.rst:2743 -#: ../../api.rst:2761 -msgid "``automod_rule_trigger``: A :class:`AutoModRuleTriggerType` representation of the rule type that was triggered." -msgstr "``automod_rule_trigger`` : 発動されたルールの :class:`AutoModRuleTriggerType` 。" +#: ../../api.rst:2852 +#: ../../api.rst:2870 +#: ../../api.rst:2888 +msgid "``automod_rule_trigger_type``: A :class:`AutoModRuleTriggerType` representation of the rule type that was triggered." +msgstr "" -#: ../../api.rst:2726 -#: ../../api.rst:2744 -#: ../../api.rst:2762 +#: ../../api.rst:2853 +#: ../../api.rst:2871 +#: ../../api.rst:2889 msgid "``channel``: The channel in which the automod rule was triggered." msgstr "``channel`` : 自動管理ルールが発動されたチャンネル。" -#: ../../api.rst:2728 -#: ../../api.rst:2746 -#: ../../api.rst:2764 +#: ../../api.rst:2855 +#: ../../api.rst:2873 +#: ../../api.rst:2891 msgid "When this is the action, :attr:`AuditLogEntry.changes` is empty." msgstr "これが上記のactionなら、 :attr:`~AuditLogEntry.changes` は空になります。" -#: ../../api.rst:2734 +#: ../../api.rst:2861 msgid "An automod rule flagged a message." msgstr "自動管理ルールによる送信されたメッセージのフラグ付け。" -#: ../../api.rst:2752 +#: ../../api.rst:2879 msgid "An automod rule timed-out a member." msgstr "自動管理ルールによるメンバーのタイムアウト。" -#: ../../api.rst:2770 +#: ../../api.rst:2897 +msgid "A request to monetize the server was created." +msgstr "" + +#: ../../api.rst:2903 +msgid "The terms and conditions for creator monetization were accepted." +msgstr "" + +#: ../../api.rst:2909 msgid "Represents the category that the :class:`AuditLogAction` belongs to." msgstr ":class:`AuditLogAction` が属するカテゴリ。" -#: ../../api.rst:2772 +#: ../../api.rst:2911 msgid "This can be retrieved via :attr:`AuditLogEntry.category`." msgstr "これは :attr:`AuditLogEntry.category` で取得できます。" -#: ../../api.rst:2776 +#: ../../api.rst:2915 msgid "The action is the creation of something." msgstr "アクションは何かの作成です。" -#: ../../api.rst:2780 +#: ../../api.rst:2919 msgid "The action is the deletion of something." msgstr "アクションは何かの削除です。" -#: ../../api.rst:2784 +#: ../../api.rst:2923 msgid "The action is the update of something." msgstr "アクションは何かの更新です。" -#: ../../api.rst:2788 +#: ../../api.rst:2927 msgid "Represents the membership state of a team member retrieved through :func:`Client.application_info`." msgstr ":func:`Client.application_info` で取得したチームメンバーのメンバーシップ状態。" -#: ../../api.rst:2794 +#: ../../api.rst:2933 msgid "Represents an invited member." msgstr "招待されたメンバー。" -#: ../../api.rst:2798 +#: ../../api.rst:2937 msgid "Represents a member currently in the team." msgstr "現在チームにいるメンバー。" -#: ../../api.rst:2802 +#: ../../api.rst:2941 +msgid "Represents the type of role of a team member retrieved through :func:`Client.application_info`." +msgstr "" + +#: ../../api.rst:2947 +msgid "The team member is an admin. This allows them to invite members to the team, access credentials, edit the application, and do most things the owner can do. However they cannot do destructive actions." +msgstr "" + +#: ../../api.rst:2952 +msgid "The team member is a developer. This allows them to access information, like the client secret or public key. They can also configure interaction endpoints or reset the bot token. Developers cannot invite anyone to the team nor can they do destructive actions." +msgstr "" + +#: ../../api.rst:2958 +msgid "The team member is a read-only member. This allows them to access information, but not edit anything." +msgstr "" + +#: ../../api.rst:2962 msgid "Represents the type of webhook that can be received." msgstr "受け取れるWebhookの種類。" -#: ../../api.rst:2808 +#: ../../api.rst:2968 msgid "Represents a webhook that can post messages to channels with a token." msgstr "トークンでチャンネルにメッセージを投稿できるWebhook。" -#: ../../api.rst:2812 +#: ../../api.rst:2972 msgid "Represents a webhook that is internally managed by Discord, used for following channels." msgstr "チャンネルのフォローのためDiscord内部で管理されるWebhook。" -#: ../../api.rst:2816 +#: ../../api.rst:2976 msgid "Represents a webhook that is used for interactions or applications." msgstr "インタラクションやアプリケーションに用いられるWebhook。" -#: ../../api.rst:2822 +#: ../../api.rst:2982 msgid "Represents the behaviour the :class:`Integration` should perform when a user's subscription has finished." msgstr "ユーザーのサブスクリプションが終了した後の :class:`Integration` の動作。" -#: ../../api.rst:2825 +#: ../../api.rst:2985 msgid "There is an alias for this called ``ExpireBehavior``." msgstr "``ExpireBehavior`` という名のエイリアスがあります。" -#: ../../api.rst:2831 +#: ../../api.rst:2991 msgid "This will remove the :attr:`StreamIntegration.role` from the user when their subscription is finished." msgstr "サブスクリプションが終了したユーザーから :attr:`StreamIntegration.role` を除去します。" -#: ../../api.rst:2836 +#: ../../api.rst:2996 msgid "This will kick the user when their subscription is finished." msgstr "サブスクリプションが終了したユーザーをキックします。" -#: ../../api.rst:2840 +#: ../../api.rst:3000 msgid "Represents the default avatar of a Discord :class:`User`" msgstr "Discord :class:`User` のデフォルトのアバター。" -#: ../../api.rst:2844 -msgid "Represents the default avatar with the color blurple. See also :attr:`Colour.blurple`" +#: ../../api.rst:3004 +msgid "Represents the default avatar with the colour blurple. See also :attr:`Colour.blurple`" msgstr "ブループル色のデフォルトのアバター。 :attr:`Colour.blurple` も参照してください。" -#: ../../api.rst:2848 -msgid "Represents the default avatar with the color grey. See also :attr:`Colour.greyple`" +#: ../../api.rst:3008 +msgid "Represents the default avatar with the colour grey. See also :attr:`Colour.greyple`" msgstr "灰色のデフォルトのアバター。 :attr:`Colour.greyple` も参照してください。" -#: ../../api.rst:2852 +#: ../../api.rst:3012 msgid "An alias for :attr:`grey`." msgstr ":attr:`grey` のエイリアス。" -#: ../../api.rst:2855 -msgid "Represents the default avatar with the color green. See also :attr:`Colour.green`" +#: ../../api.rst:3015 +msgid "Represents the default avatar with the colour green. See also :attr:`Colour.green`" msgstr "緑色のデフォルトのアバター。 :attr:`Colour.green` も参照してください。" -#: ../../api.rst:2859 -msgid "Represents the default avatar with the color orange. See also :attr:`Colour.orange`" +#: ../../api.rst:3019 +msgid "Represents the default avatar with the colour orange. See also :attr:`Colour.orange`" msgstr "オレンジ色のデフォルトのアバター。 :attr:`Colour.orange` も参照してください。" -#: ../../api.rst:2863 -msgid "Represents the default avatar with the color red. See also :attr:`Colour.red`" +#: ../../api.rst:3023 +msgid "Represents the default avatar with the colour red. See also :attr:`Colour.red`" msgstr "赤色のデフォルトのアバター。 :attr:`Colour.red` も参照してください。" -#: ../../api.rst:2868 +#: ../../api.rst:3027 +msgid "Represents the default avatar with the colour pink. See also :attr:`Colour.pink`" +msgstr "ピンク色のデフォルトのアバター。 :attr:`Colour.pink` も参照してください。" + +#: ../../api.rst:3034 msgid "Represents the type of sticker." msgstr "スタンプの種類。" -#: ../../api.rst:2874 +#: ../../api.rst:3040 msgid "Represents a standard sticker that all Nitro users can use." msgstr "Nitroユーザー全員が使用できる標準スタンプ。" -#: ../../api.rst:2878 +#: ../../api.rst:3044 msgid "Represents a custom sticker created in a guild." msgstr "ギルドで作成されたカスタムスタンプ。" -#: ../../api.rst:2882 +#: ../../api.rst:3048 msgid "Represents the type of sticker images." msgstr "スタンプ画像の種類。" -#: ../../api.rst:2888 +#: ../../api.rst:3054 msgid "Represents a sticker with a png image." msgstr "PNG画像のスタンプ。" -#: ../../api.rst:2892 +#: ../../api.rst:3058 msgid "Represents a sticker with an apng image." msgstr "APNG画像のスタンプ。" -#: ../../api.rst:2896 +#: ../../api.rst:3062 msgid "Represents a sticker with a lottie image." msgstr "ロッティー画像のスタンプ。" -#: ../../api.rst:2900 +#: ../../api.rst:3066 msgid "Represents a sticker with a gif image." msgstr "GIF画像のスタンプ。" -#: ../../api.rst:2906 +#: ../../api.rst:3072 msgid "Represents the invite type for voice channel invites." msgstr "ボイスチャンネル招待の招待タイプ。" -#: ../../api.rst:2912 +#: ../../api.rst:3078 msgid "The invite doesn't target anyone or anything." msgstr "招待の対象がないもの。" -#: ../../api.rst:2916 +#: ../../api.rst:3082 msgid "A stream invite that targets a user." msgstr "ユーザーを対象とするもの。" -#: ../../api.rst:2920 +#: ../../api.rst:3086 msgid "A stream invite that targets an embedded application." msgstr "埋め込まれたアプリケーションを対象とするもの。" -#: ../../api.rst:2924 +#: ../../api.rst:3090 msgid "Represents the camera video quality mode for voice channel participants." msgstr "ボイスチャンネル参加者のカメラビデオの画質モード。" -#: ../../api.rst:2930 +#: ../../api.rst:3096 msgid "Represents auto camera video quality." msgstr "自動のカメラビデオ画質。" -#: ../../api.rst:2934 +#: ../../api.rst:3100 msgid "Represents full camera video quality." msgstr "フルのカメラビデオ画質。" -#: ../../api.rst:2938 +#: ../../api.rst:3104 msgid "Represents the privacy level of a stage instance or scheduled event." msgstr "ステージインスタンスやスケジュールイベントのプライバシーレベル。" -#: ../../api.rst:2944 +#: ../../api.rst:3110 msgid "The stage instance or scheduled event is only accessible within the guild." msgstr "ステージインスタンスやスケジュールイベントはギルド内でのみアクセスできます。" -#: ../../api.rst:2948 +#: ../../api.rst:3114 msgid "Represents the NSFW level of a guild." msgstr "ギルドの年齢制限レベル。" -#: ../../api.rst:2956 +#: ../../api.rst:3122 msgid "Checks if two NSFW levels are equal." msgstr "二つの年齢制限レベルが等しいかを比較します。" -#: ../../api.rst:2959 +#: ../../api.rst:3125 msgid "Checks if two NSFW levels are not equal." msgstr "二つの年齢制限レベルが等しくないかを比較します。" -#: ../../api.rst:2962 +#: ../../api.rst:3128 msgid "Checks if a NSFW level is higher than another." msgstr "年齢制限レベルがあるレベルより高いか確認します。" -#: ../../api.rst:2965 +#: ../../api.rst:3131 msgid "Checks if a NSFW level is lower than another." msgstr "年齢制限レベルがあるレベルより低いか確認します。" -#: ../../api.rst:2968 +#: ../../api.rst:3134 msgid "Checks if a NSFW level is higher or equal to another." msgstr "年齢制限レベルがあるレベルと同じ、又は高いか確認します。" -#: ../../api.rst:2971 +#: ../../api.rst:3137 msgid "Checks if a NSFW level is lower or equal to another." msgstr "年齢制限レベルがあるレベルと同じ、又は低いか確認します。" -#: ../../api.rst:2975 +#: ../../api.rst:3141 msgid "The guild has not been categorised yet." msgstr "未分類のギルド。" -#: ../../api.rst:2979 +#: ../../api.rst:3145 msgid "The guild contains NSFW content." msgstr "年齢制限されたコンテンツを含むギルド。" -#: ../../api.rst:2983 +#: ../../api.rst:3149 msgid "The guild does not contain any NSFW content." msgstr "年齢制限されたコンテンツを一切含まないギルド。" -#: ../../api.rst:2987 +#: ../../api.rst:3153 msgid "The guild may contain NSFW content." msgstr "年齢制限されたコンテンツを含む可能性のあるギルド。" -#: ../../api.rst:2991 +#: ../../api.rst:3157 msgid "Supported locales by Discord. Mainly used for application command localisation." msgstr "Discordでサポートされているロケール。主にアプリケーションコマンドの多言語化に使用されます。" -#: ../../api.rst:2997 +#: ../../api.rst:3163 msgid "The ``en-US`` locale." msgstr "``en-US`` ロケール。" -#: ../../api.rst:3001 +#: ../../api.rst:3167 msgid "The ``en-GB`` locale." msgstr "``en-GB`` ロケール。" -#: ../../api.rst:3005 +#: ../../api.rst:3171 msgid "The ``bg`` locale." msgstr "``bg`` ロケール。" -#: ../../api.rst:3009 +#: ../../api.rst:3175 msgid "The ``zh-CN`` locale." msgstr "``zh-CN`` ロケール。" -#: ../../api.rst:3013 +#: ../../api.rst:3179 msgid "The ``zh-TW`` locale." msgstr "``zh-TW`` ロケール。" -#: ../../api.rst:3017 +#: ../../api.rst:3183 msgid "The ``hr`` locale." msgstr "``hr`` ロケール。" -#: ../../api.rst:3021 +#: ../../api.rst:3187 msgid "The ``cs`` locale." msgstr "``cs`` ロケール。" -#: ../../api.rst:3025 +#: ../../api.rst:3191 msgid "The ``id`` locale." msgstr "``id`` ロケール。" -#: ../../api.rst:3031 +#: ../../api.rst:3197 msgid "The ``da`` locale." msgstr "``da`` ロケール。" -#: ../../api.rst:3035 +#: ../../api.rst:3201 msgid "The ``nl`` locale." msgstr "``nl`` ロケール。" -#: ../../api.rst:3039 +#: ../../api.rst:3205 msgid "The ``fi`` locale." msgstr "``fi`` ロケール。" -#: ../../api.rst:3043 +#: ../../api.rst:3209 msgid "The ``fr`` locale." msgstr "``fr`` ロケール。" -#: ../../api.rst:3047 +#: ../../api.rst:3213 msgid "The ``de`` locale." msgstr "``de`` ロケール。" -#: ../../api.rst:3051 +#: ../../api.rst:3217 msgid "The ``el`` locale." msgstr "``el`` ロケール。" -#: ../../api.rst:3055 +#: ../../api.rst:3221 msgid "The ``hi`` locale." msgstr "``hi`` ロケール。" -#: ../../api.rst:3059 +#: ../../api.rst:3225 msgid "The ``hu`` locale." msgstr "``hu`` ロケール。" -#: ../../api.rst:3063 +#: ../../api.rst:3229 msgid "The ``it`` locale." msgstr "``it`` ロケール。" -#: ../../api.rst:3067 +#: ../../api.rst:3233 msgid "The ``ja`` locale." msgstr "``ja`` ロケール。" -#: ../../api.rst:3071 +#: ../../api.rst:3237 msgid "The ``ko`` locale." msgstr "``ko`` ロケール。" -#: ../../api.rst:3075 +#: ../../api.rst:3241 +msgid "The ``es-419`` locale." +msgstr "" + +#: ../../api.rst:3247 msgid "The ``lt`` locale." msgstr "``lt`` ロケール。" -#: ../../api.rst:3079 +#: ../../api.rst:3251 msgid "The ``no`` locale." msgstr "``no`` ロケール。" -#: ../../api.rst:3083 +#: ../../api.rst:3255 msgid "The ``pl`` locale." msgstr "``pl`` ロケール。" -#: ../../api.rst:3087 +#: ../../api.rst:3259 msgid "The ``pt-BR`` locale." msgstr "``pt-BR`` ロケール。" -#: ../../api.rst:3091 +#: ../../api.rst:3263 msgid "The ``ro`` locale." msgstr "``ro`` ロケール。" -#: ../../api.rst:3095 +#: ../../api.rst:3267 msgid "The ``ru`` locale." msgstr "``ru`` ロケール。" -#: ../../api.rst:3099 +#: ../../api.rst:3271 msgid "The ``es-ES`` locale." msgstr "``es-ES`` ロケール。" -#: ../../api.rst:3103 +#: ../../api.rst:3275 msgid "The ``sv-SE`` locale." msgstr "``sv-SE`` ロケール。" -#: ../../api.rst:3107 +#: ../../api.rst:3279 msgid "The ``th`` locale." msgstr "``th`` ロケール。" -#: ../../api.rst:3111 +#: ../../api.rst:3283 msgid "The ``tr`` locale." msgstr "``tr`` ロケール。" -#: ../../api.rst:3115 +#: ../../api.rst:3287 msgid "The ``uk`` locale." msgstr "``uk`` ロケール。" -#: ../../api.rst:3119 +#: ../../api.rst:3291 msgid "The ``vi`` locale." msgstr "``vi`` ロケール。" -#: ../../api.rst:3124 +#: ../../api.rst:3296 msgid "Represents the Multi-Factor Authentication requirement level of a guild." msgstr "ギルドの多要素認証要件レベル。" -#: ../../api.rst:3132 +#: ../../api.rst:3304 msgid "Checks if two MFA levels are equal." msgstr "二つのMFAレベルが等しいかを比較します。" -#: ../../api.rst:3135 +#: ../../api.rst:3307 msgid "Checks if two MFA levels are not equal." msgstr "二つのMFAレベルが等しくないかを比較します。" -#: ../../api.rst:3138 +#: ../../api.rst:3310 msgid "Checks if a MFA level is higher than another." msgstr "多要素認証レベルがあるレベルより厳しいか確認します。" -#: ../../api.rst:3141 +#: ../../api.rst:3313 msgid "Checks if a MFA level is lower than another." msgstr "多要素認証レベルがあるレベルより緩いか確認します。" -#: ../../api.rst:3144 +#: ../../api.rst:3316 msgid "Checks if a MFA level is higher or equal to another." msgstr "多要素認証レベルがあるレベルと同じ、又は厳しいか確認します。" -#: ../../api.rst:3147 +#: ../../api.rst:3319 msgid "Checks if a MFA level is lower or equal to another." msgstr "多要素認証レベルがあるレベルと同じ、又は緩いか確認します。" -#: ../../api.rst:3151 +#: ../../api.rst:3323 msgid "The guild has no MFA requirement." msgstr "多要素認証要件がないギルド。" -#: ../../api.rst:3155 +#: ../../api.rst:3327 msgid "The guild requires 2 factor authentication." msgstr "二要素認証を必須とするギルド。" -#: ../../api.rst:3159 +#: ../../api.rst:3331 msgid "Represents the type of entity that a scheduled event is for." msgstr "スケジュールイベントの開催場所の種類。" -#: ../../api.rst:3165 +#: ../../api.rst:3337 msgid "The scheduled event will occur in a stage instance." msgstr "ステージインスタンスで起こるスケジュールイベント。" -#: ../../api.rst:3169 +#: ../../api.rst:3341 msgid "The scheduled event will occur in a voice channel." msgstr "ボイスチャンネルで起こるスケジュールイベント。" -#: ../../api.rst:3173 +#: ../../api.rst:3345 msgid "The scheduled event will occur externally." msgstr "外部で起こるスケジュールイベント。" -#: ../../api.rst:3177 +#: ../../api.rst:3349 msgid "Represents the status of an event." msgstr "イベントの状態。" -#: ../../api.rst:3183 +#: ../../api.rst:3355 msgid "The event is scheduled." msgstr "予定されたイベント。" -#: ../../api.rst:3187 +#: ../../api.rst:3359 msgid "The event is active." msgstr "開催中のイベント。" -#: ../../api.rst:3191 +#: ../../api.rst:3363 msgid "The event has ended." msgstr "終了したイベント。" -#: ../../api.rst:3195 +#: ../../api.rst:3367 msgid "The event has been cancelled." msgstr "キャンセルされたイベント。" -#: ../../api.rst:3199 +#: ../../api.rst:3371 msgid "An alias for :attr:`cancelled`." msgstr ":attr:`cancelled` のエイリアス。" -#: ../../api.rst:3203 +#: ../../api.rst:3375 msgid "An alias for :attr:`completed`." msgstr ":attr:`completed` のエイリアス。" -#: ../../api.rst:3207 +#: ../../api.rst:3379 msgid "Represents the trigger type of an automod rule." msgstr "自動管理ルールの発動条件の種類を表します。" -#: ../../api.rst:3213 +#: ../../api.rst:3385 msgid "The rule will trigger when a keyword is mentioned." msgstr "キーワードに言及したときに発動されるルール。" -#: ../../api.rst:3217 +#: ../../api.rst:3389 msgid "The rule will trigger when a harmful link is posted." msgstr "有害なリンクを投稿したときに発動されるルール。" -#: ../../api.rst:3221 +#: ../../api.rst:3393 msgid "The rule will trigger when a spam message is posted." msgstr "スパムメッセージを投稿したときに発動されるルール。" -#: ../../api.rst:3225 +#: ../../api.rst:3397 msgid "The rule will trigger when something triggers based on the set keyword preset types." msgstr "事前に定められたキーワードプリセットに基づき発動したときに発動されるルール。" -#: ../../api.rst:3229 +#: ../../api.rst:3401 msgid "The rule will trigger when combined number of role and user mentions is greater than the set limit." msgstr "ロールとユーザーのメンションの合計数が設定された制限よりも多い場合に発動されるルール。" -#: ../../api.rst:3234 +#: ../../api.rst:3406 +msgid "The rule will trigger when a user's profile contains a keyword." +msgstr "" + +#: ../../api.rst:3412 msgid "Represents the event type of an automod rule." msgstr "自動管理ルールのイベントの種類を表します。" -#: ../../api.rst:3240 +#: ../../api.rst:3418 msgid "The rule will trigger when a message is sent." msgstr "メッセージを投稿したときにルールが発動します。" -#: ../../api.rst:3244 +#: ../../api.rst:3422 +msgid "The rule will trigger when a member's profile is updated." +msgstr "" + +#: ../../api.rst:3428 msgid "Represents the action type of an automod rule." msgstr "自動管理ルールの対応の種類を表します。" -#: ../../api.rst:3250 +#: ../../api.rst:3434 msgid "The rule will block a message from being sent." msgstr "メッセージを送信できないようにします。" -#: ../../api.rst:3254 +#: ../../api.rst:3438 msgid "The rule will send an alert message to a predefined channel." msgstr "事前に指定したチャンネルに警告メッセージを送信します。" -#: ../../api.rst:3258 +#: ../../api.rst:3442 msgid "The rule will timeout a user." msgstr "ユーザーをタイムアウトします。" -#: ../../api.rst:3263 +#: ../../api.rst:3446 +msgid "Similar to :attr:`timeout`, except the user will be timed out indefinitely. This will request the user to edit it's profile." +msgstr "" + +#: ../../api.rst:3453 msgid "Represents how a forum's posts are layed out in the client." msgstr "フォーラムの投稿がクライアントでどのように配列されるかを表します。" -#: ../../api.rst:3269 +#: ../../api.rst:3459 msgid "No default has been set, so it is up to the client to know how to lay it out." msgstr "デフォルトが設定されていないので、配列方法はクライアントによります。" -#: ../../api.rst:3273 +#: ../../api.rst:3463 msgid "Displays posts as a list." msgstr "投稿を一覧として表示します。" -#: ../../api.rst:3277 +#: ../../api.rst:3467 msgid "Displays posts as a collection of tiles." msgstr "投稿をタイルの集まりとして表示します。" -#: ../../api.rst:3283 +#: ../../api.rst:3472 +msgid "Represents how a forum's posts are sorted in the client." +msgstr "フォーラムの投稿がクライアントでどのように並び替えられるかを表します。" + +#: ../../api.rst:3478 +msgid "Sort forum posts by activity." +msgstr "最終更新日時順でフォーラム投稿を並び替えます。" + +#: ../../api.rst:3482 +msgid "Sort forum posts by creation time (from most recent to oldest)." +msgstr "作成日時順 (新しいものから古いものの順) でフォーラム投稿を並び替えます。" + +#: ../../api.rst:3486 +msgid "Represents the default value of a select menu." +msgstr "" + +#: ../../api.rst:3492 +msgid "The underlying type of the ID is a user." +msgstr "" + +#: ../../api.rst:3496 +msgid "The underlying type of the ID is a role." +msgstr "" + +#: ../../api.rst:3500 +msgid "The underlying type of the ID is a channel or thread." +msgstr "" + +#: ../../api.rst:3505 +msgid "Represents the type of a SKU." +msgstr "" + +#: ../../api.rst:3511 +msgid "The SKU is a recurring subscription." +msgstr "" + +#: ../../api.rst:3515 +msgid "The SKU is a system-generated group which is created for each :attr:`SKUType.subscription`." +msgstr "" + +#: ../../api.rst:3520 +msgid "Represents the type of an entitlement." +msgstr "" + +#: ../../api.rst:3526 +msgid "The entitlement was purchased as an app subscription." +msgstr "" + +#: ../../api.rst:3531 +msgid "Represents the type of an entitlement owner." +msgstr "" + +#: ../../api.rst:3537 +msgid "The entitlement owner is a guild." +msgstr "" + +#: ../../api.rst:3541 +msgid "The entitlement owner is a user." +msgstr "" + +#: ../../api.rst:3547 msgid "Audit Log Data" msgstr "監査ログデータ" -#: ../../api.rst:3285 +#: ../../api.rst:3549 msgid "Working with :meth:`Guild.audit_logs` is a complicated process with a lot of machinery involved. The library attempts to make it easy to use and friendly. In order to accomplish this goal, it must make use of a couple of data classes that aid in this goal." msgstr ":meth:`Guild.audit_logs` の使用は複雑なプロセスです。このライブラリーはこれを使いやすくフレンドリーにしようと試みています。この目標の達成のためにいくつかのデータクラスを使用しています。" -#: ../../api.rst:3290 +#: ../../api.rst:3554 msgid "AuditLogEntry" msgstr "AuditLogEntry" @@ -6317,402 +6877,406 @@ msgstr ":class:`AuditLogDiff`" msgid "The target's subsequent state." msgstr "対象の直後の状態。" -#: ../../api.rst:3298 +#: ../../api.rst:3562 msgid "AuditLogChanges" msgstr "AuditLogChanges" -#: ../../api.rst:3304 +#: ../../api.rst:3568 msgid "An audit log change set." msgstr "監査ログの変更のセット。" -#: ../../api.rst:3308 +#: ../../api.rst:3572 msgid "The old value. The attribute has the type of :class:`AuditLogDiff`." msgstr "以前の値。この属性は :class:`AuditLogDiff` 型です。" -#: ../../api.rst:3310 -#: ../../api.rst:3330 +#: ../../api.rst:3574 +#: ../../api.rst:3594 msgid "Depending on the :class:`AuditLogActionCategory` retrieved by :attr:`~AuditLogEntry.category`\\, the data retrieved by this attribute differs:" msgstr ":attr:`~AuditLogEntry.category` で取得される :class:`AuditLogActionCategory` によりこの属性の値が異なります:" -#: ../../api.rst:3315 -#: ../../api.rst:3335 +#: ../../api.rst:3579 +#: ../../api.rst:3599 msgid "Category" msgstr "カテゴリー" -#: ../../api.rst:3317 -#: ../../api.rst:3337 +#: ../../api.rst:3581 +#: ../../api.rst:3601 msgid ":attr:`~AuditLogActionCategory.create`" msgstr ":attr:`~AuditLogActionCategory.create`" -#: ../../api.rst:3317 +#: ../../api.rst:3581 msgid "All attributes are set to ``None``." msgstr "全ての属性は ``None`` です。" -#: ../../api.rst:3319 -#: ../../api.rst:3339 +#: ../../api.rst:3583 +#: ../../api.rst:3603 msgid ":attr:`~AuditLogActionCategory.delete`" msgstr ":attr:`~AuditLogActionCategory.delete`" -#: ../../api.rst:3319 +#: ../../api.rst:3583 msgid "All attributes are set the value before deletion." msgstr "全ての属性は削除前の値に設定されています。" -#: ../../api.rst:3321 -#: ../../api.rst:3341 +#: ../../api.rst:3585 +#: ../../api.rst:3605 msgid ":attr:`~AuditLogActionCategory.update`" msgstr ":attr:`~AuditLogActionCategory.update`" -#: ../../api.rst:3321 +#: ../../api.rst:3585 msgid "All attributes are set the value before updating." msgstr "全ての属性は更新前の値に設定されています。" -#: ../../api.rst:3323 -#: ../../api.rst:3343 +#: ../../api.rst:3587 +#: ../../api.rst:3607 msgid "``None``" msgstr "``None``" -#: ../../api.rst:3323 -#: ../../api.rst:3343 +#: ../../api.rst:3587 +#: ../../api.rst:3607 msgid "No attributes are set." msgstr "属性が設定されていません。" -#: ../../api.rst:3328 +#: ../../api.rst:3592 msgid "The new value. The attribute has the type of :class:`AuditLogDiff`." msgstr "新しい値。この属性は :class:`AuditLogDiff` 型です。" -#: ../../api.rst:3337 +#: ../../api.rst:3601 msgid "All attributes are set to the created value" msgstr "全ての属性は作成時の値に設定されています。" -#: ../../api.rst:3339 +#: ../../api.rst:3603 msgid "All attributes are set to ``None``" msgstr "全ての属性は ``None`` です。" -#: ../../api.rst:3341 +#: ../../api.rst:3605 msgid "All attributes are set the value after updating." msgstr "全ての属性は更新後の値に設定されています。" -#: ../../api.rst:3347 +#: ../../api.rst:3611 msgid "AuditLogDiff" msgstr "AuditLogDiff" -#: ../../api.rst:3353 +#: ../../api.rst:3617 msgid "Represents an audit log \"change\" object. A change object has dynamic attributes that depend on the type of action being done. Certain actions map to certain attributes being set." msgstr "監査ログの「変更」オブジェクト。変更オブジェクトには、行われたアクションの種類によって異なる属性があります。特定のアクションが行われた場合に特定の属性が設定されます。" -#: ../../api.rst:3357 +#: ../../api.rst:3621 msgid "Note that accessing an attribute that does not match the specified action will lead to an attribute error." msgstr "指定されたアクションに一致しない属性にアクセスすると、AttributeErrorが発生することに注意してください。" -#: ../../api.rst:3360 +#: ../../api.rst:3624 msgid "To get a list of attributes that have been set, you can iterate over them. To see a list of all possible attributes that could be set based on the action being done, check the documentation for :class:`AuditLogAction`, otherwise check the documentation below for all attributes that are possible." msgstr "設定された属性のリストを取得するには、イテレートすることができます。 行われたアクションに対応した可能な属性の一覧は、 :class:`AuditLogAction` の説明を確認してください。あるいは、可能なすべての属性について、以下の説明を確認してください。" -#: ../../api.rst:3369 +#: ../../api.rst:3633 msgid "Returns an iterator over (attribute, value) tuple of this diff." msgstr "差分の(属性、値)タプルのイテレーターを返します。" -#: ../../api.rst:3373 +#: ../../api.rst:3637 msgid "A name of something." msgstr "何かの名前。" -#: ../../api.rst:3379 +#: ../../api.rst:3643 +msgid "The guild of something." +msgstr "ギルド属性。" + +#: ../../api.rst:3649 msgid "A guild's or role's icon. See also :attr:`Guild.icon` or :attr:`Role.icon`." msgstr "ギルドまたはロールのアイコン。 :attr:`Guild.icon` と :attr:`Role.icon` も参照してください。" -#: ../../api.rst:3385 +#: ../../api.rst:3655 msgid "The guild's invite splash. See also :attr:`Guild.splash`." msgstr "ギルドの招待のスプラッシュ。 :attr:`Guild.splash` も参照してください。" -#: ../../api.rst:3391 +#: ../../api.rst:3661 msgid "The guild's discovery splash. See also :attr:`Guild.discovery_splash`." msgstr "ギルドのディスカバリースプラッシュ。 :attr:`Guild.discovery_splash` も参照してください。" -#: ../../api.rst:3397 +#: ../../api.rst:3667 msgid "The guild's banner. See also :attr:`Guild.banner`." msgstr "ギルドのバナー。 :attr:`Guild.banner` も参照してください。" -#: ../../api.rst:3403 +#: ../../api.rst:3673 msgid "The guild's owner. See also :attr:`Guild.owner`" msgstr "ギルドの所有者。 :attr:`Guild.owner` も参照してください。" -#: ../../api.rst:3405 +#: ../../api.rst:3675 msgid "Union[:class:`Member`, :class:`User`]" msgstr "Union[:class:`Member`, :class:`User`]" -#: ../../api.rst:3409 +#: ../../api.rst:3679 msgid "The guild's AFK channel." msgstr "ギルドのAFKチャンネル。" -#: ../../api.rst:3411 -#: ../../api.rst:3422 +#: ../../api.rst:3681 +#: ../../api.rst:3692 msgid "If this could not be found, then it falls back to a :class:`Object` with the ID being set." msgstr "見つからない場合は、IDが設定された :class:`Object` になります。" -#: ../../api.rst:3414 +#: ../../api.rst:3684 msgid "See :attr:`Guild.afk_channel`." msgstr ":attr:`Guild.afk_channel` を参照してください。" -#: ../../api.rst:3416 +#: ../../api.rst:3686 msgid "Union[:class:`VoiceChannel`, :class:`Object`]" msgstr "Union[:class:`VoiceChannel`, :class:`Object`]" -#: ../../api.rst:3420 +#: ../../api.rst:3690 msgid "The guild's system channel." msgstr "ギルドのシステムチャンネル。" -#: ../../api.rst:3425 +#: ../../api.rst:3695 msgid "See :attr:`Guild.system_channel`." msgstr ":attr:`Guild.system_channel` を参照してください。" -#: ../../api.rst:3427 -#: ../../api.rst:3439 -#: ../../api.rst:3451 -#: ../../api.rst:3478 +#: ../../api.rst:3697 +#: ../../api.rst:3709 +#: ../../api.rst:3721 +#: ../../api.rst:3748 msgid "Union[:class:`TextChannel`, :class:`Object`]" msgstr "Union[:class:`TextChannel`, :class:`Object`]" -#: ../../api.rst:3432 +#: ../../api.rst:3702 msgid "The guild's rules channel." msgstr "ギルドのルールチャンネル。" -#: ../../api.rst:3434 -#: ../../api.rst:3446 -#: ../../api.rst:3475 +#: ../../api.rst:3704 +#: ../../api.rst:3716 +#: ../../api.rst:3745 msgid "If this could not be found then it falls back to a :class:`Object` with the ID being set." msgstr "見つからない場合は、IDが設定された :class:`Object` になります。" -#: ../../api.rst:3437 +#: ../../api.rst:3707 msgid "See :attr:`Guild.rules_channel`." msgstr ":attr:`Guild.rules_channel` を参照してください。" -#: ../../api.rst:3444 +#: ../../api.rst:3714 msgid "The guild's public updates channel." msgstr "ギルドのパブリックアップデートチャンネル。" -#: ../../api.rst:3449 +#: ../../api.rst:3719 msgid "See :attr:`Guild.public_updates_channel`." msgstr ":attr:`Guild.public_updates_channel` を参照してください。" -#: ../../api.rst:3455 +#: ../../api.rst:3725 msgid "The guild's AFK timeout. See :attr:`Guild.afk_timeout`." msgstr "ギルドのAFKタイムアウト。 :attr:`Guild.afk_timeout` も参照してください。" -#: ../../api.rst:3461 +#: ../../api.rst:3731 msgid "The guild's MFA level. See :attr:`Guild.mfa_level`." msgstr "ギルドの多要素認証レベル。 :attr:`Guild.mfa_level` も参照してください。" -#: ../../api.rst:3463 -#: ../../../discord/guild.py:docstring of discord.guild.Guild:179 +#: ../../api.rst:3733 +#: ../../../discord/guild.py:docstring of discord.guild.Guild:173 msgid ":class:`MFALevel`" msgstr ":class:`MFALevel`" -#: ../../api.rst:3467 +#: ../../api.rst:3737 msgid "The guild's widget has been enabled or disabled." msgstr "ギルドのウィジェットが有効化または無効化された。" -#: ../../api.rst:3473 +#: ../../api.rst:3743 msgid "The widget's channel." msgstr "ウィジェットのチャンネル。" -#: ../../api.rst:3482 -#: ../../../discord/guild.py:docstring of discord.guild.Guild:109 +#: ../../api.rst:3752 +#: ../../../discord/guild.py:docstring of discord.guild.Guild:103 msgid "The guild's verification level." msgstr "ギルドの認証レベル。" -#: ../../api.rst:3484 +#: ../../api.rst:3754 msgid "See also :attr:`Guild.verification_level`." msgstr ":attr:`Guild.verification_level` も参照してください。" -#: ../../api.rst:3486 -#: ../../../discord/guild.py:docstring of discord.guild.Guild:111 +#: ../../api.rst:3756 +#: ../../../discord/guild.py:docstring of discord.guild.Guild:105 #: ../../../discord/invite.py:docstring of discord.invite.PartialInviteGuild:40 msgid ":class:`VerificationLevel`" msgstr ":class:`VerificationLevel`" -#: ../../api.rst:3490 +#: ../../api.rst:3760 msgid "The guild's default notification level." msgstr "ギルドのデフォルト通知レベル。" -#: ../../api.rst:3492 +#: ../../api.rst:3762 msgid "See also :attr:`Guild.default_notifications`." msgstr ":attr:`Guild.default_notifications` も参照してください。" -#: ../../api.rst:3494 -#: ../../../discord/guild.py:docstring of discord.guild.Guild:131 +#: ../../api.rst:3764 +#: ../../../discord/guild.py:docstring of discord.guild.Guild:125 msgid ":class:`NotificationLevel`" msgstr ":class:`NotificationLevel`" -#: ../../api.rst:3498 +#: ../../api.rst:3768 msgid "The guild's content filter." msgstr "ギルドのコンテンツフィルター。" -#: ../../api.rst:3500 +#: ../../api.rst:3770 msgid "See also :attr:`Guild.explicit_content_filter`." msgstr ":attr:`Guild.explicit_content_filter` も参照してください。" -#: ../../api.rst:3502 -#: ../../../discord/guild.py:docstring of discord.guild.Guild:125 +#: ../../api.rst:3772 +#: ../../../discord/guild.py:docstring of discord.guild.Guild:119 msgid ":class:`ContentFilter`" msgstr ":class:`ContentFilter`" -#: ../../api.rst:3506 +#: ../../api.rst:3776 msgid "The guild's vanity URL." msgstr "ギルドのバニティURL。" -#: ../../api.rst:3508 +#: ../../api.rst:3778 msgid "See also :meth:`Guild.vanity_invite` and :meth:`Guild.edit`." msgstr ":meth:`Guild.vanity_invite` と :meth:`Guild.edit` も参照してください。" -#: ../../api.rst:3514 +#: ../../api.rst:3784 msgid "The position of a :class:`Role` or :class:`abc.GuildChannel`." msgstr ":class:`Role` や :class:`abc.GuildChannel` の位置。" -#: ../../api.rst:3520 -msgid "The type of channel, sticker or integration." -msgstr "チャンネル、スタンプまたは連携サービスのタイプ。" +#: ../../api.rst:3790 +msgid "The type of channel, sticker, webhook or integration." +msgstr "チャンネル、スタンプ、Webhookまたは連携サービスのタイプ。" -#: ../../api.rst:3522 -msgid "Union[:class:`ChannelType`, :class:`StickerType`, :class:`str`]" -msgstr "Union[:class:`ChannelType`, :class:`StickerType`, :class:`str`]" +#: ../../api.rst:3792 +msgid "Union[:class:`ChannelType`, :class:`StickerType`, :class:`WebhookType`, :class:`str`]" +msgstr "Union[:class:`ChannelType`, :class:`StickerType`, :class:`WebhookType`, :class:`str`]" -#: ../../api.rst:3526 +#: ../../api.rst:3796 msgid "The topic of a :class:`TextChannel` or :class:`StageChannel`." msgstr ":class:`TextChannel` または :class:`StageChannel` のトピック。" -#: ../../api.rst:3528 +#: ../../api.rst:3798 msgid "See also :attr:`TextChannel.topic` or :attr:`StageChannel.topic`." msgstr ":attr:`TextChannel.topic` または :attr:`StageChannel.topic` も参照してください。" -#: ../../api.rst:3534 +#: ../../api.rst:3804 msgid "The bitrate of a :class:`VoiceChannel`." msgstr ":class:`VoiceChannel` のビットレート。" -#: ../../api.rst:3536 +#: ../../api.rst:3806 msgid "See also :attr:`VoiceChannel.bitrate`." msgstr ":attr:`VoiceChannel.bitrate` も参照してください。" -#: ../../api.rst:3542 +#: ../../api.rst:3812 msgid "A list of permission overwrite tuples that represents a target and a :class:`PermissionOverwrite` for said target." msgstr "対象とその :class:`PermissionOverwrite` のタプルで示された権限の上書きのリスト。" -#: ../../api.rst:3545 +#: ../../api.rst:3815 msgid "The first element is the object being targeted, which can either be a :class:`Member` or :class:`User` or :class:`Role`. If this object is not found then it is a :class:`Object` with an ID being filled and a ``type`` attribute set to either ``'role'`` or ``'member'`` to help decide what type of ID it is." msgstr "最初の要素は対象のオブジェクトで、 :class:`Member` か :class:`User` か :class:`Role` です。このオブジェクトが見つからない場合はこれはIDが設定され、 ``type`` 属性が ``'role'`` か ``'member'`` に設定された :class:`Object` になります。" -#: ../../api.rst:3551 +#: ../../api.rst:3821 msgid "List[Tuple[target, :class:`PermissionOverwrite`]]" msgstr "List[Tuple[target, :class:`PermissionOverwrite`]]" -#: ../../api.rst:3555 +#: ../../api.rst:3825 msgid "The privacy level of the stage instance or scheduled event" msgstr "ステージインスタンスやスケジュールイベントのプライバシーレベル。" -#: ../../api.rst:3557 +#: ../../api.rst:3827 #: ../../../discord/scheduled_event.py:docstring of discord.scheduled_event.ScheduledEvent:65 #: ../../../discord/stage_instance.py:docstring of discord.stage_instance.StageInstance:47 msgid ":class:`PrivacyLevel`" msgstr ":class:`PrivacyLevel`" -#: ../../api.rst:3561 +#: ../../api.rst:3831 msgid "A list of roles being added or removed from a member." msgstr "メンバーから追加または削除されたロールのリスト。" -#: ../../api.rst:3563 +#: ../../api.rst:3833 msgid "If a role is not found then it is a :class:`Object` with the ID and name being filled in." msgstr "ロールが見つからない場合は、IDとnameが設定された :class:`Object` になります。" -#: ../../api.rst:3566 -#: ../../api.rst:3907 +#: ../../api.rst:3836 +#: ../../api.rst:4183 msgid "List[Union[:class:`Role`, :class:`Object`]]" msgstr "List[Union[:class:`Role`, :class:`Object`]]" -#: ../../api.rst:3570 +#: ../../api.rst:3840 msgid "The nickname of a member." msgstr "メンバーのニックネーム。" -#: ../../api.rst:3572 +#: ../../api.rst:3842 msgid "See also :attr:`Member.nick`" msgstr ":attr:`Member.nick` も参照してください。" -#: ../../api.rst:3578 +#: ../../api.rst:3848 msgid "Whether the member is being server deafened." msgstr "メンバーがサーバーでスピーカーミュートされているかどうか。" -#: ../../api.rst:3580 +#: ../../api.rst:3850 msgid "See also :attr:`VoiceState.deaf`." msgstr ":attr:`VoiceState.deaf` も参照してください。" -#: ../../api.rst:3586 +#: ../../api.rst:3856 msgid "Whether the member is being server muted." msgstr "メンバーがサーバーでミュートされているかどうか。" -#: ../../api.rst:3588 +#: ../../api.rst:3858 msgid "See also :attr:`VoiceState.mute`." msgstr ":attr:`VoiceState.mute` も参照してください。" -#: ../../api.rst:3594 +#: ../../api.rst:3864 msgid "The permissions of a role." msgstr "ロールの権限。" -#: ../../api.rst:3596 +#: ../../api.rst:3866 msgid "See also :attr:`Role.permissions`." msgstr ":attr:`Role.permissions` も参照してください。" -#: ../../api.rst:3603 +#: ../../api.rst:3873 msgid "The colour of a role." msgstr "ロールの色。" -#: ../../api.rst:3605 +#: ../../api.rst:3875 msgid "See also :attr:`Role.colour`" msgstr ":attr:`Role.colour` も参照してください。" -#: ../../api.rst:3611 +#: ../../api.rst:3881 msgid "Whether the role is being hoisted or not." msgstr "役割が別に表示されるかどうか。" -#: ../../api.rst:3613 +#: ../../api.rst:3883 msgid "See also :attr:`Role.hoist`" msgstr ":attr:`Role.hoist` も参照してください。" -#: ../../api.rst:3619 +#: ../../api.rst:3889 msgid "Whether the role is mentionable or not." msgstr "役割がメンションできるかどうか。" -#: ../../api.rst:3621 +#: ../../api.rst:3891 msgid "See also :attr:`Role.mentionable`" msgstr ":attr:`Role.mentionable` も参照してください。" -#: ../../api.rst:3627 +#: ../../api.rst:3897 msgid "The invite's code." msgstr "招待のコード。" -#: ../../api.rst:3629 +#: ../../api.rst:3899 msgid "See also :attr:`Invite.code`" msgstr ":attr:`Invite.code` も参照してください。" -#: ../../api.rst:3635 +#: ../../api.rst:3905 msgid "A guild channel." msgstr "ギルドのチャンネル。" -#: ../../api.rst:3637 +#: ../../api.rst:3907 msgid "If the channel is not found then it is a :class:`Object` with the ID being set. In some cases the channel name is also set." msgstr "チャンネルが見つからない場合は、IDが設定された :class:`Object` になります。 場合によっては、チャンネル名も設定されています。" -#: ../../api.rst:3640 +#: ../../api.rst:3910 msgid "Union[:class:`abc.GuildChannel`, :class:`Object`]" msgstr "Union[:class:`abc.GuildChannel`, :class:`Object`]" -#: ../../api.rst:3644 +#: ../../api.rst:3914 #: ../../../discord/invite.py:docstring of discord.invite.Invite:101 msgid "The user who created the invite." msgstr "招待を作成したユーザー。" -#: ../../api.rst:3646 +#: ../../api.rst:3916 msgid "See also :attr:`Invite.inviter`." msgstr ":attr:`Invite.inviter` も参照してください。" -#: ../../api.rst:3648 +#: ../../api.rst:3918 #: ../../../discord/scheduled_event.py:docstring of discord.scheduled_event.ScheduledEvent:83 #: ../../../discord/integrations.py:docstring of discord.integrations.IntegrationApplication:39 #: ../../../discord/emoji.py:docstring of discord.emoji.Emoji:76 @@ -6720,96 +7284,97 @@ msgstr ":attr:`Invite.inviter` も参照してください。" msgid "Optional[:class:`User`]" msgstr "Optional[:class:`User`]" -#: ../../api.rst:3652 +#: ../../api.rst:3922 msgid "The invite's max uses." msgstr "招待の最大使用回数。" -#: ../../api.rst:3654 +#: ../../api.rst:3924 msgid "See also :attr:`Invite.max_uses`." msgstr ":attr:`Invite.max_uses` も参照してください。" -#: ../../api.rst:3660 +#: ../../api.rst:3930 msgid "The invite's current uses." msgstr "招待の現在の使用回数。" -#: ../../api.rst:3662 +#: ../../api.rst:3932 msgid "See also :attr:`Invite.uses`." msgstr ":attr:`Invite.uses` も参照してください。" -#: ../../api.rst:3668 +#: ../../api.rst:3938 msgid "The invite's max age in seconds." msgstr "招待者の最大時間は秒数です。" -#: ../../api.rst:3670 +#: ../../api.rst:3940 msgid "See also :attr:`Invite.max_age`." msgstr ":attr:`Invite.max_age` も参照してください。" -#: ../../api.rst:3676 +#: ../../api.rst:3946 msgid "If the invite is a temporary invite." msgstr "招待が一時的な招待であるか。" -#: ../../api.rst:3678 +#: ../../api.rst:3948 msgid "See also :attr:`Invite.temporary`." msgstr ":attr:`Invite.temporary` も参照してください。" -#: ../../api.rst:3685 +#: ../../api.rst:3955 msgid "The permissions being allowed or denied." msgstr "許可または拒否された権限。" -#: ../../api.rst:3691 +#: ../../api.rst:3961 msgid "The ID of the object being changed." msgstr "変更されたオブジェクトのID。" -#: ../../api.rst:3697 +#: ../../api.rst:3967 msgid "The avatar of a member." msgstr "メンバーのアバター。" -#: ../../api.rst:3699 +#: ../../api.rst:3969 msgid "See also :attr:`User.avatar`." msgstr ":attr:`User.avatar` も参照してください。" -#: ../../api.rst:3705 +#: ../../api.rst:3975 msgid "The number of seconds members have to wait before sending another message in the channel." msgstr "メンバーが別のメッセージをチャンネルに送信するまでの秒単位の待ち時間。" -#: ../../api.rst:3708 +#: ../../api.rst:3978 msgid "See also :attr:`TextChannel.slowmode_delay`." msgstr ":attr:`TextChannel.slowmode_delay` も参照してください。" -#: ../../api.rst:3714 +#: ../../api.rst:3984 msgid "The region for the voice channel’s voice communication. A value of ``None`` indicates automatic voice region detection." msgstr "ボイスチャンネルの音声通信のためのリージョン。値が ``None`` の場合は自動で検出されます。" -#: ../../api.rst:3717 +#: ../../api.rst:3987 msgid "See also :attr:`VoiceChannel.rtc_region`." msgstr ":attr:`VoiceChannel.rtc_region` も参照してください。" -#: ../../api.rst:3723 +#: ../../api.rst:3993 #: ../../../discord/guild.py:docstring of discord.guild.Guild.create_voice_channel:31 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_stage_channel:37 #: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel:86 #: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.edit:49 msgid "The camera video quality for the voice channel's participants." msgstr "ボイスチャンネル参加者のカメラビデオの画質。" -#: ../../api.rst:3725 +#: ../../api.rst:3995 msgid "See also :attr:`VoiceChannel.video_quality_mode`." msgstr ":attr:`VoiceChannel.video_quality_mode` も参照してください。" -#: ../../api.rst:3727 +#: ../../api.rst:3997 #: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel:90 #: ../../../discord/channel.py:docstring of discord.channel.StageChannel:93 msgid ":class:`VideoQualityMode`" msgstr ":class:`VideoQualityMode`" -#: ../../api.rst:3731 +#: ../../api.rst:4001 msgid "The format type of a sticker being changed." msgstr "変更されたスタンプのフォーマットの種類。" -#: ../../api.rst:3733 +#: ../../api.rst:4003 msgid "See also :attr:`GuildSticker.format`" msgstr ":attr:`GuildSticker.format` も参照してください。" -#: ../../api.rst:3735 +#: ../../api.rst:4005 #: ../../../discord/sticker.py:docstring of discord.sticker.StickerItem:35 #: ../../../discord/sticker.py:docstring of discord.sticker.Sticker:47 #: ../../../discord/sticker.py:docstring of discord.sticker.StandardSticker:47 @@ -6817,218 +7382,315 @@ msgstr ":attr:`GuildSticker.format` も参照してください。" msgid ":class:`StickerFormatType`" msgstr ":class:`StickerFormatType`" -#: ../../api.rst:3739 +#: ../../api.rst:4009 msgid "The name of the emoji that represents a sticker being changed." msgstr "変更されたスタンプを示す絵文字の名前。" -#: ../../api.rst:3741 +#: ../../api.rst:4011 msgid "See also :attr:`GuildSticker.emoji`." msgstr ":attr:`GuildSticker.emoji` も参照してください。" -#: ../../api.rst:3747 +#: ../../api.rst:4017 msgid "The unicode emoji that is used as an icon for the role being changed." msgstr "変更されたロールのアイコンとして使用されるUnicode絵文字。" -#: ../../api.rst:3749 +#: ../../api.rst:4019 msgid "See also :attr:`Role.unicode_emoji`." msgstr ":attr:`Role.unicode_emoji` も参照してください。" -#: ../../api.rst:3755 +#: ../../api.rst:4025 msgid "The description of a guild, a sticker, or a scheduled event." msgstr "ギルド、スタンプ、またはスケジュールイベントの説明。" -#: ../../api.rst:3757 +#: ../../api.rst:4027 msgid "See also :attr:`Guild.description`, :attr:`GuildSticker.description`, or :attr:`ScheduledEvent.description`." msgstr ":attr:`Guild.description` 、 :attr:`GuildSticker.description` 、または :attr:`ScheduledEvent.description` も参照してください。" -#: ../../api.rst:3764 +#: ../../api.rst:4034 msgid "The availability of a sticker being changed." msgstr "変更されたスタンプの利用可能かどうかの状態。" -#: ../../api.rst:3766 +#: ../../api.rst:4036 msgid "See also :attr:`GuildSticker.available`" msgstr ":attr:`GuildSticker.available` も参照してください。" -#: ../../api.rst:3772 +#: ../../api.rst:4042 msgid "The thread is now archived." msgstr "スレッドがアーカイブされたか。" -#: ../../api.rst:3778 +#: ../../api.rst:4048 msgid "The thread is being locked or unlocked." msgstr "スレッドがロックされ、またはロックが解除されたかどうか。" -#: ../../api.rst:3784 +#: ../../api.rst:4054 msgid "The thread's auto archive duration being changed." msgstr "変更されたスレッドの自動アーカイブ期間。" -#: ../../api.rst:3786 +#: ../../api.rst:4056 msgid "See also :attr:`Thread.auto_archive_duration`" msgstr ":attr:`Thread.auto_archive_duration` も参照してください。" -#: ../../api.rst:3792 +#: ../../api.rst:4062 msgid "The default auto archive duration for newly created threads being changed." msgstr "変更された新規作成されたスレッドの既定の自動アーカイブ期間。" -#: ../../api.rst:3798 +#: ../../api.rst:4068 msgid "Whether non-moderators can add users to this private thread." msgstr "モデレータ以外がプライベートスレッドにユーザーを追加できるかどうか。" -#: ../../api.rst:3804 +#: ../../api.rst:4074 msgid "Whether the user is timed out, and if so until when." msgstr "ユーザーがタイムアウトされているかどうか、そしてその場合はいつまでか。" -#: ../../api.rst:3806 +#: ../../api.rst:4076 #: ../../../discord/webhook/async_.py:docstring of discord.WebhookMessage.edited_at:3 #: ../../../discord/message.py:docstring of discord.Message.edited_at:3 -#: ../../../discord/scheduled_event.py:docstring of discord.scheduled_event.ScheduledEvent:59 -#: ../../../discord/member.py:docstring of discord.member.Member:30 +#: ../../../discord/guild.py:docstring of discord.Guild.invites_paused_until:6 +#: ../../../discord/guild.py:docstring of discord.Guild.dms_paused_until:6 msgid "Optional[:class:`datetime.datetime`]" msgstr "Optional[:class:`datetime.datetime`]" -#: ../../api.rst:3810 +#: ../../api.rst:4080 msgid "Integration emoticons were enabled or disabled." msgstr "連携サービスの絵文字が有効化され、または無効化されたか。" -#: ../../api.rst:3812 +#: ../../api.rst:4082 msgid "See also :attr:`StreamIntegration.enable_emoticons`" msgstr ":attr:`StreamIntegration.enable_emoticons` も参照してください。" -#: ../../api.rst:3819 +#: ../../api.rst:4089 msgid "The behaviour of expiring subscribers changed." msgstr "変更された期限切れのサブスクライバーの動作。" -#: ../../api.rst:3821 +#: ../../api.rst:4091 msgid "See also :attr:`StreamIntegration.expire_behaviour`" msgstr ":attr:`StreamIntegration.expire_behaviour` も参照してください。" -#: ../../api.rst:3823 +#: ../../api.rst:4093 #: ../../../discord/integrations.py:docstring of discord.integrations.StreamIntegration:51 #: ../../../discord/integrations.py:docstring of discord.StreamIntegration.expire_behavior:3 msgid ":class:`ExpireBehaviour`" msgstr ":class:`ExpireBehaviour`" -#: ../../api.rst:3827 +#: ../../api.rst:4097 msgid "The grace period before expiring subscribers changed." msgstr "変更された期限切れのサブスクライバーの猶予期間。" -#: ../../api.rst:3829 +#: ../../api.rst:4099 msgid "See also :attr:`StreamIntegration.expire_grace_period`" msgstr ":attr:`StreamIntegration.expire_grace_period` も参照してください。" -#: ../../api.rst:3835 +#: ../../api.rst:4105 msgid "The preferred locale for the guild changed." msgstr "変更されたギルドの優先ローケル。" -#: ../../api.rst:3837 +#: ../../api.rst:4107 msgid "See also :attr:`Guild.preferred_locale`" msgstr ":attr:`Guild.preferred_locale` も参照してください。" -#: ../../api.rst:3839 -#: ../../../discord/guild.py:docstring of discord.guild.Guild:162 +#: ../../api.rst:4109 +#: ../../../discord/guild.py:docstring of discord.guild.Guild:156 msgid ":class:`Locale`" msgstr ":class:`Locale`" -#: ../../api.rst:3843 +#: ../../api.rst:4113 msgid "The number of days after which inactive and role-unassigned members are kicked has been changed." msgstr "変更された活動していない、かつロールが割り当てられていないメンバーがキックさえるまでの日数。" -#: ../../api.rst:3849 +#: ../../api.rst:4119 #: ../../../discord/scheduled_event.py:docstring of discord.scheduled_event.ScheduledEvent:69 msgid "The status of the scheduled event." msgstr "スケジュールイベントのステータス。" -#: ../../api.rst:3851 +#: ../../api.rst:4121 #: ../../../discord/scheduled_event.py:docstring of discord.scheduled_event.ScheduledEvent:71 msgid ":class:`EventStatus`" msgstr ":class:`EventStatus`" -#: ../../api.rst:3855 +#: ../../api.rst:4125 msgid "The type of entity this scheduled event is for." msgstr "スケジュールイベントの開催場所の種類。" -#: ../../api.rst:3857 +#: ../../api.rst:4127 #: ../../../discord/scheduled_event.py:docstring of discord.scheduled_event.ScheduledEvent:41 msgid ":class:`EntityType`" msgstr ":class:`EntityType`" -#: ../../api.rst:3861 +#: ../../api.rst:4131 #: ../../../discord/scheduled_event.py:docstring of discord.ScheduledEvent.cover_image:1 msgid "The scheduled event's cover image." msgstr "スケジュールイベントのカバー画像。" -#: ../../api.rst:3863 +#: ../../api.rst:4133 msgid "See also :attr:`ScheduledEvent.cover_image`." msgstr ":attr:`ScheduledEvent.cover_image` も参照してください。" -#: ../../api.rst:3869 +#: ../../api.rst:4139 msgid "List of permissions for the app command." msgstr "アプリケーションコマンドの権限のリスト。" -#: ../../api.rst:3871 +#: ../../api.rst:4141 #: ../../../discord/raw_models.py:docstring of discord.raw_models.RawAppCommandPermissionsUpdateEvent:29 msgid "List[:class:`~discord.app_commands.AppCommandPermissions`]" msgstr "List[:class:`~discord.app_commands.AppCommandPermissions`]" -#: ../../api.rst:3875 +#: ../../api.rst:4145 msgid "Whether the automod rule is active or not." msgstr "自動管理ルールが有効かどうか。" -#: ../../api.rst:3881 +#: ../../api.rst:4151 msgid "The event type for triggering the automod rule." msgstr "自動管理ルールを発動させるイベントの種類。" -#: ../../api.rst:3883 +#: ../../api.rst:4153 +#: ../../../discord/automod.py:docstring of discord.automod.AutoModRule:57 msgid ":class:`AutoModRuleEventType`" msgstr ":class:`AutoModRuleEventType`" -#: ../../api.rst:3887 +#: ../../api.rst:4157 msgid "The trigger type for the automod rule." msgstr "自動管理ルールの発動条件の種類。" -#: ../../api.rst:3889 +#: ../../api.rst:4159 #: ../../../discord/automod.py:docstring of discord.automod.AutoModAction:28 -#: ../../../discord/automod.py:docstring of discord.automod.AutoModTrigger:24 +#: ../../../discord/automod.py:docstring of discord.automod.AutoModTrigger:28 msgid ":class:`AutoModRuleTriggerType`" msgstr ":class:`AutoModRuleTriggerType`" -#: ../../api.rst:3893 +#: ../../api.rst:4163 msgid "The trigger for the automod rule." msgstr "自動管理ルールの発動条件。" -#: ../../api.rst:3895 +#: ../../api.rst:4167 +msgid "The :attr:`~AutoModTrigger.type` of the trigger may be incorrect. Some attributes such as :attr:`~AutoModTrigger.keyword_filter`, :attr:`~AutoModTrigger.regex_patterns`, and :attr:`~AutoModTrigger.allow_list` will only have the added or removed values." +msgstr "" + +#: ../../api.rst:4171 #: ../../../discord/automod.py:docstring of discord.automod.AutoModRule:33 msgid ":class:`AutoModTrigger`" msgstr ":class:`AutoModTrigger`" -#: ../../api.rst:3899 +#: ../../api.rst:4175 msgid "The actions to take when an automod rule is triggered." msgstr "自動管理ルールの発動時の対応。" -#: ../../api.rst:3901 +#: ../../api.rst:4177 msgid "List[AutoModRuleAction]" msgstr "List[AutoModRuleAction]" -#: ../../api.rst:3905 +#: ../../api.rst:4181 msgid "The list of roles that are exempt from the automod rule." msgstr "自動管理ルールの除外対象のロールのリスト。" -#: ../../api.rst:3911 +#: ../../api.rst:4187 msgid "The list of channels or threads that are exempt from the automod rule." msgstr "自動管理ルールの除外対象のチャンネルまたはスレッドのリスト。" -#: ../../api.rst:3913 +#: ../../api.rst:4189 msgid "List[:class:`abc.GuildChannel`, :class:`Thread`, :class:`Object`]" msgstr "List[:class:`abc.GuildChannel`, :class:`Thread`, :class:`Object`]" -#: ../../api.rst:3919 +#: ../../api.rst:4193 +msgid "The guild’s display setting to show boost progress bar." +msgstr "ギルドのブーストの進捗バーを表示するかの設定。" + +#: ../../api.rst:4199 +msgid "The guild’s system channel settings." +msgstr "ギルドのシステムチャンネルの設定。" + +#: ../../api.rst:4201 +msgid "See also :attr:`Guild.system_channel_flags`" +msgstr ":attr:`Guild.system_channel_flags` を参照してください。" + +#: ../../api.rst:4203 +#: ../../../discord/guild.py:docstring of discord.Guild.system_channel_flags:3 +msgid ":class:`SystemChannelFlags`" +msgstr ":class:`SystemChannelFlags`" + +#: ../../api.rst:4207 +msgid "Whether the channel is marked as “not safe for work” or “age restricted”." +msgstr "チャンネルに年齢制限がかかっているか。" + +#: ../../api.rst:4213 +msgid "The channel’s limit for number of members that can be in a voice or stage channel." +msgstr "ボイスまたはステージチャンネルに参加できるメンバー数の制限。" + +#: ../../api.rst:4215 +msgid "See also :attr:`VoiceChannel.user_limit` and :attr:`StageChannel.user_limit`" +msgstr ":attr:`VoiceChannel.user_limit` と :attr:`StageChannel.user_limit` も参照してください。" + +#: ../../api.rst:4221 +msgid "The channel flags associated with this thread or forum post." +msgstr "このスレッドやフォーラム投稿に関連付けられたチャンネルフラグ。" + +#: ../../api.rst:4223 +msgid "See also :attr:`ForumChannel.flags` and :attr:`Thread.flags`" +msgstr ":attr:`ForumChannel.flags` と :attr:`Thread.flags` も参照してください。" + +#: ../../api.rst:4225 +#: ../../../discord/channel.py:docstring of discord.ForumChannel.flags:5 +#: ../../../discord/threads.py:docstring of discord.Thread.flags:3 +msgid ":class:`ChannelFlags`" +msgstr ":class:`ChannelFlags`" + +#: ../../api.rst:4229 +msgid "The default slowmode delay for threads created in this text channel or forum." +msgstr "このテキストチャンネルやフォーラムで作成されたスレッドのデフォルトの低速モードのレート制限。" + +#: ../../api.rst:4231 +msgid "See also :attr:`TextChannel.default_thread_slowmode_delay` and :attr:`ForumChannel.default_thread_slowmode_delay`" +msgstr ":attr:`TextChannel.default_thread_slowmode_delay` と :attr:`ForumChannel.default_thread_slowmode_delay` も参照してください。" + +#: ../../api.rst:4237 +msgid "The applied tags of a forum post." +msgstr "フォーラム投稿に適用されたタグ。" + +#: ../../api.rst:4239 +msgid "See also :attr:`Thread.applied_tags`" +msgstr ":attr:`Thread.applied_tags` も参照してください。" + +#: ../../api.rst:4241 +msgid "List[Union[:class:`ForumTag`, :class:`Object`]]" +msgstr "List[Union[:class:`ForumTag`, :class:`Object`]]" + +#: ../../api.rst:4245 +msgid "The available tags of a forum." +msgstr "フォーラムにて利用可能なタグ。" + +#: ../../api.rst:4247 +msgid "See also :attr:`ForumChannel.available_tags`" +msgstr ":attr:`ForumChannel.available_tags` も参照してください。" + +#: ../../api.rst:4249 +#: ../../../discord/channel.py:docstring of discord.ForumChannel.available_tags:5 +msgid "Sequence[:class:`ForumTag`]" +msgstr "Sequence[:class:`ForumTag`]" + +#: ../../api.rst:4253 +msgid "The default_reaction_emoji for forum posts." +msgstr "フォーラム投稿の default_reaction_emoji。" + +#: ../../api.rst:4255 +msgid "See also :attr:`ForumChannel.default_reaction_emoji`" +msgstr ":attr:`ForumChannel.default_reaction_emoji` も参照してください。" + +#: ../../api.rst:4257 +#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel:105 +#: ../../../discord/activity.py:docstring of discord.activity.Activity:100 +#: ../../../discord/activity.py:docstring of discord.activity.CustomActivity:33 +#: ../../../discord/channel.py:docstring of discord.channel.ForumTag:48 +msgid "Optional[:class:`PartialEmoji`]" +msgstr "Optional[:class:`PartialEmoji`]" + +#: ../../api.rst:4263 msgid "Webhook Support" msgstr "Webhookサポート" -#: ../../api.rst:3921 +#: ../../api.rst:4265 msgid "discord.py offers support for creating, editing, and executing webhooks through the :class:`Webhook` class." msgstr "discord.pyは、 :class:`Webhook` クラスからWebhookの作成、編集、実行をサポートします。" -#: ../../api.rst:3924 +#: ../../api.rst:4268 msgid "Webhook" msgstr "Webhook" @@ -7163,23 +7825,31 @@ msgstr "リクエストを送信するために使用するセッション。ラ #: ../../../discord/webhook/async_.py:docstring of discord.webhook.async_.Webhook.partial:13 #: ../../../discord/webhook/async_.py:docstring of discord.webhook.async_.Webhook.from_url:15 +msgid "The client to initialise this webhook with. This allows it to attach the client's internal state. If ``session`` is not given while this is given then the client's internal session will be used." +msgstr "このWebhookを初期化するためのクライアント。これによりクライアントの内部状態を付属できます。このとき、 ``session`` が指定されていない場合は、クライアントの内部セッションが使用されます。" + +#: ../../../discord/webhook/async_.py:docstring of discord.webhook.async_.Webhook.partial:19 +#: ../../../discord/webhook/async_.py:docstring of discord.webhook.async_.Webhook.from_url:21 #: ../../../discord/webhook/sync.py:docstring of discord.webhook.sync.SyncWebhook.partial:12 #: ../../../discord/webhook/sync.py:docstring of discord.webhook.sync.SyncWebhook.from_url:10 msgid "The bot authentication token for authenticated requests involving the webhook." msgstr "Webhook関連の認証が必要なリクエストに使用するボットの認証トークン。" -#: ../../../discord/webhook/async_.py:docstring of discord.webhook.async_.Webhook.partial:19 -#: ../../../discord/webhook/async_.py:docstring of discord.webhook.async_.Webhook.from_url:23 -#: ../../../discord/webhook/sync.py:docstring of discord.webhook.sync.SyncWebhook.partial:16 -#: ../../../discord/webhook/sync.py:docstring of discord.webhook.sync.SyncWebhook.from_url:16 +#: ../../../discord/webhook/async_.py:docstring of discord.webhook.async_.Webhook.partial:25 +#: ../../../discord/webhook/async_.py:docstring of discord.webhook.async_.Webhook.from_url:28 +msgid "Neither ``session`` nor ``client`` were given." +msgstr "``session`` と ``client`` のどちらも指定されていない場合。" + +#: ../../../discord/webhook/async_.py:docstring of discord.webhook.async_.Webhook.partial:27 +#: ../../../discord/webhook/async_.py:docstring of discord.webhook.async_.Webhook.from_url:30 msgid "A partial :class:`Webhook`. A partial webhook is just a webhook object with an ID and a token." msgstr "部分的な :class:`Webhook` 。部分的なWebhookはただのIDとトークンのみを持つWebhookオブジェクトです。" -#: ../../../discord/webhook/async_.py:docstring of discord.webhook.async_.Webhook.partial:21 -#: ../../../discord/webhook/async_.py:docstring of discord.webhook.async_.Webhook.from_url:25 +#: ../../../discord/webhook/async_.py:docstring of discord.webhook.async_.Webhook.partial:29 +#: ../../../discord/webhook/async_.py:docstring of discord.webhook.async_.Webhook.from_url:32 #: ../../../discord/webhook/async_.py:docstring of discord.webhook.async_.Webhook.fetch:24 -#: ../../../discord/webhook/sync.py:docstring of discord.webhook.sync.SyncWebhook.partial:18 -#: ../../../discord/webhook/sync.py:docstring of discord.webhook.sync.SyncWebhook.from_url:18 +#: ../../../discord/channel.py:docstring of discord.channel.TextChannel.create_webhook:22 +#: ../../../discord/channel.py:docstring of discord.channel.TextChannel.follow:31 msgid ":class:`Webhook`" msgstr ":class:`Webhook`" @@ -7193,7 +7863,7 @@ msgstr "WebhookのURLから部分的な :class:`Webhook` を作成します。" msgid "The URL of the webhook." msgstr "WebhookのURL。" -#: ../../../discord/webhook/async_.py:docstring of discord.webhook.async_.Webhook.from_url:21 +#: ../../../discord/webhook/async_.py:docstring of discord.webhook.async_.Webhook.from_url:27 #: ../../../discord/webhook/sync.py:docstring of discord.webhook.sync.SyncWebhook.from_url:14 msgid "The URL is invalid." msgstr "URLが無効な場合。" @@ -7468,39 +8138,51 @@ msgstr "Webhookが :class:`~discord.ForumChannel` に属する場合、このWeb msgid "Whether to suppress embeds for the message. This sends the message without any embeds if set to ``True``." msgstr "メッセージの埋め込みを抑制するかどうか。これが ``True`` に設定されている場合、埋め込みなしでメッセージを送信します。" -#: ../../../discord/webhook/async_.py:docstring of discord.webhook.async_.Webhook.send:79 +#: ../../../discord/webhook/async_.py:docstring of discord.webhook.async_.Webhook.send:78 +#: ../../../discord/webhook/sync.py:docstring of discord.webhook.sync.SyncWebhook.send:57 +#: ../../../discord/abc.py:docstring of discord.abc.Messageable.send:76 +#: ../../../discord/user.py:docstring of discord.abc.Messageable.send:76 +#: ../../../discord/member.py:docstring of discord.abc.Messageable.send:76 +msgid "Whether to suppress push and desktop notifications for the message. This will increment the mention counter in the UI, but will not actually send a notification." +msgstr "メッセージのプッシュ通知とデスクトップ通知を抑制するかどうか。 抑制した場合、UIのメンションカウンターを増やしますが、実際に通知を送信することはありません。" + +#: ../../../discord/webhook/async_.py:docstring of discord.webhook.async_.Webhook.send:83 +msgid "Tags to apply to the thread if the webhook belongs to a :class:`~discord.ForumChannel`." +msgstr "" + +#: ../../../discord/webhook/async_.py:docstring of discord.webhook.async_.Webhook.send:88 #: ../../../discord/webhook/async_.py:docstring of discord.message.PartialMessage.reply:12 -#: ../../../discord/webhook/sync.py:docstring of discord.webhook.sync.SyncWebhook.send:58 -#: ../../../discord/abc.py:docstring of discord.abc.Messageable.send:77 -#: ../../../discord/user.py:docstring of discord.abc.Messageable.send:77 +#: ../../../discord/webhook/sync.py:docstring of discord.webhook.sync.SyncWebhook.send:63 +#: ../../../discord/abc.py:docstring of discord.abc.Messageable.send:82 +#: ../../../discord/user.py:docstring of discord.abc.Messageable.send:82 msgid "Sending the message failed." msgstr "メッセージの送信に失敗した場合。" -#: ../../../discord/webhook/async_.py:docstring of discord.webhook.async_.Webhook.send:80 -#: ../../../discord/webhook/sync.py:docstring of discord.webhook.sync.SyncWebhook.send:59 +#: ../../../discord/webhook/async_.py:docstring of discord.webhook.async_.Webhook.send:89 +#: ../../../discord/webhook/sync.py:docstring of discord.webhook.sync.SyncWebhook.send:64 msgid "This webhook was not found." msgstr "Webhookが見つからなかった場合。" -#: ../../../discord/webhook/async_.py:docstring of discord.webhook.async_.Webhook.send:81 -#: ../../../discord/webhook/sync.py:docstring of discord.webhook.sync.SyncWebhook.send:60 +#: ../../../discord/webhook/async_.py:docstring of discord.webhook.async_.Webhook.send:90 +#: ../../../discord/webhook/sync.py:docstring of discord.webhook.sync.SyncWebhook.send:65 msgid "The authorization token for the webhook is incorrect." msgstr "Webhookの認証トークンが正しくない場合。" -#: ../../../discord/webhook/async_.py:docstring of discord.webhook.async_.Webhook.send:82 -#: ../../../discord/webhook/sync.py:docstring of discord.webhook.sync.SyncWebhook.send:61 +#: ../../../discord/webhook/async_.py:docstring of discord.webhook.async_.Webhook.send:91 +#: ../../../discord/webhook/sync.py:docstring of discord.webhook.sync.SyncWebhook.send:66 msgid "You specified both ``embed`` and ``embeds`` or ``file`` and ``files`` or ``thread`` and ``thread_name``." msgstr "``embed`` と ``embeds`` または ``file`` と ``files`` または ``thread`` と ``thread_name`` の両方を指定した場合。" -#: ../../../discord/webhook/async_.py:docstring of discord.webhook.async_.Webhook.send:83 +#: ../../../discord/webhook/async_.py:docstring of discord.webhook.async_.Webhook.send:92 msgid "The length of ``embeds`` was invalid, there was no token associated with this webhook or ``ephemeral`` was passed with the improper webhook type or there was no state attached with this webhook when giving it a view." msgstr "``embeds`` の長さが不正な場合、Webhookにトークンが紐づいていない場合、 ``ephemeral`` が適切でない種類のWebhookに渡された場合、またはステートが付属していないWebhookにビューを渡した場合。" -#: ../../../discord/webhook/async_.py:docstring of discord.webhook.async_.Webhook.send:85 -#: ../../../discord/webhook/sync.py:docstring of discord.webhook.sync.SyncWebhook.send:64 +#: ../../../discord/webhook/async_.py:docstring of discord.webhook.async_.Webhook.send:94 +#: ../../../discord/webhook/sync.py:docstring of discord.webhook.sync.SyncWebhook.send:69 msgid "If ``wait`` is ``True`` then the message that was sent, otherwise ``None``." msgstr "``wait`` が ``True`` のとき、送信されたメッセージ、それ以外の場合は ``None`` 。" -#: ../../../discord/webhook/async_.py:docstring of discord.webhook.async_.Webhook.send:86 +#: ../../../discord/webhook/async_.py:docstring of discord.webhook.async_.Webhook.send:95 msgid "Optional[:class:`WebhookMessage`]" msgstr "Optional[:class:`WebhookMessage`]" @@ -7701,7 +8383,7 @@ msgstr "メッセージの削除に失敗した場合。" msgid "Deleted a message that is not yours." msgstr "自分以外のメッセージを削除しようとした場合。" -#: ../../api.rst:3933 +#: ../../api.rst:4277 msgid "WebhookMessage" msgstr "WebhookMessage" @@ -7741,7 +8423,7 @@ msgid "The updated view to update this message with. If ``None`` is passed then msgstr "このメッセージを更新するために更新されたビュー。 ``None`` が渡された場合、ビューは削除されます。" #: ../../../discord/webhook/async_.py:docstring of discord.webhook.async_.WebhookMessage.edit:42 -#: ../../../discord/webhook/sync.py:docstring of discord.webhook.sync.SyncWebhook.send:62 +#: ../../../discord/webhook/sync.py:docstring of discord.webhook.sync.SyncWebhook.send:67 #: ../../../discord/webhook/sync.py:docstring of discord.webhook.sync.SyncWebhook.edit_message:33 #: ../../../discord/webhook/sync.py:docstring of discord.webhook.sync.SyncWebhookMessage.edit:30 msgid "The length of ``embeds`` was invalid or there was no token associated with this webhook." @@ -7969,54 +8651,72 @@ msgstr "スレッドの名前。" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.create_thread:16 #: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.create_thread:13 #: ../../../discord/message.py:docstring of discord.message.PartialMessage.create_thread:14 -msgid "The duration in minutes before a thread is automatically archived for inactivity. If not provided, the channel's default auto archive duration is used." -msgstr "スレッドが非アクティブ時に、自動的にアーカイブされるまでの分単位の時間。指定しない場合は、チャンネルのデフォルトの自動アーカイブ期間が使用されます。" +msgid "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." +msgstr "スレッドがチャンネルリストから自動的に非表示になるまでの分単位の時間。 指定されていない場合、チャンネルのデフォルトの自動アーカイブ期間が使用されます。指定された場合は、 ``60`` 、 ``1440`` 、 ``4320`` 、または ``10080`` のいずれかでないといけません。" + +#: ../../../discord/webhook/async_.py:docstring of discord.message.PartialMessage.create_thread:14 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage.create_thread:14 +#: ../../../discord/channel.py:docstring of discord.channel.TextChannel.create_thread:16 +#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.create_thread:13 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage.create_thread:14 +msgid "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." +msgstr "スレッドがチャンネルリストから自動的に非表示になるまでの分単位の時間。 指定されていない場合、チャンネルのデフォルトの自動アーカイブ期間が使用されます。" #: ../../../discord/webhook/async_.py:docstring of discord.message.PartialMessage.create_thread:17 #: ../../../discord/message.py:docstring of discord.message.PartialMessage.create_thread:17 -#: ../../../discord/channel.py:docstring of discord.channel.TextChannel.create_thread:28 +#: ../../../discord/channel.py:docstring of discord.channel.TextChannel.create_thread:19 #: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.create_thread:16 #: ../../../discord/message.py:docstring of discord.message.PartialMessage.create_thread:17 +msgid "Must be one of ``60``, ``1440``, ``4320``, or ``10080``, if provided." +msgstr "指定された場合は、 ``60`` 、 ``1440`` 、 ``4320`` 、または ``10080`` のいずれかでないといけません。" + +#: ../../../discord/webhook/async_.py:docstring of discord.message.PartialMessage.create_thread:19 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage.create_thread:19 +#: ../../../discord/channel.py:docstring of discord.channel.TextChannel.create_thread:30 +#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.create_thread:18 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage.create_thread:19 msgid "Specifies the slowmode rate limit for user in this channel, in seconds. The maximum value possible is ``21600``. By default no slowmode rate limit if this is ``None``." msgstr "このチャンネルの秒単位での低速モードレート制限。 最大値は ``21600`` です。デフォルトは ``None`` でこの場合は低速モードレート制限が無しとなります。" -#: ../../../discord/webhook/async_.py:docstring of discord.message.PartialMessage.create_thread:21 -#: ../../../discord/message.py:docstring of discord.message.PartialMessage.create_thread:21 -#: ../../../discord/channel.py:docstring of discord.channel.TextChannel.create_thread:23 -#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.create_thread:49 -#: ../../../discord/message.py:docstring of discord.message.PartialMessage.create_thread:21 +#: ../../../discord/webhook/async_.py:docstring of discord.message.PartialMessage.create_thread:23 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage.create_thread:23 +#: ../../../discord/channel.py:docstring of discord.channel.TextChannel.create_thread:25 +#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.create_thread:51 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage.create_thread:23 msgid "The reason for creating a new thread. Shows up on the audit log." msgstr "スレッドを作成する理由。監査ログに表示されます。" -#: ../../../discord/webhook/async_.py:docstring of discord.message.PartialMessage.create_thread:24 -#: ../../../discord/message.py:docstring of discord.message.PartialMessage.create_thread:24 -#: ../../../discord/channel.py:docstring of discord.channel.TextChannel.create_thread:33 -#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.create_thread:52 -#: ../../../discord/message.py:docstring of discord.message.PartialMessage.create_thread:24 +#: ../../../discord/webhook/async_.py:docstring of discord.message.PartialMessage.create_thread:26 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage.create_thread:26 +#: ../../../discord/channel.py:docstring of discord.channel.TextChannel.create_thread:35 +#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.create_thread:54 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage.create_thread:26 msgid "You do not have permissions to create a thread." msgstr "スレッドを作成する権限を持っていない場合。" -#: ../../../discord/webhook/async_.py:docstring of discord.message.PartialMessage.create_thread:25 -#: ../../../discord/message.py:docstring of discord.message.PartialMessage.create_thread:25 -#: ../../../discord/message.py:docstring of discord.message.PartialMessage.create_thread:25 +#: ../../../discord/webhook/async_.py:docstring of discord.message.PartialMessage.create_thread:27 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage.create_thread:27 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage.create_thread:27 msgid "Creating the thread failed." msgstr "スレッドの作成に失敗した場合。" -#: ../../../discord/webhook/async_.py:docstring of discord.message.PartialMessage.create_thread:26 -#: ../../../discord/message.py:docstring of discord.message.PartialMessage.create_thread:26 -#: ../../../discord/message.py:docstring of discord.message.PartialMessage.create_thread:26 -msgid "This message does not have guild info attached." -msgstr "メッセージがギルド情報を持っていない場合。" - #: ../../../discord/webhook/async_.py:docstring of discord.message.PartialMessage.create_thread:28 #: ../../../discord/message.py:docstring of discord.message.PartialMessage.create_thread:28 #: ../../../discord/message.py:docstring of discord.message.PartialMessage.create_thread:28 +msgid "This message does not have guild info attached." +msgstr "メッセージがギルド情報を持っていない場合。" + +#: ../../../discord/webhook/async_.py:docstring of discord.message.PartialMessage.create_thread:30 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage.create_thread:30 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage.create_thread:30 msgid "The created thread." msgstr "作成されたスレッド。" -#: ../../../discord/webhook/async_.py:docstring of discord.message.PartialMessage.create_thread:29 -#: ../../../discord/message.py:docstring of discord.message.PartialMessage.create_thread:29 -#: ../../../discord/message.py:docstring of discord.message.PartialMessage.create_thread:29 +#: ../../../discord/webhook/async_.py:docstring of discord.message.PartialMessage.create_thread:31 +#: ../../../discord/webhook/async_.py:docstring of discord.message.PartialMessage.fetch_thread:17 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage.create_thread:31 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage.fetch_thread:17 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage.create_thread:31 msgid ":class:`.Thread`" msgstr ":class:`.Thread`" @@ -8056,6 +8756,42 @@ msgstr "完全なメッセージ。" msgid ":class:`Message`" msgstr ":class:`Message`" +#: ../../../discord/webhook/async_.py:docstring of discord.message.PartialMessage.fetch_thread:3 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage.fetch_thread:3 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage.fetch_thread:3 +msgid "Retrieves the public thread attached to this message." +msgstr "" + +#: ../../../discord/webhook/async_.py:docstring of discord.message.PartialMessage.fetch_thread:7 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage.fetch_thread:7 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage.fetch_thread:7 +msgid "This method is an API call. For general usage, consider :attr:`thread` instead." +msgstr "" + +#: ../../../discord/webhook/async_.py:docstring of discord.message.PartialMessage.fetch_thread:11 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage.fetch_thread:11 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage.fetch_thread:11 +msgid "An unknown channel type was received from Discord or the guild the thread belongs to is not the same as the one in this object points to." +msgstr "" + +#: ../../../discord/webhook/async_.py:docstring of discord.message.PartialMessage.fetch_thread:12 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage.fetch_thread:12 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage.fetch_thread:12 +msgid "Retrieving the thread failed." +msgstr "" + +#: ../../../discord/webhook/async_.py:docstring of discord.message.PartialMessage.fetch_thread:13 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage.fetch_thread:13 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage.fetch_thread:13 +msgid "There is no thread attached to this message." +msgstr "" + +#: ../../../discord/webhook/async_.py:docstring of discord.message.PartialMessage.fetch_thread:16 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage.fetch_thread:16 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage.fetch_thread:16 +msgid "The public thread attached to this message." +msgstr "" + #: ../../../discord/webhook/async_.py:docstring of discord.message.Message.is_system:1 #: ../../../discord/message.py:docstring of discord.message.Message.is_system:1 msgid ":class:`bool`: Whether the message is a system message." @@ -8115,30 +8851,30 @@ msgstr "チャンネルにすでに50個ピン留めされたメッセージが #: ../../../discord/webhook/async_.py:docstring of discord.message.PartialMessage.publish:3 #: ../../../discord/message.py:docstring of discord.message.PartialMessage.publish:3 #: ../../../discord/message.py:docstring of discord.message.PartialMessage.publish:3 -msgid "Publishes this message to your announcement channel." -msgstr "アナウンスチャンネルのメッセージを公開します。" +msgid "Publishes this message to the channel's followers." +msgstr "このメッセージをチャンネルのフォロワーに公開します。" #: ../../../discord/webhook/async_.py:docstring of discord.message.PartialMessage.publish:5 #: ../../../discord/message.py:docstring of discord.message.PartialMessage.publish:5 #: ../../../discord/message.py:docstring of discord.message.PartialMessage.publish:5 -msgid "You must have :attr:`~Permissions.send_messages` to do this." -msgstr "これを行うには、 :attr:`~Permissions.send_messages` が必要です。" +msgid "The message must have been sent in a news channel. You must have :attr:`~Permissions.send_messages` to do this." +msgstr "メッセージはニュースチャンネルで送信されている必要があります。これを行うには、 :attr:`~Permissions.send_messages` が必要です。" -#: ../../../discord/webhook/async_.py:docstring of discord.message.PartialMessage.publish:7 -#: ../../../discord/message.py:docstring of discord.message.PartialMessage.publish:7 -#: ../../../discord/message.py:docstring of discord.message.PartialMessage.publish:7 +#: ../../../discord/webhook/async_.py:docstring of discord.message.PartialMessage.publish:8 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage.publish:8 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage.publish:8 msgid "If the message is not your own then :attr:`~Permissions.manage_messages` is also needed." msgstr "自身のメッセージ以外の場合は :attr:`~Permissions.manage_messages` も必要です。" -#: ../../../discord/webhook/async_.py:docstring of discord.message.PartialMessage.publish:10 -#: ../../../discord/message.py:docstring of discord.message.PartialMessage.publish:10 -#: ../../../discord/message.py:docstring of discord.message.PartialMessage.publish:10 -msgid "You do not have the proper permissions to publish this message." -msgstr "メッセージを公開する適切な権限がない場合。" - #: ../../../discord/webhook/async_.py:docstring of discord.message.PartialMessage.publish:11 #: ../../../discord/message.py:docstring of discord.message.PartialMessage.publish:11 #: ../../../discord/message.py:docstring of discord.message.PartialMessage.publish:11 +msgid "You do not have the proper permissions to publish this message or the channel is not a news channel." +msgstr "このメッセージを公開するための適切な権限がないか、チャンネルがニュースチャンネルでない場合。" + +#: ../../../discord/webhook/async_.py:docstring of discord.message.PartialMessage.publish:12 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage.publish:12 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage.publish:12 msgid "Publishing the message failed." msgstr "メッセージの公開に失敗した場合。" @@ -8235,10 +8971,10 @@ msgid "This function will now raise :exc:`TypeError` or :exc:`ValueError` instea msgstr "この関数は ``InvalidArgument`` の代わりに :exc:`TypeError` や :exc:`ValueError` を送出します。" #: ../../../discord/webhook/async_.py:docstring of discord.message.PartialMessage.reply:13 -#: ../../../discord/abc.py:docstring of discord.abc.Messageable.send:78 -#: ../../../discord/user.py:docstring of discord.abc.Messageable.send:78 +#: ../../../discord/abc.py:docstring of discord.abc.Messageable.send:83 +#: ../../../discord/user.py:docstring of discord.abc.Messageable.send:83 #: ../../../discord/message.py:docstring of discord.message.PartialMessage.reply:13 -#: ../../../discord/member.py:docstring of discord.abc.Messageable.send:78 +#: ../../../discord/member.py:docstring of discord.abc.Messageable.send:83 msgid "You do not have the proper permissions to send the message." msgstr "メッセージを送信する適切な権限がない場合。" @@ -8255,10 +8991,10 @@ msgid "You specified both ``file`` and ``files``." msgstr "``file`` と ``filess`` の両方を指定した場合。" #: ../../../discord/webhook/async_.py:docstring of discord.message.PartialMessage.reply:17 -#: ../../../discord/abc.py:docstring of discord.abc.Messageable.send:82 -#: ../../../discord/user.py:docstring of discord.abc.Messageable.send:82 +#: ../../../discord/abc.py:docstring of discord.abc.Messageable.send:87 +#: ../../../discord/user.py:docstring of discord.abc.Messageable.send:87 #: ../../../discord/message.py:docstring of discord.message.PartialMessage.reply:17 -#: ../../../discord/member.py:docstring of discord.abc.Messageable.send:82 +#: ../../../discord/member.py:docstring of discord.abc.Messageable.send:87 msgid "The message that was sent." msgstr "送信されたメッセージ。" @@ -8278,6 +9014,25 @@ msgstr ":attr:`Message.type` に関わらず、レンダリングされた際の msgid "In the case of :attr:`MessageType.default` and :attr:`MessageType.reply`\\, this just returns the regular :attr:`Message.content`. Otherwise this returns an English message denoting the contents of the system message." msgstr ":attr:`MessageType.default` と :attr:`MessageType.reply` の場合、これは :attr:`Message.content` と同じものを返すだけです。しかしそれ以外の場合は、システムメッセージの英語版を返します。" +#: ../../../discord/webhook/async_.py:docstring of discord.WebhookMessage.thread:1 +#: ../../../discord/message.py:docstring of discord.Message.thread:1 +#: ../../../discord/message.py:docstring of discord.PartialMessage.thread:1 +msgid "The public thread created from this message, if it exists." +msgstr "" + +#: ../../../discord/webhook/async_.py:docstring of discord.WebhookMessage.thread:5 +#: ../../../discord/message.py:docstring of discord.Message.thread:5 +msgid "For messages received via the gateway this does not retrieve archived threads, as they are not retained in the internal cache. Use :meth:`fetch_thread` instead." +msgstr "" + +#: ../../../discord/webhook/async_.py:docstring of discord.WebhookMessage.thread:10 +#: ../../../discord/message.py:docstring of discord.Message.thread:10 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.get_thread:14 +#: ../../../discord/channel.py:docstring of discord.channel.TextChannel.get_thread:14 +#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.get_thread:14 +msgid "Optional[:class:`Thread`]" +msgstr "Optional[:class:`Thread`]" + #: ../../../discord/webhook/async_.py:docstring of discord.message.PartialMessage.to_reference:1 #: ../../../discord/message.py:docstring of discord.message.PartialMessage.to_reference:1 #: ../../../discord/message.py:docstring of discord.message.PartialMessage.to_reference:1 @@ -8326,7 +9081,7 @@ msgstr "このメッセージのピン留めを外す権限を持っていない msgid "Unpinning the message failed." msgstr "メッセージのピン留め解除に失敗した場合。" -#: ../../api.rst:3942 +#: ../../api.rst:4286 msgid "SyncWebhook" msgstr "SyncWebhook" @@ -8343,6 +9098,13 @@ msgstr "非同期対応については、 :class:`Webhook` を参照してくだ msgid "The session to use to send requests with. Note that the library does not manage the session and will not close it. If not given, the ``requests`` auto session creation functions are used instead." msgstr "リクエストを送信するために使うセッション。ライブラリはセッションを管理しておらず、セッションを閉じないことに注意してください。指定されていない場合は、代わりに ``requests`` の自動セッション作成関数が使用されます。" +#: ../../../discord/webhook/sync.py:docstring of discord.webhook.sync.SyncWebhook.partial:16 +#: ../../../discord/webhook/sync.py:docstring of discord.webhook.sync.SyncWebhook.from_url:16 +msgid "A partial :class:`SyncWebhook`. A partial :class:`SyncWebhook` is just a :class:`SyncWebhook` object with an ID and a token." +msgstr "部分的な :class:`SyncWebhook` 。部分的な :class:`SyncWebhook` はただのIDとトークンのみを持つ :class:`SyncWebhook` オブジェクトです。" + +#: ../../../discord/webhook/sync.py:docstring of discord.webhook.sync.SyncWebhook.partial:18 +#: ../../../discord/webhook/sync.py:docstring of discord.webhook.sync.SyncWebhook.from_url:18 #: ../../../discord/webhook/sync.py:docstring of discord.webhook.sync.SyncWebhook.fetch:20 #: ../../../discord/webhook/sync.py:docstring of discord.webhook.sync.SyncWebhook.edit:22 msgid ":class:`SyncWebhook`" @@ -8360,7 +9122,7 @@ msgstr "サーバーが応答を送信するまで待機するかどうか。``T msgid "The thread to send this message to." msgstr "このメッセージを送信するスレッド。" -#: ../../../discord/webhook/sync.py:docstring of discord.webhook.sync.SyncWebhook.send:65 +#: ../../../discord/webhook/sync.py:docstring of discord.webhook.sync.SyncWebhook.send:70 msgid "Optional[:class:`SyncWebhookMessage`]" msgstr "Optional[:class:`SyncWebhookMessage`]" @@ -8372,7 +9134,7 @@ msgstr "このwebhookが送信した :class:`~discord.SyncWebhookMessage` を1 msgid ":class:`~discord.SyncWebhookMessage`" msgstr ":class:`~discord.SyncWebhookMessage`" -#: ../../api.rst:3951 +#: ../../api.rst:4295 msgid "SyncWebhookMessage" msgstr "SyncWebhookMessage" @@ -8386,19 +9148,19 @@ msgstr ":class:`SyncWebhookMessage`" msgid "If provided, the number of seconds to wait before deleting the message. This blocks the thread." msgstr "指定された場合、メッセージを削除するまでの待機秒数。これはスレッドをブロックします。" -#: ../../api.rst:3961 +#: ../../api.rst:4305 msgid "Abstract Base Classes" msgstr "抽象基底クラス" -#: ../../api.rst:3963 +#: ../../api.rst:4307 msgid "An :term:`abstract base class` (also known as an ``abc``) is a class that models can inherit to get their behaviour. **Abstract base classes should not be instantiated**. They are mainly there for usage with :func:`isinstance` and :func:`issubclass`\\." msgstr ":term:`abstract base class` ( ``abc`` という名称でも知られています)はモデルが振る舞いを得るために継承するクラスです。 **抽象基底クラスはインスタンス化されるべきではありません。** これらは主に :func:`isinstance` や :func:`issubclass` で使用するために存在します。" -#: ../../api.rst:3967 +#: ../../api.rst:4311 msgid "This library has a module related to abstract base classes, in which all the ABCs are subclasses of :class:`typing.Protocol`." msgstr "このライブラリには抽象基底クラスに関連するモジュールがあり、その中の抽象基底クラスは全て :class:`typing.Protocol` のサブクラスです。" -#: ../../api.rst:3971 +#: ../../api.rst:4315 msgid "Snowflake" msgstr "Snowflake" @@ -8418,8 +9180,8 @@ msgstr "スノーフレークを自分で作成したい場合は、 :class:`.Ob msgid "The model's unique ID." msgstr "モデルのユニークなID。" -#: ../../api.rst:3979 -#: ../../api.rst:4059 +#: ../../api.rst:4323 +#: ../../api.rst:4403 msgid "User" msgstr "User" @@ -8440,7 +9202,7 @@ msgid ":class:`~discord.ClientUser`" msgstr ":class:`~discord.ClientUser`" #: ../../../discord/abc.py:docstring of discord.abc.User:7 -#: ../../../discord/abc.py:docstring of discord.abc.Messageable:11 +#: ../../../discord/abc.py:docstring of discord.abc.Messageable:12 msgid ":class:`~discord.Member`" msgstr ":class:`~discord.Member`" @@ -8457,14 +9219,20 @@ msgid "The user's username." msgstr "ユーザー名。" #: ../../../discord/abc.py:docstring of discord.abc.User:19 -msgid "The user's discriminator." -msgstr "ユーザーのディスクリミネーター。" +#: ../../../discord/user.py:docstring of discord.user.ClientUser:35 +#: ../../../discord/user.py:docstring of discord.user.User:35 +msgid "The user's discriminator. This is a legacy concept that is no longer used." +msgstr "ユーザーのタグ。これは、現在は使用されていない、過去の遺物です。" #: ../../../discord/abc.py:docstring of discord.abc.User:25 +msgid "The user's global nickname." +msgstr "ユーザーのグローバルの表示名。" + +#: ../../../discord/abc.py:docstring of discord.abc.User:31 msgid "If the user is a bot account." msgstr "ユーザーがBotであるかどうか。" -#: ../../../discord/abc.py:docstring of discord.abc.User:31 +#: ../../../discord/abc.py:docstring of discord.abc.User:37 msgid "If the user is a system account." msgstr "ユーザーがシステムアカウントであるかどうか。" @@ -8473,15 +9241,24 @@ msgid "Returns an Asset that represents the user's avatar, if present." msgstr "ユーザーのアバターが存在する場合は、それを表すアセットを返します。" #: ../../../discord/abc.py:docstring of discord.abc.User.avatar:3 +#: ../../../discord/abc.py:docstring of discord.abc.User.avatar_decoration:5 msgid "Optional[:class:`~discord.Asset`]" msgstr "Optional[:class:`~discord.Asset`]" +#: ../../../discord/abc.py:docstring of discord.abc.User.avatar_decoration:1 +msgid "Returns an Asset that represents the user's avatar decoration, if present." +msgstr "" + +#: ../../../discord/abc.py:docstring of discord.abc.User.avatar_decoration_sku_id:1 +msgid "Returns an integer that represents the user's avatar decoration SKU ID, if present." +msgstr "" + #: ../../../discord/abc.py:docstring of discord.abc.User.default_avatar:3 #: ../../../discord/abc.py:docstring of discord.abc.User.display_avatar:7 msgid ":class:`~discord.Asset`" msgstr ":class:`~discord.Asset`" -#: ../../api.rst:3987 +#: ../../api.rst:4331 msgid "PrivateChannel" msgstr "PrivateChannel" @@ -8490,12 +9267,12 @@ msgid "An ABC that details the common operations on a private Discord channel." msgstr "Discordのプライベートチャンネルの一般的な操作を説明するABC。" #: ../../../discord/abc.py:docstring of discord.abc.PrivateChannel:5 -#: ../../../discord/abc.py:docstring of discord.abc.Messageable:7 +#: ../../../discord/abc.py:docstring of discord.abc.Messageable:8 msgid ":class:`~discord.DMChannel`" msgstr ":class:`~discord.DMChannel`" #: ../../../discord/abc.py:docstring of discord.abc.PrivateChannel:6 -#: ../../../discord/abc.py:docstring of discord.abc.Messageable:8 +#: ../../../discord/abc.py:docstring of discord.abc.Messageable:9 msgid ":class:`~discord.GroupChannel`" msgstr ":class:`~discord.GroupChannel`" @@ -8505,7 +9282,7 @@ msgstr ":class:`~discord.GroupChannel`" msgid "The user presenting yourself." msgstr "あなた自身を示すユーザー。" -#: ../../api.rst:3995 +#: ../../api.rst:4339 msgid "GuildChannel" msgstr "GuildChannel" @@ -8529,6 +9306,7 @@ msgid ":class:`~discord.CategoryChannel`" msgstr ":class:`~discord.CategoryChannel`" #: ../../../discord/abc.py:docstring of discord.abc.GuildChannel:8 +#: ../../../discord/abc.py:docstring of discord.abc.Messageable:7 #: ../../../discord/abc.py:docstring of discord.abc.Connectable:7 msgid ":class:`~discord.StageChannel`" msgstr ":class:`~discord.StageChannel`" @@ -8559,8 +9337,8 @@ msgstr ":class:`~discord.Guild`" #: ../../../discord/abc.py:docstring of discord.abc.GuildChannel:27 #: ../../../discord/guild.py:docstring of discord.guild.Guild.create_text_channel:51 #: ../../../discord/guild.py:docstring of discord.guild.Guild.create_voice_channel:19 -#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_stage_channel:23 -#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_forum:20 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_stage_channel:21 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_forum:24 msgid "The position in the channel list. This is a number that starts at 0. e.g. the top channel is position 0." msgstr "チャンネルリストにおける位置。これは0から始まる番号で、最も上のチャンネルの位置は0です。" @@ -9077,17 +9855,17 @@ msgid "The reason for cloning this channel. Shows up on the audit log." msgstr "チャンネルを複製する理由。監査ログに表示されます。" #: ../../../discord/abc.py:docstring of discord.abc.GuildChannel.clone:16 -#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_text_channel:73 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_text_channel:77 #: ../../../discord/guild.py:docstring of discord.guild.Guild.create_voice_channel:38 -#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_stage_channel:29 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_stage_channel:44 #: ../../../discord/guild.py:docstring of discord.guild.Guild.create_category:14 msgid "You do not have the proper permissions to create this channel." msgstr "チャンネルを作成するのに必要な権限がない場合。" #: ../../../discord/abc.py:docstring of discord.abc.GuildChannel.clone:17 -#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_text_channel:74 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_text_channel:78 #: ../../../discord/guild.py:docstring of discord.guild.Guild.create_voice_channel:39 -#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_stage_channel:30 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_stage_channel:45 #: ../../../discord/guild.py:docstring of discord.guild.Guild.create_category:15 msgid "Creating the channel failed." msgstr "チャンネルの作成に失敗した場合。" @@ -9372,7 +10150,7 @@ msgstr "現時点でアクティブな招待のリスト。" msgid "List[:class:`~discord.Invite`]" msgstr "List[:class:`~discord.Invite`]" -#: ../../api.rst:4003 +#: ../../api.rst:4347 msgid "Messageable" msgstr "Messageable" @@ -9384,15 +10162,15 @@ msgstr "メッセージを送信できるモデルに共通する操作を説明 msgid "The following classes implement this ABC:" msgstr "以下のクラスがこのABCを実装しています:" -#: ../../../discord/abc.py:docstring of discord.abc.Messageable:9 +#: ../../../discord/abc.py:docstring of discord.abc.Messageable:10 msgid ":class:`~discord.PartialMessageable`" msgstr ":class:`~discord.PartialMessageable`" -#: ../../../discord/abc.py:docstring of discord.abc.Messageable:12 +#: ../../../discord/abc.py:docstring of discord.abc.Messageable:13 msgid ":class:`~discord.ext.commands.Context`" msgstr ":class:`~discord.ext.commands.Context`" -#: ../../../discord/abc.py:docstring of discord.abc.Messageable:13 +#: ../../../discord/abc.py:docstring of discord.abc.Messageable:14 msgid ":class:`~discord.Thread`" msgstr ":class:`~discord.Thread`" @@ -9548,26 +10326,26 @@ msgstr "メッセージに追加するDiscord UI ビュー。" msgid "A list of stickers to upload. Must be a maximum of 3." msgstr "送信するスタンプのリスト。最大3個まで送信できます。" -#: ../../../discord/abc.py:docstring of discord.abc.Messageable.send:79 -#: ../../../discord/user.py:docstring of discord.abc.Messageable.send:79 -#: ../../../discord/member.py:docstring of discord.abc.Messageable.send:79 -#: ../../../discord/channel.py:docstring of discord.abc.Messageable.send:79 -#: ../../../discord/channel.py:docstring of discord.abc.Messageable.send:79 +#: ../../../discord/abc.py:docstring of discord.abc.Messageable.send:84 +#: ../../../discord/user.py:docstring of discord.abc.Messageable.send:84 +#: ../../../discord/member.py:docstring of discord.abc.Messageable.send:84 +#: ../../../discord/channel.py:docstring of discord.abc.Messageable.send:84 +#: ../../../discord/channel.py:docstring of discord.abc.Messageable.send:84 msgid "The ``files`` or ``embeds`` list is not of the appropriate size." msgstr "``files`` または ``embeds`` リストの大きさが適切でない場合。" -#: ../../../discord/abc.py:docstring of discord.abc.Messageable.send:80 -#: ../../../discord/user.py:docstring of discord.abc.Messageable.send:80 -#: ../../../discord/member.py:docstring of discord.abc.Messageable.send:80 -#: ../../../discord/channel.py:docstring of discord.abc.Messageable.send:80 -#: ../../../discord/channel.py:docstring of discord.abc.Messageable.send:80 +#: ../../../discord/abc.py:docstring of discord.abc.Messageable.send:85 +#: ../../../discord/user.py:docstring of discord.abc.Messageable.send:85 +#: ../../../discord/member.py:docstring of discord.abc.Messageable.send:85 +#: ../../../discord/channel.py:docstring of discord.abc.Messageable.send:85 +#: ../../../discord/channel.py:docstring of discord.abc.Messageable.send:85 msgid "You specified both ``file`` and ``files``, or you specified both ``embed`` and ``embeds``, or the ``reference`` object is not a :class:`~discord.Message`, :class:`~discord.MessageReference` or :class:`~discord.PartialMessage`." msgstr "``file`` と ``files`` の両方が指定された場合、 ``embed`` と ``embeds`` の両方が指定された場合、または ``reference`` が :class:`~discord.Message` 、 :class:`~discord.MessageReference` 、 :class:`~discord.PartialMessage` でない場合。" -#: ../../../discord/abc.py:docstring of discord.abc.Messageable.send:83 +#: ../../../discord/abc.py:docstring of discord.abc.Messageable.send:88 #: ../../../discord/abc.py:docstring of discord.abc.Messageable.fetch_message:13 #: ../../../discord/user.py:docstring of discord.abc.Messageable.fetch_message:13 -#: ../../../discord/user.py:docstring of discord.abc.Messageable.send:83 +#: ../../../discord/user.py:docstring of discord.abc.Messageable.send:88 #: ../../../discord/member.py:docstring of discord.abc.Messageable.fetch_message:13 msgid ":class:`~discord.Message`" msgstr ":class:`~discord.Message`" @@ -9716,7 +10494,7 @@ msgstr "メッセージ履歴の取得に失敗した場合。" msgid ":class:`~discord.Message` -- The message with the message data parsed." msgstr ":class:`~discord.Message` -- メッセージデータをパースしたメッセージ。" -#: ../../api.rst:4015 +#: ../../api.rst:4359 msgid "Connectable" msgstr "Connectable" @@ -9739,8 +10517,8 @@ msgstr ":attr:`~discord.Intents.voice_states` が必要です。" #: ../../../discord/abc.py:docstring of discord.abc.Connectable.connect:8 #: ../../../discord/channel.py:docstring of discord.abc.Connectable.connect:8 #: ../../../discord/channel.py:docstring of discord.abc.Connectable.connect:8 -msgid "The timeout in seconds to wait for the voice endpoint." -msgstr "ボイスエンドポイントを待つためのタイムアウト秒数。" +msgid "The timeout in seconds to wait the connection to complete." +msgstr "" #: ../../../discord/abc.py:docstring of discord.abc.Connectable.connect:10 #: ../../../discord/channel.py:docstring of discord.abc.Connectable.connect:10 @@ -9784,32 +10562,32 @@ msgstr "ボイスサーバーに完全に接続されたボイスクライアン msgid ":class:`~discord.VoiceProtocol`" msgstr ":class:`~discord.VoiceProtocol`" -#: ../../api.rst:4025 +#: ../../api.rst:4369 msgid "Discord Models" msgstr "Discordモデル" -#: ../../api.rst:4027 +#: ../../api.rst:4371 msgid "Models are classes that are received from Discord and are not meant to be created by the user of the library." msgstr "モデルはDiscordから受け取るクラスであり、ユーザーによって作成されることを想定していません。" -#: ../../api.rst:4032 +#: ../../api.rst:4376 msgid "The classes listed below are **not intended to be created by users** and are also **read-only**." msgstr "下記のクラスは、 **ユーザーによって作成されることを想定しておらず** 、中には **読み取り専用** のものもあります。" -#: ../../api.rst:4035 +#: ../../api.rst:4379 msgid "For example, this means that you should not make your own :class:`User` instances nor should you modify the :class:`User` instance yourself." msgstr "つまり、独自の :class:`User` を作成したりするべきではなく、また、 :class:`User` インスタンスの値の変更もするべきではありません。" -#: ../../api.rst:4038 +#: ../../api.rst:4382 msgid "If you want to get one of these model classes instances they'd have to be through the cache, and a common way of doing so is through the :func:`utils.find` function or attributes of model classes that you receive from the events specified in the :ref:`discord-api-events`." msgstr "このようなモデルクラスのインスタンスを取得したい場合は、 キャッシュを経由して取得する必要があります。一般的な方法としては :func:`utils.find` 関数を用いるか、 :ref:`discord-api-events` の特定のイベントから受け取る方法が挙げられます。" -#: ../../api.rst:4045 -#: ../../api.rst:4621 +#: ../../api.rst:4389 +#: ../../api.rst:5000 msgid "Nearly all classes here have :ref:`py:slots` defined which means that it is impossible to have dynamic attributes to the data classes." msgstr "ほぼすべてのクラスに :ref:`py:slots` が定義されています。つまり、データクラスに動的に変数を追加することは不可能です。" -#: ../../api.rst:4050 +#: ../../api.rst:4394 msgid "ClientUser" msgstr "ClientUser" @@ -9834,33 +10612,33 @@ msgstr "ユーザーのハッシュ値を返します。" #: ../../../discord/user.py:docstring of discord.user.ClientUser:19 #: ../../../discord/user.py:docstring of discord.user.User:19 -msgid "Returns the user's name with discriminator." -msgstr "ユーザー名とディスクリミネータを返します。" +msgid "Returns the user's handle (e.g. ``name`` or ``name#discriminator``)." +msgstr "ユーザーのハンドル(例えば ``name`` や ``name#discriminator`` など)を返します。" #: ../../../discord/user.py:docstring of discord.user.ClientUser:29 #: ../../../discord/user.py:docstring of discord.user.User:29 msgid "The user's unique ID." msgstr "ユーザーのユニークなID。" -#: ../../../discord/user.py:docstring of discord.user.ClientUser:35 -#: ../../../discord/user.py:docstring of discord.user.User:35 -msgid "The user's discriminator. This is given when the username has conflicts." -msgstr "ユーザーのディスクリミネーター。これはユーザー名が重複しているときに与えられます。" +#: ../../../discord/user.py:docstring of discord.user.ClientUser:41 +#: ../../../discord/user.py:docstring of discord.user.User:41 +msgid "The user's global nickname, taking precedence over the username in display." +msgstr "ユーザーのグローバルの表示名。ユーザー名より優先して表示されます。" -#: ../../../discord/user.py:docstring of discord.user.ClientUser:47 -#: ../../../discord/user.py:docstring of discord.user.User:47 +#: ../../../discord/user.py:docstring of discord.user.ClientUser:55 +#: ../../../discord/user.py:docstring of discord.user.User:55 msgid "Specifies if the user is a system user (i.e. represents Discord officially)." msgstr "ユーザーがシステムユーザーかどうかを示します。(つまり、Discord公式を表しているかどうか。)" -#: ../../../discord/user.py:docstring of discord.user.ClientUser:55 +#: ../../../discord/user.py:docstring of discord.user.ClientUser:63 msgid "Specifies if the user's email is verified." msgstr "ユーザーのメールアドレスが確認されているかどうかを示します。" -#: ../../../discord/user.py:docstring of discord.user.ClientUser:61 +#: ../../../discord/user.py:docstring of discord.user.ClientUser:69 msgid "The IETF language tag used to identify the language the user is using." msgstr "ユーザーが使用している言語を識別するためのIETF言語タグ。" -#: ../../../discord/user.py:docstring of discord.user.ClientUser:67 +#: ../../../discord/user.py:docstring of discord.user.ClientUser:75 msgid "Specifies if the user has MFA turned on and working." msgstr "ユーザーが多段階認証を使用しているかを示します。" @@ -9872,34 +10650,49 @@ msgstr "現在のクライアントのプロフィールを編集します。" msgid "To upload an avatar, a :term:`py:bytes-like object` must be passed in that represents the image being uploaded. If this is done through a file then the file must be opened via ``open('some_filename', 'rb')`` and the :term:`py:bytes-like object` is given through the use of ``fp.read()``." msgstr "アバターをアップロードする際には、アップロードする画像を表す :term:`py:bytes-like object` を渡す必要があります。これをファイルを介して行う場合、ファイルを ``open('some_filename', 'rb')`` で開き、 :term:`py:bytes-like object` は ``fp.read()`` で取得できます。" -#: ../../../discord/user.py:docstring of discord.user.ClientUser.edit:12 -msgid "The only image formats supported for uploading is JPEG and PNG." -msgstr "アップロードでサポートされる画像形式はJPEGとPNGのみです。" - -#: ../../../discord/user.py:docstring of discord.user.ClientUser.edit:14 +#: ../../../discord/user.py:docstring of discord.user.ClientUser.edit:13 msgid "The edit is no longer in-place, instead the newly edited client user is returned." msgstr "編集はクライアントユーザーを置き換えず、編集された新しいクライアントユーザーが返されるようになりました。" -#: ../../../discord/user.py:docstring of discord.user.ClientUser.edit:21 +#: ../../../discord/user.py:docstring of discord.user.ClientUser.edit:20 msgid "The new username you wish to change to." msgstr "変更する際の新しいユーザー名。" -#: ../../../discord/user.py:docstring of discord.user.ClientUser.edit:23 -msgid "A :term:`py:bytes-like object` representing the image to upload. Could be ``None`` to denote no avatar." -msgstr "アップロードする画像を表す :term:`py:bytes-like object` 。アバターをなしにしたい場合は ``None`` を設定することが出来ます。" +#: ../../../discord/user.py:docstring of discord.user.ClientUser.edit:22 +msgid "A :term:`py:bytes-like object` representing the image to upload. Could be ``None`` to denote no avatar. Only image formats supported for uploading are JPEG, PNG, GIF, and WEBP." +msgstr "" + +#: ../../../discord/user.py:docstring of discord.user.ClientUser.edit:26 +msgid "A :term:`py:bytes-like object` representing the image to upload. Could be ``None`` to denote no banner. Only image formats supported for uploading are JPEG, PNG, GIF and WEBP." +msgstr "" -#: ../../../discord/user.py:docstring of discord.user.ClientUser.edit:27 +#: ../../../discord/user.py:docstring of discord.user.ClientUser.edit:33 msgid "Editing your profile failed." msgstr "プロフィールの編集に失敗した場合。" -#: ../../../discord/user.py:docstring of discord.user.ClientUser.edit:28 +#: ../../../discord/user.py:docstring of discord.user.ClientUser.edit:34 msgid "Wrong image format passed for ``avatar``." msgstr "``avatar`` に渡された画像のフォーマットが間違っている場合。" -#: ../../../discord/user.py:docstring of discord.user.ClientUser.edit:30 +#: ../../../discord/user.py:docstring of discord.user.ClientUser.edit:36 msgid "The newly edited client user." msgstr "新しく編集されたクライアントユーザー。" +#: ../../../discord/user.py:docstring of discord.ClientUser.mutual_guilds:1 +#: ../../../discord/user.py:docstring of discord.User.mutual_guilds:1 +msgid "The guilds that the user shares with the client." +msgstr "ユーザーがクライアントと共有するギルド。" + +#: ../../../discord/user.py:docstring of discord.ClientUser.mutual_guilds:5 +#: ../../../discord/user.py:docstring of discord.User.mutual_guilds:5 +msgid "This will only return mutual guilds within the client's internal cache." +msgstr "クライアントの内部キャッシュ内に存在する共通のサーバーのみが返されます。" + +#: ../../../discord/user.py:docstring of discord.ClientUser.mutual_guilds:9 +#: ../../../discord/user.py:docstring of discord.User.mutual_guilds:9 +msgid "List[:class:`Guild`]" +msgstr "List[:class:`Guild`]" + #: ../../../discord/user.py:docstring of discord.user.User:1 msgid "Represents a Discord user." msgstr "Discordユーザー。" @@ -9916,18 +10709,6 @@ msgstr "これが ``None`` を返すなら、あなたは :meth:`create_dm` コ msgid "Optional[:class:`DMChannel`]" msgstr "Optional[:class:`DMChannel`]" -#: ../../../discord/user.py:docstring of discord.User.mutual_guilds:1 -msgid "The guilds that the user shares with the client." -msgstr "ユーザーがクライアントと共有するギルド。" - -#: ../../../discord/user.py:docstring of discord.User.mutual_guilds:5 -msgid "This will only return mutual guilds within the client's internal cache." -msgstr "クライアントの内部キャッシュ内に存在する共通のサーバーのみが返されます。" - -#: ../../../discord/user.py:docstring of discord.User.mutual_guilds:9 -msgid "List[:class:`Guild`]" -msgstr "List[:class:`Guild`]" - #: ../../../discord/user.py:docstring of discord.user.User.create_dm:3 #: ../../../discord/member.py:docstring of discord.member.flatten_user..generate_function..general:3 msgid "Creates a :class:`DMChannel` with this user." @@ -9975,6 +10756,10 @@ msgstr "Set[:class:`int`]" msgid "The IDs of the channels that are exempt from the rule." msgstr "このルールの除外対象のチャンネルのID。" +#: ../../../discord/automod.py:docstring of discord.automod.AutoModRule:55 +msgid "The type of event that will trigger the the rule." +msgstr "" + #: ../../../discord/automod.py:docstring of discord.AutoModRule.creator:1 msgid "The member that created this rule." msgstr "このルールを作成したメンバー。" @@ -9983,7 +10768,7 @@ msgstr "このルールを作成したメンバー。" #: ../../../discord/automod.py:docstring of discord.AutoModAction.member:3 #: ../../../discord/guild.py:docstring of discord.guild.Guild.get_member:11 #: ../../../discord/guild.py:docstring of discord.Guild.owner:3 -#: ../../../discord/guild.py:docstring of discord.guild.Guild.get_member_named:24 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.get_member_named:26 msgid "Optional[:class:`Member`]" msgstr "Optional[:class:`Member`]" @@ -10198,7 +10983,7 @@ msgstr "ルールの取得に失敗した場合。" msgid "The rule that was executed." msgstr "発動されたルール。" -#: ../../api.rst:4085 +#: ../../api.rst:4429 msgid "Attachment" msgstr "Attachment" @@ -10266,10 +11051,34 @@ msgstr "添付ファイルの説明。画像にのみ適用されます。" msgid "Whether the attachment is ephemeral." msgstr "添付ファイルが一時的であるかどうか。" +#: ../../../discord/message.py:docstring of discord.message.Attachment:95 +msgid "The duration of the audio file in seconds. Returns ``None`` if it's not a voice message." +msgstr "音声ファイルの秒単位の長さ。ボイスメッセージでない場合 ``None`` を返します。" + +#: ../../../discord/message.py:docstring of discord.message.Attachment:103 +msgid "The waveform (amplitudes) of the audio in bytes. Returns ``None`` if it's not a voice message." +msgstr "音声の波形(振幅)をバイト単位で返します。ボイスメッセージでない場合は ``None`` を返します。" + +#: ../../../discord/message.py:docstring of discord.message.Attachment:107 +msgid "Optional[:class:`bytes`]" +msgstr "Optional[:class:`bytes`]" + +#: ../../../discord/message.py:docstring of discord.Attachment.flags:1 +msgid "The attachment's flags." +msgstr "" + +#: ../../../discord/message.py:docstring of discord.Attachment.flags:3 +msgid ":class:`AttachmentFlags`" +msgstr "" + #: ../../../discord/message.py:docstring of discord.message.Attachment.is_spoiler:1 msgid ":class:`bool`: Whether this attachment contains a spoiler." msgstr ":class:`bool`: この添付ファイルにスポイラーが含まれているかどうか。" +#: ../../../discord/message.py:docstring of discord.message.Attachment.is_voice_message:1 +msgid ":class:`bool`: Whether this attachment is a voice message." +msgstr ":class:`bool`: 添付ファイルがボイスメッセージであるかどうか。" + #: ../../../discord/message.py:docstring of discord.message.Attachment.save:3 msgid "Saves this attachment into a file-like object." msgstr "この添付ファイルをファイルライクオブジェクトに保存します。" @@ -10356,7 +11165,7 @@ msgstr "送信に適したファイルに変換された添付ファイル。" msgid ":class:`File`" msgstr ":class:`File`" -#: ../../api.rst:4093 +#: ../../api.rst:4437 msgid "Asset" msgstr "Asset" @@ -10554,7 +11363,7 @@ msgstr "アセットがロッティータイプのスタンプであった場合 msgid "The asset as a file suitable for sending." msgstr "送信に適したファイルとしてのアセット。" -#: ../../api.rst:4102 +#: ../../api.rst:4446 msgid "Message" msgstr "Message" @@ -10611,8 +11420,8 @@ msgid "The :class:`TextChannel` or :class:`Thread` that the message was sent fro msgstr "メッセージの送信された :class:`TextChannel` や :class:`Thread` 。もしメッセージがプライベートチャンネルで送信されたなら、これは :class:`DMChannel` か :class:`GroupChannel` になります。" #: ../../../discord/message.py:docstring of discord.message.Message:67 -msgid "Union[:class:`TextChannel`, :class:`VoiceChannel`, :class:`Thread`, :class:`DMChannel`, :class:`GroupChannel`, :class:`PartialMessageable`]" -msgstr "Union[:class:`TextChannel`, :class:`VoiceChannel`, :class:`Thread`, :class:`DMChannel`, :class:`GroupChannel`, :class:`PartialMessageable`]" +msgid "Union[:class:`TextChannel`, :class:`StageChannel`, :class:`VoiceChannel`, :class:`Thread`, :class:`DMChannel`, :class:`GroupChannel`, :class:`PartialMessageable`]" +msgstr "Union[:class:`TextChannel`, :class:`StageChannel`, :class:`VoiceChannel`, :class:`Thread`, :class:`DMChannel`, :class:`GroupChannel`, :class:`PartialMessageable`]" #: ../../../discord/message.py:docstring of discord.message.Message:71 msgid "The message that this message references. This is only applicable to messages of type :attr:`MessageType.pins_add`, crossposted messages created by a followed channel integration, or message replies." @@ -10651,7 +11460,7 @@ msgid "A list of :class:`Role` that were mentioned. If the message is in a priva msgstr "メンションされた :class:`Role` のリスト。もしメッセージがプライベートチャンネル内のものなら、このリストは常に空になります。" #: ../../../discord/message.py:docstring of discord.message.Message:121 -#: ../../../discord/message.py:docstring of discord.message.PartialMessage:38 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage:39 msgid "The message ID." msgstr "メッセージのID。" @@ -10826,7 +11635,7 @@ msgstr "もし指定したなら、これはメッセージを編集したあと msgid "Tried to suppress a message without permissions or edited a message's content or embed that isn't yours." msgstr "権限なしに埋め込みを除去しようとした場合や、他人のメッセージの内容や埋め込みを編集しようとした場合。" -#: ../../api.rst:4111 +#: ../../api.rst:4455 msgid "DeletedReferencedMessage" msgstr "DeletedReferencedMessage" @@ -10850,7 +11659,7 @@ msgstr "削除された参照されたメッセージが属していたチャン msgid "The guild ID of the deleted referenced message." msgstr "削除された参照されたメッセージが属していたギルドのID。" -#: ../../api.rst:4120 +#: ../../api.rst:4464 msgid "Reaction" msgstr "Reaction" @@ -10889,8 +11698,8 @@ msgid "Union[:class:`Emoji`, :class:`PartialEmoji`, :class:`str`]" msgstr "Union[:class:`Emoji`, :class:`PartialEmoji`, :class:`str`]" #: ../../../discord/reaction.py:docstring of discord.reaction.Reaction:34 -msgid "Number of times this reaction was made" -msgstr "リアクションが付けられた回数。" +msgid "Number of times this reaction was made. This is a sum of :attr:`normal_count` and :attr:`burst_count`." +msgstr "" #: ../../../discord/reaction.py:docstring of discord.reaction.Reaction:40 msgid "If the user sent this reaction." @@ -10900,6 +11709,18 @@ msgstr "ボットがこのリアクションを付けたか。" msgid "Message this reaction is for." msgstr "このリアクションのメッセージ。" +#: ../../../discord/reaction.py:docstring of discord.reaction.Reaction:52 +msgid "If the user sent this super reaction." +msgstr "" + +#: ../../../discord/reaction.py:docstring of discord.reaction.Reaction:60 +msgid "The number of times this reaction was made using normal reactions. This is not available in the gateway events such as :func:`on_reaction_add` or :func:`on_reaction_remove`." +msgstr "" + +#: ../../../discord/reaction.py:docstring of discord.reaction.Reaction:70 +msgid "The number of times this reaction was made using super reactions. This is not available in the gateway events such as :func:`on_reaction_add` or :func:`on_reaction_remove`." +msgstr "" + #: ../../../discord/reaction.py:docstring of discord.reaction.Reaction.is_custom_emoji:1 msgid ":class:`bool`: If this is a custom emoji." msgstr ":class:`bool`: カスタム絵文字が使用されているかどうか。" @@ -10956,7 +11777,7 @@ msgstr "リアクションを付けたユーザーの取得に失敗した場合 msgid "Union[:class:`User`, :class:`Member`] -- The member (if retrievable) or the user that has reacted to this message. The case where it can be a :class:`Member` is in a guild message context. Sometimes it can be a :class:`User` if the member has left the guild." msgstr "Union[:class:`User`, :class:`Member`] -- リアクションを付けたメンバー(取得できる場合)かユーザー。ギルド内では :class:`Member` となります。メンバーがギルドを脱退した場合は :class:`User` になります。" -#: ../../api.rst:4128 +#: ../../api.rst:4472 msgid "Guild" msgstr "Guild" @@ -11005,120 +11826,116 @@ msgid "Tuple[:class:`GuildSticker`, ...]" msgstr "Tuple[:class:`GuildSticker`, ...]" #: ../../../discord/guild.py:docstring of discord.guild.Guild:45 -#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:60 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:49 msgid "The number of seconds until someone is moved to the AFK channel." msgstr "メンバーをAFKチャンネルに移動させるまでの秒数。" #: ../../../discord/guild.py:docstring of discord.guild.Guild:51 -msgid "The channel that denotes the AFK channel. ``None`` if it doesn't exist." -msgstr "AFKチャンネルに指定されているチャンネル。もしなければこれは ``None`` です。" - -#: ../../../discord/guild.py:docstring of discord.guild.Guild:53 -msgid "Optional[:class:`VoiceChannel`]" -msgstr "Optional[:class:`VoiceChannel`]" - -#: ../../../discord/guild.py:docstring of discord.guild.Guild:57 #: ../../../discord/widget.py:docstring of discord.widget.Widget:19 msgid "The guild's ID." msgstr "ギルドのID。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild:63 +#: ../../../discord/guild.py:docstring of discord.guild.Guild:57 msgid "The guild owner's ID. Use :attr:`Guild.owner` instead." msgstr "ギルドのオーナーのID。代わりに :attr:`Guild.owner` を使用してください。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild:69 +#: ../../../discord/guild.py:docstring of discord.guild.Guild:63 msgid "Indicates if the guild is unavailable. If this is ``True`` then the reliability of other attributes outside of :attr:`Guild.id` is slim and they might all be ``None``. It is best to not do anything with the guild if it is unavailable." msgstr "ギルドが利用できないかどうか。これが ``True`` の場合 :attr:`Guild.id` 以外の属性の信頼度は低く ``None`` の場合もあります。利用不可能なギルドに対しては何も行わないことをおすすめします。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild:73 +#: ../../../discord/guild.py:docstring of discord.guild.Guild:67 msgid "Check the :func:`on_guild_unavailable` and :func:`on_guild_available` events." msgstr ":func:`on_guild_unavailable` と :func:`on_guild_available` イベントも確認してください。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild:79 +#: ../../../discord/guild.py:docstring of discord.guild.Guild:73 msgid "The maximum amount of presences for the guild." msgstr "ギルドのオンラインメンバーの最大数。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild:85 +#: ../../../discord/guild.py:docstring of discord.guild.Guild:79 msgid "The maximum amount of members for the guild." msgstr "ギルドのメンバーの最大数。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild:89 +#: ../../../discord/guild.py:docstring of discord.guild.Guild:83 msgid "This attribute is only available via :meth:`.Client.fetch_guild`." msgstr "この情報は :meth:`Client.fetch_guild` 経由でのみ入手できます。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild:95 +#: ../../../discord/guild.py:docstring of discord.guild.Guild:89 msgid "The maximum amount of users in a video channel." msgstr "ビデオチャンネルのユーザーの最大数。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild:103 +#: ../../../discord/guild.py:docstring of discord.guild.Guild:97 msgid "The guild's description." msgstr "ギルドの説明。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild:115 +#: ../../../discord/guild.py:docstring of discord.guild.Guild:109 msgid "The guild's vanity url code, if any" msgstr "存在する場合は、ギルドのバニティURLコード。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild:123 +#: ../../../discord/guild.py:docstring of discord.guild.Guild:117 msgid "The guild's explicit content filter." msgstr "メディアコンテンツフィルターのレベル。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild:129 +#: ../../../discord/guild.py:docstring of discord.guild.Guild:123 msgid "The guild's notification settings." msgstr "ギルドの標準の通知設定。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild:135 +#: ../../../discord/guild.py:docstring of discord.guild.Guild:129 msgid "A list of features that the guild has. The features that a guild can have are subject to arbitrary change by Discord. A list of guild features can be found in :ddocs:`the Discord documentation `." msgstr "ギルドの機能のリスト。ギルドの機能の種類はDiscordによって変更されることがあります。 ギルドの機能の一覧は :ddocs:`the Discord documentation ` にあります。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild:143 +#: ../../../discord/guild.py:docstring of discord.guild.Guild:137 msgid "The premium tier for this guild. Corresponds to \"Nitro Server\" in the official UI. The number goes from 0 to 3 inclusive." msgstr "ギルドのプレミアム階級。これは公式UIの「ニトロサーバー」に対応します。これは0以上3以下です。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild:150 +#: ../../../discord/guild.py:docstring of discord.guild.Guild:144 msgid "The number of \"boosts\" this guild currently has." msgstr "ギルドの現在の「ブースト」数。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild:156 +#: ../../../discord/guild.py:docstring of discord.guild.Guild:150 msgid "The preferred locale for the guild. Used when filtering Server Discovery results to a specific language." msgstr "ギルドの優先ローケル。これはサーバー発見画面の結果を特定の言語で絞り込むときに利用されます。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild:159 +#: ../../../discord/guild.py:docstring of discord.guild.Guild:153 msgid "This field is now an enum instead of a :class:`str`." msgstr "このフィールドが :class:`str` から列挙型に変更されました。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild:166 +#: ../../../discord/guild.py:docstring of discord.guild.Guild:160 msgid "The guild's NSFW level." msgstr "ギルドの年齢制限レベル。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild:170 +#: ../../../discord/guild.py:docstring of discord.guild.Guild:164 #: ../../../discord/invite.py:docstring of discord.invite.PartialInviteGuild:60 msgid ":class:`NSFWLevel`" msgstr ":class:`NSFWLevel`" -#: ../../../discord/guild.py:docstring of discord.guild.Guild:174 +#: ../../../discord/guild.py:docstring of discord.guild.Guild:168 msgid "The guild's Multi-Factor Authentication requirement level." msgstr "ギルドの多要素認証要件レベル。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild:176 +#: ../../../discord/guild.py:docstring of discord.guild.Guild:170 msgid "This field is now an enum instead of an :class:`int`." msgstr "このフィールドが :class:`int` から列挙型に変更されました。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild:183 -msgid "The approximate number of members in the guild. This is ``None`` unless the guild is obtained using :meth:`Client.fetch_guild` with ``with_counts=True``." -msgstr "ギルドのおおよそのメンバーの数を示します。 :meth:`Client.fetch_guild` にて ``with_counts=True`` を指定してギルドを取得しない限り、 ``None`` を返します。" +#: ../../../discord/guild.py:docstring of discord.guild.Guild:177 +msgid "The approximate number of members in the guild. This is ``None`` unless the guild is obtained using :meth:`Client.fetch_guild` or :meth:`Client.fetch_guilds` with ``with_counts=True``." +msgstr "ギルドのおおよそのメンバーの数を示します。 :meth:`Client.fetch_guild` や :meth:`Client.fetch_guilds` にて ``with_counts=True`` を指定してギルドを取得しない限り、 ``None`` を返します。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild:192 -msgid "The approximate number of members currently active in the guild. Offline members are excluded. This is ``None`` unless the guild is obtained using :meth:`Client.fetch_guild` with ``with_counts=True``." -msgstr "ギルドで現在アクティブなメンバーのおおよその数を示します。オフラインのメンバーは除外された値となります。 :meth:`Client.fetch_guild` にて ``with_counts=True`` を指定してギルドを取得しない限り、 ``None`` を返します。" +#: ../../../discord/guild.py:docstring of discord.guild.Guild:186 +msgid "The approximate number of members currently active in the guild. Offline members are excluded. This is ``None`` unless the guild is obtained using :meth:`Client.fetch_guild` or :meth:`Client.fetch_guilds` with ``with_counts=True``." +msgstr "ギルドで現在アクティブなメンバーのおおよその数を示します。オフラインのメンバーは除外された値となります。 :meth:`Client.fetch_guild` や :meth:`Client.fetch_guilds` にて ``with_counts=True`` を指定してギルドを取得しない限り、 ``None`` を返します。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild:202 +#: ../../../discord/guild.py:docstring of discord.guild.Guild:196 msgid "Indicates if the guild has premium AKA server boost level progress bar enabled." msgstr "ギルドがプレミアム、すなわちサーバーブーストレベルの進捗バーを有効化しているか。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild:210 +#: ../../../discord/guild.py:docstring of discord.guild.Guild:204 msgid "Indicates if the guild has widget enabled." msgstr "ギルドにてウィジェットが有効になっているかどうかを示します。" +#: ../../../discord/guild.py:docstring of discord.guild.Guild:212 +msgid "The maximum amount of users in a stage video channel." +msgstr "ステージビデオチャンネルのユーザーの最大数。" + #: ../../../discord/guild.py:docstring of discord.Guild.channels:1 msgid "A list of channels that belongs to this guild." msgstr "このギルド内に存在するチャンネルのリスト。" @@ -11209,6 +12026,7 @@ msgid "A list of forum channels that belongs to this guild." msgstr "このギルド内に存在するフォーラムチャンネルのリスト。" #: ../../../discord/guild.py:docstring of discord.Guild.forums:5 +#: ../../../discord/channel.py:docstring of discord.CategoryChannel.forums:5 msgid "List[:class:`ForumChannel`]" msgstr "List[:class:`ForumChannel`]" @@ -11268,25 +12086,40 @@ msgstr "内部キャッシュに保持されていないため、アーカイブ msgid "The returned thread or ``None`` if not found." msgstr "スレッド、または該当するものが見つからない場合 ``None`` が返ります。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.get_thread:14 -#: ../../../discord/channel.py:docstring of discord.channel.TextChannel.get_thread:14 -#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.get_thread:14 -msgid "Optional[:class:`Thread`]" -msgstr "Optional[:class:`Thread`]" +#: ../../../discord/guild.py:docstring of discord.guild.Guild.get_emoji:8 +msgid "The returned Emoji or ``None`` if not found." +msgstr "絵文字、または該当するものが見つからない場合 ``None`` が返ります。" -#: ../../../discord/guild.py:docstring of discord.Guild.system_channel:1 -msgid "Returns the guild's channel used for system messages." -msgstr "ギルドの「システムのメッセージチャンネル」として設定されているチャンネルを返します。" +#: ../../../discord/guild.py:docstring of discord.guild.Guild.get_emoji:9 +msgid "Optional[:class:`Emoji`]" +msgstr "Optional[:class:`Emoji`]" + +#: ../../../discord/guild.py:docstring of discord.Guild.afk_channel:1 +msgid "The channel that denotes the AFK channel." +msgstr "AFKチャンネルを示すチャンネル。" +#: ../../../discord/guild.py:docstring of discord.Guild.afk_channel:3 #: ../../../discord/guild.py:docstring of discord.Guild.system_channel:3 #: ../../../discord/guild.py:docstring of discord.Guild.rules_channel:4 #: ../../../discord/guild.py:docstring of discord.Guild.public_updates_channel:5 +#: ../../../discord/guild.py:docstring of discord.Guild.widget_channel:4 msgid "If no channel is set, then this returns ``None``." msgstr "チャンネルが設定されていない場合はこれは ``None`` になります。" +#: ../../../discord/guild.py:docstring of discord.Guild.afk_channel:5 +#: ../../../discord/scheduled_event.py:docstring of discord.ScheduledEvent.channel:3 +#: ../../../discord/member.py:docstring of discord.member.VoiceState:74 +msgid "Optional[Union[:class:`VoiceChannel`, :class:`StageChannel`]]" +msgstr "Optional[Union[:class:`VoiceChannel`, :class:`StageChannel`]]" + +#: ../../../discord/guild.py:docstring of discord.Guild.system_channel:1 +msgid "Returns the guild's channel used for system messages." +msgstr "ギルドの「システムのメッセージチャンネル」として設定されているチャンネルを返します。" + #: ../../../discord/guild.py:docstring of discord.Guild.system_channel:5 #: ../../../discord/guild.py:docstring of discord.Guild.rules_channel:8 #: ../../../discord/guild.py:docstring of discord.Guild.public_updates_channel:9 +#: ../../../discord/guild.py:docstring of discord.Guild.safety_alerts_channel:7 msgid "Optional[:class:`TextChannel`]" msgstr "Optional[:class:`TextChannel`]" @@ -11294,10 +12127,6 @@ msgstr "Optional[:class:`TextChannel`]" msgid "Returns the guild's system channel settings." msgstr "ギルドのシステムチャンネルの設定を返します。" -#: ../../../discord/guild.py:docstring of discord.Guild.system_channel_flags:3 -msgid ":class:`SystemChannelFlags`" -msgstr ":class:`SystemChannelFlags`" - #: ../../../discord/guild.py:docstring of discord.Guild.rules_channel:1 msgid "Return's the guild's channel used for the rules. The guild must be a Community guild." msgstr "ルールに使用されるギルドのチャンネルを返します。ギルドはコミュニティギルドでなければなりません。" @@ -11306,6 +12135,22 @@ msgstr "ルールに使用されるギルドのチャンネルを返します。 msgid "Return's the guild's channel where admins and moderators of the guilds receive notices from Discord. The guild must be a Community guild." msgstr "ギルドの管理者とモデレータがDiscordからの通知を受け取るギルドのチャンネルを返します。ギルドはコミュニティギルドでなければなりません。" +#: ../../../discord/guild.py:docstring of discord.Guild.safety_alerts_channel:1 +msgid "Return's the guild's channel used for safety alerts, if set." +msgstr "存在する場合、ギルドの「セーフティ通知チャンネル」として設定されているチャンネルを返します。" + +#: ../../../discord/guild.py:docstring of discord.Guild.safety_alerts_channel:3 +msgid "For example, this is used for the raid protection setting. The guild must have the ``COMMUNITY`` feature." +msgstr "例えば、これはレイドプロテクションの設定に使用されます。ギルドには ``COMMUNITY`` 機能が必要です。" + +#: ../../../discord/guild.py:docstring of discord.Guild.widget_channel:1 +msgid "Returns the widget channel of the guild." +msgstr "ギルドのウィジェットチャンネルを返します。" + +#: ../../../discord/guild.py:docstring of discord.Guild.widget_channel:8 +msgid "Optional[Union[:class:`TextChannel`, :class:`ForumChannel`, :class:`VoiceChannel`, :class:`StageChannel`]]" +msgstr "Optional[Union[:class:`TextChannel`, :class:`ForumChannel`, :class:`VoiceChannel`, :class:`StageChannel`]]" + #: ../../../discord/guild.py:docstring of discord.Guild.emoji_limit:1 msgid "The maximum number of emoji slots this guild has." msgstr "ギルドに追加できるカスタム絵文字のスロットの最大数。" @@ -11501,26 +12346,46 @@ msgid "Returns the first member found that matches the name provided." msgstr "指定された名前に一致する最初のメンバーを返します。" #: ../../../discord/guild.py:docstring of discord.guild.Guild.get_member_named:3 -msgid "The name can have an optional discriminator argument, e.g. \"Jake#0001\" or \"Jake\" will both do the lookup. However the former will give a more precise result. Note that the discriminator must have all 4 digits for this to work." -msgstr "名前にはタグを含めることができます。例:\"Jake#0001\" も \"Jake\" も利用できます。ただ、前者のほうがより正確な結果になります。この場合にはタグは4桁すべて必要です。" +msgid "The name is looked up in the following order:" +msgstr "名前は以下の順番で検索されます:" + +#: ../../../discord/guild.py:docstring of discord.guild.Guild.get_member_named:5 +msgid "Username#Discriminator (deprecated)" +msgstr "ユーザー名#タグ (非推奨)" + +#: ../../../discord/guild.py:docstring of discord.guild.Guild.get_member_named:6 +msgid "Username#0 (deprecated, only gets users that migrated from their discriminator)" +msgstr "ユーザー名#0 (非推奨、タグから移行したユーザーのみ取得)" + +#: ../../../discord/guild.py:docstring of discord.guild.Guild.get_member_named:7 +msgid "Nickname" +msgstr "ニックネーム" #: ../../../discord/guild.py:docstring of discord.guild.Guild.get_member_named:8 -msgid "If a nickname is passed, then it is looked up via the nickname. Note however, that a nickname + discriminator combo will not lookup the nickname but rather the username + discriminator combo due to nickname + discriminator not being unique." -msgstr "ニックネームが渡された場合、ニックネームで検索が行われます。なお、ニックネームとタグの組み合わせでは、ニックネームではなくユーザー名とタグの組み合わせとして扱われます。これは、ニックネームとタグの組み合わせが一意でないためです。" +msgid "Global name" +msgstr "グローバルの表示名" + +#: ../../../discord/guild.py:docstring of discord.guild.Guild.get_member_named:9 +msgid "Username" +msgstr "ユーザー名" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.get_member_named:13 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.get_member_named:11 msgid "If no member is found, ``None`` is returned." msgstr "メンバーが見つからない場合は ``None`` が返されます。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.get_member_named:17 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.get_member_named:15 msgid "``name`` parameter is now positional-only." msgstr "``name`` 引数は位置限定引数になりました。" #: ../../../discord/guild.py:docstring of discord.guild.Guild.get_member_named:19 -msgid "The name of the member to lookup with an optional discriminator." -msgstr "検索するメンバーの名前。タグをつけることもできます。" +msgid "Looking up users via discriminator due to Discord API change." +msgstr "Discord APIの変更のため、ユーザーのタグによる検索。" + +#: ../../../discord/guild.py:docstring of discord.guild.Guild.get_member_named:21 +msgid "The name of the member to lookup." +msgstr "検索するメンバーの名前。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.get_member_named:22 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.get_member_named:24 msgid "The member in this guild with the associated name. If not found then ``None`` is returned." msgstr "渡された名前のメンバー。見つからなかった場合は ``None`` が返ります。" @@ -11559,19 +12424,19 @@ msgstr "チャンネルの名前。" #: ../../../discord/guild.py:docstring of discord.guild.Guild.create_text_channel:43 #: ../../../discord/guild.py:docstring of discord.guild.Guild.create_voice_channel:11 -#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_stage_channel:15 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_stage_channel:13 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_forum:14 msgid "A :class:`dict` of target (either a role or a member) to :class:`PermissionOverwrite` to apply upon creation of a channel. Useful for creating secret channels." msgstr "チャンネル作成時に適用すべき、対象(ロールまたはメンバー)をキーとし :class:`PermissionOverwrite` を値とする :class:`dict` 。秘密のチャンネルの作成に便利です。" #: ../../../discord/guild.py:docstring of discord.guild.Guild.create_text_channel:47 #: ../../../discord/guild.py:docstring of discord.guild.Guild.create_voice_channel:15 -#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_stage_channel:19 -#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_forum:16 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_stage_channel:17 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_forum:20 msgid "The category to place the newly created channel under. The permissions will be automatically synced to category if no overwrites are provided." msgstr "新しく作成されたチャンネルを配置するカテゴリ。上書きがない場合、権限は自動的にカテゴリと同期されます。" #: ../../../discord/guild.py:docstring of discord.guild.Guild.create_text_channel:54 -#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_stage_channel:13 #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.edit:22 msgid "The new channel's topic." msgstr "新しいチャンネルのトピック。" @@ -11581,7 +12446,7 @@ msgid "Specifies the slowmode rate limit for user in this channel, in seconds. T msgstr "チャンネル内の低速モードの時間を秒単位で指定します。最大値は ``21600`` です。" #: ../../../discord/guild.py:docstring of discord.guild.Guild.create_text_channel:59 -#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_forum:23 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_forum:27 #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.edit:26 #: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.edit:24 #: ../../../discord/channel.py:docstring of discord.channel.StageChannel.edit:24 @@ -11597,28 +12462,32 @@ msgid "The default auto archive duration for threads created in the text channel msgstr "このテキストチャンネルで作成されたスレッドの分単位のデフォルトの自動アーカイブ期間。これは ``60`` 、 ``1440`` 、 ``4320`` 、または ``10080`` でないといけません。" #: ../../../discord/guild.py:docstring of discord.guild.Guild.create_text_channel:70 +msgid "The default slowmode delay in seconds for threads created in the text channel." +msgstr "このテキストチャンネルで作成されたスレッドの、デフォルトの秒単位の低速モードレート制限。" + +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_text_channel:74 #: ../../../discord/guild.py:docstring of discord.guild.Guild.create_voice_channel:35 -#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_stage_channel:26 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_stage_channel:41 msgid "The reason for creating this channel. Shows up on the audit log." msgstr "チャンネルを作成する理由。監査ログに表示されます。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_text_channel:75 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_text_channel:79 #: ../../../discord/guild.py:docstring of discord.guild.Guild.create_voice_channel:40 -#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_stage_channel:31 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_stage_channel:46 #: ../../../discord/guild.py:docstring of discord.guild.Guild.create_category:16 #: ../../../discord/guild.py:docstring of discord.guild.Guild.create_category:16 msgid "The permission overwrite information is not in proper form." msgstr "権限の上書きの情報が適切なものでない場合。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_text_channel:77 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_text_channel:81 #: ../../../discord/guild.py:docstring of discord.guild.Guild.create_voice_channel:42 -#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_stage_channel:33 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_stage_channel:48 #: ../../../discord/guild.py:docstring of discord.guild.Guild.create_category:18 #: ../../../discord/guild.py:docstring of discord.guild.Guild.create_category:18 msgid "The channel that was just created." msgstr "作成されたチャンネル。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_text_channel:78 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_text_channel:82 #: ../../../discord/channel.py:docstring of discord.channel.CategoryChannel.create_text_channel:6 msgid ":class:`TextChannel`" msgstr ":class:`TextChannel`" @@ -11628,17 +12497,20 @@ msgid "This is similar to :meth:`create_text_channel` except makes a :class:`Voi msgstr "これは :class:`VoiceChannel` を作る点以外では :meth:`create_text_channel` と似ています。" #: ../../../discord/guild.py:docstring of discord.guild.Guild.create_voice_channel:22 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_stage_channel:24 #: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel:62 #: ../../../discord/channel.py:docstring of discord.channel.StageChannel:70 msgid "The channel's preferred audio bitrate in bits per second." msgstr "チャンネルのビット毎秒単位の推奨オーディオビットレート設定。" #: ../../../discord/guild.py:docstring of discord.guild.Guild.create_voice_channel:24 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_stage_channel:28 #: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel:68 msgid "The channel's limit for number of members that can be in a voice channel." msgstr "ボイスチャンネルに参加できるメンバー数の制限。" #: ../../../discord/guild.py:docstring of discord.guild.Guild.create_voice_channel:26 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_stage_channel:32 #: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel:74 msgid "The region for the voice channel's voice communication. A value of ``None`` indicates automatic voice region detection." msgstr "ボイスチャンネルの音声通信のためのリージョン。値が ``None`` の場合は自動で検出されます。" @@ -11652,7 +12524,7 @@ msgstr ":class:`VoiceChannel`" msgid "This is similar to :meth:`create_text_channel` except makes a :class:`StageChannel` instead." msgstr "これは :class:`StageChannel` を作る点以外では :meth:`create_text_channel` と似ています。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_stage_channel:34 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_stage_channel:49 #: ../../../discord/channel.py:docstring of discord.channel.CategoryChannel.create_stage_channel:8 msgid ":class:`StageChannel`" msgstr ":class:`StageChannel`" @@ -11676,32 +12548,46 @@ msgstr ":class:`CategoryChannel`" msgid "Similar to :meth:`create_text_channel` except makes a :class:`ForumChannel` instead." msgstr "これは :class:`ForumChannel` を作る点以外では :meth:`create_text_channel` と似ています。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_forum:14 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_forum:18 msgid "The channel's topic." msgstr "チャンネルのトピック。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_forum:25 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_forum:29 msgid "Specifies the slowmode rate limit for users in this channel, in seconds. The maximum possible value is ``21600``." msgstr "チャンネル内の低速モードの時間を秒単位で指定します。最大値は ``21600`` です。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_forum:28 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_forum:32 msgid "The reason for creating this channel. Shows up in the audit log." msgstr "チャンネルを作成する理由。監査ログに表示されます。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_forum:30 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_forum:34 msgid "The default auto archive duration for threads created in the forum channel (in minutes). Must be one of ``60``, ``1440``, ``4320``, or ``10080``." msgstr "このフォーラムチャンネルで作成されたスレッドの分単位のデフォルトの自動アーカイブ期間。これは ``60`` 、 ``1440`` 、 ``4320`` 、または ``10080`` でないといけません。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_forum:33 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_forum:37 #: ../../../discord/channel.py:docstring of discord.channel.ForumChannel:92 msgid "The default slowmode delay in seconds for threads created in this forum." msgstr "このフォーラムで作成されたスレッドの、デフォルトの秒単位の低速モードレート制限。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_forum:37 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_forum:41 +#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel:118 +msgid "The default sort order for posts in this forum channel." +msgstr "このフォーラムチャネルの投稿の並び替え順。" + +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_forum:45 +#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel:100 +msgid "The default reaction emoji for threads created in this forum to show in the add reaction button." +msgstr "このフォーラムで作成されたスレッドで、デフォルトでリアクション追加ボタンに表示するリアクション絵文字。" + +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_forum:50 +msgid "The default layout for posts in this forum." +msgstr "このフォーラムの投稿のデフォルトの表示レイアウト。" + +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_forum:54 msgid "The available tags for this forum channel." msgstr "このフォーラムチャンネルで利用可能なタグ。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_forum:47 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_forum:64 #: ../../../discord/channel.py:docstring of discord.channel.CategoryChannel.create_forum:8 msgid ":class:`ForumChannel`" msgstr ":class:`ForumChannel`" @@ -11739,139 +12625,153 @@ msgid "You must have :attr:`~Permissions.manage_guild` to edit the guild." msgstr "ルールを編集するには、 :attr:`~Permissions.manage_guild` が必要です。" #: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:7 -msgid "The ``rules_channel`` and ``public_updates_channel`` keyword parameters were added." -msgstr "``rules_channel`` と ``public_updates_channel`` キーワード引数が追加されました。" - -#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:10 -msgid "The ``discovery_splash`` and ``community`` keyword parameters were added." -msgstr "``discovery_splash`` と ``community`` キーワード引数が追加されました。" - -#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:13 msgid "The newly updated guild is returned." msgstr "新しく更新されたギルドが返されるようになりました。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:16 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:10 msgid "The ``region`` keyword parameter has been removed." msgstr "``region`` キーワード引数が削除されました。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:23 -msgid "The ``preferred_locale`` keyword parameter now accepts an enum instead of :class:`str`." -msgstr "``preferred_locale`` キーワード引数が :class:`str` の代わりに列挙型を受け付けるようになりました。" - -#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:26 -msgid "The ``premium_progress_bar_enabled`` keyword parameter was added." -msgstr "``premium_progress_bar_enabled`` キーワード引数が追加されました。" - -#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:29 -msgid "The ``discoverable`` and ``invites_disabled`` keyword parameters were added." -msgstr "``discoverable`` と ``invites_disabled`` キーワード引数が追加されました。" - -#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:32 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:17 msgid "The new name of the guild." msgstr "ギルドの新しい名前。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:34 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:19 msgid "The new description of the guild. Could be ``None`` for no description. This is only available to guilds that contain ``COMMUNITY`` in :attr:`Guild.features`." msgstr "ギルドの新しい説明。説明がない場合 ``None`` を指定することができます。ただし、これは :attr:`Guild.features` に ``COMMUNITY`` があるギルドでのみ使用できます。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:37 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:22 msgid "A :term:`py:bytes-like object` representing the icon. Only PNG/JPEG is supported. GIF is only available to guilds that contain ``ANIMATED_ICON`` in :attr:`Guild.features`. Could be ``None`` to denote removal of the icon." msgstr "アイコンを示す :term:`py:bytes-like object` 。PNGとJPEGのみ使用できます。 :attr:`Guild.features` に ``ANIMATED_ICON`` を含むギルドではGIFも使用できます。アイコンを削除する場合には ``None`` を指定できます。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:41 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:26 msgid "A :term:`py:bytes-like object` representing the banner. Could be ``None`` to denote removal of the banner. This is only available to guilds that contain ``BANNER`` in :attr:`Guild.features`." msgstr "バナーを示す :term:`py:bytes-like object` 。バナーを削除する場合には ``None`` を指定できます。 :attr:`Guild.features` に ``BANNER`` を含むギルドでのみ利用可能です。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:45 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:30 msgid "A :term:`py:bytes-like object` representing the invite splash. Only PNG/JPEG supported. Could be ``None`` to denote removing the splash. This is only available to guilds that contain ``INVITE_SPLASH`` in :attr:`Guild.features`." msgstr "招待スプラッシュを示す :term:`py:bytes-like object` 。PNGとJPEGのみ使用できます。スプラッシュを削除する場合には ``None`` を指定できます。 :attr:`Guild.features` に ``INVITE_SPLASH`` を含むギルドでのみ利用可能です。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:50 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:35 msgid "A :term:`py:bytes-like object` representing the discovery splash. Only PNG/JPEG supported. Could be ``None`` to denote removing the splash. This is only available to guilds that contain ``DISCOVERABLE`` in :attr:`Guild.features`." msgstr "発見画面のスプラッシュを示す :term:`py:bytes-like object` 。PNGとJPEGのみ使用できます。スプラッシュを削除する場合には ``None`` を指定できます。 :attr:`Guild.features` に ``DISCOVERABLE`` を含むギルドでのみ利用可能です。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:55 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:42 msgid "Whether the guild should be a Community guild. If set to ``True``\\, both ``rules_channel`` and ``public_updates_channel`` parameters are required." msgstr "ギルドをコミュニティギルドにするかどうか。``True`` に設定した場合、``rules_channel`` と ``public_updates_channel`` の両方のパラメータが必要です。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:58 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:47 msgid "The new channel that is the AFK channel. Could be ``None`` for no AFK channel." msgstr "AFKチャンネルに設定する新しいチャンネル。AFKチャンネルを設定しない場合には ``None`` を指定することができます。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:62 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:51 msgid "The new owner of the guild to transfer ownership to. Note that you must be owner of the guild to do this." msgstr "ギルドの新しい所有者。ただし、これを設定する場合には、ギルドの所有者である必要があります。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:65 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:54 msgid "The new verification level for the guild." msgstr "ギルドの新しい認証レベル。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:67 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:56 msgid "The new default notification level for the guild." msgstr "ギルドの新しい標準の通知レベル。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:69 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:58 msgid "The new explicit content filter for the guild." msgstr "ギルドの新しい、不適切な表現のフィルター設定。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:71 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:60 msgid "The new vanity code for the guild." msgstr "ギルドの新しいバニティコード。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:73 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:62 msgid "The new channel that is used for the system channel. Could be ``None`` for no system channel." msgstr "システムチャンネルに設定する新しいチャンネル。システムチャンネルを設定しない場合には ``None`` を指定することができます。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:75 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:64 msgid "The new system channel settings to use with the new system channel." msgstr "新しいシステムチャンネルの使用設定。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:77 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:66 msgid "The new preferred locale for the guild. Used as the primary language in the guild." msgstr "ギルドの新しい優先ロケール。ギルドの主要言語として使用されます。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:79 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:70 +msgid "Now accepts an enum instead of :class:`str`." +msgstr ":class:`str` の代わりに列挙型を受け付けます。" + +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:72 msgid "The new channel that is used for rules. This is only available to guilds that contain ``COMMUNITY`` in :attr:`Guild.features`. Could be ``None`` for no rules channel." msgstr "ルールに使用される新しいチャンネル。これは :attr:`Guild.features` に ``COMMUNITY`` を含むギルドでのみ利用できます。ルールチャネルがない場合は ``None`` を指定できます。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:83 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:78 msgid "The new channel that is used for public updates from Discord. This is only available to guilds that contain ``COMMUNITY`` in :attr:`Guild.features`. Could be ``None`` for no public updates channel." msgstr "Discordからのコミュニティーアップデートに使用される新しいチャンネル。これは :attr:`Guild.features` に ``COMMUNITY`` を含むギルドでのみ利用できます。コミュニティーアップデートチャネルがない場合は ``None`` を指定できます。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:87 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:84 msgid "Whether the premium AKA server boost level progress bar should be enabled for the guild." msgstr "ギルドのプレミアム、すなわちサーバーブーストレベルの進捗バーを有効化すべきか。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:89 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:88 msgid "Whether server discovery is enabled for this guild." msgstr "このギルドでサーバー発見を有効にするかどうか。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:91 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:92 msgid "Whether joining via invites should be disabled for the guild." msgstr "招待でのギルドへの参加を無効にするかどうか。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:93 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:96 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit_widget:9 +msgid "Whether to enable the widget for the guild." +msgstr "ギルドのウィジェットを有効にするかどうか。" + +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:100 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit_widget:11 +msgid "The new widget channel. ``None`` removes the widget channel." +msgstr "新しいウィジェットチャンネル。``None`` を指定するとウィジェットチャンネルを除去できます。" + +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:104 +msgid "The new guild's Multi-Factor Authentication requirement level. Note that you must be owner of the guild to do this." +msgstr "新しいギルドの二要素認証要件レベル。これを行うにはギルドの所有者でないといけません。" + +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:109 msgid "The reason for editing this guild. Shows up on the audit log." msgstr "ギルドを編集する理由。監査ログに表示されます。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:96 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:111 +msgid "Whether the alerts for raid protection should be disabled for the guild." +msgstr "レイドプロテクションのアラートをギルドで無効にするかどうか。" + +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:115 +msgid "The new channel that is used for safety alerts. This is only available to guilds that contain ``COMMUNITY`` in :attr:`Guild.features`. Could be ``None`` for no safety alerts channel." +msgstr "安全アラートに使用される新しいチャンネル。これは :attr:`Guild.features` に ``COMMUNITY`` を含むギルドでのみ利用できます。安全アラートチャネルがない場合は ``None`` を指定できます。" + +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:121 +msgid "The time when invites should be enabled again, or ``None`` to disable the action. This must be a timezone-aware datetime object. Consider using :func:`utils.utcnow`." +msgstr "" + +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:126 +msgid "The time when direct messages should be allowed again, or ``None`` to disable the action. This must be a timezone-aware datetime object. Consider using :func:`utils.utcnow`." +msgstr "" + +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:132 msgid "You do not have permissions to edit the guild." msgstr "ギルドを編集する権限がない場合。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:97 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:133 #: ../../../discord/integrations.py:docstring of discord.integrations.StreamIntegration.edit:19 msgid "Editing the guild failed." msgstr "ギルドの編集に失敗した場合。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:98 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:134 msgid "The image format passed in to ``icon`` is invalid. It must be PNG or JPG. This is also raised if you are not the owner of the guild and request an ownership transfer." msgstr "``icon`` に渡された画像形式が無効な場合。これはPNGかJPGでなくてはいけません。また、あなたがギルドの所有者でないのに、ギルドの所有権の移動を行おうとした場合にも発生します。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:99 -msgid "The type passed to the ``default_notifications``, ``verification_level``, ``explicit_content_filter``, or ``system_channel_flags`` parameter was of the incorrect type." -msgstr "``default_notifications`` 、 ``verification_level`` 、 ``explicit_content_filter`` 、または ``system_channel_flags`` に間違った型の値が渡されたとき。" +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:135 +msgid "The type passed to the ``default_notifications``, ``rules_channel``, ``public_updates_channel``, ``safety_alerts_channel`` ``verification_level``, ``explicit_content_filter``, ``system_channel_flags``, or ``mfa_level`` parameter was of the incorrect type." +msgstr "``default_notifications`` 、 ``rules_channel`` 、 ``public_updates_channel`` 、 ``safety_alerts_channel`` 、 ``verification_level`` 、 ``explicit_content_filter`` 、 ``system_channel_flags`` 、 ``mfa_level`` に間違った型の値が渡されたとき。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:101 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit:137 msgid "The newly updated guild. Note that this has the same limitations as mentioned in :meth:`Client.fetch_guild` and may not have full data." msgstr "新しく更新されたギルド。これは :meth:`Client.fetch_guild` に記載されているものと同じ制限があり、完全なデータを持っていない可能性があることに注意してください。" @@ -11958,6 +12858,10 @@ msgstr "``member_id`` 引数は位置専用引数となりました。" msgid "The member's ID to fetch from." msgstr "取得したいメンバーのID。" +#: ../../../discord/guild.py:docstring of discord.guild.Guild.fetch_member:16 +msgid "You do not have access to the guild." +msgstr "ギルドにアクセスする権限がない場合。" + #: ../../../discord/guild.py:docstring of discord.guild.Guild.fetch_member:17 msgid "Fetching the member failed." msgstr "メンバーの取得に失敗した場合。" @@ -12031,10 +12935,6 @@ msgstr "このユーザ以前のBANを取得します。" msgid "Retrieve bans after this user." msgstr "このユーザ以降のBANを取得します。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.bans:33 -msgid "Both ``after`` and ``before`` were provided, as Discord does not support this type of pagination." -msgstr "``after`` と ``before`` の両方が渡された場合。Discordはこのタイプのページネーションをサポートしていません。" - #: ../../../discord/guild.py:docstring of discord.guild.Guild.bans:35 msgid ":class:`BanEntry` -- The ban entry of the banned user." msgstr ":class:`BanEntry` -- BANされたユーザーのBANエントリー。" @@ -12048,9 +12948,8 @@ msgid "The inactive members are denoted if they have not logged on in ``days`` n msgstr "``days`` 日間ログインせずロールを持たないメンバーが非アクティブとされます。" #: ../../../discord/guild.py:docstring of discord.guild.Guild.prune_members:8 -#: ../../../discord/guild.py:docstring of discord.guild.Guild.kick:7 -msgid "You must have :attr:`~Permissions.kick_members` to do this." -msgstr "これを行うには、 :attr:`~Permissions.kick_members` が必要です。" +msgid "You must have both :attr:`~Permissions.kick_members` and :attr:`~Permissions.manage_guild` to do this." +msgstr "" #: ../../../discord/guild.py:docstring of discord.guild.Guild.prune_members:10 msgid "To check how many members you would prune without actually pruning, see the :meth:`estimate_pruned_members` function." @@ -12130,7 +13029,8 @@ msgstr "これを行うには、 :attr:`~.Permissions.manage_webhooks` が必要 #: ../../../discord/guild.py:docstring of discord.guild.Guild.webhooks:7 #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.webhooks:7 #: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.webhooks:7 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.webhooks:9 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.webhooks:9 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.webhooks:9 msgid "You don't have permissions to get the webhooks." msgstr "Webhookを取得する権限がない場合。" @@ -12141,7 +13041,8 @@ msgstr "ギルド内のWebhook。" #: ../../../discord/guild.py:docstring of discord.guild.Guild.webhooks:10 #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.webhooks:10 #: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.webhooks:10 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.webhooks:12 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.webhooks:12 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.webhooks:12 msgid "List[:class:`Webhook`]" msgstr "List[:class:`Webhook`]" @@ -12403,7 +13304,7 @@ msgid "The scheduled event." msgstr "スケジュールイベント。" #: ../../../discord/guild.py:docstring of discord.guild.Guild.fetch_scheduled_event:17 -#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_scheduled_event:49 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_scheduled_event:51 #: ../../../discord/scheduled_event.py:docstring of discord.scheduled_event.ScheduledEvent.start:19 #: ../../../discord/scheduled_event.py:docstring of discord.scheduled_event.ScheduledEvent.end:19 #: ../../../discord/scheduled_event.py:docstring of discord.scheduled_event.ScheduledEvent.cancel:19 @@ -12463,48 +13364,54 @@ msgid "Required if the entity type is :attr:`EntityType.external`." msgstr ":attr:`EntityType.external` の場合必須です。" #: ../../../discord/guild.py:docstring of discord.guild.Guild.create_scheduled_event:28 +#: ../../../discord/scheduled_event.py:docstring of discord.scheduled_event.ScheduledEvent:63 +#: ../../../discord/scheduled_event.py:docstring of discord.scheduled_event.ScheduledEvent.edit:30 +msgid "The privacy level of the scheduled event." +msgstr "スケジュールイベントのプライバシーレベル。" + +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_scheduled_event:30 msgid "The entity type of the scheduled event. If the channel is a :class:`StageInstance` or :class:`VoiceChannel` then this is automatically set to the appropriate entity type. If no channel is passed then the entity type is assumed to be :attr:`EntityType.external`" msgstr "スケジュールイベントの開催場所の種類。もしチャンネルが :class:`StageInstance` または :class:`VoiceChannel` の場合これは適切な種類に自動で設定されます。もしチャンネルが渡されなかった場合は :attr:`EntityType.external` とみなされます。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_scheduled_event:34 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_scheduled_event:36 msgid "The image of the scheduled event." msgstr "スケジュールイベントの画像。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_scheduled_event:36 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_scheduled_event:38 msgid "The location of the scheduled event. Required if the ``entity_type`` is :attr:`EntityType.external`." msgstr "スケジュールイベントの場所。 ``entity_type`` が :attr:`EntityType.external` の場合必須です。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_scheduled_event:36 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_scheduled_event:38 #: ../../../discord/scheduled_event.py:docstring of discord.scheduled_event.ScheduledEvent:95 msgid "The location of the scheduled event." msgstr "スケジュールイベントの場所。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_scheduled_event:38 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_scheduled_event:40 msgid "Required if the ``entity_type`` is :attr:`EntityType.external`." msgstr "``entity_type`` が :attr:`EntityType.external` の場合必須です。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_scheduled_event:40 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_scheduled_event:42 msgid "The reason for creating this scheduled event. Shows up on the audit log." msgstr "スケジュールイベントを作成する理由。監査ログに表示されます。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_scheduled_event:43 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_scheduled_event:45 msgid "``image`` was not a :term:`py:bytes-like object`, or ``privacy_level`` was not a :class:`PrivacyLevel`, or ``entity_type`` was not an :class:`EntityType`, ``status`` was not an :class:`EventStatus`, or an argument was provided that was incompatible with the provided ``entity_type``." msgstr "``image`` が :term:`py:bytes-like object` でない場合、 ``privacy_level`` が :class:`PrivacyLevel` でない場合、 ``entity_type`` が :class:`EntityType` でない場合、 ``status`` が :class:`EventStatus` でない場合、または引数が渡された ``entity_type`` と互換性のない場合。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_scheduled_event:44 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_scheduled_event:46 #: ../../../discord/scheduled_event.py:docstring of discord.scheduled_event.ScheduledEvent.edit:48 msgid "``start_time`` or ``end_time`` was not a timezone-aware datetime object." msgstr "``start_time`` や ``end_time`` がtimezone awareなdatetimeオブジェクトでない場合。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_scheduled_event:45 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_scheduled_event:47 msgid "You are not allowed to create scheduled events." msgstr "スケジュールイベントを作成する権限がない場合。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_scheduled_event:46 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_scheduled_event:48 msgid "Creating the scheduled event failed." msgstr "スケジュールイベントの作成に失敗した場合。" -#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_scheduled_event:48 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.create_scheduled_event:50 msgid "The created scheduled event." msgstr "作成されたスケジュールイベント。" @@ -12751,7 +13658,6 @@ msgid "The guild must have ``COMMUNITY`` in :attr:`~Guild.features`." msgstr ":attr:`~Guild.features` に ``COMMUNITY`` が含まれている必要があります。" #: ../../../discord/guild.py:docstring of discord.guild.Guild.welcome_screen:7 -#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit_welcome_screen:8 #: ../../../discord/guild.py:docstring of discord.guild.Guild.vanity_invite:7 msgid "You must have :attr:`~Permissions.manage_guild` to do this.as well." msgstr "これを行うには、 :attr:`~Permissions.manage_guild` も必要です。" @@ -12778,6 +13684,10 @@ msgstr ":class:`WelcomeScreen`" msgid "A shorthand method of :attr:`WelcomeScreen.edit` without needing to fetch the welcome screen beforehand." msgstr "ようこそ画面を事前に取得せずに :attr:`WelcomeScreen.edit` を呼び出すことのできるメソッド。" +#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit_welcome_screen:8 +msgid "You must have :attr:`~Permissions.manage_guild` to do this as well." +msgstr "" + #: ../../../discord/guild.py:docstring of discord.guild.Guild.edit_welcome_screen:12 msgid "The edited welcome screen." msgstr "編集した後のようこそ画面。" @@ -12792,6 +13702,10 @@ msgstr "サーバーからユーザーをキックします。" msgid "The user must meet the :class:`abc.Snowflake` abc." msgstr "ユーザーは :class:`abc.Snowflake` 抽象基底クラスのサブクラスである必要があります。" +#: ../../../discord/guild.py:docstring of discord.guild.Guild.kick:7 +msgid "You must have :attr:`~Permissions.kick_members` to do this." +msgstr "これを行うには、 :attr:`~Permissions.kick_members` が必要です。" + #: ../../../discord/guild.py:docstring of discord.guild.Guild.kick:9 msgid "The user to kick from their guild." msgstr "ギルドからキックするユーザー。" @@ -12814,10 +13728,12 @@ msgstr "ギルドからユーザーをBANします。" #: ../../../discord/guild.py:docstring of discord.guild.Guild.ban:7 #: ../../../discord/guild.py:docstring of discord.guild.Guild.unban:7 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.bulk_ban:7 msgid "You must have :attr:`~Permissions.ban_members` to do this." msgstr "これを行うには、 :attr:`~Permissions.ban_members` が必要です。" #: ../../../discord/guild.py:docstring of discord.guild.Guild.ban:9 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.bulk_ban:11 msgid "The user to ban from their guild." msgstr "ギルドからBANするユーザー。" @@ -12838,10 +13754,12 @@ msgid "The requested user was not found." msgstr "ユーザーが見つからなかった場合。" #: ../../../discord/guild.py:docstring of discord.guild.Guild.ban:29 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.bulk_ban:20 msgid "You do not have the proper permissions to ban." msgstr "BANするのに適切な権限がない場合。" #: ../../../discord/guild.py:docstring of discord.guild.Guild.ban:30 +#: ../../../discord/guild.py:docstring of discord.guild.Guild.bulk_ban:21 msgid "Banning failed." msgstr "BANに失敗した場合。" @@ -12869,6 +13787,30 @@ msgstr "BANを解除するのに適切な権限がない場合。" msgid "Unbanning failed." msgstr "BAN解除に失敗した場合。" +#: ../../../discord/guild.py:docstring of discord.guild.Guild.bulk_ban:3 +msgid "Bans multiple users from the guild." +msgstr "" + +#: ../../../discord/guild.py:docstring of discord.guild.Guild.bulk_ban:5 +msgid "The users must meet the :class:`abc.Snowflake` abc." +msgstr "" + +#: ../../../discord/guild.py:docstring of discord.guild.Guild.bulk_ban:13 +msgid "The number of seconds worth of messages to delete from the user in the guild. The minimum is 0 and the maximum is 604800 (7 days). Defaults to 1 day." +msgstr "" + +#: ../../../discord/guild.py:docstring of discord.guild.Guild.bulk_ban:17 +msgid "The reason the users got banned." +msgstr "" + +#: ../../../discord/guild.py:docstring of discord.guild.Guild.bulk_ban:23 +msgid "The result of the bulk ban operation." +msgstr "" + +#: ../../../discord/guild.py:docstring of discord.guild.Guild.bulk_ban:24 +msgid ":class:`BulkBanResult`" +msgstr "" + #: ../../../discord/guild.py:docstring of discord.Guild.vanity_url:1 msgid "The Discord vanity invite URL for this guild, if available." msgstr "存在する場合、ギルドのDiscord バニティURLを返します。" @@ -12959,16 +13901,8 @@ msgid ":class:`Widget`" msgstr ":class:`Widget`" #: ../../../discord/guild.py:docstring of discord.guild.Guild.edit_widget:3 -msgid "Edits the widget of the guild." -msgstr "ギルドのウィジェットを編集します。" - -#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit_widget:9 -msgid "Whether to enable the widget for the guild." -msgstr "ギルドのウィジェットを有効にするかどうか。" - -#: ../../../discord/guild.py:docstring of discord.guild.Guild.edit_widget:11 -msgid "The new widget channel. ``None`` removes the widget channel." -msgstr "新しいウィジェットチャンネル。``None`` を指定するとウィジェットチャンネルを除去できます。" +msgid "Edits the widget of the guild. This can also be done with :attr:`~Guild.edit`." +msgstr "ギルドのウィジェットを編集します。これは :attr:`~Guild.edit` でも行えます。" #: ../../../discord/guild.py:docstring of discord.guild.Guild.edit_widget:13 msgid "The reason for editing this widget. Shows up on the audit log." @@ -13100,8 +14034,8 @@ msgid "The actions that will be taken when the automod rule is triggered." msgstr "自動管理ルールが発動したときの対応。" #: ../../../discord/guild.py:docstring of discord.guild.Guild.create_automod_rule:17 -msgid "Whether the automod rule is enabled. Discord will default to ``False``." -msgstr "自動管理ルールを有効にするか。Discordでのデフォルトは ``False`` です。" +msgid "Whether the automod rule is enabled. Defaults to ``False``." +msgstr "自動管理ルールを有効にするか。デフォルトは ``False`` です。" #: ../../../discord/guild.py:docstring of discord.guild.Guild.create_automod_rule:20 msgid "A list of roles that will be exempt from the automod rule." @@ -13127,19 +14061,52 @@ msgstr "自動管理ルールの作成に失敗した場合。" msgid "The automod rule that was created." msgstr "作成された自動管理ルール。" -#: ../../api.rst:4137 +#: ../../../discord/guild.py:docstring of discord.Guild.invites_paused_until:1 +msgid "If invites are paused, returns when invites will get enabled in UTC, otherwise returns None." +msgstr "" + +#: ../../../discord/guild.py:docstring of discord.Guild.dms_paused_until:1 +msgid "If DMs are paused, returns when DMs will get enabled in UTC, otherwise returns None." +msgstr "" + +#: ../../../discord/guild.py:docstring of discord.guild.Guild.invites_paused:1 +msgid ":class:`bool`: Whether invites are paused in the guild." +msgstr "" + +#: ../../../discord/guild.py:docstring of discord.guild.Guild.dms_paused:1 +msgid ":class:`bool`: Whether DMs are paused in the guild." +msgstr "" + +#: ../../api.rst:4481 msgid "A namedtuple which represents a ban returned from :meth:`~Guild.bans`." msgstr ":meth:`~Guild.bans` から返されたBANを表すnamedtuple。" -#: ../../api.rst:4141 +#: ../../api.rst:4485 msgid "The reason this user was banned." msgstr "ユーザーがBANされた理由。" -#: ../../api.rst:4146 +#: ../../api.rst:4490 msgid "The :class:`User` that was banned." msgstr "BANされた :class:`User` 。" -#: ../../api.rst:4152 +#: ../../api.rst:4496 +msgid "A namedtuple which represents the result returned from :meth:`~Guild.bulk_ban`." +msgstr "" + +#: ../../api.rst:4502 +msgid "The list of users that were banned. The inner :class:`Object` of the list has the :attr:`Object.type` set to :class:`User`." +msgstr "" + +#: ../../api.rst:4505 +#: ../../api.rst:4511 +msgid "List[:class:`Object`]" +msgstr "" + +#: ../../api.rst:4508 +msgid "The list of users that could not be banned. The inner :class:`Object` of the list has the :attr:`Object.type` set to :class:`User`." +msgstr "" + +#: ../../api.rst:4515 msgid "ScheduledEvent" msgstr "ScheduledEvent" @@ -13179,11 +14146,6 @@ msgstr "スケジュールイベントのUTCの開始予定時間。" msgid "The time that the scheduled event will end in UTC." msgstr "スケジュールイベントのUTCの終了予定時間。" -#: ../../../discord/scheduled_event.py:docstring of discord.scheduled_event.ScheduledEvent:63 -#: ../../../discord/scheduled_event.py:docstring of discord.scheduled_event.ScheduledEvent.edit:30 -msgid "The privacy level of the scheduled event." -msgstr "スケジュールイベントのプライバシーレベル。" - #: ../../../discord/scheduled_event.py:docstring of discord.scheduled_event.ScheduledEvent:75 msgid "The number of users subscribed to the scheduled event." msgstr "スケジュールイベントに購読しているユーザー数。" @@ -13204,11 +14166,6 @@ msgstr "スケジュールイベントの属するギルド。" msgid "The channel this scheduled event is in." msgstr "スケジュールイベントの属するチャンネル。" -#: ../../../discord/scheduled_event.py:docstring of discord.ScheduledEvent.channel:3 -#: ../../../discord/member.py:docstring of discord.member.VoiceState:74 -msgid "Optional[Union[:class:`VoiceChannel`, :class:`StageChannel`]]" -msgstr "Optional[Union[:class:`VoiceChannel`, :class:`StageChannel`]]" - #: ../../../discord/scheduled_event.py:docstring of discord.ScheduledEvent.url:1 msgid "The url for the scheduled event." msgstr "スケジュールイベントのURL。" @@ -13402,7 +14359,7 @@ msgstr "このイベントに購読済みのユーザー。" msgid "List[:class:`User`]" msgstr "List[:class:`User`]" -#: ../../api.rst:4161 +#: ../../api.rst:4524 msgid "Integration" msgstr "Integration" @@ -13544,7 +14501,7 @@ msgstr "この連携サービスで絵文字を同期するか(現在Twitch専 #: ../../../discord/invite.py:docstring of discord.invite.Invite:71 #: ../../../discord/invite.py:docstring of discord.invite.Invite:84 #: ../../../discord/template.py:docstring of discord.template.Template:60 -#: ../../../discord/widget.py:docstring of discord.widget.WidgetMember:73 +#: ../../../discord/widget.py:docstring of discord.widget.WidgetMember:81 msgid "Optional[:class:`bool`]" msgstr "Optional[:class:`bool`]" @@ -13616,7 +14573,7 @@ msgstr "不完全なギルドの連携サービス。" msgid "The id of the application this integration belongs to." msgstr "このインテグレーションが属するアプリケーションのID。" -#: ../../api.rst:4194 +#: ../../api.rst:4557 msgid "Member" msgstr "Member" @@ -13641,8 +14598,8 @@ msgid "Returns the member's hash." msgstr "メンバーのハッシュ値を返します。" #: ../../../discord/member.py:docstring of discord.member.Member:23 -msgid "Returns the member's name with the discriminator." -msgstr "メンバー名とそのDiscordタグを返します。" +msgid "Returns the member's handle (e.g. ``name`` or ``name#discriminator``)." +msgstr "メンバーのハンドル(例えば ``name`` や ``name#discriminator`` など)を返します。" #: ../../../discord/member.py:docstring of discord.member.Member:27 msgid "An aware datetime object that specifies the date and time in UTC that the member joined the guild. If the member left and rejoined the guild, this will be the latest date. In certain cases, this can be ``None``." @@ -13665,8 +14622,8 @@ msgid "The guild that the member belongs to." msgstr "メンバーが属するギルド。" #: ../../../discord/member.py:docstring of discord.member.Member:52 -msgid "The guild specific nickname of the user." -msgstr "ユーザーのギルド固有のニックネーム。" +msgid "The guild specific nickname of the user. Takes precedence over the global name." +msgstr "ユーザーのギルド内専用のニックネーム。グローバルの表示名よりも優先されます。" #: ../../../discord/member.py:docstring of discord.member.Member:58 msgid "Whether the member is pending member verification." @@ -13692,6 +14649,10 @@ msgstr ":attr:`User.id` と同じです。" msgid "Equivalent to :attr:`User.discriminator`" msgstr ":attr:`User.discriminator` と同じです。" +#: ../../../discord/member.py:docstring of discord.Member.global_name:1 +msgid "Equivalent to :attr:`User.global_name`" +msgstr ":attr:`User.global_name` と同じです。" + #: ../../../discord/member.py:docstring of discord.Member.bot:1 msgid "Equivalent to :attr:`User.bot`" msgstr ":attr:`User.bot` と同じです。" @@ -13736,6 +14697,14 @@ msgstr ":attr:`User.accent_color` と同じです。" msgid "Equivalent to :attr:`User.accent_colour`" msgstr ":attr:`User.accent_colour` と同じです。" +#: ../../../discord/member.py:docstring of discord.Member.avatar_decoration:1 +msgid "Equivalent to :attr:`User.avatar_decoration`" +msgstr "" + +#: ../../../discord/member.py:docstring of discord.Member.avatar_decoration_sku_id:1 +msgid "Equivalent to :attr:`User.avatar_decoration_sku_id`" +msgstr "" + #: ../../../discord/member.py:docstring of discord.Member.raw_status:1 msgid "The member's overall status as a string value." msgstr "メンバーのステータスを文字列として返します。" @@ -13748,7 +14717,7 @@ msgstr "メンバーのステータス。値が不明なものである場合こ #: ../../../discord/member.py:docstring of discord.Member.mobile_status:3 #: ../../../discord/member.py:docstring of discord.Member.desktop_status:3 #: ../../../discord/member.py:docstring of discord.Member.web_status:3 -#: ../../../discord/widget.py:docstring of discord.widget.WidgetMember:49 +#: ../../../discord/widget.py:docstring of discord.widget.WidgetMember:57 msgid ":class:`Status`" msgstr ":class:`Status`" @@ -13821,7 +14790,7 @@ msgid "A user may have multiple activities, these can be accessed under :attr:`a msgstr "ユーザーは複数のアクティビティを行っていることがあります。これらは :attr:`activities` でアクセスできます。" #: ../../../discord/member.py:docstring of discord.Member.activity:14 -#: ../../../discord/widget.py:docstring of discord.widget.WidgetMember:67 +#: ../../../discord/widget.py:docstring of discord.widget.WidgetMember:75 msgid "Optional[Union[:class:`BaseActivity`, :class:`Spotify`]]" msgstr "Optional[Union[:class:`BaseActivity`, :class:`Spotify`]]" @@ -13954,6 +14923,7 @@ msgid "timed_out_until" msgstr "timed_out_until" #: ../../../discord/member.py:docstring of discord.member.Member.edit:20 +#: ../../../discord/member.py:docstring of discord.member.Member.edit:22 msgid ":attr:`Permissions.moderate_members`" msgstr ":attr:`Permissions.moderate_members`" @@ -13961,10 +14931,6 @@ msgstr ":attr:`Permissions.moderate_members`" msgid "bypass_verification" msgstr "bypass_verification" -#: ../../../discord/member.py:docstring of discord.member.Member.edit:22 -msgid ":attr:`Permissions.manage_guild`" -msgstr ":attr:`Permissions.manage_guild`" - #: ../../../discord/member.py:docstring of discord.member.Member.edit:27 msgid "Can now pass ``None`` to ``voice_channel`` to kick a member from voice." msgstr "``None`` を ``voice_channel`` に渡してボイスからメンバーを追放できるようになりました。" @@ -14011,8 +14977,8 @@ msgstr "メンバーを編集する理由。監査ログに表示されます。 #: ../../../discord/member.py:docstring of discord.member.Member.edit:60 #: ../../../discord/member.py:docstring of discord.member.Member.request_to_speak:15 -msgid "You do not have the proper permissions to the action requested." -msgstr "リクエストされたアクションをするための適切な権限がない場合。" +msgid "You do not have the proper permissions to do the action requested." +msgstr "" #: ../../../discord/member.py:docstring of discord.member.Member.edit:61 #: ../../../discord/member.py:docstring of discord.member.Member.request_to_speak:16 @@ -14157,7 +15123,7 @@ msgstr "このメンバーがタイムアウトされているかどうかを返 msgid "``True`` if the member is timed out. ``False`` otherwise." msgstr "メンバーがタイムアウトした場合は ``True`` 。そうでなければ、 ``False`` 。" -#: ../../api.rst:4207 +#: ../../api.rst:4570 msgid "Spotify" msgstr "Spotify" @@ -14274,7 +15240,7 @@ msgstr ":class:`datetime.timedelta`" msgid "The party ID of the listening party." msgstr "リスニングパーティーのパーティーID。" -#: ../../api.rst:4215 +#: ../../api.rst:4578 msgid "VoiceState" msgstr "VoiceState" @@ -14326,7 +15292,7 @@ msgstr "ユーザーがギルドのAFKチャンネルにいるかどうかを示 msgid "The voice channel that the user is currently connected to. ``None`` if the user is not currently in a voice channel." msgstr "ユーザーが現在接続しているボイスチャンネル。ユーザーがボイスチャンネルに接続していない場合は ``None`` 。" -#: ../../api.rst:4223 +#: ../../api.rst:4586 msgid "Emoji" msgstr "Emoji" @@ -14446,7 +15412,7 @@ msgstr "絵文字の編集中にエラーが発生した場合。" msgid "The newly updated emoji." msgstr "新しく更新された絵文字。" -#: ../../api.rst:4232 +#: ../../api.rst:4595 msgid "PartialEmoji" msgstr "PartialEmoji" @@ -14544,7 +15510,7 @@ msgstr "これがカスタム絵文字でない場合は、空の文字列が返 msgid "The PartialEmoji is not a custom emoji." msgstr "PartialEmojiがカスタム絵文字でない場合。" -#: ../../api.rst:4241 +#: ../../api.rst:4604 msgid "Role" msgstr "Role" @@ -14700,6 +15666,14 @@ msgstr "ロールをメンションすることのできる文字列を返しま msgid "Returns all the members with this role." msgstr "このロールを持つすべてのメンバーを返します。" +#: ../../../discord/role.py:docstring of discord.Role.flags:1 +msgid "Returns the role's flags." +msgstr "" + +#: ../../../discord/role.py:docstring of discord.Role.flags:5 +msgid ":class:`RoleFlags`" +msgstr "" + #: ../../../discord/role.py:docstring of discord.role.Role.edit:3 msgid "Edits the role." msgstr "ロールを編集します。" @@ -14772,7 +15746,7 @@ msgstr "ロールを削除する権限がない場合。" msgid "Deleting the role failed." msgstr "ロールの削除に失敗した場合。" -#: ../../api.rst:4249 +#: ../../api.rst:4612 msgid "RoleTags" msgstr "RoleTags" @@ -14808,7 +15782,7 @@ msgstr ":class:`bool`: ロールが購入可能かどうか。" msgid ":class:`bool`: Whether the role is a guild's linked role." msgstr ":class:`bool`: ロールがギルドの関連付けられたロールかどうか。" -#: ../../api.rst:4257 +#: ../../api.rst:4620 msgid "PartialMessageable" msgstr "PartialMessageable" @@ -14821,7 +15795,7 @@ msgid "The only way to construct this class is through :meth:`Client.get_partial msgstr "これは :meth:`Client.get_partial_messageable` によってのみ作成できます。" #: ../../../discord/channel.py:docstring of discord.channel.PartialMessageable:6 -#: ../../../discord/message.py:docstring of discord.message.PartialMessage:12 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage:13 msgid "Note that this class is trimmed down and has no rich attributes." msgstr "これは機能が削られていてリッチな属性を持ちません。" @@ -14886,44 +15860,44 @@ msgstr "解決された権限。" #: ../../../discord/channel.py:docstring of discord.channel.PartialMessageable.get_partial_message:1 #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.get_partial_message:1 #: ../../../discord/threads.py:docstring of discord.threads.Thread.get_partial_message:1 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.get_partial_message:1 -#: ../../../discord/channel.py:docstring of discord.channel.DMChannel.get_partial_message:1 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.get_partial_message:1 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.get_partial_message:1 msgid "Creates a :class:`PartialMessage` from the message ID." msgstr "メッセージIDから :class:`PartialMessage` を作成します。" #: ../../../discord/channel.py:docstring of discord.channel.PartialMessageable.get_partial_message:3 #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.get_partial_message:3 #: ../../../discord/threads.py:docstring of discord.threads.Thread.get_partial_message:3 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.get_partial_message:3 -#: ../../../discord/channel.py:docstring of discord.channel.DMChannel.get_partial_message:3 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.get_partial_message:3 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.get_partial_message:3 msgid "This is useful if you want to work with a message and only have its ID without doing an unnecessary API call." msgstr "これは、IDしかない場合に不必要なAPI呼び出しなくメッセージに関する作業をする時に便利です。" #: ../../../discord/channel.py:docstring of discord.channel.PartialMessageable.get_partial_message:6 #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.get_partial_message:12 #: ../../../discord/threads.py:docstring of discord.threads.Thread.get_partial_message:8 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.get_partial_message:8 -#: ../../../discord/channel.py:docstring of discord.channel.DMChannel.get_partial_message:12 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.get_partial_message:8 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.get_partial_message:8 msgid "The message ID to create a partial message for." msgstr "部分的なメッセージのID。" #: ../../../discord/channel.py:docstring of discord.channel.PartialMessageable.get_partial_message:9 #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.get_partial_message:15 #: ../../../discord/threads.py:docstring of discord.threads.Thread.get_partial_message:11 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.get_partial_message:11 -#: ../../../discord/channel.py:docstring of discord.channel.DMChannel.get_partial_message:15 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.get_partial_message:11 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.get_partial_message:11 msgid "The partial message." msgstr "部分的なメッセージ。" #: ../../../discord/channel.py:docstring of discord.channel.PartialMessageable.get_partial_message:10 #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.get_partial_message:16 #: ../../../discord/threads.py:docstring of discord.threads.Thread.get_partial_message:12 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.get_partial_message:12 -#: ../../../discord/channel.py:docstring of discord.channel.DMChannel.get_partial_message:16 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.get_partial_message:12 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.get_partial_message:12 msgid ":class:`PartialMessage`" msgstr ":class:`PartialMessage`" -#: ../../api.rst:4266 +#: ../../api.rst:4629 msgid "TextChannel" msgstr "TextChannel" @@ -14979,12 +15953,13 @@ msgstr "チャンネルのトピック。存在しない場合は ``None`` に #: ../../../discord/channel.py:docstring of discord.channel.TextChannel:60 #: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel:94 +#: ../../../discord/channel.py:docstring of discord.channel.StageChannel:97 msgid "The last message ID of the message sent to this channel. It may *not* point to an existing or valid message." msgstr "このチャンネルに送信された最後のメッセージのID。既存または有効なメッセージを指して *いない* 場合があります。" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel:67 #: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel:103 -#: ../../../discord/channel.py:docstring of discord.channel.StageChannel:97 +#: ../../../discord/channel.py:docstring of discord.channel.StageChannel:106 msgid "The number of seconds a member must wait between sending messages in this channel. A value of ``0`` denotes that it is disabled. Bots and users with :attr:`~Permissions.manage_channels` or :attr:`~Permissions.manage_messages` bypass slowmode." msgstr "このチャンネルにメッセージを送信する間にメンバーが待たなければいけない時間。値が ``0`` の場合これは無効です。ボットやユーザーが :attr:`~Permissions.manage_channels` か :attr:`~Permissions.manage_messages` 権限を有する場合低速モードを回避できます。" @@ -14998,6 +15973,10 @@ msgstr "チャンネルに年齢制限がかかっているか。" msgid "The default auto archive duration in minutes for threads created in this channel." msgstr "このチャンネルで作成されたスレッドのデフォルトの分単位の自動アーカイブ期間。" +#: ../../../discord/channel.py:docstring of discord.channel.TextChannel:90 +msgid "The default slowmode delay in seconds for threads created in this channel." +msgstr "このチャンネルで作成されたスレッドの、デフォルトの秒単位の低速モードレート制限。" + #: ../../../discord/channel.py:docstring of discord.TextChannel.type:1 #: ../../../discord/channel.py:docstring of discord.ForumChannel.type:1 #: ../../../discord/threads.py:docstring of discord.Thread.type:1 @@ -15035,30 +16014,35 @@ msgstr ":class:`bool`: チャンネルがニュースチャンネルかをチェ #: ../../../discord/channel.py:docstring of discord.TextChannel.last_message:1 #: ../../../discord/channel.py:docstring of discord.VoiceChannel.last_message:1 +#: ../../../discord/channel.py:docstring of discord.StageChannel.last_message:1 msgid "Retrieves the last message from this channel in cache." msgstr "キャッシュからこのチャンネルに最後に送信されたメッセージを取得します。" #: ../../../discord/channel.py:docstring of discord.TextChannel.last_message:3 #: ../../../discord/threads.py:docstring of discord.Thread.last_message:3 #: ../../../discord/channel.py:docstring of discord.VoiceChannel.last_message:3 +#: ../../../discord/channel.py:docstring of discord.StageChannel.last_message:3 msgid "The message might not be valid or point to an existing message." msgstr "このメッセージは有効でなかったり、存在するものでない場合があります。" #: ../../../discord/channel.py:docstring of discord.TextChannel.last_message:5 #: ../../../discord/threads.py:docstring of discord.Thread.last_message:5 #: ../../../discord/channel.py:docstring of discord.VoiceChannel.last_message:7 +#: ../../../discord/channel.py:docstring of discord.StageChannel.last_message:7 msgid "Reliable Fetching" msgstr "信頼性の高い取得方法" #: ../../../discord/channel.py:docstring of discord.TextChannel.last_message:8 #: ../../../discord/threads.py:docstring of discord.Thread.last_message:8 #: ../../../discord/channel.py:docstring of discord.VoiceChannel.last_message:10 +#: ../../../discord/channel.py:docstring of discord.StageChannel.last_message:10 msgid "For a slightly more reliable method of fetching the last message, consider using either :meth:`history` or :meth:`fetch_message` with the :attr:`last_message_id` attribute." msgstr "より信頼性のある最後のメッセージの取得方法として、 :meth:`history` や :attr:`last_message_id` と :meth:`fetch_message` の組み合わせがあります。" #: ../../../discord/channel.py:docstring of discord.TextChannel.last_message:13 #: ../../../discord/threads.py:docstring of discord.Thread.last_message:13 #: ../../../discord/channel.py:docstring of discord.VoiceChannel.last_message:15 +#: ../../../discord/channel.py:docstring of discord.StageChannel.last_message:15 msgid "The last message in this channel or ``None`` if not found." msgstr "このチャンネルの最後のメッセージ。見つからない場合は ``None`` です。" @@ -15066,7 +16050,7 @@ msgstr "このチャンネルの最後のメッセージ。見つからない場 #: ../../../discord/threads.py:docstring of discord.Thread.starter_message:8 #: ../../../discord/threads.py:docstring of discord.Thread.last_message:14 #: ../../../discord/channel.py:docstring of discord.VoiceChannel.last_message:16 -#: ../../../discord/raw_models.py:docstring of discord.raw_models.RawMessageDeleteEvent:25 +#: ../../../discord/channel.py:docstring of discord.StageChannel.last_message:16 msgid "Optional[:class:`Message`]" msgstr "Optional[:class:`Message`]" @@ -15114,19 +16098,19 @@ msgstr "チャンネルの位置。" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.edit:28 #: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.edit:30 -#: ../../../discord/channel.py:docstring of discord.channel.StageChannel.edit:26 +#: ../../../discord/channel.py:docstring of discord.channel.StageChannel.edit:28 msgid "Whether to sync permissions with the channel's new or pre-existing category. Defaults to ``False``." msgstr "チャンネルの新しい、又は既存のカテゴリと権限を同期するか。デフォルトは ``False`` です。" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.edit:31 #: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.edit:33 -#: ../../../discord/channel.py:docstring of discord.channel.StageChannel.edit:29 +#: ../../../discord/channel.py:docstring of discord.channel.StageChannel.edit:31 msgid "The new category for this channel. Can be ``None`` to remove the category." msgstr "チャンネルの新しいカテゴリ。カテゴリを削除するには ``None`` を指定できます。" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.edit:34 #: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.edit:36 -#: ../../../discord/channel.py:docstring of discord.channel.StageChannel.edit:32 +#: ../../../discord/channel.py:docstring of discord.channel.StageChannel.edit:34 msgid "Specifies the slowmode rate limit for user in this channel, in seconds. A value of ``0`` disables slowmode. The maximum value possible is ``21600``." msgstr "秒単位でこのチャンネルのユーザーの低速モードの値を指定します。 ``0`` を渡すと低速モードを無効にできます。可能な最大の値は ``21600`` です。" @@ -15136,13 +16120,13 @@ msgstr "このテキストチャンネルのタイプを変更します。現時 #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.edit:41 #: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.edit:39 -#: ../../../discord/channel.py:docstring of discord.channel.StageChannel.edit:35 +#: ../../../discord/channel.py:docstring of discord.channel.StageChannel.edit:37 msgid "The reason for editing this channel. Shows up on the audit log." msgstr "チャンネルを編集する理由。監査ログに表示されます。" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.edit:43 #: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.edit:41 -#: ../../../discord/channel.py:docstring of discord.channel.StageChannel.edit:37 +#: ../../../discord/channel.py:docstring of discord.channel.StageChannel.edit:39 #: ../../../discord/channel.py:docstring of discord.channel.CategoryChannel.edit:25 msgid "A :class:`Mapping` of target (either a role or a member) to :class:`PermissionOverwrite` to apply to the channel." msgstr "ターゲット(ロール又はメンバー)をキーとし適用される :class:`PermissionOverwrite` を値とする :class:`Mapping` 。" @@ -15152,46 +16136,53 @@ msgstr "ターゲット(ロール又はメンバー)をキーとし適用さ msgid "The new default auto archive duration in minutes for threads created in this channel. Must be one of ``60``, ``1440``, ``4320``, or ``10080``." msgstr "このチャンネルで作成されたスレッドの分単位のデフォルトの自動アーカイブ期間。これは ``60`` 、 ``1440`` 、 ``4320`` 、または ``10080`` でないといけません。" -#: ../../../discord/channel.py:docstring of discord.channel.TextChannel.edit:52 -#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.edit:57 +#: ../../../discord/channel.py:docstring of discord.channel.TextChannel.edit:51 +msgid "The new default slowmode delay in seconds for threads created in this channel." +msgstr "このチャンネルで作成されたスレッドの、新しいデフォルトの秒単位の低速モードレート制限。" + +#: ../../../discord/channel.py:docstring of discord.channel.TextChannel.edit:56 +#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.edit:61 msgid "The new ``position`` is less than 0 or greater than the number of channels." msgstr "新しい ``position`` が0より小さいか、カテゴリの数より大きい場合。" -#: ../../../discord/channel.py:docstring of discord.channel.TextChannel.edit:54 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.edit:55 -#: ../../../discord/channel.py:docstring of discord.channel.StageChannel.edit:49 +#: ../../../discord/channel.py:docstring of discord.channel.TextChannel.edit:58 +#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.edit:60 +#: ../../../discord/channel.py:docstring of discord.channel.StageChannel.edit:51 msgid "You do not have permissions to edit the channel." msgstr "チャンネルを編集する権限がない場合。" -#: ../../../discord/channel.py:docstring of discord.channel.TextChannel.edit:55 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.edit:56 -#: ../../../discord/channel.py:docstring of discord.channel.StageChannel.edit:50 +#: ../../../discord/channel.py:docstring of discord.channel.TextChannel.edit:59 +#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.edit:61 +#: ../../../discord/channel.py:docstring of discord.channel.StageChannel.edit:52 msgid "Editing the channel failed." msgstr "チャンネルの編集に失敗した場合。" -#: ../../../discord/channel.py:docstring of discord.channel.TextChannel.edit:57 +#: ../../../discord/channel.py:docstring of discord.channel.TextChannel.edit:61 msgid "The newly edited text channel. If the edit was only positional then ``None`` is returned instead." msgstr "新しく編集されたテキストチャンネル。編集が位置のみだった場合は代わりに ``None`` が返されます。" -#: ../../../discord/channel.py:docstring of discord.channel.TextChannel.edit:59 +#: ../../../discord/channel.py:docstring of discord.channel.TextChannel.edit:63 msgid "Optional[:class:`.TextChannel`]" msgstr "Optional[:class:`.TextChannel`]" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.delete_messages:3 #: ../../../discord/threads.py:docstring of discord.threads.Thread.delete_messages:3 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.delete_messages:3 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.delete_messages:3 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.delete_messages:3 msgid "Deletes a list of messages. This is similar to :meth:`Message.delete` except it bulk deletes multiple messages." msgstr "メッセージのリストを削除します。複数のメッセージを一括削除する点を除き、これは :meth:`Message.delete` に似ています。" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.delete_messages:6 #: ../../../discord/threads.py:docstring of discord.threads.Thread.delete_messages:6 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.delete_messages:6 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.delete_messages:6 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.delete_messages:6 msgid "As a special case, if the number of messages is 0, then nothing is done. If the number of messages is 1 then single message delete is done. If it's more than two, then bulk delete is used." msgstr "スペシャルケースとして、もしメッセージ数が0の場合は何もせず、メッセージ数が1の場合は単独のメッセージ削除が行われます。これが2以上の場合一括削除が行われます。" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.delete_messages:10 #: ../../../discord/threads.py:docstring of discord.threads.Thread.delete_messages:10 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.delete_messages:10 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.delete_messages:10 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.delete_messages:10 msgid "You cannot bulk delete more than 100 messages or messages that are older than 14 days old." msgstr "14日以上前のメッセージや100件以上のメッセージを一括削除することはできません。" @@ -15206,144 +16197,168 @@ msgstr "``reason`` キーワード引数が追加されました。" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.delete_messages:21 #: ../../../discord/threads.py:docstring of discord.threads.Thread.delete_messages:15 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.delete_messages:17 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.delete_messages:17 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.delete_messages:17 msgid "An iterable of messages denoting which ones to bulk delete." msgstr "一括削除するものを示すメッセージのiterableオブジェクト。" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.delete_messages:23 #: ../../../discord/threads.py:docstring of discord.threads.Thread.delete_messages:17 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.delete_messages:19 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.delete_messages:19 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.delete_messages:19 msgid "The reason for deleting the messages. Shows up on the audit log." msgstr "メッセージを一括削除する理由。監査ログに表示されます。" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.delete_messages:26 #: ../../../discord/threads.py:docstring of discord.threads.Thread.delete_messages:20 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.delete_messages:22 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.delete_messages:22 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.delete_messages:22 msgid "The number of messages to delete was more than 100." msgstr "削除するメッセージの数が100以上の場合。" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.delete_messages:27 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.delete_messages:23 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.delete_messages:23 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.delete_messages:23 msgid "You do not have proper permissions to delete the messages." msgstr "メッセージを一括削除するための適切な権限がない場合。" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.delete_messages:28 #: ../../../discord/threads.py:docstring of discord.threads.Thread.delete_messages:22 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.delete_messages:24 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.delete_messages:24 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.delete_messages:24 msgid "If single delete, then the message was already deleted." msgstr "1メッセージのみ削除する時、メッセージが既に削除されていた場合。" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.delete_messages:29 #: ../../../discord/threads.py:docstring of discord.threads.Thread.delete_messages:23 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.delete_messages:25 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.delete_messages:25 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.delete_messages:25 msgid "Deleting the messages failed." msgstr "メッセージの削除に失敗した場合。" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.purge:3 #: ../../../discord/threads.py:docstring of discord.threads.Thread.purge:3 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.purge:3 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.purge:3 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.purge:3 msgid "Purges a list of messages that meet the criteria given by the predicate ``check``. If a ``check`` is not provided then all messages are deleted without discrimination." msgstr "``check`` 関数の要件を満たすメッセージを一括削除します。 ``check`` が渡されない場合はすべてのメッセージが削除されます。" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.purge:7 #: ../../../discord/threads.py:docstring of discord.threads.Thread.purge:7 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.purge:7 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.purge:7 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.purge:7 msgid "You must have :attr:`~Permissions.manage_messages` to delete messages even if they are your own. Having :attr:`~Permissions.read_message_history` is also needed to retrieve message history." msgstr "メッセージを削除するときに、それが自分のものであったとしても、削除するには :attr:`~Permissions.manage_messages` が必要です。メッセージの履歴を取得するために :attr:`~Permissions.read_message_history` も必要になります。" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.purge:18 #: ../../../discord/threads.py:docstring of discord.threads.Thread.purge:14 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.purge:16 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.purge:16 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.purge:16 msgid "Deleting bot's messages ::" msgstr "Botによるメッセージを削除する ::" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.purge:26 #: ../../../discord/threads.py:docstring of discord.threads.Thread.purge:22 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.purge:24 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.purge:24 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.purge:24 msgid "The number of messages to search through. This is not the number of messages that will be deleted, though it can be." msgstr "検索するメッセージ数。これは削除するメッセージ数になるとは必ずしも限りません。" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.purge:29 #: ../../../discord/threads.py:docstring of discord.threads.Thread.purge:25 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.purge:27 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.purge:27 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.purge:27 msgid "The function used to check if a message should be deleted. It must take a :class:`Message` as its sole parameter." msgstr "メッセージが削除されるかどうかを確認するために使用される関数。 :class:`Message` を唯一のパラメータとして受け取る必要があります。" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.purge:32 #: ../../../discord/threads.py:docstring of discord.threads.Thread.purge:28 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.purge:30 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.purge:30 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.purge:30 msgid "Same as ``before`` in :meth:`history`." msgstr ":meth:`history` での ``before`` と同じです。" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.purge:34 #: ../../../discord/threads.py:docstring of discord.threads.Thread.purge:30 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.purge:32 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.purge:32 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.purge:32 msgid "Same as ``after`` in :meth:`history`." msgstr ":meth:`history` での ``after`` と同じです。" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.purge:36 #: ../../../discord/threads.py:docstring of discord.threads.Thread.purge:32 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.purge:34 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.purge:34 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.purge:34 msgid "Same as ``around`` in :meth:`history`." msgstr ":meth:`history` での ``around`` と同じです。" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.purge:38 #: ../../../discord/threads.py:docstring of discord.threads.Thread.purge:34 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.purge:36 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.purge:36 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.purge:36 msgid "Same as ``oldest_first`` in :meth:`history`." msgstr ":meth:`history` での ``oldest_first`` と同じです。" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.purge:40 #: ../../../discord/threads.py:docstring of discord.threads.Thread.purge:36 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.purge:38 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.purge:38 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.purge:38 msgid "If ``True``, use bulk delete. Setting this to ``False`` is useful for mass-deleting a bot's own messages without :attr:`Permissions.manage_messages`. When ``True``, will fall back to single delete if messages are older than two weeks." msgstr "``True`` の場合、一括削除を使用します。これを ``False`` に設定すると、 :attr:`Permissions.manage_messages` なしでボット自身のメッセージを一括削除することができます。 ``True`` の場合でも、2週間以上前のメッセージは個別に削除されます。" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.purge:44 #: ../../../discord/threads.py:docstring of discord.threads.Thread.purge:40 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.purge:42 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.purge:42 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.purge:42 msgid "The reason for purging the messages. Shows up on the audit log." msgstr "メッセージを一括削除する理由。監査ログに表示されます。" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.purge:47 #: ../../../discord/threads.py:docstring of discord.threads.Thread.purge:43 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.purge:45 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.purge:45 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.purge:45 msgid "You do not have proper permissions to do the actions required." msgstr "必要なアクションをするための適切な権限がない場合。" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.purge:48 #: ../../../discord/threads.py:docstring of discord.threads.Thread.purge:44 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.purge:46 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.purge:46 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.purge:46 msgid "Purging the messages failed." msgstr "メッセージの一括削除に失敗した場合。" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.purge:50 #: ../../../discord/threads.py:docstring of discord.threads.Thread.purge:46 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.purge:48 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.purge:48 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.purge:48 msgid "The list of messages that were deleted." msgstr "削除されたメッセージのlist。" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.purge:51 #: ../../../discord/threads.py:docstring of discord.threads.Thread.purge:47 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.purge:49 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.purge:49 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.purge:49 msgid "List[:class:`.Message`]" msgstr "List[:class:`.Message`]" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.webhooks:3 #: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.webhooks:3 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.webhooks:3 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.webhooks:3 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.webhooks:3 msgid "Gets the list of webhooks from this channel." msgstr "チャンネル内のWebhookのリストを取得します。" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.webhooks:9 #: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.webhooks:9 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.webhooks:11 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.webhooks:11 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.webhooks:11 msgid "The webhooks for this channel." msgstr "チャンネル内のWebhook。" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.create_webhook:3 #: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.create_webhook:3 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.create_webhook:3 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.create_webhook:3 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.create_webhook:3 msgid "Creates a webhook for this channel." msgstr "チャンネルに新しいWebhookを作ります。" @@ -15353,38 +16368,44 @@ msgstr "``reason`` キーワード引数が追加されました。" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.create_webhook:10 #: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.create_webhook:7 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.create_webhook:9 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.create_webhook:9 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.create_webhook:9 msgid "The webhook's name." msgstr "Webhookの名前。" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.create_webhook:12 #: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.create_webhook:9 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.create_webhook:11 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.create_webhook:11 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.create_webhook:11 msgid "A :term:`py:bytes-like object` representing the webhook's default avatar. This operates similarly to :meth:`~ClientUser.edit`." msgstr "Webhookのデフォルトアバターを表す :term:`py:bytes-like object` 。 :meth:`~ClientUser.edit` と同様に動作します。" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.create_webhook:15 #: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.create_webhook:12 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.create_webhook:14 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.create_webhook:14 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.create_webhook:14 msgid "The reason for creating this webhook. Shows up in the audit logs." msgstr "Webhookを作成する理由。監査ログに表示されます。" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.create_webhook:18 #: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.create_webhook:15 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.create_webhook:17 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.create_webhook:17 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.create_webhook:17 msgid "Creating the webhook failed." msgstr "Webhookの作成に失敗した場合。" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.create_webhook:19 #: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.create_webhook:16 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.create_webhook:18 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.create_webhook:18 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.create_webhook:18 msgid "You do not have permissions to create a webhook." msgstr "Webhookを作成する権限を持っていない場合。" #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.create_webhook:21 #: ../../../discord/channel.py:docstring of discord.channel.TextChannel.follow:30 #: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.create_webhook:18 -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.create_webhook:20 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.create_webhook:20 +#: ../../../discord/channel.py:docstring of discord.channel.VocalGuildChannel.create_webhook:20 msgid "The created webhook." msgstr "作成されたwebhook。" @@ -15441,24 +16462,24 @@ msgstr "パブリックスレッドを作成するには、 :attr:`~discord.Perm msgid "A snowflake representing the message to create the thread with. If ``None`` is passed then a private thread is created. Defaults to ``None``." msgstr "スレッドを開始するメッセージを表すスノーフレーク。``None`` が渡された場合、プライベートスレッドが作成されます。デフォルトは ``None`` です。" -#: ../../../discord/channel.py:docstring of discord.channel.TextChannel.create_thread:19 +#: ../../../discord/channel.py:docstring of discord.channel.TextChannel.create_thread:21 msgid "The type of thread to create. If a ``message`` is passed then this parameter is ignored, as a thread created with a message is always a public thread. By default this creates a private thread if this is ``None``." msgstr "作成するスレッドの種類。 ``message`` が渡された場合、メッセージで作成されたスレッドは常に公開スレッドであるためこのパラメータは無視されます。 デフォルトでは、これが ``None`` の場合、プライベートスレッドが作成されます。" -#: ../../../discord/channel.py:docstring of discord.channel.TextChannel.create_thread:25 +#: ../../../discord/channel.py:docstring of discord.channel.TextChannel.create_thread:27 msgid "Whether non-moderators can add users to the thread. Only applicable to private threads. Defaults to ``True``." msgstr "モデレータ以外がスレッドにユーザーを追加できるかどうか。プライベートスレッドにのみ適用されます。デフォルトは ``True`` です。" -#: ../../../discord/channel.py:docstring of discord.channel.TextChannel.create_thread:34 -#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.create_thread:53 +#: ../../../discord/channel.py:docstring of discord.channel.TextChannel.create_thread:36 +#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.create_thread:55 msgid "Starting the thread failed." msgstr "スレッドの作成に失敗した場合。" -#: ../../../discord/channel.py:docstring of discord.channel.TextChannel.create_thread:36 +#: ../../../discord/channel.py:docstring of discord.channel.TextChannel.create_thread:38 msgid "The created thread" msgstr "作成されたスレッド。" -#: ../../../discord/channel.py:docstring of discord.channel.TextChannel.create_thread:37 +#: ../../../discord/channel.py:docstring of discord.channel.TextChannel.create_thread:39 #: ../../../discord/threads.py:docstring of discord.threads.Thread.edit:40 #: ../../../discord/threads.py:docstring of discord.ThreadMember.thread:3 msgid ":class:`Thread`" @@ -15509,7 +16530,7 @@ msgstr "``joined`` が ``True`` に設定され、``private`` が ``False`` に msgid ":class:`Thread` -- The archived threads." msgstr ":class:`Thread` -- アーカイブされたスレッド。" -#: ../../api.rst:4279 +#: ../../api.rst:4642 msgid "ForumChannel" msgstr "ForumChannel" @@ -15569,42 +16590,26 @@ msgstr "フォーラムに年齢制限がかかっているか。" msgid "The default auto archive duration in minutes for threads created in this forum." msgstr "このフォーラムで作成されたスレッドのデフォルトの分単位の自動アーカイブ期間。" -#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel:100 -msgid "The default reaction emoji for threads created in this forum to show in the add reaction button." -msgstr "このフォーラムで作成されたスレッドで、デフォルトでリアクション追加ボタンに表示するリアクション絵文字。" - -#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel:105 -#: ../../../discord/activity.py:docstring of discord.activity.Activity:92 -#: ../../../discord/activity.py:docstring of discord.activity.CustomActivity:33 -#: ../../../discord/channel.py:docstring of discord.channel.ForumTag:48 -msgid "Optional[:class:`PartialEmoji`]" -msgstr "Optional[:class:`PartialEmoji`]" - #: ../../../discord/channel.py:docstring of discord.channel.ForumChannel:109 msgid "The default layout for posts in this forum channel. Defaults to :attr:`ForumLayoutType.not_set`." -msgstr "このフォーラムチャネルの投稿のデフォルトの配列。デフォルトは :attr:`ForumLayoutType.not_set` です。" +msgstr "このフォーラムチャネルの投稿のデフォルトの表示レイアウト。デフォルトは :attr:`ForumLayoutType.not_set` です。" #: ../../../discord/channel.py:docstring of discord.channel.ForumChannel:114 msgid ":class:`ForumLayoutType`" msgstr ":class:`ForumLayoutType`" +#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel:122 +msgid "Optional[:class:`ForumOrderType`]" +msgstr "Optional[:class:`ForumOrderType`]" + #: ../../../discord/channel.py:docstring of discord.ForumChannel.flags:1 #: ../../../discord/threads.py:docstring of discord.Thread.flags:1 msgid "The flags associated with this thread." msgstr "このスレッドに関連付けられたフラグ。" -#: ../../../discord/channel.py:docstring of discord.ForumChannel.flags:5 -#: ../../../discord/threads.py:docstring of discord.Thread.flags:3 -msgid ":class:`ChannelFlags`" -msgstr ":class:`ChannelFlags`" - -#: ../../../discord/channel.py:docstring of discord.ForumChannel.available_tags:1 -msgid "Returns all the available tags for this forum." -msgstr "このフォーラムで利用可能なタグをすべて返します。" - -#: ../../../discord/channel.py:docstring of discord.ForumChannel.available_tags:5 -msgid "Sequence[:class:`ForumTag`]" -msgstr "Sequence[:class:`ForumTag`]" +#: ../../../discord/channel.py:docstring of discord.ForumChannel.available_tags:1 +msgid "Returns all the available tags for this forum." +msgstr "このフォーラムで利用可能なタグをすべて返します。" #: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.get_tag:1 msgid "Returns the tag with the given ID." @@ -15622,6 +16627,10 @@ msgstr "Optional[:class:`ForumTag`]" msgid ":class:`bool`: Checks if the forum is NSFW." msgstr ":class:`bool`: フォーラムに年齢制限があるかどうかをチェックします。" +#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.is_media:1 +msgid ":class:`bool`: Checks if the channel is a media channel." +msgstr "" + #: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.edit:3 msgid "Edits the forum." msgstr "フォーラムを編集します。" @@ -15680,29 +16689,33 @@ msgstr "新しいチャンネル内のスレッドのデフォルトのリアク #: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.edit:48 msgid "The new default layout for posts in this forum." -msgstr "このフォーラムの新しい投稿のデフォルトの配列。" +msgstr "このフォーラムの新しい投稿のデフォルトの表示レイアウト。" #: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.edit:52 +msgid "The new default sort order for posts in this forum." +msgstr "このフォーラムの新しい投稿のデフォルトの並び替え順。" + +#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.edit:56 msgid "Whether to require a tag for threads in this channel or not." msgstr "チャンネル内のスレッドにタグが必要であるかどうか。" -#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.edit:58 +#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.edit:62 msgid "The permission overwrite information is not in proper form or a type is not the expected type." msgstr "権限上書きが適切でないか、または期待された型でない場合。" -#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.edit:59 +#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.edit:63 msgid "You do not have permissions to edit the forum." msgstr "フォーラムを編集する権限がない場合。" -#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.edit:60 +#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.edit:64 msgid "Editing the forum failed." msgstr "フォーラムの編集に失敗した場合。" -#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.edit:62 +#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.edit:66 msgid "The newly edited forum channel. If the edit was only positional then ``None`` is returned instead." msgstr "新しく編集されたフォーラムチャンネル。編集が位置のみだった場合は代わりに ``None`` が返されます。" -#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.edit:64 +#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.edit:68 msgid "Optional[:class:`.ForumChannel`]" msgstr "Optional[:class:`.ForumChannel`]" @@ -15755,23 +16768,23 @@ msgstr "このスレッドは、最初のメッセージが与えられたパブ msgid "You must send at least one of ``content``, ``embed``, ``embeds``, ``file``, ``files``, or ``view`` to create a thread in a forum, since forum channels must have a starter message." msgstr "フォーラムチャンネルには最初ののメッセージが必要であるため、 ``content`` 、 ``embed`` 、 ``embed`` 、 ``file`` 、 ``files`` 、 ``files`` 、 ``view`` のうち少なくとも1つを送信する必要があります。" -#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.create_thread:20 +#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.create_thread:22 msgid "The content of the message to send with the thread." msgstr "スレッドで送信するメッセージの内容。" -#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.create_thread:41 +#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.create_thread:43 msgid "A list of tags to apply to the thread." msgstr "スレッドに適用するタグのリスト。" -#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.create_thread:55 +#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.create_thread:57 msgid "You specified both ``file`` and ``files``, or you specified both ``embed`` and ``embeds``." msgstr "``file`` と ``files`` の両方が指定された場合、または ``embed`` と ``embeds`` の両方が指定された場合。" -#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.create_thread:57 +#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.create_thread:59 msgid "The created thread with the created message. This is also accessible as a namedtuple with ``thread`` and ``message`` fields." msgstr "作成されたスレッドとメッセージ。これは、 ``thread`` と ``message`` フィールドを持つ名前付きタプルとしてもアクセスできます。" -#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.create_thread:59 +#: ../../../discord/channel.py:docstring of discord.channel.ForumChannel.create_thread:61 msgid "Tuple[:class:`Thread`, :class:`Message`]" msgstr "Tuple[:class:`Thread`, :class:`Message`]" @@ -15783,7 +16796,7 @@ msgstr "このフォーラムののアーカイブされたスレッドを :attr msgid "You must have :attr:`~Permissions.read_message_history` to do this." msgstr "これを行うためには、 :attr:`~Permissions.read_message_history` が必要です。" -#: ../../api.rst:4288 +#: ../../api.rst:4651 msgid "Thread" msgstr "Thread" @@ -15868,8 +16881,8 @@ msgid "The user's ID that archived this thread." msgstr "このスレッドをアーカイブしたユーザーのID。" #: ../../../discord/threads.py:docstring of discord.threads.Thread:115 -msgid "The duration in minutes until the thread is automatically archived due to inactivity. Usually a value of 60, 1440, 4320 and 10080." -msgstr "アクティブでないスレッドが自動的にアーカイブされるまでの分単位の期間です。通常値は60、1440、4320、または10080です。" +msgid "The duration in minutes until the thread is automatically hidden from the channel list. Usually a value of 60, 1440, 4320 and 10080." +msgstr "スレッドがチャンネルリストから自動的に非表示になるまでの分単位の期間。通常は60、1440、4320、10080のいずれかの値です。" #: ../../../discord/threads.py:docstring of discord.threads.Thread:122 msgid "An aware timestamp of when the thread's archived status was last updated in UTC." @@ -16038,8 +17051,8 @@ msgid "Whether non-moderators can add other non-moderators to this thread. Only msgstr "モデレータでないユーザーがこのスレッドに他のモデレータでないユーザーを追加できるかどうか。プライベートスレッドでのみ利用できます。" #: ../../../discord/threads.py:docstring of discord.threads.Thread.edit:23 -msgid "The new duration in minutes before a thread is automatically archived for inactivity. Must be one of ``60``, ``1440``, ``4320``, or ``10080``." -msgstr "スレッドがアクティブでないために自動的にアーカイブされるまでの分単位の新しい期間。``60``、``1440``、``4320``、または ``10080`` のいずれかでなければなりません。" +msgid "The new duration in minutes before a thread is automatically hidden from the channel list. Must be one of ``60``, ``1440``, ``4320``, or ``10080``." +msgstr "スレッドが自動的にチャンネルリストから非表示となるまでの分単位の新しい期間。``60``、``1440``、``4320``、または ``10080`` のいずれかでなければなりません。" #: ../../../discord/threads.py:docstring of discord.threads.Thread.edit:26 msgid "Specifies the slowmode rate limit for user in this thread, in seconds. A value of ``0`` disables slowmode. The maximum value possible is ``21600``." @@ -16231,7 +17244,7 @@ msgstr "スレッドを削除する権限がない場合。" msgid "Deleting the thread failed." msgstr "スレッドの削除に失敗した場合。" -#: ../../api.rst:4301 +#: ../../api.rst:4664 msgid "ThreadMember" msgstr "ThreadMember" @@ -16271,7 +17284,7 @@ msgstr "メンバーがスレッドに参加したUTC時刻。" msgid "The thread this member belongs to." msgstr "メンバーが属するスレッド。" -#: ../../api.rst:4309 +#: ../../api.rst:4672 msgid "VoiceChannel" msgstr "VoiceChannel" @@ -16298,6 +17311,7 @@ msgid "The new channel's bitrate." msgstr "チャンネルの新しいビットレート。" #: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.edit:26 +#: ../../../discord/channel.py:docstring of discord.channel.StageChannel.edit:26 msgid "The new channel's user limit." msgstr "チャンネルの新しいユーザー人数制限。" @@ -16305,16 +17319,20 @@ msgstr "チャンネルの新しいユーザー人数制限。" msgid "The new region for the voice channel's voice communication. A value of ``None`` indicates automatic voice region detection." msgstr "ボイスチャンネルの新しい音声通信のためのリージョン。値が ``None`` の場合は自動で検出されます。" -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.edit:54 -#: ../../../discord/channel.py:docstring of discord.channel.StageChannel.edit:48 +#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.edit:53 +msgid "The new voice channel status. It can be up to 500 characters. Can be ``None`` to remove the status." +msgstr "" + +#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.edit:59 +#: ../../../discord/channel.py:docstring of discord.channel.StageChannel.edit:50 msgid "If the permission overwrite information is not in proper form." msgstr "権限の上書きの情報が適切なものでない場合。" -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.edit:58 +#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.edit:63 msgid "The newly edited voice channel. If the edit was only positional then ``None`` is returned instead." msgstr "新しく編集されたボイスチャンネル。編集が位置のみだった場合は代わりに ``None`` が返されます。" -#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.edit:60 +#: ../../../discord/channel.py:docstring of discord.channel.VoiceChannel.edit:65 msgid "Optional[:class:`.VoiceChannel`]" msgstr "Optional[:class:`.VoiceChannel`]" @@ -16348,7 +17366,7 @@ msgstr "メンバーIDをキーとしボイス状態を値とするマッピン msgid "Mapping[:class:`int`, :class:`VoiceState`]" msgstr "Mapping[:class:`int`, :class:`VoiceState`]" -#: ../../api.rst:4318 +#: ../../api.rst:4681 msgid "StageChannel" msgstr "StageChannel" @@ -16369,7 +17387,7 @@ msgid "The region for the stage channel's voice communication. A value of ``None msgstr "ステージチャンネルの音声通信のためのリージョン。値が ``None`` の場合は自動で検出されます。" #: ../../../discord/channel.py:docstring of discord.channel.StageChannel:89 -#: ../../../discord/channel.py:docstring of discord.channel.StageChannel.edit:43 +#: ../../../discord/channel.py:docstring of discord.channel.StageChannel.edit:45 msgid "The camera video quality for the stage channel's participants." msgstr "ステージチャンネル参加者のカメラビデオの画質。" @@ -16406,27 +17424,35 @@ msgid "The stage instance's privacy level. Defaults to :attr:`PrivacyLevel.guild msgstr "ステージインスタンスのプライバシーレベル。デフォルトは :attr:`PrivacyLevel.guild_only` です。" #: ../../../discord/channel.py:docstring of discord.channel.StageChannel.create_instance:13 +msgid "Whether to send a start notification. This sends a push notification to @everyone if ``True``. Defaults to ``False``. You must have :attr:`~Permissions.mention_everyone` to do this." +msgstr "開始通知を送信するかどうか。 ``True`` の場合、プッシュ通知を@everyoneに送信します。 デフォルトは ``False`` です。これを行うには、 :attr:`~Permissions.mention_everyone` が必要です。" + +#: ../../../discord/channel.py:docstring of discord.channel.StageChannel.create_instance:18 +msgid "The guild scheduled event associated with the stage instance." +msgstr "" + +#: ../../../discord/channel.py:docstring of discord.channel.StageChannel.create_instance:22 msgid "The reason the stage instance was created. Shows up on the audit log." msgstr "ステージインスタンスを作成する理由。監査ログに表示されます。" -#: ../../../discord/channel.py:docstring of discord.channel.StageChannel.create_instance:16 +#: ../../../discord/channel.py:docstring of discord.channel.StageChannel.create_instance:25 #: ../../../discord/stage_instance.py:docstring of discord.stage_instance.StageInstance.edit:14 msgid "If the ``privacy_level`` parameter is not the proper type." msgstr "引数 ``privacy_level`` が適切な型でない場合。" -#: ../../../discord/channel.py:docstring of discord.channel.StageChannel.create_instance:17 +#: ../../../discord/channel.py:docstring of discord.channel.StageChannel.create_instance:26 msgid "You do not have permissions to create a stage instance." msgstr "ステージインスタンスを作成する権限がない場合。" -#: ../../../discord/channel.py:docstring of discord.channel.StageChannel.create_instance:18 +#: ../../../discord/channel.py:docstring of discord.channel.StageChannel.create_instance:27 msgid "Creating a stage instance failed." msgstr "ステージインスタンスの作成に失敗した場合。" -#: ../../../discord/channel.py:docstring of discord.channel.StageChannel.create_instance:20 +#: ../../../discord/channel.py:docstring of discord.channel.StageChannel.create_instance:29 msgid "The newly created stage instance." msgstr "新しく作成されたステージインスタンス。" -#: ../../../discord/channel.py:docstring of discord.channel.StageChannel.create_instance:21 +#: ../../../discord/channel.py:docstring of discord.channel.StageChannel.create_instance:30 #: ../../../discord/channel.py:docstring of discord.channel.StageChannel.fetch_instance:11 msgid ":class:`StageInstance`" msgstr ":class:`StageInstance`" @@ -16443,19 +17469,19 @@ msgstr "ステージインスタンス。" msgid "The ``topic`` parameter must now be set via :attr:`create_instance`." msgstr "``topic`` パラメーターは :attr:`create_instance` で設定する必要があります。" -#: ../../../discord/channel.py:docstring of discord.channel.StageChannel.edit:40 +#: ../../../discord/channel.py:docstring of discord.channel.StageChannel.edit:42 msgid "The new region for the stage channel's voice communication. A value of ``None`` indicates automatic voice region detection." msgstr "新しいステージチャンネルの音声通信のためのリージョン。値が ``None`` の場合は自動で検出されます。" -#: ../../../discord/channel.py:docstring of discord.channel.StageChannel.edit:52 +#: ../../../discord/channel.py:docstring of discord.channel.StageChannel.edit:54 msgid "The newly edited stage channel. If the edit was only positional then ``None`` is returned instead." msgstr "新しく編集されたステージチャンネル。編集が位置のみだった場合は代わりに ``None`` が返されます。" -#: ../../../discord/channel.py:docstring of discord.channel.StageChannel.edit:54 +#: ../../../discord/channel.py:docstring of discord.channel.StageChannel.edit:56 msgid "Optional[:class:`.StageChannel`]" msgstr "Optional[:class:`.StageChannel`]" -#: ../../api.rst:4328 +#: ../../api.rst:4691 msgid "StageInstance" msgstr "StageInstance" @@ -16555,7 +17581,7 @@ msgstr "ステージインスタンスを削除する権限がない場合。" msgid "Deleting the stage instance failed." msgstr "ステージインスタンスの削除に失敗した場合。" -#: ../../api.rst:4336 +#: ../../api.rst:4699 msgid "CategoryChannel" msgstr "CategoryChannel" @@ -16667,6 +17693,10 @@ msgstr "このカテゴリに属するボイスチャンネルを返します。 msgid "Returns the stage channels that are under this category." msgstr "このカテゴリに属するステージチャンネルを返します。" +#: ../../../discord/channel.py:docstring of discord.CategoryChannel.forums:1 +msgid "Returns the forum channels that are under this category." +msgstr "" + #: ../../../discord/channel.py:docstring of discord.channel.CategoryChannel.create_text_channel:3 msgid "A shortcut method to :meth:`Guild.create_text_channel` to create a :class:`TextChannel` in the category." msgstr "カテゴリ内に :class:`TextChannel` を作成するための :meth:`Guild.create_text_channel` のショートカット。" @@ -16683,7 +17713,7 @@ msgstr "カテゴリ内に :class:`StageChannel` を作成するための :meth: msgid "A shortcut method to :meth:`Guild.create_forum` to create a :class:`ForumChannel` in the category." msgstr "カテゴリ内に :class:`ForumChannel` を作成するための :meth:`Guild.create_forum` のショートカット。" -#: ../../api.rst:4345 +#: ../../api.rst:4708 msgid "DMChannel" msgstr "DMChannel" @@ -16762,7 +17792,7 @@ msgstr ":attr:`~Permissions.send_messages_in_threads`: DMにはスレッドが msgid "Thread related permissions are now set to ``False``." msgstr "スレッド関連の権限が ``False`` に設定されるようになりました。" -#: ../../api.rst:4358 +#: ../../api.rst:4721 msgid "GroupChannel" msgstr "GroupChannel" @@ -16822,7 +17852,7 @@ msgstr "あなたがグループにいる唯一の者である場合、グルー msgid "Leaving the group failed." msgstr "グループの脱退に失敗した場合。" -#: ../../api.rst:4371 +#: ../../api.rst:4734 msgid "PartialInviteGuild" msgstr "PartialInviteGuild" @@ -16889,7 +17919,7 @@ msgstr "部分的なギルドの現在の「ブースト」数。" msgid "The Discord vanity invite URL for this partial guild, if available." msgstr "存在する場合、部分的なギルドのDiscord バニティURL。" -#: ../../api.rst:4379 +#: ../../api.rst:4742 msgid "PartialInviteChannel" msgstr "PartialInviteChannel" @@ -16931,7 +17961,7 @@ msgstr "部分的なチャンネルのID。" msgid "The partial channel's type." msgstr "部分的なチャンネルの種類。" -#: ../../api.rst:4387 +#: ../../api.rst:4750 msgid "Invite" msgstr "Invite" @@ -17136,7 +18166,7 @@ msgstr "インスタント招待を取り消します。" msgid "The reason for deleting this invite. Shows up on the audit log." msgstr "招待を削除する理由。監査ログに表示されます。" -#: ../../api.rst:4395 +#: ../../api.rst:4758 msgid "Template" msgstr "Template" @@ -17239,7 +18269,7 @@ msgstr "テンプレートを削除します。" msgid "The template url." msgstr "テンプレートのURL。" -#: ../../api.rst:4403 +#: ../../api.rst:4766 msgid "WelcomeScreen" msgstr "WelcomeScreen" @@ -17311,7 +18341,7 @@ msgstr "ようこそ画面を編集するのに必要な権限がない場合。 msgid "This welcome screen does not exist." msgstr "ようこそ画面が存在しない場合。" -#: ../../api.rst:4411 +#: ../../api.rst:4774 msgid "WelcomeChannel" msgstr "WelcomeChannel" @@ -17339,7 +18369,7 @@ msgstr "チャンネルの説明の横に使用される絵文字。" msgid "Optional[:class:`PartialEmoji`, :class:`Emoji`, :class:`str`]" msgstr "Optional[:class:`PartialEmoji`, :class:`Emoji`, :class:`str`]" -#: ../../api.rst:4419 +#: ../../api.rst:4782 msgid "WidgetChannel" msgstr "WidgetChannel" @@ -17355,7 +18385,7 @@ msgstr "チャンネルのID。" msgid "The channel's position" msgstr "チャンネルの位置。" -#: ../../api.rst:4427 +#: ../../api.rst:4790 msgid "WidgetMember" msgstr "WidgetMember" @@ -17376,8 +18406,8 @@ msgid "Return the widget member's hash." msgstr "ウィジェットメンバーのハッシュ値を返します。" #: ../../../discord/widget.py:docstring of discord.widget.WidgetMember:19 -msgid "Returns the widget member's ``name#discriminator``." -msgstr "ウィジェットメンバーの ``name#discriminator`` を返します。" +msgid "Returns the widget member's handle (e.g. ``name`` or ``name#discriminator``)." +msgstr "ウィジェットメンバーのハンドル(例えば ``name`` や ``name#discriminator`` など)を返します。" #: ../../../discord/widget.py:docstring of discord.widget.WidgetMember:23 msgid "The member's ID." @@ -17388,46 +18418,50 @@ msgid "The member's username." msgstr "メンバーのユーザー名。" #: ../../../discord/widget.py:docstring of discord.widget.WidgetMember:35 -msgid "The member's discriminator." -msgstr "メンバーのタグ。" +msgid "The member's discriminator. This is a legacy concept that is no longer used." +msgstr "メンバーのタグ。これは、現在は使用されていない、過去の遺物です。" #: ../../../discord/widget.py:docstring of discord.widget.WidgetMember:41 +msgid "The member's global nickname, taking precedence over the username in display." +msgstr "メンバーのグローバルの表示名。ユーザー名より優先して表示されます。" + +#: ../../../discord/widget.py:docstring of discord.widget.WidgetMember:49 msgid "Whether the member is a bot." msgstr "メンバーがボットであるかどうか。" -#: ../../../discord/widget.py:docstring of discord.widget.WidgetMember:47 +#: ../../../discord/widget.py:docstring of discord.widget.WidgetMember:55 msgid "The member's status." msgstr "メンバーのステータス。" -#: ../../../discord/widget.py:docstring of discord.widget.WidgetMember:53 -msgid "The member's nickname." -msgstr "メンバーのニックネーム。" +#: ../../../discord/widget.py:docstring of discord.widget.WidgetMember:61 +msgid "The member's guild-specific nickname. Takes precedence over the global name." +msgstr "メンバーのギルド内専用のニックネーム。グローバルの表示名よりも優先されます。" -#: ../../../discord/widget.py:docstring of discord.widget.WidgetMember:59 +#: ../../../discord/widget.py:docstring of discord.widget.WidgetMember:67 msgid "The member's avatar hash." msgstr "メンバーのアバターのハッシュ値。" -#: ../../../discord/widget.py:docstring of discord.widget.WidgetMember:65 +#: ../../../discord/widget.py:docstring of discord.widget.WidgetMember:73 msgid "The member's activity." msgstr "メンバーのアクティビティ。" -#: ../../../discord/widget.py:docstring of discord.widget.WidgetMember:71 +#: ../../../discord/widget.py:docstring of discord.widget.WidgetMember:79 msgid "Whether the member is currently deafened." msgstr "メンバーがスピーカーミュートされているかどうか。" -#: ../../../discord/widget.py:docstring of discord.widget.WidgetMember:77 +#: ../../../discord/widget.py:docstring of discord.widget.WidgetMember:85 msgid "Whether the member is currently muted." msgstr "メンバーがミュートされているかどうか。" -#: ../../../discord/widget.py:docstring of discord.widget.WidgetMember:83 +#: ../../../discord/widget.py:docstring of discord.widget.WidgetMember:91 msgid "Whether the member is currently being suppressed." msgstr "メンバーが現在抑制されているのかどうか。" -#: ../../../discord/widget.py:docstring of discord.widget.WidgetMember:89 +#: ../../../discord/widget.py:docstring of discord.widget.WidgetMember:97 msgid "Which channel the member is connected to." msgstr "メンバーが接続しているチャンネル。" -#: ../../../discord/widget.py:docstring of discord.widget.WidgetMember:91 +#: ../../../discord/widget.py:docstring of discord.widget.WidgetMember:99 msgid "Optional[:class:`WidgetChannel`]" msgstr "Optional[:class:`WidgetChannel`]" @@ -17435,7 +18469,7 @@ msgstr "Optional[:class:`WidgetChannel`]" msgid "Returns the member's display name." msgstr "メンバーの表示名を返します。" -#: ../../api.rst:4436 +#: ../../api.rst:4799 msgid "Widget" msgstr "Widget" @@ -17475,6 +18509,10 @@ msgstr "ギルド内のオンラインのメンバー。オフラインのメン msgid "Due to a Discord limitation, if this data is available the users will be \"anonymized\" with linear IDs and discriminator information being incorrect. Likewise, the number of members retrieved is capped." msgstr "Discordの制限により、このデータが利用可能な場合、ユーザーのIDとタグは「仮名化」され、誤った情報になります。同様に、取得できるメンバー数にも制限があります。" +#: ../../../discord/widget.py:docstring of discord.widget.Widget:47 +msgid "List[:class:`WidgetMember`]" +msgstr "List[:class:`WidgetMember`]" + #: ../../../discord/widget.py:docstring of discord.widget.Widget:51 msgid "The approximate number of online members in the guild. Offline members are not included in this count." msgstr "ギルド内のオンラインのメンバーのおおよその数。オフラインのメンバーはこの数には含まれません。" @@ -17503,7 +18541,7 @@ msgstr "招待にカウント情報を含めるかどうか。これにより :a msgid "The invite from the widget's invite URL, if available." msgstr "利用可能な場合は、ウィジェットの招待URLからの招待。" -#: ../../api.rst:4444 +#: ../../api.rst:4807 msgid "StickerPack" msgstr "StickerPack" @@ -17563,7 +18601,7 @@ msgstr "Optional[:class:`StandardSticker`]" msgid "The banner asset of the sticker pack." msgstr "スタンプパックのバナーアセット。" -#: ../../api.rst:4452 +#: ../../api.rst:4815 msgid "StickerItem" msgstr "StickerItem" @@ -17617,7 +18655,7 @@ msgstr "スタンプアイテムの完全なスタンプデータを取得する msgid "Union[:class:`StandardSticker`, :class:`GuildSticker`]" msgstr "Union[:class:`StandardSticker`, :class:`GuildSticker`]" -#: ../../api.rst:4460 +#: ../../api.rst:4823 msgid "Sticker" msgstr "Sticker" @@ -17658,7 +18696,7 @@ msgstr "スタンプパックのID。" msgid "Returns the sticker's creation time in UTC." msgstr "スタンプの作成された時間をUTCで返します。" -#: ../../api.rst:4468 +#: ../../api.rst:4831 msgid "StandardSticker" msgstr "StandardSticker" @@ -17694,7 +18732,7 @@ msgstr "取得したスタンプパック。" msgid ":class:`StickerPack`" msgstr ":class:`StickerPack`" -#: ../../api.rst:4476 +#: ../../api.rst:4839 msgid "GuildSticker" msgstr "GuildSticker" @@ -17750,7 +18788,7 @@ msgstr "スタンプの編集中にエラーが発生した場合。" msgid "The newly modified sticker." msgstr "新しく変更されたスタンプ。" -#: ../../api.rst:4484 +#: ../../api.rst:4847 msgid "ShardInfo" msgstr "ShardInfo" @@ -17794,7 +18832,127 @@ msgstr "シャードを接続します。もしすでに接続されている場 msgid "Measures latency between a HEARTBEAT and a HEARTBEAT_ACK in seconds for this shard." msgstr "このシャードのHEARTBEATとHEARTBEAT_ACK間の待ち時間を秒単位で測定します。" -#: ../../api.rst:4492 +#: ../../api.rst:4855 +msgid "SKU" +msgstr "" + +#: ../../../discord/sku.py:docstring of discord.sku.SKU:1 +msgid "Represents a premium offering as a stock-keeping unit (SKU)." +msgstr "" + +#: ../../../discord/sku.py:docstring of discord.sku.SKU:7 +msgid "The SKU's ID." +msgstr "" + +#: ../../../discord/sku.py:docstring of discord.sku.SKU:13 +msgid "The type of the SKU." +msgstr "" + +#: ../../../discord/sku.py:docstring of discord.sku.SKU:15 +msgid ":class:`SKUType`" +msgstr "" + +#: ../../../discord/sku.py:docstring of discord.sku.SKU:19 +msgid "The ID of the application that the SKU belongs to." +msgstr "" + +#: ../../../discord/sku.py:docstring of discord.sku.SKU:25 +msgid "The consumer-facing name of the premium offering." +msgstr "" + +#: ../../../discord/sku.py:docstring of discord.sku.SKU:31 +msgid "A system-generated URL slug based on the SKU name." +msgstr "" + +#: ../../../discord/sku.py:docstring of discord.SKU.flags:1 +msgid "Returns the flags of the SKU." +msgstr "" + +#: ../../../discord/sku.py:docstring of discord.SKU.flags:3 +msgid ":class:`SKUFlags`" +msgstr "" + +#: ../../../discord/sku.py:docstring of discord.SKU.created_at:1 +msgid "Returns the sku's creation time in UTC." +msgstr "" + +#: ../../api.rst:4863 +msgid "Entitlement" +msgstr "" + +#: ../../../discord/sku.py:docstring of discord.sku.Entitlement:1 +msgid "Represents an entitlement from user or guild which has been granted access to a premium offering." +msgstr "" + +#: ../../../discord/sku.py:docstring of discord.sku.Entitlement:7 +msgid "The entitlement's ID." +msgstr "" + +#: ../../../discord/sku.py:docstring of discord.sku.Entitlement:13 +msgid "The ID of the SKU that the entitlement belongs to." +msgstr "" + +#: ../../../discord/sku.py:docstring of discord.sku.Entitlement:19 +msgid "The ID of the application that the entitlement belongs to." +msgstr "" + +#: ../../../discord/sku.py:docstring of discord.sku.Entitlement:25 +msgid "The ID of the user that is granted access to the entitlement." +msgstr "" + +#: ../../../discord/sku.py:docstring of discord.sku.Entitlement:31 +msgid "The type of the entitlement." +msgstr "" + +#: ../../../discord/sku.py:docstring of discord.sku.Entitlement:33 +msgid ":class:`EntitlementType`" +msgstr "" + +#: ../../../discord/sku.py:docstring of discord.sku.Entitlement:37 +msgid "Whether the entitlement has been deleted." +msgstr "" + +#: ../../../discord/sku.py:docstring of discord.sku.Entitlement:43 +msgid "A UTC start date which the entitlement is valid. Not present when using test entitlements." +msgstr "" + +#: ../../../discord/sku.py:docstring of discord.sku.Entitlement:49 +msgid "A UTC date which entitlement is no longer valid. Not present when using test entitlements." +msgstr "" + +#: ../../../discord/sku.py:docstring of discord.sku.Entitlement:55 +msgid "The ID of the guild that is granted access to the entitlement" +msgstr "" + +#: ../../../discord/sku.py:docstring of discord.Entitlement.user:1 +msgid "The user that is granted access to the entitlement." +msgstr "" + +#: ../../../discord/sku.py:docstring of discord.Entitlement.guild:1 +msgid "The guild that is granted access to the entitlement." +msgstr "" + +#: ../../../discord/sku.py:docstring of discord.Entitlement.created_at:1 +msgid "Returns the entitlement's creation time in UTC." +msgstr "" + +#: ../../../discord/sku.py:docstring of discord.sku.Entitlement.is_expired:1 +msgid ":class:`bool`: Returns ``True`` if the entitlement is expired. Will be always False for test entitlements." +msgstr "" + +#: ../../../discord/sku.py:docstring of discord.sku.Entitlement.delete:3 +msgid "Deletes the entitlement." +msgstr "" + +#: ../../../discord/sku.py:docstring of discord.sku.Entitlement.delete:6 +msgid "The entitlement could not be found." +msgstr "" + +#: ../../../discord/sku.py:docstring of discord.sku.Entitlement.delete:7 +msgid "Deleting the entitlement failed." +msgstr "" + +#: ../../api.rst:4871 msgid "RawMessageDeleteEvent" msgstr "RawMessageDeleteEvent" @@ -17819,7 +18977,7 @@ msgstr "削除されたメッセージ ID。" msgid "The cached message, if found in the internal message cache." msgstr "内部のメッセージキャッシュに見つかった場合、そのキャッシュされたメッセージ。" -#: ../../api.rst:4500 +#: ../../api.rst:4879 msgid "RawBulkMessageDeleteEvent" msgstr "RawBulkMessageDeleteEvent" @@ -17847,7 +19005,7 @@ msgstr "内部のメッセージキャッシュに見つかった場合、その msgid "List[:class:`Message`]" msgstr "List[:class:`Message`]" -#: ../../api.rst:4508 +#: ../../api.rst:4887 msgid "RawMessageUpdateEvent" msgstr "RawMessageUpdateEvent" @@ -17874,8 +19032,8 @@ msgstr ":ddocs:`ゲートウェイ ` によって #: ../../../discord/raw_models.py:docstring of discord.raw_models.RawMessageUpdateEvent:29 #: ../../../discord/raw_models.py:docstring of discord.raw_models.RawThreadUpdateEvent:33 #: ../../../discord/raw_models.py:docstring of discord.raw_models.RawThreadMembersUpdate:27 -#: ../../../discord/activity.py:docstring of discord.activity.Activity:57 -#: ../../../discord/activity.py:docstring of discord.activity.Activity:69 +#: ../../../discord/activity.py:docstring of discord.activity.Activity:65 +#: ../../../discord/activity.py:docstring of discord.activity.Activity:77 msgid ":class:`dict`" msgstr ":class:`dict`" @@ -17883,7 +19041,7 @@ msgstr ":class:`dict`" msgid "The cached message, if found in the internal message cache. Represents the message before it is modified by the data in :attr:`RawMessageUpdateEvent.data`." msgstr "内部メッセージキャッシュで見つかった場合、そのキャッシュされたメッセージ。 :attr:`RawMessageUpdateEvent.data` のデータによって変更される前のメッセージを表します。" -#: ../../api.rst:4516 +#: ../../api.rst:4895 msgid "RawReactionActionEvent" msgstr "RawReactionActionEvent" @@ -17916,10 +19074,30 @@ msgid "The member who added the reaction. Only available if ``event_type`` is `` msgstr "リアクションを追加したメンバー。 ``event_type`` が ``REACTION_ADD`` でリアクションがギルド内にある場合にのみ利用できます。" #: ../../../discord/raw_models.py:docstring of discord.raw_models.RawReactionActionEvent:44 +msgid "The author ID of the message being reacted to. Only available if ``event_type`` is ``REACTION_ADD``." +msgstr "" + +#: ../../../discord/raw_models.py:docstring of discord.raw_models.RawReactionActionEvent:52 msgid "The event type that triggered this action. Can be ``REACTION_ADD`` for reaction addition or ``REACTION_REMOVE`` for reaction removal." msgstr "このアクションの原因であるイベントタイプ。リアクションの追加は ``REACTION_ADD`` 、リアクションの除去は ``REACTION_REMOVE`` です。" -#: ../../api.rst:4524 +#: ../../../discord/raw_models.py:docstring of discord.raw_models.RawReactionActionEvent:62 +msgid "Whether the reaction was a burst reaction, also known as a \"super reaction\"." +msgstr "" + +#: ../../../discord/raw_models.py:docstring of discord.raw_models.RawReactionActionEvent:70 +msgid "A list of colours used for burst reaction animation. Only available if ``burst`` is ``True`` and if ``event_type`` is ``REACTION_ADD``." +msgstr "" + +#: ../../../discord/raw_models.py:docstring of discord.raw_models.RawReactionActionEvent:75 +msgid "List[:class:`Colour`]" +msgstr "" + +#: ../../../discord/raw_models.py:docstring of discord.RawReactionActionEvent.burst_colors:1 +msgid "An alias of :attr:`burst_colours`." +msgstr "" + +#: ../../api.rst:4903 msgid "RawReactionClearEvent" msgstr "RawReactionClearEvent" @@ -17942,7 +19120,7 @@ msgstr "リアクションの一括除去が行われたチャンネルのID。" msgid "The guild ID where the reactions got cleared." msgstr "リアクションの一括除去が行われたギルドのID。" -#: ../../api.rst:4532 +#: ../../api.rst:4911 msgid "RawReactionClearEmojiEvent" msgstr "RawReactionClearEmojiEvent" @@ -17954,7 +19132,7 @@ msgstr ":func:`on_raw_reaction_clear_emoji` イベントのペイロードを表 msgid "The custom or unicode emoji being removed." msgstr "除去されたカスタムまたはユニコード絵文字。" -#: ../../api.rst:4540 +#: ../../api.rst:4919 msgid "RawIntegrationDeleteEvent" msgstr "RawIntegrationDeleteEvent" @@ -17974,7 +19152,7 @@ msgstr "削除された連携サービスのボットやOAuth2 アプリケー msgid "The guild ID where the integration got deleted." msgstr "連携サービスが削除されたギルドのID。" -#: ../../api.rst:4548 +#: ../../api.rst:4927 msgid "RawThreadUpdateEvent" msgstr "RawThreadUpdateEvent" @@ -18019,7 +19197,7 @@ msgstr "スレッドが内部キャッシュで見つかった場合、そのス msgid "Optional[:class:`discord.Thread`]" msgstr "Optional[:class:`discord.Thread`]" -#: ../../api.rst:4556 +#: ../../api.rst:4935 msgid "RawThreadMembersUpdate" msgstr "RawThreadMembersUpdate" @@ -18035,7 +19213,7 @@ msgstr "スレッドのおおよそのメンバー数。この値は50の上限 msgid "The raw data given by the :ddocs:`gateway `." msgstr ":ddocs:`ゲートウェイ ` によって与えられた生のデータ。" -#: ../../api.rst:4564 +#: ../../api.rst:4943 msgid "RawThreadDeleteEvent" msgstr "RawThreadDeleteEvent" @@ -18059,7 +19237,7 @@ msgstr "スレッドが削除されたギルドのID。" msgid "The ID of the channel the thread belonged to." msgstr "スレッドが属したチャンネルの ID。" -#: ../../api.rst:4572 +#: ../../api.rst:4951 msgid "RawTypingEvent" msgstr "RawTypingEvent" @@ -18087,7 +19265,7 @@ msgstr "Optional[Union[:class:`discord.User`, :class:`discord.Member`]]" msgid "The ID of the guild the user started typing in, if applicable." msgstr "該当する場合、ユーザーが入力し始めたギルドのID。" -#: ../../api.rst:4580 +#: ../../api.rst:4959 msgid "RawMemberRemoveEvent" msgstr "RawMemberRemoveEvent" @@ -18107,7 +19285,7 @@ msgstr "Union[:class:`discord.User`, :class:`discord.Member`]" msgid "The ID of the guild the user left." msgstr "ユーザーが脱退したギルドのID。" -#: ../../api.rst:4588 +#: ../../api.rst:4967 msgid "RawAppCommandPermissionsUpdateEvent" msgstr "RawAppCommandPermissionsUpdateEvent" @@ -18131,7 +19309,7 @@ msgstr "権限が更新されたギルド。" msgid "List of new permissions for the app command." msgstr "アプリケーションコマンドの新しい権限のリスト。" -#: ../../api.rst:4596 +#: ../../api.rst:4975 msgid "PartialWebhookGuild" msgstr "PartialWebhookGuild" @@ -18144,7 +19322,7 @@ msgstr "Webhook用の部分的なギルドを表します。" msgid "These are typically given for channel follower webhooks." msgstr "これは通常、チャンネルをフォローするWebhookから与えられます。" -#: ../../api.rst:4604 +#: ../../api.rst:4983 msgid "PartialWebhookChannel" msgstr "PartialWebhookChannel" @@ -18152,23 +19330,23 @@ msgstr "PartialWebhookChannel" msgid "Represents a partial channel for webhooks." msgstr "Webhook用の部分的なチャンネルを表します。" -#: ../../api.rst:4614 +#: ../../api.rst:4993 msgid "Data Classes" msgstr "データクラス" -#: ../../api.rst:4616 +#: ../../api.rst:4995 msgid "Some classes are just there to be data containers, this lists them." msgstr "一部のクラスはデータコンテナとして用いられます。ここではそのクラスを一覧表にしています。" -#: ../../api.rst:4618 +#: ../../api.rst:4997 msgid "Unlike :ref:`models ` you are allowed to create most of these yourself, even if they can also be used to hold attributes." msgstr ":ref:`models ` とは異なり、属性を持つものであっても、自分で作成することが許されています。" -#: ../../api.rst:4624 +#: ../../api.rst:5003 msgid "The only exception to this rule is :class:`Object`, which is made with dynamic attributes in mind." msgstr "このルールの唯一の例外は :class:`Object` で、動的な属性を念頭に置いて作成されます。" -#: ../../api.rst:4629 +#: ../../api.rst:5008 msgid "Object" msgstr "Object" @@ -18216,7 +19394,7 @@ msgstr "Type[:class:`abc.Snowflake`]" msgid "Returns the snowflake's creation time in UTC." msgstr "スノーフレークの作成時刻をUTCで返します。" -#: ../../api.rst:4637 +#: ../../api.rst:5016 msgid "Embed" msgstr "Embed" @@ -18529,7 +19707,7 @@ msgstr "無効なインデックスが指定された場合。" msgid "Converts this embed object into a dict." msgstr "埋め込みオブジェクトを辞書型に変換します。" -#: ../../api.rst:4645 +#: ../../api.rst:5024 msgid "AllowedMentions" msgstr "AllowedMentions" @@ -18570,7 +19748,7 @@ msgstr "すべてのフィールドが ``True`` に明示的に設定された : msgid "A factory method that returns a :class:`AllowedMentions` with all fields set to ``False``" msgstr "すべてのフィールドが ``False`` に設定された :class:`AllowedMentions` を返すファクトリメソッド。" -#: ../../api.rst:4653 +#: ../../api.rst:5032 msgid "MessageReference" msgstr "MessageReference" @@ -18635,7 +19813,7 @@ msgstr "Optional[:class:`~discord.Message`]" msgid "Returns a URL that allows the client to jump to the referenced message." msgstr "クライアントが参照されたメッセージにジャンプすることのできるURLを返します。" -#: ../../api.rst:4661 +#: ../../api.rst:5040 msgid "PartialMessage" msgstr "PartialMessage" @@ -18656,34 +19834,38 @@ msgid ":meth:`VoiceChannel.get_partial_message`" msgstr ":meth:`VoiceChannel.get_partial_message`" #: ../../../discord/message.py:docstring of discord.message.PartialMessage:9 +msgid ":meth:`StageChannel.get_partial_message`" +msgstr ":meth:`StageChannel.get_partial_message`" + +#: ../../../discord/message.py:docstring of discord.message.PartialMessage:10 msgid ":meth:`Thread.get_partial_message`" msgstr ":meth:`Thread.get_partial_message`" -#: ../../../discord/message.py:docstring of discord.message.PartialMessage:10 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage:11 msgid ":meth:`DMChannel.get_partial_message`" msgstr ":meth:`DMChannel.get_partial_message`" -#: ../../../discord/message.py:docstring of discord.message.PartialMessage:20 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage:21 msgid "Checks if two partial messages are equal." msgstr "二つの部分的なメッセージが等しいかを比較します。" -#: ../../../discord/message.py:docstring of discord.message.PartialMessage:24 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage:25 msgid "Checks if two partial messages are not equal." msgstr "二つの部分的なメッセージが等しくないかを比較します。" -#: ../../../discord/message.py:docstring of discord.message.PartialMessage:28 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage:29 msgid "Returns the partial message's hash." msgstr "部分的なメッセージのハッシュ値を返します。" -#: ../../../discord/message.py:docstring of discord.message.PartialMessage:32 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage:33 msgid "The channel associated with this partial message." msgstr "この部分的なメッセージに関連付けられたチャンネル。" -#: ../../../discord/message.py:docstring of discord.message.PartialMessage:34 -msgid "Union[:class:`PartialMessageable`, :class:`TextChannel`, :class:`VoiceChannel`, :class:`Thread`, :class:`DMChannel`]" -msgstr "Union[:class:`PartialMessageable`, :class:`TextChannel`, :class:`VoiceChannel`, :class:`Thread`, :class:`DMChannel`]" +#: ../../../discord/message.py:docstring of discord.message.PartialMessage:35 +msgid "Union[:class:`PartialMessageable`, :class:`TextChannel`, :class:`StageChannel`, :class:`VoiceChannel`, :class:`Thread`, :class:`DMChannel`]" +msgstr "Union[:class:`PartialMessageable`, :class:`TextChannel`, :class:`StageChannel`, :class:`VoiceChannel`, :class:`Thread`, :class:`DMChannel`]" -#: ../../../discord/message.py:docstring of discord.message.PartialMessage:44 +#: ../../../discord/message.py:docstring of discord.message.PartialMessage:45 msgid "The guild that the partial message belongs to, if applicable." msgstr "該当する場合、この部分的なメッセージが属するギルド。" @@ -18691,7 +19873,11 @@ msgstr "該当する場合、この部分的なメッセージが属するギル msgid "The partial message's creation time in UTC." msgstr "UTCの、部分的なメッセージが作成された時刻。" -#: ../../api.rst:4669 +#: ../../../discord/message.py:docstring of discord.PartialMessage.thread:5 +msgid "This does not retrieve archived threads, as they are not retained in the internal cache. Use :meth:`fetch_thread` instead." +msgstr "" + +#: ../../api.rst:5048 msgid "MessageApplication" msgstr "MessageApplication" @@ -18707,7 +19893,7 @@ msgstr "存在する場合、アプリケーションのアイコン。" msgid "The application's cover image, if any." msgstr "存在する場合、アプリケーションのカバー画像。" -#: ../../api.rst:4677 +#: ../../api.rst:5056 msgid "RoleSubscriptionInfo" msgstr "RoleSubscriptionInfo" @@ -18735,7 +19921,7 @@ msgstr "ユーザーが購読している月数の合計。" msgid "Whether this notification is for a renewal rather than a new purchase." msgstr "この通知が新しい購入ではなく、更新のためであるかどうか。" -#: ../../api.rst:4685 +#: ../../api.rst:5064 msgid "Intents" msgstr "Intents" @@ -18803,10 +19989,14 @@ msgid "Returns an iterator of ``(name, value)`` pairs. This allows it to be, for msgstr "``(name, value)`` ペアのイテレータを返します。これにより、例えば、辞書型やペアのリストに変換できます。" #: ../../../discord/flags.py:docstring of discord.flags.Intents:62 -#: ../../../discord/flags.py:docstring of discord.flags.MemberCacheFlags:65 -#: ../../../discord/flags.py:docstring of discord.flags.ApplicationFlags:52 -#: ../../../discord/flags.py:docstring of discord.flags.ChannelFlags:52 -#: ../../../discord/flags.py:docstring of discord.flags.AutoModPresets:54 +msgid "Returns whether any intent is enabled." +msgstr "何らかのインテントが有効かどうかを返します。" + +#: ../../../discord/flags.py:docstring of discord.flags.Intents:68 +#: ../../../discord/flags.py:docstring of discord.flags.MemberCacheFlags:71 +#: ../../../discord/flags.py:docstring of discord.flags.ApplicationFlags:56 +#: ../../../discord/flags.py:docstring of discord.flags.ChannelFlags:56 +#: ../../../discord/flags.py:docstring of discord.flags.AutoModPresets:58 msgid "The raw value. You should query flags via the properties rather than using this raw value." msgstr "生の値。この値を使用するのではなく、プロパティ経由でフラグを取得すべきです。" @@ -18986,11 +20176,15 @@ msgstr ":attr:`User.avatar`" msgid ":attr:`User.discriminator`" msgstr ":attr:`User.discriminator`" -#: ../../docstring of discord.Intents.members:27 +#: ../../docstring of discord.Intents.members:26 +msgid ":attr:`User.global_name`" +msgstr ":attr:`User.global_name`" + +#: ../../docstring of discord.Intents.members:28 msgid "For more information go to the :ref:`member intent documentation `." msgstr "詳細については、 :ref:`メンバーインテントの説明 ` を参照してください。" -#: ../../docstring of discord.Intents.members:31 +#: ../../docstring of discord.Intents.members:32 #: ../../docstring of discord.Intents.presences:17 #: ../../docstring of discord.Intents.message_content:19 msgid "Currently, this requires opting in explicitly via the developer portal as well. Bots in over 100 guilds will need to apply to Discord for verification." @@ -19509,7 +20703,7 @@ msgstr "自動管理ルール対応関係のイベントが有効になってい msgid "This corresponds to the following events: - :func:`on_automod_action`" msgstr "これは以下のイベントに対応します: - :func:`on_automod_action`" -#: ../../api.rst:4693 +#: ../../api.rst:5072 msgid "MemberCacheFlags" msgstr "MemberCacheFlags" @@ -19545,6 +20739,14 @@ msgstr "x と y のいずれか一方のみにて有効化されたフラグの msgid "Returns a MemberCacheFlags instance with all flags inverted from x." msgstr "x のすべてのフラグが反転したMemberCacheFlagsインスタンスを返します。" +#: ../../../discord/flags.py:docstring of discord.flags.MemberCacheFlags:65 +#: ../../../discord/flags.py:docstring of discord.flags.ApplicationFlags:50 +#: ../../../discord/flags.py:docstring of discord.flags.ChannelFlags:50 +#: ../../../discord/flags.py:docstring of discord.flags.AutoModPresets:54 +#: ../../../discord/flags.py:docstring of discord.flags.SystemChannelFlags:58 +msgid "Returns whether any flag is set to ``True``." +msgstr "何らかのフラグが ``True`` に設定されているかどうかを返します。" + #: ../../../discord/flags.py:docstring of discord.flags.MemberCacheFlags.all:1 msgid "A factory method that creates a :class:`MemberCacheFlags` with everything enabled." msgstr "すべて有効化された :class:`MemberCacheFlags` を作成するファクトリメソッド。" @@ -19593,7 +20795,7 @@ msgstr "結果として生成されるメンバーキャッシュフラグ。" msgid ":class:`MemberCacheFlags`" msgstr ":class:`MemberCacheFlags`" -#: ../../api.rst:4701 +#: ../../api.rst:5080 msgid "ApplicationFlags" msgstr "ApplicationFlags" @@ -19633,6 +20835,10 @@ msgstr "x のすべてのフラグが反転したApplicationFlagsインスタン msgid "Returns an iterator of ``(name, value)`` pairs. This allows it to be, for example, constructed as a dict or a list of pairs. Note that aliases are not shown." msgstr "``(name, value)`` ペアのイテレータを返します。これにより、例えば、辞書型やペアのリストに変換できます。エイリアスは含まれません。" +#: ../../docstring of discord.ApplicationFlags.auto_mod_badge:1 +msgid "Returns ``True`` if the application uses at least 100 automod rules across all guilds. This shows up as a badge in the official client." +msgstr "アプリケーションがすべてのギルドを合算して少なくとも100の自動管理ルールを使用している場合 ``True`` を返します。これは公式クライアントにバッジとして表示されます。" + #: ../../docstring of discord.ApplicationFlags.gateway_presence:1 msgid "Returns ``True`` if the application is verified and is allowed to receive presence information over the gateway." msgstr "アプリケーションが認証済みでプレゼンス情報をゲートウェイ経由で受け取ることができる場合に ``True`` を返します。" @@ -19673,7 +20879,7 @@ msgstr "アプリケーションがグローバルアプリケーションコマ msgid "Returns ``True`` if the application has had at least one global application command used in the last 30 days." msgstr "過去30日間で少なくとも1つのグローバルアプリケーションコマンドが使用されている場合に ``True`` を返します。" -#: ../../api.rst:4709 +#: ../../api.rst:5088 msgid "ChannelFlags" msgstr "ChannelFlags" @@ -19713,7 +20919,11 @@ msgstr "スレッドがフォーラムチャンネルにピン留めされてい msgid "Returns ``True`` if a tag is required to be specified when creating a thread in a :class:`ForumChannel`." msgstr ":class:`ForumChannel` でスレッドを作成する際にタグを指定する必要がある場合に ``True`` を返します。" -#: ../../api.rst:4717 +#: ../../docstring of discord.ChannelFlags.hide_media_download_options:1 +msgid "Returns ``True`` if the client hides embedded media download options in a :class:`ForumChannel`. Only available in media channels." +msgstr "" + +#: ../../api.rst:5096 msgid "AutoModPresets" msgstr "AutoModPresets" @@ -19765,7 +20975,7 @@ msgstr "すべて有効化された :class:`AutoModPresets` を作成するフ msgid "A factory method that creates a :class:`AutoModPresets` with everything disabled." msgstr "すべて無効化された :class:`AutoModPresets` を作成するファクトリメソッド。" -#: ../../api.rst:4725 +#: ../../api.rst:5104 msgid "AutoModRuleAction" msgstr "AutoModRuleAction" @@ -19773,27 +20983,35 @@ msgstr "AutoModRuleAction" msgid "Represents an auto moderation's rule action." msgstr "自動管理ルールの対応を表します。" -#: ../../../discord/automod.py:docstring of discord.automod.AutoModRuleAction:7 +#: ../../../discord/automod.py:docstring of discord.automod.AutoModRuleAction:4 +msgid "Only one of ``channel_id``, ``duration``, or ``custom_message`` can be used." +msgstr "``channel_id`` 、 ``duration`` 、 ``custom_message`` のいずれか一つのみが使用できます。" + +#: ../../../discord/automod.py:docstring of discord.automod.AutoModRuleAction:10 msgid "The type of action to take. Defaults to :attr:`~AutoModRuleActionType.block_message`." msgstr "行う対応の種類。デフォルトは :attr:`~AutoModRuleActionType.block_message` です。" -#: ../../../discord/automod.py:docstring of discord.automod.AutoModRuleAction:10 +#: ../../../discord/automod.py:docstring of discord.automod.AutoModRuleAction:13 msgid ":class:`AutoModRuleActionType`" msgstr ":class:`AutoModRuleActionType`" -#: ../../../discord/automod.py:docstring of discord.automod.AutoModRuleAction:14 +#: ../../../discord/automod.py:docstring of discord.automod.AutoModRuleAction:17 msgid "The ID of the channel or thread to send the alert message to, if any. Passing this sets :attr:`type` to :attr:`~AutoModRuleActionType.send_alert_message`." msgstr "該当する場合、アラートメッセージを送信するチャンネルまたはスレッドのID。これを渡すと、 :attr:`type` が :attr:`~AutoModRuleActionType.send_alert_message` に設定されます。" -#: ../../../discord/automod.py:docstring of discord.automod.AutoModRuleAction:21 +#: ../../../discord/automod.py:docstring of discord.automod.AutoModRuleAction:24 msgid "The duration of the timeout to apply, if any. Has a maximum of 28 days. Passing this sets :attr:`type` to :attr:`~AutoModRuleActionType.timeout`." msgstr "該当する場合、適用するタイムアウトの長さ。最大28日間です。これを渡すと、 :attr:`type` が :attr:`~AutoModRuleActionType.timeout` に設定されます。" -#: ../../../discord/automod.py:docstring of discord.automod.AutoModRuleAction:25 +#: ../../../discord/automod.py:docstring of discord.automod.AutoModRuleAction:28 msgid "Optional[:class:`datetime.timedelta`]" msgstr "Optional[:class:`datetime.timedelta`]" -#: ../../api.rst:4733 +#: ../../../discord/automod.py:docstring of discord.automod.AutoModRuleAction:32 +msgid "A custom message which will be shown to a user when their message is blocked. Passing this sets :attr:`type` to :attr:`~AutoModRuleActionType.block_message`." +msgstr "メッセージがブロックされたときに送信者に表示されるカスタムメッセージ。 :attr:`type` を :attr:`~AutoModRuleActionType.block_message` に設定します。" + +#: ../../api.rst:5112 msgid "AutoModTrigger" msgstr "AutoModTrigger" @@ -19818,6 +21036,7 @@ msgid ":attr:`AutoModRuleTriggerType.keyword`" msgstr ":attr:`AutoModRuleTriggerType.keyword`" #: ../../../discord/automod.py:docstring of discord.automod.AutoModTrigger:8 +#: ../../../discord/automod.py:docstring of discord.automod.AutoModTrigger:18 msgid ":attr:`keyword_filter`, :attr:`regex_patterns`, :attr:`allow_list`" msgstr ":attr:`keyword_filter`, :attr:`regex_patterns`, :attr:`allow_list`" @@ -19838,46 +21057,54 @@ msgid ":attr:`AutoModRuleTriggerType.mention_spam`" msgstr ":attr:`AutoModRuleTriggerType.mention_spam`" #: ../../../discord/automod.py:docstring of discord.automod.AutoModTrigger:15 -msgid ":attr:`mention_limit`" -msgstr ":attr:`mention_limit`" +msgid ":attr:`mention_limit`, :attr:`mention_raid_protection`" +msgstr "" + +#: ../../../discord/automod.py:docstring of discord.automod.AutoModTrigger:18 +msgid ":attr:`AutoModRuleTriggerType.member_profile`" +msgstr "" -#: ../../../discord/automod.py:docstring of discord.automod.AutoModTrigger:22 +#: ../../../discord/automod.py:docstring of discord.automod.AutoModTrigger:26 msgid "The type of trigger." msgstr "発動条件の種類。" -#: ../../../discord/automod.py:docstring of discord.automod.AutoModTrigger:28 -msgid "The list of strings that will trigger the keyword filter. Maximum of 1000. Keywords can only be up to 30 characters in length." -msgstr "キーワードフィルタを発動させる文字列の一覧。最大1000個まで。キーワードは各30文字以内です。" +#: ../../../discord/automod.py:docstring of discord.automod.AutoModTrigger:32 +msgid "The list of strings that will trigger the filter. Maximum of 1000. Keywords can only be up to 60 characters in length." +msgstr "" -#: ../../../discord/automod.py:docstring of discord.automod.AutoModTrigger:31 +#: ../../../discord/automod.py:docstring of discord.automod.AutoModTrigger:35 msgid "This could be combined with :attr:`regex_patterns`." msgstr ":attr:`regex_patterns` と組み合わせることができます。" -#: ../../../discord/automod.py:docstring of discord.automod.AutoModTrigger:37 -msgid "The regex pattern that will trigger the filter. The syntax is based off of `Rust's regex syntax `_. Maximum of 10. Regex strings can only be up to 250 characters in length." -msgstr "フィルタを発動させる正規表現パターン。構文は `Rust の正規表現構文 `_ に基づいています。 最大 10 個まで。正規表現文字列は 250 文字までしか使用できません。" - #: ../../../discord/automod.py:docstring of discord.automod.AutoModTrigger:41 +msgid "The regex pattern that will trigger the filter. The syntax is based off of `Rust's regex syntax `_. Maximum of 10. Regex strings can only be up to 260 characters in length." +msgstr "フィルタを発動させる正規表現パターン。構文は `Rust の正規表現構文 `_ に基づいています。 最大 10 個まで。正規表現文字列は 260 文字までしか使用できません。" + +#: ../../../discord/automod.py:docstring of discord.automod.AutoModTrigger:45 msgid "This could be combined with :attr:`keyword_filter` and/or :attr:`allow_list`" msgstr ":attr:`keyword_filter` や :attr:`allow_list` と組み合わせることができます。" -#: ../../../discord/automod.py:docstring of discord.automod.AutoModTrigger:49 +#: ../../../discord/automod.py:docstring of discord.automod.AutoModTrigger:53 msgid "The presets used with the preset keyword filter." msgstr "プリセットキーワードフィルタで使用されるプリセット。" -#: ../../../discord/automod.py:docstring of discord.automod.AutoModTrigger:51 +#: ../../../discord/automod.py:docstring of discord.automod.AutoModTrigger:55 msgid ":class:`AutoModPresets`" msgstr ":class:`AutoModPresets`" -#: ../../../discord/automod.py:docstring of discord.automod.AutoModTrigger:55 -msgid "The list of words that are exempt from the commonly flagged words." -msgstr "通常ならフィルタが発動される単語のうち、除外されるもののリスト。" +#: ../../../discord/automod.py:docstring of discord.automod.AutoModTrigger:59 +msgid "The list of words that are exempt from the commonly flagged words. Maximum of 100. Keywords can only be up to 60 characters in length." +msgstr "共通のキーワードフィルタの単語から除外される単語の一覧。最大100個まで。キーワードは各60文字以内です。" -#: ../../../discord/automod.py:docstring of discord.automod.AutoModTrigger:61 +#: ../../../discord/automod.py:docstring of discord.automod.AutoModTrigger:66 msgid "The total number of user and role mentions a message can contain. Has a maximum of 50." msgstr "メッセージに含めることのできるユーザーとロールのメンションの合計数。最大で50個です。" -#: ../../api.rst:4741 +#: ../../../discord/automod.py:docstring of discord.automod.AutoModTrigger:73 +msgid "Whether mention raid protection is enabled or not." +msgstr "" + +#: ../../api.rst:5120 msgid "File" msgstr "File" @@ -19917,7 +21144,7 @@ msgstr "表示するファイルの説明。現在画像でのみサポートさ msgid "The filename to display when uploading to Discord. If this is not given then it defaults to ``fp.name`` or if ``fp`` is a string then the ``filename`` will default to the string given." msgstr "Discordにアップロードするときに表示されるファイル名。指定されていない場合はデフォルトでは ``fp.name`` 、または ``fp`` が文字列の場合、 ``filename`` は与えられた文字列をデフォルトにします。" -#: ../../api.rst:4749 +#: ../../api.rst:5128 msgid "Colour" msgstr "Colour" @@ -19950,6 +21177,10 @@ msgid "Returns the raw colour value." msgstr "生の色の値を返します。" #: ../../../discord/colour.py:docstring of discord.colour.Colour:30 +msgid "The colour values in the classmethods are mostly provided as-is and can change between versions should the Discord client's representation of that colour also change." +msgstr "クラスメソッドの色はDiscordクライアントの色をほぼそのまま提供しているため、クライアントで使用する色が変更された場合、値がバージョン間で変更されることがあります。" + +#: ../../../discord/colour.py:docstring of discord.colour.Colour:35 msgid "The raw integer colour value." msgstr "生の色の整数値。" @@ -20130,8 +21361,16 @@ msgid "A factory method that returns a :class:`Colour` with a value of ``0x99AAB msgstr "``0x99AAB5`` の値を持つ :class:`Colour` を返すクラスメソッドです。" #: ../../../discord/colour.py:docstring of discord.colour.Colour.dark_theme:1 -msgid "A factory method that returns a :class:`Colour` with a value of ``0x36393F``. This will appear transparent on Discord's dark theme." -msgstr "``0x36393F`` の値を持つ :class:`Colour` を返すクラスメソッドです。Discordのダークテーマでは透明に見えます。" +msgid "A factory method that returns a :class:`Colour` with a value of ``0x313338``." +msgstr "``0x313338`` の値を持つ :class:`Colour` を返すクラスメソッドです。" + +#: ../../../discord/colour.py:docstring of discord.colour.Colour.dark_theme:3 +msgid "This will appear transparent on Discord's dark theme." +msgstr "これはDiscordのダークテーマでは透明に見えます。" + +#: ../../../discord/colour.py:docstring of discord.colour.Colour.dark_theme:9 +msgid "Updated colour from previous ``0x36393F`` to reflect discord theme changes." +msgstr "Discordテーマの変更を反映するため以前の ``0x36393F`` から色を変更しました。" #: ../../../discord/colour.py:docstring of discord.colour.Colour.fuchsia:1 msgid "A factory method that returns a :class:`Colour` with a value of ``0xEB459E``." @@ -20141,7 +21380,19 @@ msgstr "``0xEB459E`` の値を持つ :class:`Colour` を返すクラスメソッ msgid "A factory method that returns a :class:`Colour` with a value of ``0xFEE75C``." msgstr "``0xFEE75C`` の値を持つ :class:`Colour` を返すクラスメソッドです。" -#: ../../api.rst:4757 +#: ../../../discord/colour.py:docstring of discord.colour.Colour.dark_embed:1 +msgid "A factory method that returns a :class:`Colour` with a value of ``0x2B2D31``." +msgstr "``0x2B2D31`` の値を持つ :class:`Colour` を返すクラスメソッドです。" + +#: ../../../discord/colour.py:docstring of discord.colour.Colour.light_embed:1 +msgid "A factory method that returns a :class:`Colour` with a value of ``0xEEEFF1``." +msgstr "``0xEEEFF1`` の値を持つ :class:`Colour` を返すクラスメソッドです。" + +#: ../../../discord/colour.py:docstring of discord.colour.Colour.pink:1 +msgid "A factory method that returns a :class:`Colour` with a value of ``0xEB459F``." +msgstr "``0xEB459F`` の値を持つ :class:`Colour` を返すクラスメソッドです。" + +#: ../../api.rst:5136 msgid "BaseActivity" msgstr "BaseActivity" @@ -20179,7 +21430,7 @@ msgstr "なお、ライブラリはこれらをユーザー設定可能としま msgid "When the user started doing this activity in UTC." msgstr "ユーザーがアクティビティを開始したときのUTC時刻。" -#: ../../api.rst:4765 +#: ../../api.rst:5144 msgid "Activity" msgstr "Activity" @@ -20220,54 +21471,62 @@ msgid "The detail of the user's current activity." msgstr "ユーザーの現在のアクティビティの詳細。" #: ../../../discord/activity.py:docstring of discord.activity.Activity:50 +msgid "The user's current platform." +msgstr "" + +#: ../../../discord/activity.py:docstring of discord.activity.Activity:58 msgid "A dictionary of timestamps. It contains the following optional keys:" msgstr "タイムスタンプの辞書。次のオプションキーが含まれています:" -#: ../../../discord/activity.py:docstring of discord.activity.Activity:52 +#: ../../../discord/activity.py:docstring of discord.activity.Activity:60 msgid "``start``: Corresponds to when the user started doing the activity in milliseconds since Unix epoch." msgstr "``start``: ユーザーがアクティビティを開始したときのUnixエポック起算ミリ秒数に対応します。" -#: ../../../discord/activity.py:docstring of discord.activity.Activity:54 +#: ../../../discord/activity.py:docstring of discord.activity.Activity:62 msgid "``end``: Corresponds to when the user will finish doing the activity in milliseconds since Unix epoch." msgstr "``end``: ユーザーがアクティビティを終了する予定時刻のUnixエポック起算ミリ秒数に対応します。" -#: ../../../discord/activity.py:docstring of discord.activity.Activity:61 +#: ../../../discord/activity.py:docstring of discord.activity.Activity:69 msgid "A dictionary representing the images and their hover text of an activity. It contains the following optional keys:" msgstr "アクティビティの画像とそれらのホバーテキストを表す辞書。次のオプションキーが含まれています:" -#: ../../../discord/activity.py:docstring of discord.activity.Activity:64 +#: ../../../discord/activity.py:docstring of discord.activity.Activity:72 +#: ../../../discord/activity.py:docstring of discord.activity.Game:45 msgid "``large_image``: A string representing the ID for the large image asset." msgstr "``large_image``: 大きな画像アセットのIDを表す文字列。" -#: ../../../discord/activity.py:docstring of discord.activity.Activity:65 +#: ../../../discord/activity.py:docstring of discord.activity.Activity:73 +#: ../../../discord/activity.py:docstring of discord.activity.Game:46 msgid "``large_text``: A string representing the text when hovering over the large image asset." msgstr "``large_text``: 大きな画像アセットをホバーしたときに表示するテキストを表す文字列。" -#: ../../../discord/activity.py:docstring of discord.activity.Activity:66 +#: ../../../discord/activity.py:docstring of discord.activity.Activity:74 +#: ../../../discord/activity.py:docstring of discord.activity.Game:47 msgid "``small_image``: A string representing the ID for the small image asset." msgstr "``small_image``: 小さな画像アセットのIDを表す文字列。" -#: ../../../discord/activity.py:docstring of discord.activity.Activity:67 +#: ../../../discord/activity.py:docstring of discord.activity.Activity:75 +#: ../../../discord/activity.py:docstring of discord.activity.Game:48 msgid "``small_text``: A string representing the text when hovering over the small image asset." msgstr "``small_text``: 小さな画像アセットをホバーしたときに表示するテキストを表す文字列。" -#: ../../../discord/activity.py:docstring of discord.activity.Activity:73 +#: ../../../discord/activity.py:docstring of discord.activity.Activity:81 msgid "A dictionary representing the activity party. It contains the following optional keys:" msgstr "アクティビティのパーティーを表す辞書。次のオプションキーが含まれています:" -#: ../../../discord/activity.py:docstring of discord.activity.Activity:75 +#: ../../../discord/activity.py:docstring of discord.activity.Activity:83 msgid "``id``: A string representing the party ID." msgstr "``id``: パーティー ID を表す文字列。" -#: ../../../discord/activity.py:docstring of discord.activity.Activity:76 +#: ../../../discord/activity.py:docstring of discord.activity.Activity:84 msgid "``size``: A list of up to two integer elements denoting (current_size, maximum_size)." msgstr "``size``: 現在の大きさと最大の大きさをである二個以内の整数のリスト。" -#: ../../../discord/activity.py:docstring of discord.activity.Activity:82 +#: ../../../discord/activity.py:docstring of discord.activity.Activity:90 msgid "A list of strings representing the labels of custom buttons shown in a rich presence." msgstr "リッチプレゼンスに表示されるカスタムボタンのラベルを表す文字列のリスト。" -#: ../../../discord/activity.py:docstring of discord.activity.Activity:90 +#: ../../../discord/activity.py:docstring of discord.activity.Activity:98 msgid "The emoji that belongs to this activity." msgstr "このアクティビティに属する絵文字。" @@ -20295,7 +21554,7 @@ msgstr "該当する場合、このアクティビティの大きな画像アセ msgid "Returns the small image asset hover text of this activity, if applicable." msgstr "該当する場合、このアクティビティの小さな画像アセットのホバーテキストを返します。" -#: ../../api.rst:4773 +#: ../../api.rst:5152 msgid "Game" msgstr "Game" @@ -20328,6 +21587,14 @@ msgstr "ゲームの名前を返します。" msgid "The game's name." msgstr "ゲームの名前。" +#: ../../../discord/activity.py:docstring of discord.activity.Game:34 +msgid "Where the user is playing from (ie. PS5, Xbox)." +msgstr "" + +#: ../../../discord/activity.py:docstring of discord.activity.Game:42 +msgid "A dictionary representing the images and their hover text of a game. It contains the following optional keys:" +msgstr "" + #: ../../../discord/activity.py:docstring of discord.Game.type:1 #: ../../../discord/activity.py:docstring of discord.Streaming.type:1 msgid "Returns the game's type. This is for compatibility with :class:`Activity`." @@ -20345,7 +21612,7 @@ msgstr "該当する場合、ユーザーがゲームを開始したときのUTC msgid "When the user will stop playing this game in UTC, if applicable." msgstr "該当する場合、ユーザーがゲームを終了する予定のUTC時刻。" -#: ../../api.rst:4781 +#: ../../api.rst:5160 msgid "Streaming" msgstr "Streaming" @@ -20409,7 +21676,7 @@ msgstr "提供された場合、ストリーム中のユーザーのTwitchの名 msgid "This corresponds to the ``large_image`` key of the :attr:`Streaming.assets` dictionary if it starts with ``twitch:``. Typically set by the Discord client." msgstr "これが ``twitch:`` で始まる場合、 :attr:`Streaming.assets` 辞書の ``large_image`` キーに対応します。典型的にはDiscordクライアントによって設定されます。" -#: ../../api.rst:4789 +#: ../../api.rst:5168 msgid "CustomActivity" msgstr "CustomActivity" @@ -20433,7 +21700,7 @@ msgstr "存在する場合、アクティビティに渡す絵文字。" msgid "It always returns :attr:`ActivityType.custom`." msgstr "これは常に :attr:`ActivityType.custom` を返します。" -#: ../../api.rst:4797 +#: ../../api.rst:5176 msgid "Permissions" msgstr "Permissions" @@ -20499,6 +21766,10 @@ msgid "Returns an iterator of ``(perm, value)`` pairs. This allows it to be, for msgstr "``(perm, value)`` ペアのイテレータを返します。これにより、例えば、辞書型やペアのリストに変換できます。エイリアスは含まれません。" #: ../../../discord/permissions.py:docstring of discord.permissions.Permissions:70 +msgid "Returns whether the permissions object has any permissions set to ``True``." +msgstr "権限オブジェクトの権限のいずれかが ``True`` に設定されているかどうかを返します。" + +#: ../../../discord/permissions.py:docstring of discord.permissions.Permissions:76 msgid "The raw value. This value is a bit array field of a 53-bit integer representing the currently available permissions. You should query permissions via the properties rather than using this raw value." msgstr "生の値。この値は、現在使用可能な権限を表す 53 ビット整数のビット配列フィールドです。権限の取得には、この生の値ではなくプロパティを使用すべきです。" @@ -20531,8 +21802,9 @@ msgid "A :class:`Permissions` with all channel-specific permissions set to ``Tru msgstr "チャンネル特有の権限が ``True`` に、ギルド特有の権限が ``False`` に設定された :class:`Permissions` 。ギルド特有の権限は現在以下の通りです:" #: ../../../discord/permissions.py:docstring of discord.permissions.Permissions.all_channel:5 -msgid ":attr:`manage_emojis`" -msgstr ":attr:`manage_emojis`" +#: ../../../discord/permissions.py:docstring of discord.permissions.Permissions.elevated:12 +msgid ":attr:`manage_expressions`" +msgstr ":attr:`manage_expressions`" #: ../../../discord/permissions.py:docstring of discord.permissions.Permissions.all_channel:6 msgid ":attr:`view_audit_log`" @@ -20570,14 +21842,22 @@ msgstr ":attr:`ban_members`" msgid ":attr:`administrator`" msgstr ":attr:`administrator`" -#: ../../../discord/permissions.py:docstring of discord.permissions.Permissions.all_channel:15 +#: ../../../discord/permissions.py:docstring of discord.permissions.Permissions.all_channel:14 +msgid ":attr:`create_expressions`" +msgstr ":attr:`create_expressions`" + +#: ../../../discord/permissions.py:docstring of discord.permissions.Permissions.all_channel:16 msgid "Added :attr:`stream`, :attr:`priority_speaker` and :attr:`use_application_commands` permissions." msgstr ":attr:`stream` 、 :attr:`priority_speaker` 、 :attr:`use_application_commands` 権限を追加しました。" -#: ../../../discord/permissions.py:docstring of discord.permissions.Permissions.all_channel:18 +#: ../../../discord/permissions.py:docstring of discord.permissions.Permissions.all_channel:19 msgid "Added :attr:`create_public_threads`, :attr:`create_private_threads`, :attr:`manage_threads`, :attr:`use_external_stickers`, :attr:`send_messages_in_threads` and :attr:`request_to_speak` permissions." msgstr ":attr:`create_public_threads` 、 :attr:`create_private_threads` 、 :attr:`manage_threads` 、 :attr:`use_external_stickers` 、 :attr:`send_messages_in_threads` 、 :attr:`request_to_speak` 権限を追加しました。" +#: ../../../discord/permissions.py:docstring of discord.permissions.Permissions.all_channel:24 +msgid "Added :attr:`use_soundboard`, :attr:`create_expressions` permissions." +msgstr ":attr:`use_soundboard` と :attr:`create_expressions` 権限を追加しました。" + #: ../../../discord/permissions.py:docstring of discord.permissions.Permissions.general:1 msgid "A factory method that creates a :class:`Permissions` with all \"General\" permissions from the official Discord UI set to ``True``." msgstr "Discord公式UIの「サーバー全般の権限」をすべて ``True`` に設定した :class:`Permissions` を作成するファクトリメソッド。" @@ -20586,6 +21866,10 @@ msgstr "Discord公式UIの「サーバー全般の権限」をすべて ``True`` msgid "Permission :attr:`read_messages` is now included in the general permissions, but permissions :attr:`administrator`, :attr:`create_instant_invite`, :attr:`kick_members`, :attr:`ban_members`, :attr:`change_nickname` and :attr:`manage_nicknames` are no longer part of the general permissions." msgstr ":attr:`read_messages` が全般の権限に含まれるようになり、 :attr:`administrator` 、 :attr:`create_instant_invite` 、 :attr:`kick_members` 、 :attr:`ban_members` 、 :attr:`change_nickname` 、 :attr:`manage_nicknames` は全般の権限に含まれなくなりました。" +#: ../../../discord/permissions.py:docstring of discord.permissions.Permissions.general:10 +msgid "Added :attr:`create_expressions` permission." +msgstr ":attr:`create_expressions` 権限を追加しました。" + #: ../../../discord/permissions.py:docstring of discord.permissions.Permissions.membership:1 msgid "A factory method that creates a :class:`Permissions` with all \"Membership\" permissions from the official Discord UI set to ``True``." msgstr "Discord公式UIの「メンバーシップ権限」をすべて ``True`` に設定した :class:`Permissions` を作成するファクトリメソッド。" @@ -20602,6 +21886,10 @@ msgstr ":attr:`read_messages` がテキストチャンネル権限に含まれ msgid "Added :attr:`create_public_threads`, :attr:`create_private_threads`, :attr:`manage_threads`, :attr:`send_messages_in_threads` and :attr:`use_external_stickers` permissions." msgstr ":attr:`create_public_threads` 、 :attr:`create_private_threads` 、 :attr:`manage_threads` 、 :attr:`send_messages_in_threads` 、 :attr:`use_external_stickers` 権限が追加されました。" +#: ../../../discord/permissions.py:docstring of discord.permissions.Permissions.text:12 +msgid "Added :attr:`send_voice_messages` permission." +msgstr ":attr:`send_voice_messages` 権限を追加しました。" + #: ../../../discord/permissions.py:docstring of discord.permissions.Permissions.voice:1 msgid "A factory method that creates a :class:`Permissions` with all \"Voice\" permissions from the official Discord UI set to ``True``." msgstr "Discord公式UIの「ボイスチャンネル権限」をすべて ``True`` に設定した :class:`Permissions` を作成するファクトリメソッド。" @@ -20647,10 +21935,6 @@ msgstr ":attr:`manage_roles`" msgid ":attr:`manage_webhooks`" msgstr ":attr:`manage_webhooks`" -#: ../../../discord/permissions.py:docstring of discord.permissions.Permissions.elevated:12 -msgid ":attr:`manage_emojis_and_stickers`" -msgstr ":attr:`manage_emojis_and_stickers`" - #: ../../../discord/permissions.py:docstring of discord.permissions.Permissions.elevated:13 msgid ":attr:`manage_threads`" msgstr ":attr:`manage_threads`" @@ -20659,6 +21943,10 @@ msgstr ":attr:`manage_threads`" msgid ":attr:`moderate_members`" msgstr ":attr:`moderate_members`" +#: ../../../discord/permissions.py:docstring of discord.permissions.Permissions.events:1 +msgid "A factory method that creates a :class:`Permissions` with all \"Events\" permissions from the official Discord UI set to ``True``." +msgstr "" + #: ../../../discord/permissions.py:docstring of discord.permissions.Permissions.advanced:1 msgid "A factory method that creates a :class:`Permissions` with all \"Advanced\" permissions from the official Discord UI set to ``True``." msgstr "Discord公式UIの「高度な権限」をすべて ``True`` に設定した :class:`Permissions` を作成するファクトリメソッド。" @@ -20824,13 +22112,14 @@ msgstr ":attr:`manage_roles` のエイリアス。" msgid "Returns ``True`` if a user can create, edit, or delete webhooks." msgstr "ユーザーがWebhookを作成、編集、削除できる場合は、``True`` を返します。" -#: ../../docstring of discord.Permissions.manage_emojis:1 -msgid "Returns ``True`` if a user can create, edit, or delete emojis." -msgstr "ユーザーが絵文字を作成、編集、削除できる場合は ``True`` を返します。" +#: ../../docstring of discord.Permissions.manage_expressions:1 +msgid "Returns ``True`` if a user can edit or delete emojis, stickers, and soundboard sounds." +msgstr "絵文字、スタンプ、サウンドボードのサウンドを編集または削除できる場合は ``True`` を返します。" +#: ../../docstring of discord.Permissions.manage_emojis:1 #: ../../docstring of discord.Permissions.manage_emojis_and_stickers:1 -msgid "An alias for :attr:`manage_emojis`." -msgstr ":attr:`manage_emojis` のエイリアス。" +msgid "An alias for :attr:`manage_expressions`." +msgstr ":attr:`manage_expressions` のエイリアス。" #: ../../docstring of discord.Permissions.use_application_commands:1 msgid "Returns ``True`` if a user can use slash commands." @@ -20876,7 +22165,31 @@ msgstr "ユーザーがボイスチャンネルにて埋め込みアプリケー msgid "Returns ``True`` if a user can time out other members." msgstr "ユーザーが他のユーザーをタイムアウトできる場合は ``True`` を返します。" -#: ../../api.rst:4805 +#: ../../docstring of discord.Permissions.view_creator_monetization_analytics:1 +msgid "Returns ``True`` if a user can view role subscription insights." +msgstr "" + +#: ../../docstring of discord.Permissions.use_soundboard:1 +msgid "Returns ``True`` if a user can use the soundboard." +msgstr "ユーザーがサウンドボードを使用できる場合は ``True`` を返します。" + +#: ../../docstring of discord.Permissions.create_expressions:1 +msgid "Returns ``True`` if a user can create emojis, stickers, and soundboard sounds." +msgstr "絵文字、スタンプ、サウンドボードのサウンドを作成できる場合は ``True`` を返します。" + +#: ../../docstring of discord.Permissions.create_events:1 +msgid "Returns ``True`` if a user can create guild events." +msgstr "" + +#: ../../docstring of discord.Permissions.use_external_sounds:1 +msgid "Returns ``True`` if a user can use sounds from other guilds." +msgstr "ユーザーが他のギルドのサウンドを使用できる場合は ``True`` を返します。" + +#: ../../docstring of discord.Permissions.send_voice_messages:1 +msgid "Returns ``True`` if a user can send voice messages." +msgstr "ユーザーがボイスメッセージを送信できる場合は ``True`` を返します。" + +#: ../../api.rst:5184 msgid "PermissionOverwrite" msgstr "PermissionOverwrite" @@ -20932,7 +22245,7 @@ msgstr "権限上書きオブジェクトを一括更新します。" msgid "A list of key/value pairs to bulk update with." msgstr "一括更新するためのキーと値のペアのリスト。" -#: ../../api.rst:4813 +#: ../../api.rst:5192 msgid "SystemChannelFlags" msgstr "SystemChannelFlags" @@ -20960,9 +22273,9 @@ msgstr "x と y のいずれか一方のみにて有効化されたフラグの msgid "Returns a SystemChannelFlags instance with all flags inverted from x." msgstr "x のすべてのフラグが反転したSystemChannelFlagsインスタンスを返します。" -#: ../../../discord/flags.py:docstring of discord.flags.SystemChannelFlags:58 -#: ../../../discord/flags.py:docstring of discord.flags.MessageFlags:53 -#: ../../../discord/flags.py:docstring of discord.flags.PublicUserFlags:52 +#: ../../../discord/flags.py:docstring of discord.flags.SystemChannelFlags:64 +#: ../../../discord/flags.py:docstring of discord.flags.MessageFlags:59 +#: ../../../discord/flags.py:docstring of discord.flags.PublicUserFlags:58 msgid "The raw value. This value is a bit array field of a 53-bit integer representing the currently available flags. You should query flags via the properties rather than using this raw value." msgstr "生の値。この値は、現在使用可能なフラグを表す 53 ビット整数のビット配列フィールドです。フラグの取得には、この生の値ではなくプロパティを使用すべきです。" @@ -20990,7 +22303,7 @@ msgstr "ロールサブスクリプションの購入と更新通知が有効に msgid "Returns ``True`` if the role subscription notifications have a sticker reply button." msgstr "ロールサブスクリプション通知にスタンプの返信ボタンがある場合に ``True`` を返します。" -#: ../../api.rst:4821 +#: ../../api.rst:5200 msgid "MessageFlags" msgstr "MessageFlags" @@ -21058,7 +22371,19 @@ msgstr "メッセージがインタラクションの応答で、ボットが「 msgid "Returns ``True`` if the message failed to mention some roles in a thread and add their members to the thread." msgstr "メッセージがロールをメンションし、そのメンバーをスレッドに追加するのに失敗した場合に ``True`` を返します。" -#: ../../api.rst:4829 +#: ../../docstring of discord.MessageFlags.suppress_notifications:1 +msgid "Returns ``True`` if the message will not trigger push and desktop notifications." +msgstr "メッセージがプッシュ通知やデスクトップ通知を送信しない場合は ``True`` を返します。" + +#: ../../docstring of discord.MessageFlags.silent:1 +msgid "Alias for :attr:`suppress_notifications`." +msgstr ":attr:`suppress_notifications` のエイリアス。" + +#: ../../docstring of discord.MessageFlags.voice:1 +msgid "Returns ``True`` if the message is a voice message." +msgstr "メッセージがボイスメッセージの場合に ``True`` を返します。" + +#: ../../api.rst:5208 msgid "PublicUserFlags" msgstr "PublicUserFlags" @@ -21166,7 +22491,7 @@ msgstr "ユーザーがアクティブな開発者の場合に ``True`` を返 msgid "List[:class:`UserFlags`]: Returns all public flags the user has." msgstr "List[:class:`UserFlags`]: ユーザーが持つすべての公開フラグを返します。" -#: ../../api.rst:4837 +#: ../../api.rst:5216 msgid "MemberFlags" msgstr "MemberFlags" @@ -21214,7 +22539,131 @@ msgstr "メンバーがギルドの認証要件をバイパスできる場合に msgid "Returns ``True`` if the member has started onboarding." msgstr "メンバーがオンボーディングを開始した場合に ``True`` を返します。" -#: ../../api.rst:4845 +#: ../../api.rst:5224 +msgid "AttachmentFlags" +msgstr "" + +#: ../../../discord/flags.py:docstring of discord.flags.AttachmentFlags:1 +msgid "Wraps up the Discord Attachment flags" +msgstr "" + +#: ../../../discord/flags.py:docstring of discord.flags.AttachmentFlags:9 +msgid "Checks if two AttachmentFlags are equal." +msgstr "" + +#: ../../../discord/flags.py:docstring of discord.flags.AttachmentFlags:13 +msgid "Checks if two AttachmentFlags are not equal." +msgstr "" + +#: ../../../discord/flags.py:docstring of discord.flags.AttachmentFlags:17 +msgid "Returns a AttachmentFlags instance with all enabled flags from both x and y." +msgstr "" + +#: ../../../discord/flags.py:docstring of discord.flags.AttachmentFlags:22 +msgid "Returns a AttachmentFlags instance with only flags enabled on both x and y." +msgstr "" + +#: ../../../discord/flags.py:docstring of discord.flags.AttachmentFlags:27 +msgid "Returns a AttachmentFlags instance with only flags enabled on only one of x or y, not on both." +msgstr "" + +#: ../../../discord/flags.py:docstring of discord.flags.AttachmentFlags:32 +msgid "Returns a AttachmentFlags instance with all flags inverted from x." +msgstr "" + +#: ../../docstring of discord.AttachmentFlags.clip:1 +msgid "Returns ``True`` if the attachment is a clip." +msgstr "" + +#: ../../docstring of discord.AttachmentFlags.thumbnail:1 +msgid "Returns ``True`` if the attachment is a thumbnail." +msgstr "" + +#: ../../docstring of discord.AttachmentFlags.remix:1 +msgid "Returns ``True`` if the attachment has been edited using the remix feature." +msgstr "" + +#: ../../api.rst:5232 +msgid "RoleFlags" +msgstr "" + +#: ../../../discord/flags.py:docstring of discord.flags.RoleFlags:1 +msgid "Wraps up the Discord Role flags" +msgstr "" + +#: ../../../discord/flags.py:docstring of discord.flags.RoleFlags:9 +msgid "Checks if two RoleFlags are equal." +msgstr "" + +#: ../../../discord/flags.py:docstring of discord.flags.RoleFlags:13 +msgid "Checks if two RoleFlags are not equal." +msgstr "" + +#: ../../../discord/flags.py:docstring of discord.flags.RoleFlags:17 +msgid "Returns a RoleFlags instance with all enabled flags from both x and y." +msgstr "" + +#: ../../../discord/flags.py:docstring of discord.flags.RoleFlags:22 +msgid "Returns a RoleFlags instance with only flags enabled on both x and y." +msgstr "" + +#: ../../../discord/flags.py:docstring of discord.flags.RoleFlags:27 +msgid "Returns a RoleFlags instance with only flags enabled on only one of x or y, not on both." +msgstr "" + +#: ../../../discord/flags.py:docstring of discord.flags.RoleFlags:32 +msgid "Returns a RoleFlags instance with all flags inverted from x." +msgstr "" + +#: ../../docstring of discord.RoleFlags.in_prompt:1 +msgid "Returns ``True`` if the role can be selected by members in an onboarding prompt." +msgstr "" + +#: ../../api.rst:5240 +msgid "SKUFlags" +msgstr "" + +#: ../../../discord/flags.py:docstring of discord.flags.SKUFlags:1 +msgid "Wraps up the Discord SKU flags" +msgstr "" + +#: ../../../discord/flags.py:docstring of discord.flags.SKUFlags:9 +msgid "Checks if two SKUFlags are equal." +msgstr "" + +#: ../../../discord/flags.py:docstring of discord.flags.SKUFlags:13 +msgid "Checks if two SKUFlags are not equal." +msgstr "" + +#: ../../../discord/flags.py:docstring of discord.flags.SKUFlags:17 +msgid "Returns a SKUFlags instance with all enabled flags from both x and y." +msgstr "" + +#: ../../../discord/flags.py:docstring of discord.flags.SKUFlags:22 +msgid "Returns a SKUFlags instance with only flags enabled on both x and y." +msgstr "" + +#: ../../../discord/flags.py:docstring of discord.flags.SKUFlags:27 +msgid "Returns a SKUFlags instance with only flags enabled on only one of x or y, not on both." +msgstr "" + +#: ../../../discord/flags.py:docstring of discord.flags.SKUFlags:32 +msgid "Returns a SKUFlags instance with all flags inverted from x." +msgstr "" + +#: ../../docstring of discord.SKUFlags.available:1 +msgid "Returns ``True`` if the SKU is available for purchase." +msgstr "" + +#: ../../docstring of discord.SKUFlags.guild_subscription:1 +msgid "Returns ``True`` if the SKU is a guild subscription." +msgstr "" + +#: ../../docstring of discord.SKUFlags.user_subscription:1 +msgid "Returns ``True`` if the SKU is a user subscription." +msgstr "" + +#: ../../api.rst:5248 msgid "ForumTag" msgstr "ForumTag" @@ -21250,11 +22699,11 @@ msgstr ":attr:`~Permissions.manage_threads` 権限を有するモデレータの msgid "The emoji that is used to represent this tag. Note that if the emoji is a custom emoji, it will *not* have name information." msgstr "このタグを表すために使用される絵文字。絵文字がカスタム絵文字の場合、名前情報は *提供されません* 。" -#: ../../api.rst:4854 +#: ../../api.rst:5257 msgid "Exceptions" msgstr "例外" -#: ../../api.rst:4856 +#: ../../api.rst:5259 msgid "The following exceptions are thrown by the library." msgstr "以下の例外がライブラリにより送出されます。" @@ -21412,67 +22861,67 @@ msgstr "返されたエラーコード。" msgid "An exception that is thrown for when libopus is not loaded." msgstr "libopus がロードされていないときに送出される例外。" -#: ../../api.rst:4891 +#: ../../api.rst:5294 msgid "Exception Hierarchy" msgstr "例外の階層構造" -#: ../../api.rst:4908 +#: ../../api.rst:5311 msgid ":exc:`Exception`" msgstr ":exc:`Exception`" -#: ../../api.rst:4908 +#: ../../api.rst:5311 msgid ":exc:`DiscordException`" msgstr ":exc:`DiscordException`" -#: ../../api.rst:4901 +#: ../../api.rst:5304 msgid ":exc:`ClientException`" msgstr ":exc:`ClientException`" -#: ../../api.rst:4898 +#: ../../api.rst:5301 msgid ":exc:`InvalidData`" msgstr ":exc:`InvalidData`" -#: ../../api.rst:4899 +#: ../../api.rst:5302 msgid ":exc:`LoginFailure`" msgstr ":exc:`LoginFailure`" -#: ../../api.rst:4900 +#: ../../api.rst:5303 msgid ":exc:`ConnectionClosed`" msgstr ":exc:`ConnectionClosed`" -#: ../../api.rst:4901 +#: ../../api.rst:5304 msgid ":exc:`PrivilegedIntentsRequired`" msgstr ":exc:`PrivilegedIntentsRequired`" -#: ../../api.rst:4902 +#: ../../api.rst:5305 msgid ":exc:`InteractionResponded`" msgstr ":exc:`InteractionResponded`" -#: ../../api.rst:4903 +#: ../../api.rst:5306 msgid ":exc:`GatewayNotFound`" msgstr ":exc:`GatewayNotFound`" -#: ../../api.rst:4907 +#: ../../api.rst:5310 msgid ":exc:`HTTPException`" msgstr ":exc:`HTTPException`" -#: ../../api.rst:4905 +#: ../../api.rst:5308 msgid ":exc:`Forbidden`" msgstr ":exc:`Forbidden`" -#: ../../api.rst:4906 +#: ../../api.rst:5309 msgid ":exc:`NotFound`" msgstr ":exc:`NotFound`" -#: ../../api.rst:4907 +#: ../../api.rst:5310 msgid ":exc:`DiscordServerError`" msgstr ":exc:`DiscordServerError`" -#: ../../api.rst:4908 +#: ../../api.rst:5311 msgid ":exc:`app_commands.CommandSyncFailure`" msgstr ":exc:`app_commands.CommandSyncFailure`" -#: ../../api.rst:4909 +#: ../../api.rst:5312 msgid ":exc:`RateLimited`" msgstr ":exc:`RateLimited`" diff --git a/docs/locale/ja/LC_MESSAGES/discord.po b/docs/locale/ja/LC_MESSAGES/discord.po index 5cf4a9991..66070b699 100644 --- a/docs/locale/ja/LC_MESSAGES/discord.po +++ b/docs/locale/ja/LC_MESSAGES/discord.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: discordpy\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-01-24 11:01+0000\n" -"PO-Revision-Date: 2023-01-30 13:38\n" +"POT-Creation-Date: 2023-06-21 01:17+0000\n" +"PO-Revision-Date: 2024-04-17 02:43\n" "Last-Translator: \n" "Language-Team: Japanese\n" "MIME-Version: 1.0\n" @@ -30,12 +30,12 @@ msgid "Creating a Bot account is a pretty straightforward process." msgstr "Botのアカウント作成はとても簡単です。" #: ../../discord.rst:12 -#: ../../discord.rst:66 +#: ../../discord.rst:61 msgid "Make sure you're logged on to the `Discord website `_." msgstr "`Discordのウェブサイト `_ にログインできていることを確認してください。" #: ../../discord.rst:13 -#: ../../discord.rst:67 +#: ../../discord.rst:62 msgid "Navigate to the `application page `_" msgstr "`Applicationページ `_ に移動します。" @@ -56,22 +56,14 @@ msgid "The new application form filled in." msgstr "記入された新しいアプリケーションフォーム" #: ../../discord.rst:24 -msgid "Create a Bot User by navigating to the \"Bot\" tab and clicking \"Add Bot\"." -msgstr "「Bot」タブへ移動し、「Add Bot」をクリックしてBotユーザーを作成します。" - -#: ../../discord.rst:26 -msgid "Click \"Yes, do it!\" to continue." -msgstr "「Yes, do it!」をクリックして続行します。" +msgid "Navigate to the \"Bot\" tab to configure it." +msgstr "\"Bot\" タブに移動して設定します。" -#: ../../discord.rst:0 -msgid "The Add Bot button." -msgstr "「Add Bot」ボタン" - -#: ../../discord.rst:30 +#: ../../discord.rst:25 msgid "Make sure that **Public Bot** is ticked if you want others to invite your bot." msgstr "他人にBotの招待を許可する場合には、 **Public Bot** にチェックを入れてください。" -#: ../../discord.rst:32 +#: ../../discord.rst:27 msgid "You should also make sure that **Require OAuth2 Code Grant** is unchecked unless you are developing a service that needs it. If you're unsure, then **leave it unchecked**." msgstr "また、必要なサービスを開発している場合を除いて、 **Require OAuth2 Code Grant** がオフになっていることを確認する必要があります。わからない場合は **チェックを外してください** 。" @@ -79,55 +71,55 @@ msgstr "また、必要なサービスを開発している場合を除いて、 msgid "How the Bot User options should look like for most people." msgstr "Botユーザーの設定がほとんどの人にとってどのように見えるか" -#: ../../discord.rst:38 +#: ../../discord.rst:33 msgid "Copy the token using the \"Copy\" button." msgstr "「Copy」ボタンを使ってトークンをコピーします。" -#: ../../discord.rst:40 +#: ../../discord.rst:35 msgid "**This is not the Client Secret at the General Information page.**" msgstr "**General InformationページのClient Secretではないので注意してください。**" -#: ../../discord.rst:44 +#: ../../discord.rst:39 msgid "It should be worth noting that this token is essentially your bot's password. You should **never** share this with someone else. In doing so, someone can log in to your bot and do malicious things, such as leaving servers, ban all members inside a server, or pinging everyone maliciously." msgstr "このトークンは、あなたのBotのパスワードと同義であることを覚えておきましょう。誰か他の人とトークンを共有することは絶対に避けてください。トークンがあれば、誰かがあなたのBotにログインし、サーバーから退出したり、サーバー内のすべてのメンバーをBANしたり、すべての人にメンションを送るなどといった悪質な行為を行える様になってしまいます。" -#: ../../discord.rst:49 +#: ../../discord.rst:44 msgid "The possibilities are endless, so **do not share this token.**" msgstr "可能性は無限にあるので、絶対に **トークンを共有しないでください** 。" -#: ../../discord.rst:51 +#: ../../discord.rst:46 msgid "If you accidentally leaked your token, click the \"Regenerate\" button as soon as possible. This revokes your old token and re-generates a new one. Now you need to use the new token to login." msgstr "誤ってトークンを流出させてしまった場合、可能な限り速急に「Regenerate」ボタンをクリックしましょう。これによって古いトークンが無効になり、新しいトークンが再生成されます。今度からは新しいトークンを利用してログインを行う必要があります。" -#: ../../discord.rst:55 +#: ../../discord.rst:50 msgid "And that's it. You now have a bot account and you can login with that token." msgstr "以上です。 これでボットアカウントが作成され、そのトークンでログインできます。" -#: ../../discord.rst:60 +#: ../../discord.rst:55 msgid "Inviting Your Bot" msgstr "Botを招待する" -#: ../../discord.rst:62 +#: ../../discord.rst:57 msgid "So you've made a Bot User but it's not actually in any server." msgstr "Botのユーザーを作成しましたが、現時点ではどのサーバーにも参加していない状態です。" -#: ../../discord.rst:64 +#: ../../discord.rst:59 msgid "If you want to invite your bot you must create an invite URL for it." msgstr "Botを招待したい場合は、そのための招待URLを作成する必要があります。" -#: ../../discord.rst:68 +#: ../../discord.rst:63 msgid "Click on your bot's page." msgstr "Botのページを開きます。" -#: ../../discord.rst:69 -msgid "Go to the \"OAuth2\" tab." -msgstr "「OAuth2」タブへ移動します。" +#: ../../discord.rst:64 +msgid "Go to the \"OAuth2 > URL Generator\" tab." +msgstr "\"OAuth2 > URL Generator\" タブに移動します。" #: ../../discord.rst:0 msgid "How the OAuth2 page should look like." msgstr "OAuth2ページがどのように見えるか" -#: ../../discord.rst:74 +#: ../../discord.rst:69 msgid "Tick the \"bot\" checkbox under \"scopes\"." msgstr "「scopes」下にある「bot」チェックボックスを選択してください。" @@ -135,15 +127,15 @@ msgstr "「scopes」下にある「bot」チェックボックスを選択して msgid "The scopes checkbox with \"bot\" ticked." msgstr "「bot」がチェックされたスコープのチェックボックス" -#: ../../discord.rst:79 +#: ../../discord.rst:74 msgid "Tick the permissions required for your bot to function under \"Bot Permissions\"." msgstr "「Bot Permissions」からBotの機能に必要な権限を選択してください。" -#: ../../discord.rst:81 +#: ../../discord.rst:76 msgid "Please be aware of the consequences of requiring your bot to have the \"Administrator\" permission." msgstr "Botに「管理者」権限を要求させることによる影響は認識しておきましょう。" -#: ../../discord.rst:83 +#: ../../discord.rst:78 msgid "Bot owners must have 2FA enabled for certain actions and permissions when added in servers that have Server-Wide 2FA enabled. Check the `2FA support page `_ for more information." msgstr "二段階認証が有効になっているサーバーにボットを追加する場合、ボットの所有者は特定の動作や権限を与えるために二段階認証を有効化させる必要があります。詳細は `二段階認証のサポートページ `_ を参照してください。" @@ -151,15 +143,15 @@ msgstr "二段階認証が有効になっているサーバーにボットを追 msgid "The permission checkboxes with some permissions checked." msgstr "いくつかの権限にチェックが入った権限のチェックボックス" -#: ../../discord.rst:88 +#: ../../discord.rst:83 msgid "Now the resulting URL can be used to add your bot to a server. Copy and paste the URL into your browser, choose a server to invite the bot to, and click \"Authorize\"." msgstr "結果的に生成されたURLを使ってBotをサーバーに追加することができます。URLをコピーしてブラウザに貼り付け、Botを招待したいサーバーを選択した後、「認証」をクリックしてください。" -#: ../../discord.rst:93 +#: ../../discord.rst:88 msgid "The person adding the bot needs \"Manage Server\" permissions to do so." msgstr "Botを追加する人には「サーバー管理」権限が必要です。" -#: ../../discord.rst:95 +#: ../../discord.rst:90 msgid "If you want to generate this URL dynamically at run-time inside your bot and using the :class:`discord.Permissions` interface, you can use :func:`discord.utils.oauth_url`." msgstr "このURLを実行時に動的に生成したい場合は、 :class:`discord.Permissions` インターフェイスから :func:`discord.utils.oauth_url` を使用できます。" diff --git a/docs/locale/ja/LC_MESSAGES/ext/commands/api.po b/docs/locale/ja/LC_MESSAGES/ext/commands/api.po index aa1bf60c2..04d44642a 100644 --- a/docs/locale/ja/LC_MESSAGES/ext/commands/api.po +++ b/docs/locale/ja/LC_MESSAGES/ext/commands/api.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: discordpy\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-01-29 20:45+0000\n" -"PO-Revision-Date: 2023-01-30 13:38\n" +"POT-Creation-Date: 2023-06-21 01:17+0000\n" +"PO-Revision-Date: 2023-06-21 01:20\n" "Last-Translator: \n" "Language-Team: Japanese\n" "MIME-Version: 1.0\n" @@ -581,8 +581,8 @@ msgid "A view was not passed." msgstr "Viewが渡されなかった" #: ../../../discord/ext/commands/bot.py:docstring of discord.client.Client.add_view:16 -msgid "The view is not persistent. A persistent view has no timeout and all their components have an explicitly provided custom_id." -msgstr "Viewは永続的ではありません。永続的なViewにはタイムアウトがなく、すべてのコンポーネントには明示的に渡された custom_id があります" +msgid "The view is not persistent or is already finished. A persistent view has no timeout and all their components have an explicitly provided custom_id." +msgstr "" #: ../../../discord/ext/commands/bot.py:docstring of discord.ext.commands.Bot.allowed_mentions:1 msgid "The allowed mention configuration." @@ -932,14 +932,14 @@ msgid "Retrieves an :term:`asynchronous iterator` that enables receiving your gu msgstr "Botが所属するGuildを取得できる、 :term:`asynchronous iterator` を取得します。" #: ../../../discord/ext/commands/bot.py:docstring of discord.client.Client.fetch_guilds:5 -msgid "Using this, you will only receive :attr:`.Guild.owner`, :attr:`.Guild.icon`, :attr:`.Guild.id`, and :attr:`.Guild.name` per :class:`.Guild`." -msgstr "これを使った場合、各 :class:`Guild` の :attr:`Guild.owner` 、 :attr:`Guild.icon` 、 :attr:`Guild.id` 、 :attr:`Guild.name` のみ取得できます。" +msgid "Using this, you will only receive :attr:`.Guild.owner`, :attr:`.Guild.icon`, :attr:`.Guild.id`, :attr:`.Guild.name`, :attr:`.Guild.approximate_member_count`, and :attr:`.Guild.approximate_presence_count` per :class:`.Guild`." +msgstr "" -#: ../../../discord/ext/commands/bot.py:docstring of discord.client.Client.fetch_guilds:10 +#: ../../../discord/ext/commands/bot.py:docstring of discord.client.Client.fetch_guilds:11 msgid "This method is an API call. For general usage, consider :attr:`guilds` instead." msgstr "これはAPIを呼び出します。通常は :attr:`guilds` を代わりに使用してください。" -#: ../../../discord/ext/commands/bot.py:docstring of discord.client.Client.fetch_guilds:13 +#: ../../../discord/ext/commands/bot.py:docstring of discord.client.Client.fetch_guilds:14 #: ../../../discord/ext/commands/bot.py:docstring of discord.client.Client.wait_for:22 #: ../../../discord/ext/commands/core.py:docstring of discord.ext.commands.core.check:37 #: ../../../discord/ext/commands/core.py:docstring of discord.ext.commands.core.check_any:20 @@ -947,37 +947,41 @@ msgstr "これはAPIを呼び出します。通常は :attr:`guilds` を代わ msgid "Examples" msgstr "例" -#: ../../../discord/ext/commands/bot.py:docstring of discord.client.Client.fetch_guilds:14 +#: ../../../discord/ext/commands/bot.py:docstring of discord.client.Client.fetch_guilds:15 #: ../../../discord/ext/commands/context.py:docstring of discord.abc.Messageable.history:7 msgid "Usage ::" msgstr "使い方 ::" -#: ../../../discord/ext/commands/bot.py:docstring of discord.client.Client.fetch_guilds:19 +#: ../../../discord/ext/commands/bot.py:docstring of discord.client.Client.fetch_guilds:20 msgid "Flattening into a list ::" msgstr "リストへフラット化 ::" -#: ../../../discord/ext/commands/bot.py:docstring of discord.client.Client.fetch_guilds:24 +#: ../../../discord/ext/commands/bot.py:docstring of discord.client.Client.fetch_guilds:25 #: ../../../discord/ext/commands/context.py:docstring of discord.abc.Messageable.history:19 msgid "All parameters are optional." msgstr "すべてのパラメータがオプションです。" -#: ../../../discord/ext/commands/bot.py:docstring of discord.client.Client.fetch_guilds:26 +#: ../../../discord/ext/commands/bot.py:docstring of discord.client.Client.fetch_guilds:27 msgid "The number of guilds to retrieve. If ``None``, it retrieves every guild you have access to. Note, however, that this would make it a slow operation. Defaults to ``200``." msgstr "取得するギルドの数。 ``None`` の場合、Botがアクセスできるギルドすべてを取得します。ただし、これには時間が掛かることに注意してください。デフォルトは200です。" -#: ../../../discord/ext/commands/bot.py:docstring of discord.client.Client.fetch_guilds:33 +#: ../../../discord/ext/commands/bot.py:docstring of discord.client.Client.fetch_guilds:34 msgid "The default has been changed to 200." msgstr "デフォルトが200に変更されました。" -#: ../../../discord/ext/commands/bot.py:docstring of discord.client.Client.fetch_guilds:35 +#: ../../../discord/ext/commands/bot.py:docstring of discord.client.Client.fetch_guilds:36 msgid "Retrieves guilds before this date or object. If a datetime is provided, it is recommended to use a UTC aware datetime. If the datetime is naive, it is assumed to be local time." msgstr "渡された日付、またはギルドより前のギルドを取得します。日付を指定する場合、UTC aware datetimeを利用することを推奨します。naive datetimeである場合、これはローカル時間であるとみなされます。" -#: ../../../discord/ext/commands/bot.py:docstring of discord.client.Client.fetch_guilds:39 +#: ../../../discord/ext/commands/bot.py:docstring of discord.client.Client.fetch_guilds:40 msgid "Retrieve guilds after this date or object. If a datetime is provided, it is recommended to use a UTC aware datetime. If the datetime is naive, it is assumed to be local time." msgstr "渡された日付、またはオブジェクトより後のギルドを取得します。日付を指定する場合、UTC対応の「aware」を利用することを推奨します。日付が「naive」である場合、これは地域時間であるとみなされます。" #: ../../../discord/ext/commands/bot.py:docstring of discord.client.Client.fetch_guilds:44 +msgid "Whether to include count information in the guilds. This fills the :attr:`.Guild.approximate_member_count` and :attr:`.Guild.approximate_presence_count` attributes without needing any privileged intents. Defaults to ``True``." +msgstr "" + +#: ../../../discord/ext/commands/bot.py:docstring of discord.client.Client.fetch_guilds:51 msgid "Getting the guilds failed." msgstr "Guildの取得に失敗した場合。" @@ -989,7 +993,7 @@ msgstr "Guildの取得に失敗した場合。" msgid "Yields" msgstr "Yieldする値" -#: ../../../discord/ext/commands/bot.py:docstring of discord.client.Client.fetch_guilds:46 +#: ../../../discord/ext/commands/bot.py:docstring of discord.client.Client.fetch_guilds:53 msgid ":class:`.Guild` -- The guild with the guild data parsed." msgstr ":class:`.Guild` -- データを解析したGuild。" @@ -2171,7 +2175,7 @@ msgstr "``cls`` で指定されたクラスの構築時に渡すキーワード #: ../../../discord/ext/commands/core.py:docstring of discord.ext.commands.core.command:21 #: ../../../discord/ext/commands/hybrid.py:docstring of discord.ext.commands.hybrid.hybrid_command:29 -#: ../../../discord/ext/commands/hybrid.py:docstring of discord.ext.commands.hybrid.hybrid_group:9 +#: ../../../discord/ext/commands/hybrid.py:docstring of discord.ext.commands.hybrid.hybrid_group:12 msgid "If the function is not a coroutine or is already a command." msgstr "関数がコルーチンでない場合、またはすでにコマンドが登録されている場合。" @@ -2204,7 +2208,7 @@ msgid "Checks and error handlers are dispatched and called as-if they were comma msgstr "チェックとエラーハンドラは、 :class:`.Command` のようなコマンドであるかのように呼び出されます。つまり、パラメータには :class:`discord.Interaction` ではなく :class:`Context` を取ります。" #: ../../../discord/ext/commands/hybrid.py:docstring of discord.ext.commands.hybrid.hybrid_command:24 -#: ../../../discord/ext/commands/hybrid.py:docstring of discord.ext.commands.hybrid.hybrid_group:6 +#: ../../../discord/ext/commands/hybrid.py:docstring of discord.ext.commands.hybrid.hybrid_group:9 msgid "Whether to register the command also as an application command." msgstr "アプリケーションコマンドとしてもコマンドを登録するかどうか。" @@ -2220,6 +2224,10 @@ msgstr "関数を :class:`.HybridGroup` に変換するデコレータ。" msgid "This is similar to the :func:`~discord.ext.commands.group` decorator except it creates a hybrid group instead." msgstr "これは :func:`~discord.ext.commands.group` デコレータに似ていますが、代わりにハイブリッドグループを作成します。" +#: ../../../discord/ext/commands/hybrid.py:docstring of discord.ext.commands.hybrid.hybrid_group:6 +msgid "The name to create the group with. By default this uses the function name unchanged." +msgstr "" + #: ../../ext/commands/api.rst:131 msgid "Command" msgstr "Command" @@ -2925,7 +2933,11 @@ msgstr "コグが削除された際に呼び出される特別なメソッド。 msgid "Subclasses must replace this if they want special unloading behaviour." msgstr "サブクラスは特別なアンロード動作が必要な場合にこれを置き換えなければなりません。" -#: ../../../discord/ext/commands/cog.py:docstring of discord.ext.commands.cog.Cog.cog_unload:9 +#: ../../../discord/ext/commands/cog.py:docstring of discord.ext.commands.cog.Cog.cog_unload:7 +msgid "Exceptions raised in this method are ignored during extension unloading." +msgstr "" + +#: ../../../discord/ext/commands/cog.py:docstring of discord.ext.commands.cog.Cog.cog_unload:11 msgid "This method can now be a :term:`coroutine`." msgstr "このメソッドは現在 :term:`coroutine` になりました。" @@ -2947,6 +2959,16 @@ msgstr ":meth:`.Bot.check` チェックとして登録する特別なメソッ msgid "A special method that registers as a :func:`~discord.ext.commands.check` for every command and subcommand in this cog." msgstr "このコグのすべてのコマンドとサブコマンドに対して :func:`~discord.ext.commands.check` として登録する特別なメソッド。" +#: ../../../discord/ext/commands/cog.py:docstring of discord.ext.commands.cog.Cog.interaction_check:1 +#: ../../../discord/ext/commands/cog.py:docstring of discord.ext.commands.cog.Cog.interaction_check:1 +msgid "A special method that registers as a :func:`discord.app_commands.check` for every app command and subcommand in this cog." +msgstr "" + +#: ../../../discord/ext/commands/cog.py:docstring of discord.ext.commands.cog.Cog.interaction_check:4 +#: ../../../discord/ext/commands/cog.py:docstring of discord.ext.commands.cog.Cog.interaction_check:4 +msgid "This function **can** be a coroutine and must take a sole parameter, ``interaction``, to represent the :class:`~discord.Interaction`." +msgstr "" + #: ../../../discord/ext/commands/cog.py:docstring of discord.ext.commands.cog.Cog.cog_command_error:3 msgid "A special method that is called whenever an error is dispatched inside this cog." msgstr "このコグ内でエラーが発生するたびに呼び出される特別なメソッド。" @@ -3019,8 +3041,8 @@ msgid "Decorators such as :func:`~discord.app_commands.guild_only`, :func:`~disc msgstr ":func:`~discord.app_commands.guild_only` 、 :func:`~discord.app_commands.guilds` 、 :func:`~discord.app_commands.default_permissions` のようなデコレータはコグの上に使用されている場合、グループに適用されます。" #: ../../../discord/ext/commands/cog.py:docstring of discord.ext.commands.cog.GroupCog:11 -msgid "Hybrid commands will also be added to the Group, giving the ability categorize slash commands into groups, while keeping the prefix-style command as a root-level command." -msgstr "グループにもハイブリッドコマンドが追加され、プレフィックス形式のコマンドをルートレベルのコマンドとして保持しながら、スラッシュコマンドをグループに分類できるようになります。" +msgid "Hybrid commands will also be added to the Group, giving the ability to categorize slash commands into groups, while keeping the prefix-style command as a root-level command." +msgstr "" #: ../../../discord/ext/commands/cog.py:docstring of discord.ext.commands.cog.GroupCog:14 msgid "For example:" @@ -3311,7 +3333,7 @@ msgstr "コマンドの最大の幅。" #: ../../../discord/ext/commands/help.py:docstring of discord.ext.commands.help.DefaultHelpCommand:12 #: ../../../discord/ext/commands/help.py:docstring of discord.ext.commands.help.DefaultHelpCommand:42 #: ../../../discord/ext/commands/help.py:docstring of discord.ext.commands.help.Paginator:25 -#: ../../../discord/ext/commands/flags.py:docstring of discord.ext.commands.flags.Flag:42 +#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.Context.filesize_limit:5 msgid ":class:`int`" msgstr ":class:`int`" @@ -4423,6 +4445,10 @@ msgstr "「クリーンアップ」されたプレフィックスを返します msgid "Returns the cog associated with this context's command. None if it does not exist." msgstr "このコンテキストのコマンドに関連付けられたコグを返します。存在しない場合はNoneを返します。" +#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.Context.filesize_limit:1 +msgid "Returns the maximum number of bytes files can have when uploaded to this guild or DM channel associated with this context." +msgstr "" + #: ../../docstring of discord.ext.commands.Context.guild:1 msgid "Returns the guild associated with this context's command. None if not available." msgstr "このコンテキストのコマンドに関連付けられているギルドを返します。利用できない場合はNoneを返します。" @@ -4496,72 +4522,6 @@ msgstr "ヘルプを表示するエンティティ。" msgid "The result of the help command, if any." msgstr "もしあれば、ヘルプコマンドの結果。" -#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.reply:3 -msgid "A shortcut method to :meth:`send` to reply to the :class:`~discord.Message` referenced by this context." -msgstr "このコンテキストで参照されている :class:`~discord.Message` に返信するための、 :meth:`send` のショートカットメソッド。" - -#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.reply:6 -msgid "For interaction based contexts, this is the same as :meth:`send`." -msgstr "インタラクションベースのコンテキストでは、 :meth:`send` と同じです。" - -#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.reply:10 -#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.send:13 -msgid "This function will now raise :exc:`TypeError` or :exc:`ValueError` instead of ``InvalidArgument``." -msgstr "この関数は ``InvalidArgument`` の代わりに :exc:`TypeError` または :exc:`ValueError` を発生するようになりました。" - -#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.reply:14 -#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.send:80 -msgid "Sending the message failed." -msgstr "メッセージの送信に失敗しました。" - -#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.reply:15 -#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.send:81 -msgid "You do not have the proper permissions to send the message." -msgstr "メッセージを送信するための適切な権限がありません。" - -#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.reply:16 -msgid "The ``files`` list is not of the appropriate size" -msgstr "``files`` リストの大きさが適切ではありません。" - -#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.reply:17 -msgid "You specified both ``file`` and ``files``." -msgstr "``file`` と ``files`` の両方が指定されています。" - -#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.reply:19 -#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.send:85 -msgid "The message that was sent." -msgstr "送信されたメッセージ。" - -#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.reply:20 -#: ../../../discord/ext/commands/context.py:docstring of discord.abc.Messageable.fetch_message:13 -#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.send:86 -msgid ":class:`~discord.Message`" -msgstr ":class:`~discord.Message`" - -#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.defer:3 -msgid "Defers the interaction based contexts." -msgstr "インタラクションの応答を遅らせます。" - -#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.defer:5 -msgid "This is typically used when the interaction is acknowledged and a secondary action will be done later." -msgstr "これは通常、インタラクションを認識した後、後で他のことを実行する場合に使われます。" - -#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.defer:8 -msgid "If this isn't an interaction based context then it does nothing." -msgstr "これがインタラクションベースのコンテキストでない場合、何もしません。" - -#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.defer:10 -msgid "Indicates whether the deferred message will eventually be ephemeral." -msgstr "遅れて送信するメッセージが一時的になるかを示します。" - -#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.defer:13 -msgid "Deferring the interaction failed." -msgstr "インタラクションの遅延に失敗した場合。" - -#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.defer:14 -msgid "This interaction has already been responded to before." -msgstr "既にインタラクションに応答していた場合。" - #: ../../../discord/ext/commands/context.py:docstring of discord.abc.Messageable.fetch_message:3 msgid "Retrieves a single :class:`~discord.Message` from the destination." msgstr "出力先から、単一の :class:`~discord.Message` を取得します。" @@ -4586,6 +4546,12 @@ msgstr "メッセージの取得に失敗した場合。" msgid "The message asked for." msgstr "要求されたメッセージ。" +#: ../../../discord/ext/commands/context.py:docstring of discord.abc.Messageable.fetch_message:13 +#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.reply:20 +#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.send:91 +msgid ":class:`~discord.Message`" +msgstr ":class:`~discord.Message`" + #: ../../../discord/ext/commands/context.py:docstring of discord.abc.Messageable.history:1 msgid "Returns an :term:`asynchronous iterator` that enables receiving the destination's message history." msgstr "出力先のメッセージ履歴を取得する :term:`asynchronous iterator` を返します。" @@ -4654,6 +4620,66 @@ msgstr "現時点でピン留めされているメッセージ。" msgid "List[:class:`~discord.Message`]" msgstr "List[:class:`~discord.Message`]" +#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.reply:3 +msgid "A shortcut method to :meth:`send` to reply to the :class:`~discord.Message` referenced by this context." +msgstr "このコンテキストで参照されている :class:`~discord.Message` に返信するための、 :meth:`send` のショートカットメソッド。" + +#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.reply:6 +msgid "For interaction based contexts, this is the same as :meth:`send`." +msgstr "インタラクションベースのコンテキストでは、 :meth:`send` と同じです。" + +#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.reply:10 +#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.send:13 +msgid "This function will now raise :exc:`TypeError` or :exc:`ValueError` instead of ``InvalidArgument``." +msgstr "この関数は ``InvalidArgument`` の代わりに :exc:`TypeError` または :exc:`ValueError` を発生するようになりました。" + +#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.reply:14 +#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.send:85 +msgid "Sending the message failed." +msgstr "メッセージの送信に失敗しました。" + +#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.reply:15 +#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.send:86 +msgid "You do not have the proper permissions to send the message." +msgstr "メッセージを送信するための適切な権限がありません。" + +#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.reply:16 +msgid "The ``files`` list is not of the appropriate size" +msgstr "``files`` リストの大きさが適切ではありません。" + +#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.reply:17 +msgid "You specified both ``file`` and ``files``." +msgstr "``file`` と ``files`` の両方が指定されています。" + +#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.reply:19 +#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.send:90 +msgid "The message that was sent." +msgstr "送信されたメッセージ。" + +#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.defer:3 +msgid "Defers the interaction based contexts." +msgstr "インタラクションの応答を遅らせます。" + +#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.defer:5 +msgid "This is typically used when the interaction is acknowledged and a secondary action will be done later." +msgstr "これは通常、インタラクションを認識した後、後で他のことを実行する場合に使われます。" + +#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.defer:8 +msgid "If this isn't an interaction based context then it does nothing." +msgstr "これがインタラクションベースのコンテキストでない場合、何もしません。" + +#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.defer:10 +msgid "Indicates whether the deferred message will eventually be ephemeral." +msgstr "遅れて送信するメッセージが一時的になるかを示します。" + +#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.defer:13 +msgid "Deferring the interaction failed." +msgstr "インタラクションの遅延に失敗した場合。" + +#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.defer:14 +msgid "This interaction has already been responded to before." +msgstr "既にインタラクションに応答していた場合。" + #: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.send:3 msgid "Sends a message to the destination with the content given." msgstr "指定された内容のメッセージを出力先に送信します。" @@ -4742,11 +4768,15 @@ msgstr "メッセージの埋め込みを抑制するかどうか。これが `` msgid "Indicates if the message should only be visible to the user who started the interaction. If a view is sent with an ephemeral message and it has no timeout set then the timeout is set to 15 minutes. **This is only applicable in contexts with an interaction**." msgstr "メッセージがインタラクションを開始したユーザーだけに表示されるかどうか。もしビューが一時的なメッセージで送信されている、かつタイムアウトが設定されていない場合、タイムアウトは15分に設定されます。 **これはインタラクションベースのコンテキストでのみ適用されます。**" -#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.send:82 +#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.send:79 +msgid "Whether to suppress push and desktop notifications for the message. This will increment the mention counter in the UI, but will not actually send a notification." +msgstr "" + +#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.send:87 msgid "The ``files`` list is not of the appropriate size." msgstr "``files`` リストの大きさが適切でない場合。" -#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.send:83 +#: ../../../discord/ext/commands/context.py:docstring of discord.ext.commands.context.Context.send:88 msgid "You specified both ``file`` and ``files``, or you specified both ``embed`` and ``embeds``, or the ``reference`` object is not a :class:`~discord.Message`, :class:`~discord.MessageReference` or :class:`~discord.PartialMessage`." msgstr "``file`` と ``files`` の両方が指定された場合、 ``embed`` と ``embeds`` の両方が指定された場合、または ``reference`` が :class:`~discord.Message` 、 :class:`~discord.MessageReference` 、 :class:`~discord.PartialMessage` でない場合。" @@ -4864,29 +4894,41 @@ msgstr "メンションで検索" #: ../../../discord/ext/commands/converter.py:docstring of discord.ext.commands.converter.MemberConverter:10 #: ../../../discord/ext/commands/converter.py:docstring of discord.ext.commands.converter.UserConverter:9 -msgid "Lookup by name#discrim" -msgstr "名前#タグ で検索" +msgid "Lookup by username#discriminator (deprecated)." +msgstr "" #: ../../../discord/ext/commands/converter.py:docstring of discord.ext.commands.converter.MemberConverter:11 #: ../../../discord/ext/commands/converter.py:docstring of discord.ext.commands.converter.UserConverter:10 -#: ../../../discord/ext/commands/converter.py:docstring of discord.ext.commands.converter.TextChannelConverter:10 -#: ../../../discord/ext/commands/converter.py:docstring of discord.ext.commands.converter.VoiceChannelConverter:10 -#: ../../../discord/ext/commands/converter.py:docstring of discord.ext.commands.converter.StageChannelConverter:12 -msgid "Lookup by name" -msgstr "名前 で検索" +msgid "Lookup by username#0 (deprecated, only gets users that migrated from their discriminator)." +msgstr "" #: ../../../discord/ext/commands/converter.py:docstring of discord.ext.commands.converter.MemberConverter:12 -msgid "Lookup by nickname" -msgstr "ニックネーム で検索" +msgid "Lookup by guild nickname." +msgstr "" + +#: ../../../discord/ext/commands/converter.py:docstring of discord.ext.commands.converter.MemberConverter:13 +#: ../../../discord/ext/commands/converter.py:docstring of discord.ext.commands.converter.UserConverter:11 +msgid "Lookup by global name." +msgstr "" #: ../../../discord/ext/commands/converter.py:docstring of discord.ext.commands.converter.MemberConverter:14 +#: ../../../discord/ext/commands/converter.py:docstring of discord.ext.commands.converter.UserConverter:12 +msgid "Lookup by user name." +msgstr "" + +#: ../../../discord/ext/commands/converter.py:docstring of discord.ext.commands.converter.MemberConverter:16 msgid "Raise :exc:`.MemberNotFound` instead of generic :exc:`.BadArgument`" msgstr "一般的な :exc:`.BadArgument` の代わりに :exc:`.MemberNotFound` を発生させます。" -#: ../../../discord/ext/commands/converter.py:docstring of discord.ext.commands.converter.MemberConverter:17 +#: ../../../discord/ext/commands/converter.py:docstring of discord.ext.commands.converter.MemberConverter:19 msgid "This converter now lazily fetches members from the gateway and HTTP APIs, optionally caching the result if :attr:`.MemberCacheFlags.joined` is enabled." msgstr "このコンバータは、ゲートウェイやHTTP APIからメンバーを取得でき、 :attr:`.MemberCacheFlags.joined` が有効な場合には結果がキャッシュされるようになりました。" +#: ../../../discord/ext/commands/converter.py:docstring of discord.ext.commands.converter.MemberConverter:23 +#: ../../../discord/ext/commands/converter.py:docstring of discord.ext.commands.converter.UserConverter:21 +msgid "Looking up users by discriminator will be removed in a future version due to the removal of discriminators in an API change." +msgstr "" + #: ../../../discord/ext/commands/converter.py:docstring of discord.ext.commands.converter.UserConverter:1 msgid "Converts to a :class:`~discord.User`." msgstr ":class:`~discord.User` に変換します。" @@ -4895,11 +4937,11 @@ msgstr ":class:`~discord.User` に変換します。" msgid "All lookups are via the global user cache." msgstr "すべての検索はグローバルユーザーキャッシュを介して行われます。" -#: ../../../discord/ext/commands/converter.py:docstring of discord.ext.commands.converter.UserConverter:12 +#: ../../../discord/ext/commands/converter.py:docstring of discord.ext.commands.converter.UserConverter:14 msgid "Raise :exc:`.UserNotFound` instead of generic :exc:`.BadArgument`" msgstr "一般的な :exc:`.BadArgument` の代わりに :exc:`.UserNotFound` を発生させます。" -#: ../../../discord/ext/commands/converter.py:docstring of discord.ext.commands.converter.UserConverter:15 +#: ../../../discord/ext/commands/converter.py:docstring of discord.ext.commands.converter.UserConverter:17 msgid "This converter now lazily fetches users from the HTTP APIs if an ID is passed and it's not available in cache." msgstr "このコンバータは、ID が渡され、キャッシュされていない場合、HTTP API からユーザーを取得するようになりました。" @@ -4958,6 +5000,14 @@ msgstr "名前で検索" msgid "Converts to a :class:`~discord.TextChannel`." msgstr ":class:`~discord.TextChannel` に変換します。" +#: ../../../discord/ext/commands/converter.py:docstring of discord.ext.commands.converter.TextChannelConverter:10 +#: ../../../discord/ext/commands/converter.py:docstring of discord.ext.commands.converter.VoiceChannelConverter:10 +#: ../../../discord/ext/commands/converter.py:docstring of discord.ext.commands.converter.StageChannelConverter:12 +#: ../../../discord/ext/commands/converter.py:docstring of discord.ext.commands.converter.CategoryChannelConverter:10 +#: ../../../discord/ext/commands/converter.py:docstring of discord.ext.commands.converter.ForumChannelConverter:10 +msgid "Lookup by name" +msgstr "名前 で検索" + #: ../../../discord/ext/commands/converter.py:docstring of discord.ext.commands.converter.TextChannelConverter:12 #: ../../../discord/ext/commands/converter.py:docstring of discord.ext.commands.converter.VoiceChannelConverter:12 #: ../../../discord/ext/commands/converter.py:docstring of discord.ext.commands.converter.CategoryChannelConverter:12 @@ -5189,11 +5239,19 @@ msgstr "``Range[int, None, 10]`` は最小値なし、最大値10を意味しま msgid "``Range[int, 1, 10]`` means the minimum is 1 and the maximum is 10." msgstr "``Range[int, 1, 10]`` は最小値1、最大値10を意味します。" +#: ../../../discord/ext/commands/converter.py:docstring of discord.ext.commands.converter.Range:12 +msgid "``Range[float, 1.0, 5.0]`` means the minimum is 1.0 and the maximum is 5.0." +msgstr "" + #: ../../../discord/ext/commands/converter.py:docstring of discord.ext.commands.converter.Range:13 +msgid "``Range[str, 1, 10]`` means the minimum length is 1 and the maximum length is 10." +msgstr "" + +#: ../../../discord/ext/commands/converter.py:docstring of discord.ext.commands.converter.Range:15 msgid "Inside a :class:`HybridCommand` this functions equivalently to :class:`discord.app_commands.Range`." msgstr ":class:`HybridCommand` 内では、この関数は :class:`discord.app_commands.Range` と同様に動作します。" -#: ../../../discord/ext/commands/converter.py:docstring of discord.ext.commands.converter.Range:15 +#: ../../../discord/ext/commands/converter.py:docstring of discord.ext.commands.converter.Range:17 msgid "If the value cannot be converted to the provided type or is outside the given range, :class:`~.ext.commands.BadArgument` or :class:`~.ext.commands.RangeError` is raised to the appropriate error handlers respectively." msgstr "もし値が渡された型に変換できず、または指定された範囲外である場合、:class:`~.ext.commands.BadArgument` や :class:`~.ext.commands.RangeError` が適切なエラーハンドラに送出されます。" @@ -5412,6 +5470,11 @@ msgstr "このパラメータの説明。" msgid "The displayed default in :class:`Command.signature`." msgstr ":class:`Command.signature` で表示される既定値。" +#: ../../../discord/ext/commands/parameters.py:docstring of discord.ext.commands.Parameter.displayed_name:1 +#: ../../../discord/ext/commands/parameters.py:docstring of discord.ext.commands.parameters.parameter:24 +msgid "The name that is displayed to the user." +msgstr "" + #: ../../../discord/ext/commands/parameters.py:docstring of discord.ext.commands.parameters.Parameter.get_default:3 msgid "Gets this parameter's default value." msgstr "このパラメータの既定値を取得します。" @@ -5441,8 +5504,8 @@ msgid "The displayed default in :attr:`Command.signature`." msgstr ":attr:`Command.signature` で表示される既定値。" #: ../../../discord/ext/commands/parameters.py:docstring of discord.ext.commands.parameters.parameter:1 -msgid "param(\\*, converter=..., default=..., description=..., displayed_default=...)" -msgstr "param(\\*, converter=..., default=..., description=..., displayed_default=...)" +msgid "param(\\*, converter=..., default=..., description=..., displayed_default=..., displayed_name=...)" +msgstr "" #: ../../../discord/ext/commands/parameters.py:docstring of discord.ext.commands.parameters.parameter:3 msgid "An alias for :func:`parameter`." @@ -5628,6 +5691,10 @@ msgstr "比較が失敗した値の、失敗した順のタプル。" msgid "Tuple[Any, ``...``]" msgstr "Tuple[Any, ``...``]" +#: ../../../discord/ext/commands/errors.py:docstring of discord.ext.commands.errors.BadLiteralArgument:28 +msgid "The argument's value that failed to be converted. Defaults to an empty string." +msgstr "" + #: ../../../discord/ext/commands/errors.py:docstring of discord.ext.commands.errors.PrivateMessageOnly:1 msgid "Exception raised when an operation does not work outside of private message contexts." msgstr "プライベートメッセージコンテキスト外で、要求された処理が実行できない場合に発生する例外。" diff --git a/docs/locale/ja/LC_MESSAGES/ext/commands/cogs.po b/docs/locale/ja/LC_MESSAGES/ext/commands/cogs.po index e7b02f5e6..2f87a1d27 100644 --- a/docs/locale/ja/LC_MESSAGES/ext/commands/cogs.po +++ b/docs/locale/ja/LC_MESSAGES/ext/commands/cogs.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: discordpy\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-01-24 11:01+0000\n" -"PO-Revision-Date: 2023-01-30 13:38\n" +"POT-Creation-Date: 2023-06-21 01:17+0000\n" +"PO-Revision-Date: 2023-06-21 01:20\n" "Last-Translator: \n" "Language-Team: Japanese\n" "MIME-Version: 1.0\n" diff --git a/docs/locale/ja/LC_MESSAGES/ext/commands/commands.po b/docs/locale/ja/LC_MESSAGES/ext/commands/commands.po index bac6599fa..ee29cded9 100644 --- a/docs/locale/ja/LC_MESSAGES/ext/commands/commands.po +++ b/docs/locale/ja/LC_MESSAGES/ext/commands/commands.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: discordpy\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-01-24 11:01+0000\n" -"PO-Revision-Date: 2023-01-30 13:38\n" +"POT-Creation-Date: 2023-06-21 01:17+0000\n" +"PO-Revision-Date: 2023-06-21 01:20\n" "Last-Translator: \n" "Language-Team: Japanese\n" "MIME-Version: 1.0\n" @@ -953,103 +953,103 @@ msgstr "もし例外が発生した場合、 :ref:`エラーハンドラ`." +msgstr "**サンプル:** :resource:`レポジトリ ` にたくさんのサンプルが用意されています。" -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop:1 -msgid "A background task helper that abstracts the loop and reconnection logic for you." -msgstr "ループと再接続処理を抽象化するバックグラウンドタスクのヘルパー。" +#: ../../index.rst:33 +msgid "Getting help" +msgstr "ヘルプの参照" -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop:3 -msgid "The main interface to create this is through :func:`loop`." -msgstr ":func:`loop` はこれを作成するための主要なインタフェースです。" +#: ../../index.rst:35 +msgid "If you're having trouble with something, these resources might help." +msgstr "何か困ることがあれば、次のリソースが役立つかもしれません。" -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.after_loop:1 -msgid "A decorator that registers a coroutine to be called after the loop finishes running." -msgstr "ループが終了した後に呼び出されるようにコルーチンを登録するデコレータ。" +#: ../../index.rst:37 +msgid "Try the :doc:`faq` first, it's got answers to all common questions." +msgstr "初めに :doc:`faq` を確認してみましょう。よくある質問に対する回答がまとめられています。" -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.after_loop:3 -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.before_loop:6 -msgid "The coroutine must take no arguments (except ``self`` in a class context)." -msgstr "コルーチンは、引数をとりません。(クラス内での ``self`` は除く)" +#: ../../index.rst:38 +msgid "Ask us and hang out with us in our :resource:`Discord ` server." +msgstr ":resource:`Discord ` サーバーに参加し、質問してみましょう。" -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.after_loop:7 -msgid "This coroutine is called even during cancellation. If it is desirable to tell apart whether something was cancelled or not, check to see whether :meth:`is_being_cancelled` is ``True`` or not." -msgstr "このコルーチンはキャンセル中も呼び出されます。キャンセルされたかを判断したい場合は、 :meth:`is_being_cancelled` が ``True`` か確認してください。" +#: ../../index.rst:39 +msgid "If you're looking for something specific, try the :ref:`index ` or :ref:`searching `." +msgstr "特定の何かを探している場合は :ref:`目次 ` か :ref:`検索 ` を利用してください。" -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.after_loop:0 -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.before_loop:0 -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.error:0 -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.__call__:0 -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.start:0 -msgid "Parameters" -msgstr "パラメーター" +#: ../../index.rst:40 +msgid "Report bugs in the :resource:`issue tracker `." +msgstr "バグは :resource:`issue tracker ` で報告してください。" -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.after_loop:11 -msgid "The coroutine to register after the loop finishes." -msgstr "ループが終了した後に登録するコルーチン。" +#: ../../index.rst:41 +msgid "Ask in our :resource:`GitHub discussions page `." +msgstr ":resource:`GitHubの議論ページ ` で質問してください。" -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.after_loop:0 -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.before_loop:0 -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.error:0 -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.start:0 -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.add_exception_type:0 -msgid "Raises" -msgstr "例外" +#: ../../index.rst:44 +msgid "Extensions" +msgstr "拡張機能" -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.after_loop:14 -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.before_loop:14 -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.error:17 -msgid "The function was not a coroutine." -msgstr "渡された関数がコルーチンでない場合に発生します。" +#: ../../index.rst:46 +msgid "These extensions help you during development when it comes to common tasks." +msgstr "この拡張機能は開発中におけるよくあるタスクを解決するのに役立ちます。" -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.before_loop:1 -msgid "A decorator that registers a coroutine to be called before the loop starts running." -msgstr "ループが始まる前に呼び出されるようにコルーチンを登録するデコレータ。" +#: ../../index.rst:55 +msgid "Manuals" +msgstr "マニュアル" -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.before_loop:3 -msgid "This is useful if you want to wait for some bot state before the loop starts, such as :meth:`discord.Client.wait_until_ready`." -msgstr ":meth:`discord.Client.wait_until_ready` などを用いて、ループ開始前にボットの状態が整うまで待つのに便利です。" +#: ../../index.rst:57 +msgid "These pages go into great detail about everything the API can do." +msgstr "次のページではAPIができることすべてについて詳細に解説しています。" -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.before_loop:8 -msgid "Calling :meth:`stop` in this coroutine will stop the loop before the initial iteration is run." -msgstr "このコルーチンで :meth:`stop` を呼び出すと、最初のループの実行前にループが停止します。" +#: ../../index.rst:68 +msgid "Meta" +msgstr "メタ情報" -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.before_loop:11 -msgid "The coroutine to register before the loop runs." -msgstr "ループを実行する前に登録するコルーチンを示します。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.error:1 -msgid "A decorator that registers a coroutine to be called if the task encounters an unhandled exception." -msgstr "タスクが未処理の例外に遭遇した場合に呼び出されるコルーチンを登録するデコレータ。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.error:3 -msgid "The coroutine must take only one argument the exception raised (except ``self`` in a class context)." -msgstr "コルーチンは、送出された例外を引数としてとります。(クラス内での ``self`` は除く)" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.error:5 -msgid "By default this logs to the library logger however it could be overridden to have a different implementation." -msgstr "デフォルトではライブラリロガーに出力しますが、他の実装をするために上書きすることもできます。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.error:12 -msgid "Instead of writing to ``sys.stderr``, the library's logger is used." -msgstr "``sys.stderr`` に出力する代わりに、ライブラリロガーを使用するようになりました。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.error:14 -msgid "The coroutine to register in the event of an unhandled exception." -msgstr "処理されない例外が発生した場合に登録するコルーチンを示します。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.seconds:1 -msgid "Read-only value for the number of seconds between each iteration. ``None`` if an explicit ``time`` value was passed instead." -msgstr "各ループ間の秒数を示す読み取り専用の値です。代わりに、明示的な ``time`` が渡された場合は ``None`` となります。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.seconds:0 -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.minutes:0 -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.hours:0 -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.time:0 -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.current_loop:0 -msgid "type" -msgstr "型" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.seconds:6 -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.minutes:6 -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.hours:6 -msgid "Optional[:class:`float`]" -msgstr "Optional[:class:`float`]" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.minutes:1 -msgid "Read-only value for the number of minutes between each iteration. ``None`` if an explicit ``time`` value was passed instead." -msgstr "各ループ間の分数を示す読み取り専用の値です。代わりに、明示的な ``time`` が渡された場合は ``None`` となります。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.hours:1 -msgid "Read-only value for the number of hours between each iteration. ``None`` if an explicit ``time`` value was passed instead." -msgstr "各ループ間の時間数を示す読み取り専用の値です。代わりに、明示的な ``time`` が渡された場合は ``None`` となります。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.time:1 -msgid "Read-only list for the exact times this loop runs at. ``None`` if relative times were passed instead." -msgstr "このループが実行する正確な時刻の読み取り専用のリスト。 相対的な時間が代わりに渡された場合は ``None`` 。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.time:6 -msgid "Optional[List[:class:`datetime.time`]]" -msgstr "Optional[List[:class:`datetime.time`]]" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.current_loop:1 -msgid "The current iteration of the loop." -msgstr "ループの現在の実行回数。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.current_loop:3 -msgid ":class:`int`" -msgstr ":class:`int`" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.next_iteration:1 -msgid "When the next iteration of the loop will occur." -msgstr "次のループがいつ発火するか。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.next_iteration:5 -msgid "Optional[:class:`datetime.datetime`]" -msgstr "Optional[:class:`datetime.datetime`]" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.__call__:1 -msgid "|coro|" -msgstr "|coro|" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.__call__:3 -msgid "Calls the internal callback that the task holds." -msgstr "タスク内のコールバックを呼び出します。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.__call__:7 -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.start:3 -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.restart:8 -msgid "The arguments to use." -msgstr "使用する引数。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.__call__:8 -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.start:4 -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.restart:9 -msgid "The keyword arguments to use." -msgstr "使用するキーワード引数。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.start:1 -msgid "Starts the internal task in the event loop." -msgstr "イベントループでタスクを開始します。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.start:6 -msgid "A task has already been launched and is running." -msgstr "タスクはすでに実行されていて実行中である。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.start:0 -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.remove_exception_type:0 -msgid "Returns" -msgstr "戻り値" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.start:8 -msgid "The task that has been created." -msgstr "作成されたタスク。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.start:0 -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.remove_exception_type:0 -msgid "Return type" -msgstr "戻り値の型" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.start:9 -msgid ":class:`asyncio.Task`" -msgstr ":class:`asyncio.Task`" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.stop:1 -msgid "Gracefully stops the task from running." -msgstr "タスクを正常に停止させます。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.stop:3 -msgid "Unlike :meth:`cancel`\\, this allows the task to finish its current iteration before gracefully exiting." -msgstr ":meth:`cancel` とは違い、現在のループが終了してからタスクを終了します。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.stop:8 -msgid "If the internal function raises an error that can be handled before finishing then it will retry until it succeeds." -msgstr "もし終了前に内部の関数が処理可能な例外を送出した場合は成功するまで再試行します。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.stop:12 -msgid "If this is undesirable, either remove the error handling before stopping via :meth:`clear_exception_types` or use :meth:`cancel` instead." -msgstr "この動作が望ましくない場合は、停止前に :meth:`clear_exception_types` を用いてエラー処理を除去するか、代わりに :meth:`cancel` を使用してください。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.stop:16 -msgid "Calling this method in :meth:`before_loop` will stop the loop before the initial iteration is run." -msgstr ":meth:`before_loop` にてこのメソッドを呼び出すと、最初のループの実行前にループが停止します。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.cancel:1 -msgid "Cancels the internal task, if it is running." -msgstr "実行中の場合、内部のタスクをキャンセルします。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.restart:1 -msgid "A convenience method to restart the internal task." -msgstr "内部タスクを再起動する便利なメソッド。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.restart:5 -msgid "Due to the way this function works, the task is not returned like :meth:`start`." -msgstr "この関数の動作により、 :meth:`start` とは異なりタスクは返されません。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.add_exception_type:1 -msgid "Adds exception types to be handled during the reconnect logic." -msgstr "再接続中に処理する例外を追加します。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.add_exception_type:3 -msgid "By default the exception types handled are those handled by :meth:`discord.Client.connect`\\, which includes a lot of internet disconnection errors." -msgstr "デフォルトでは、 :meth:`discord.Client.connect` によって処理される例外は、多くのインターネット切断エラーを含んでいます。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.add_exception_type:7 -msgid "This function is useful if you're interacting with a 3rd party library that raises its own set of exceptions." -msgstr "独自の例外を送出するサードパーティー・ライブラリーを使用している場合に便利です。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.add_exception_type:10 -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.remove_exception_type:3 -msgid "An argument list of exception classes to handle." -msgstr "処理するべき例外のクラスの引数リスト。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.add_exception_type:13 -msgid "An exception passed is either not a class or not inherited from :class:`BaseException`." -msgstr "渡された例外はクラスでないか、 :class:`BaseException` を継承していません。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.clear_exception_types:1 -msgid "Removes all exception types that are handled." -msgstr "処理すべき例外タイプをすべて除去します。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.clear_exception_types:5 -msgid "This operation obviously cannot be undone!" -msgstr "この動作は当然ながら元に戻せません!" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.remove_exception_type:1 -msgid "Removes exception types from being handled during the reconnect logic." -msgstr "再接続中に処理する例外を除去します:" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.remove_exception_type:6 -msgid "Whether all exceptions were successfully removed." -msgstr "すべての例外が正常に除去されたかどうか。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.remove_exception_type:7 -msgid ":class:`bool`" -msgstr ":class:`bool`" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.get_task:1 -msgid "Optional[:class:`asyncio.Task`]: Fetches the internal task or ``None`` if there isn't one running." -msgstr "Optional[:class:`asyncio.Task`]: 内部タスクを取得します。実行中のものがない場合は ``None`` を返します。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.is_being_cancelled:1 -msgid "Whether the task is being cancelled." -msgstr "タスクがキャンセルされているかどうか。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.failed:1 -msgid ":class:`bool`: Whether the internal task has failed." -msgstr ":class:`bool`: 内部タスクが失敗したかどうか。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.is_running:1 -msgid ":class:`bool`: Check if the task is currently running." -msgstr ":class:`bool`: タスクが現在実行されているか確認します。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.change_interval:1 -msgid "Changes the interval for the sleep time." -msgstr "ループ間の間隔を変更します。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.change_interval:5 -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.loop:4 -msgid "The number of seconds between every iteration." -msgstr "各繰り返しの間の秒数。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.change_interval:7 -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.loop:6 -msgid "The number of minutes between every iteration." -msgstr "各繰り返しの間の 分数。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.change_interval:9 -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.loop:8 -msgid "The number of hours between every iteration." -msgstr "各繰り返しの間の時間数。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.change_interval:11 -msgid "The exact times to run this loop at. Either a non-empty list or a single value of :class:`datetime.time` should be passed. This cannot be used in conjunction with the relative time parameters." -msgstr "ループを実行する正確な時刻。空でないリストか、 :class:`datetime.time` が単独で渡されるべきです。これは、相対的な時間を指定するパラメーターと同時に使用できません。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.change_interval:19 -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.loop:18 -msgid "Duplicate times will be ignored, and only run once." -msgstr "重複された時刻は無視され、一回だけ実行されます。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.change_interval:22 -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.loop:30 -msgid "An invalid value was given." -msgstr "無効な値が渡された場合。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.Loop.change_interval:23 -msgid "An invalid value for the ``time`` parameter was passed, or the ``time`` parameter was passed in conjunction with relative time parameters." -msgstr "``time`` パラメーターに無効な値が渡され、または ``time`` パラメーターと相対的な時間を指定するパラメーターが同時に使用された場合。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.loop:1 -msgid "A decorator that schedules a task in the background for you with optional reconnect logic. The decorator returns a :class:`Loop`." -msgstr "バックグラウンドでタスクをスケジュールし、オプションで再接続ロジックを扱うデコレータ。デコレータは :class:`Loop` を返します。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.loop:10 -msgid "The exact times to run this loop at. Either a non-empty list or a single value of :class:`datetime.time` should be passed. Timezones are supported. If no timezone is given for the times, it is assumed to represent UTC time." -msgstr "ループを実行する正確な時刻。空でないリストか、 :class:`datetime.time` が単独で渡されるべきです。タイムゾーンが使用できます。タイムゾーンが設定されてない場合はUTCと推定されます。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.loop:14 -msgid "This cannot be used in conjunction with the relative time parameters." -msgstr "これは、相対的な時間を指定するパラメーターと同時に使用できません。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.loop:22 -msgid "The number of loops to do, ``None`` if it should be an infinite loop." -msgstr "実行するループの回数、無限ループの場合は ``None`` です。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.loop:25 -msgid "Whether to handle errors and restart the task using an exponential back-off algorithm similar to the one used in :meth:`discord.Client.connect`." -msgstr "エラーを処理し、 :meth:`discord.Client.connect` で使用されたものと同様のエクスポネンシャルバックオフ アルゴリズムを用いてタスクを再起動するべきか。" - -#: ../../../discord/ext/tasks/__init__.py:docstring of discord.ext.tasks.loop:31 -msgid "The function was not a coroutine, an invalid value for the ``time`` parameter was passed, or ``time`` parameter was passed in conjunction with relative time parameters." -msgstr "関数がコルーチンではない場合、 ``time`` パラメーターに無効な値が渡された場合、または ``time`` パラメーターと相対的な時間を指定するパラメーターが同時に使用された場合。" +#: ../../index.rst:70 +msgid "If you're looking for something related to the project itself, it's here." +msgstr "プロジェクト自体に関連するものを探しているのであれば、こちらを参照してください。" diff --git a/docs/locale/ja/LC_MESSAGES/faq.po b/docs/locale/ja/LC_MESSAGES/faq.po index ded5a115c..4fb1dddff 100644 --- a/docs/locale/ja/LC_MESSAGES/faq.po +++ b/docs/locale/ja/LC_MESSAGES/faq.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: discordpy\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-01-24 11:01+0000\n" -"PO-Revision-Date: 2023-01-30 13:38\n" +"POT-Creation-Date: 2023-06-21 01:17+0000\n" +"PO-Revision-Date: 2024-04-17 02:43\n" "Last-Translator: \n" "Language-Team: Japanese\n" "MIME-Version: 1.0\n" diff --git a/docs/locale/ja/LC_MESSAGES/index.po b/docs/locale/ja/LC_MESSAGES/index.po index 0085505c3..8a934b4e0 100644 --- a/docs/locale/ja/LC_MESSAGES/index.po +++ b/docs/locale/ja/LC_MESSAGES/index.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: discordpy\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-01-24 11:01+0000\n" -"PO-Revision-Date: 2023-01-30 13:38\n" +"POT-Creation-Date: 2023-06-21 01:17+0000\n" +"PO-Revision-Date: 2023-06-21 01:20\n" "Last-Translator: \n" "Language-Team: Japanese\n" "MIME-Version: 1.0\n" diff --git a/docs/locale/ja/LC_MESSAGES/intents.po b/docs/locale/ja/LC_MESSAGES/intents.po index a3636d1e0..907f1e79e 100644 --- a/docs/locale/ja/LC_MESSAGES/intents.po +++ b/docs/locale/ja/LC_MESSAGES/intents.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: discordpy\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-01-24 11:01+0000\n" -"PO-Revision-Date: 2023-01-30 13:38\n" +"POT-Creation-Date: 2023-06-21 01:17+0000\n" +"PO-Revision-Date: 2024-04-17 02:43\n" "Last-Translator: \n" "Language-Team: Japanese\n" "MIME-Version: 1.0\n" diff --git a/docs/locale/ja/LC_MESSAGES/interactions/api.po b/docs/locale/ja/LC_MESSAGES/interactions/api.po index 00d66e935..a65a799f7 100644 --- a/docs/locale/ja/LC_MESSAGES/interactions/api.po +++ b/docs/locale/ja/LC_MESSAGES/interactions/api.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: discordpy\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-01-29 20:45+0000\n" -"PO-Revision-Date: 2023-01-30 13:38\n" +"POT-Creation-Date: 2023-06-21 01:17+0000\n" +"PO-Revision-Date: 2023-06-21 01:20\n" "Last-Translator: \n" "Language-Team: Japanese\n" "MIME-Version: 1.0\n" @@ -62,7 +62,7 @@ msgid "type" msgstr "型" #: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:12 -#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:36 +#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:38 #: ../../../discord/message.py:docstring of discord.message.MessageInteraction:23 #: ../../../discord/components.py:docstring of discord.components.SelectMenu:36 #: ../../../discord/components.py:docstring of discord.components.SelectMenu:43 @@ -84,7 +84,7 @@ msgid "The guild ID the interaction was sent from." msgstr "インタラクションが送信されたギルドのID。" #: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:24 -#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:30 +#: ../../../discord/interactions.py:docstring of discord.Interaction.channel_id:3 #: ../../../discord/components.py:docstring of discord.components.TextInput:49 #: ../../../discord/components.py:docstring of discord.components.TextInput:55 #: ../../../discord/app_commands/models.py:docstring of discord.app_commands.models.AppCommand:91 @@ -92,39 +92,47 @@ msgid "Optional[:class:`int`]" msgstr "Optional[:class:`int`]" #: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:28 -msgid "The channel ID the interaction was sent from." -msgstr "インタラクションが送信されたチャンネルのID。" +msgid "The channel the interaction was sent from." +msgstr "インタラクションが送信されたチャンネル。" + +#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:30 +msgid "Note that due to a Discord limitation, if sent from a DM channel :attr:`~DMChannel.recipient` is ``None``." +msgstr "" + +#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:32 +msgid "Optional[Union[:class:`abc.GuildChannel`, :class:`abc.PrivateChannel`, :class:`Thread`]]" +msgstr "" -#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:34 +#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:36 msgid "The application ID that the interaction was for." msgstr "インタラクションの対象となったアプリケーションのID。" -#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:40 +#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:42 msgid "The user or member that sent the interaction." msgstr "インタラクションを送信したユーザーまたはメンバー。" -#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:42 +#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:44 #: ../../../discord/message.py:docstring of discord.message.MessageInteraction:41 msgid "Union[:class:`User`, :class:`Member`]" msgstr "Union[:class:`User`, :class:`Member`]" -#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:46 +#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:48 msgid "The message that sent this interaction." msgstr "このインタラクションを送信したメッセージ。" -#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:48 +#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:50 msgid "This is only available for :attr:`InteractionType.component` interactions." msgstr "これは :attr:`InteractionType.component` インタラクションの場合にのみ使用できます。" -#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:50 +#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:52 msgid "Optional[:class:`Message`]" msgstr "Optional[:class:`Message`]" -#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:54 +#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:56 msgid "The token to continue the interaction. These are valid for 15 minutes." msgstr "インタラクションを続行するのに使うトークン。有効期限は15分です。" -#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:57 +#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:59 #: ../../docstring of discord.InteractionMessage.clean_content:15 #: ../../../discord/interactions.py:docstring of discord.InteractionMessage.jump_url:3 #: ../../docstring of discord.InteractionMessage.system_content:8 @@ -132,43 +140,43 @@ msgstr "インタラクションを続行するのに使うトークン。有効 msgid ":class:`str`" msgstr ":class:`str`" -#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:61 +#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:63 msgid "The raw interaction data." msgstr "生のインタラクションデータ。" -#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:63 -#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:83 +#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:65 +#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:85 #: ../../../discord/app_commands/commands.py:docstring of discord.app_commands.commands.Command:95 #: ../../../discord/app_commands/commands.py:docstring of discord.app_commands.commands.ContextMenu:80 #: ../../../discord/app_commands/commands.py:docstring of discord.app_commands.commands.Group:105 msgid ":class:`dict`" msgstr ":class:`dict`" -#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:67 +#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:69 msgid "The locale of the user invoking the interaction." msgstr "インタラクションを呼び出したユーザーのロケール。" -#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:69 +#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:71 msgid ":class:`Locale`" msgstr ":class:`Locale`" -#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:73 +#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:75 msgid "The preferred locale of the guild the interaction was sent from, if any." msgstr "インタラクションの送信元のギルドの優先ロケール。もし無ければ ``None`` となります。" -#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:75 +#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:77 msgid "Optional[:class:`Locale`]" msgstr "Optional[:class:`Locale`]" -#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:79 +#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:81 msgid "A dictionary that can be used to store extraneous data for use during interaction processing. The library will not touch any values or keys within this dictionary." msgstr "インタラクションの処理中に使用する追加のデータを保管できる辞書型。ライブラリは辞書型の中のキーや値を一切操作しません。" -#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:87 +#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:89 msgid "Whether the command associated with this interaction failed to execute. This includes checks and execution." msgstr "このインタラクションに関連付けられたコマンドの実行に失敗したかどうか。これにはチェックとコマンドの実行が含まれます。" -#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:90 +#: ../../../discord/interactions.py:docstring of discord.interactions.Interaction:92 #: ../../../discord/components.py:docstring of discord.components.Button:35 #: ../../../discord/components.py:docstring of discord.components.SelectMenu:55 #: ../../../discord/components.py:docstring of discord.components.TextInput:43 @@ -196,17 +204,9 @@ msgstr "インタラクションが送信されたギルド。" msgid "Optional[:class:`Guild`]" msgstr "Optional[:class:`Guild`]" -#: ../../docstring of discord.Interaction.channel:1 -msgid "The channel the interaction was sent from." -msgstr "インタラクションが送信されたチャンネル。" - -#: ../../docstring of discord.Interaction.channel:3 -msgid "Note that due to a Discord limitation, DM channels are not resolved since there is no data to complete them. These are :class:`PartialMessageable` instead." -msgstr "Discordの制限により、DMチャンネルは入れるデータがないため解決されないことに注意してください。代わりに :class:`PartialMessageable` があります。" - -#: ../../docstring of discord.Interaction.channel:6 -msgid "Optional[Union[:class:`abc.GuildChannel`, :class:`PartialMessageable`, :class:`Thread`]]" -msgstr "Optional[Union[:class:`abc.GuildChannel`, :class:`PartialMessageable`, :class:`Thread`]]" +#: ../../../discord/interactions.py:docstring of discord.Interaction.channel_id:1 +msgid "The ID of the channel the interaction was sent from." +msgstr "" #: ../../../discord/interactions.py:docstring of discord.Interaction.permissions:1 msgid "The resolved permissions of the member in the channel, including overwrites." @@ -430,7 +430,7 @@ msgid "You specified both ``embed`` and ``embeds``" msgstr "``embed`` と ``embeds`` の両方を指定した場合。" #: ../../../discord/interactions.py:docstring of discord.interactions.Interaction.edit_original_response:36 -#: ../../../discord/interactions.py:docstring of discord.interactions.InteractionResponse.send_message:39 +#: ../../../discord/interactions.py:docstring of discord.interactions.InteractionResponse.send_message:44 #: ../../../discord/interactions.py:docstring of discord.interactions.InteractionMessage.edit:35 msgid "The length of ``embeds`` was invalid." msgstr "``embeds`` の長さが無効だった場合。" @@ -564,7 +564,7 @@ msgstr "インタラクションの遅延に失敗した場合。" #: ../../../discord/interactions.py:docstring of discord.interactions.InteractionResponse.defer:25 #: ../../../discord/interactions.py:docstring of discord.interactions.InteractionResponse.pong:8 -#: ../../../discord/interactions.py:docstring of discord.interactions.InteractionResponse.send_message:40 +#: ../../../discord/interactions.py:docstring of discord.interactions.InteractionResponse.send_message:45 #: ../../../discord/interactions.py:docstring of discord.interactions.InteractionResponse.edit_message:35 #: ../../../discord/interactions.py:docstring of discord.interactions.InteractionResponse.send_modal:9 msgid "This interaction has already been responded to before." @@ -623,16 +623,20 @@ msgid "Whether to suppress embeds for the message. This sends the message withou msgstr "メッセージの埋め込みを抑制するかどうか。これが ``True`` に設定されている場合、埋め込みなしでメッセージを送信します。" #: ../../../discord/interactions.py:docstring of discord.interactions.InteractionResponse.send_message:30 +msgid "Whether to suppress push and desktop notifications for the message. This will increment the mention counter in the UI, but will not actually send a notification." +msgstr "" + +#: ../../../discord/interactions.py:docstring of discord.interactions.InteractionResponse.send_message:35 #: ../../../discord/interactions.py:docstring of discord.interactions.InteractionMessage.edit:25 msgid "If provided, the number of seconds to wait in the background before deleting the message we just sent. If the deletion fails, then it is silently ignored." msgstr "指定すると、これはメッセージを送信したあと削除するまでにバックグラウンドで待機する秒数となります。もし削除が失敗しても、それは静かに無視されます。" -#: ../../../discord/interactions.py:docstring of discord.interactions.InteractionResponse.send_message:37 +#: ../../../discord/interactions.py:docstring of discord.interactions.InteractionResponse.send_message:42 #: ../../../discord/interactions.py:docstring of discord.message.PartialMessage.reply:12 msgid "Sending the message failed." msgstr "メッセージの送信に失敗した場合。" -#: ../../../discord/interactions.py:docstring of discord.interactions.InteractionResponse.send_message:38 +#: ../../../discord/interactions.py:docstring of discord.interactions.InteractionResponse.send_message:43 msgid "You specified both ``embed`` and ``embeds`` or ``file`` and ``files``." msgstr "``embed`` と ``embeds`` または ``file`` と ``files`` の両方を指定した場合。" @@ -851,34 +855,42 @@ msgid "The name of the thread." msgstr "スレッドの名前。" #: ../../../discord/interactions.py:docstring of discord.message.PartialMessage.create_thread:14 -msgid "The duration in minutes before a thread is automatically archived for inactivity. If not provided, the channel's default auto archive duration is used." -msgstr "スレッドが非アクティブ時に、自動的にアーカイブされるまでの分単位の長さ。指定しない場合は、チャンネルのデフォルトの自動アーカイブ期間が使用されます。" +msgid "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." +msgstr "" + +#: ../../../discord/interactions.py:docstring of discord.message.PartialMessage.create_thread:14 +msgid "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." +msgstr "" #: ../../../discord/interactions.py:docstring of discord.message.PartialMessage.create_thread:17 +msgid "Must be one of ``60``, ``1440``, ``4320``, or ``10080``, if provided." +msgstr "" + +#: ../../../discord/interactions.py:docstring of discord.message.PartialMessage.create_thread:19 msgid "Specifies the slowmode rate limit for user in this channel, in seconds. The maximum value possible is ``21600``. By default no slowmode rate limit if this is ``None``." msgstr "このチャンネルの秒単位での低速モードレート制限。 最大値は ``21600`` です。デフォルトは ``None`` でこの場合は低速モードレート制限が無しとなります。" -#: ../../../discord/interactions.py:docstring of discord.message.PartialMessage.create_thread:21 +#: ../../../discord/interactions.py:docstring of discord.message.PartialMessage.create_thread:23 msgid "The reason for creating a new thread. Shows up on the audit log." msgstr "スレッドを作成する理由。監査ログに表示されます。" -#: ../../../discord/interactions.py:docstring of discord.message.PartialMessage.create_thread:24 +#: ../../../discord/interactions.py:docstring of discord.message.PartialMessage.create_thread:26 msgid "You do not have permissions to create a thread." msgstr "スレッドを作成する権限を持っていない場合。" -#: ../../../discord/interactions.py:docstring of discord.message.PartialMessage.create_thread:25 +#: ../../../discord/interactions.py:docstring of discord.message.PartialMessage.create_thread:27 msgid "Creating the thread failed." msgstr "スレッドの作成に失敗した場合。" -#: ../../../discord/interactions.py:docstring of discord.message.PartialMessage.create_thread:26 +#: ../../../discord/interactions.py:docstring of discord.message.PartialMessage.create_thread:28 msgid "This message does not have guild info attached." msgstr "メッセージがギルド情報を持っていない場合。" -#: ../../../discord/interactions.py:docstring of discord.message.PartialMessage.create_thread:28 +#: ../../../discord/interactions.py:docstring of discord.message.PartialMessage.create_thread:30 msgid "The created thread." msgstr "作成されたスレッド" -#: ../../../discord/interactions.py:docstring of discord.message.PartialMessage.create_thread:29 +#: ../../../discord/interactions.py:docstring of discord.message.PartialMessage.create_thread:31 msgid ":class:`.Thread`" msgstr ":class:`Thread`" @@ -957,22 +969,22 @@ msgid "Pinning the message failed, probably due to the channel having more t msgstr "チャンネルにすでに50個ピン留めされたメッセージがあるなどの理由で、メッセージのピン留めに失敗した場合。" #: ../../../discord/interactions.py:docstring of discord.message.PartialMessage.publish:3 -msgid "Publishes this message to your announcement channel." -msgstr "このメッセージをアナウンスチャンネルに公開します。" +msgid "Publishes this message to the channel's followers." +msgstr "" #: ../../../discord/interactions.py:docstring of discord.message.PartialMessage.publish:5 -msgid "You must have :attr:`~Permissions.send_messages` to do this." -msgstr "これを行うには、 :attr:`~Permissions.send_messages` が必要です。" +msgid "The message must have been sent in a news channel. You must have :attr:`~Permissions.send_messages` to do this." +msgstr "" -#: ../../../discord/interactions.py:docstring of discord.message.PartialMessage.publish:7 +#: ../../../discord/interactions.py:docstring of discord.message.PartialMessage.publish:8 msgid "If the message is not your own then :attr:`~Permissions.manage_messages` is also needed." msgstr "自身のメッセージ以外の場合は :attr:`~Permissions.manage_messages` も必要です。" -#: ../../../discord/interactions.py:docstring of discord.message.PartialMessage.publish:10 -msgid "You do not have the proper permissions to publish this message." -msgstr "メッセージを公開する適切な権限がない場合。" - #: ../../../discord/interactions.py:docstring of discord.message.PartialMessage.publish:11 +msgid "You do not have the proper permissions to publish this message or the channel is not a news channel." +msgstr "" + +#: ../../../discord/interactions.py:docstring of discord.message.PartialMessage.publish:12 msgid "Publishing the message failed." msgstr "メッセージの公開に失敗した場合。" @@ -1843,8 +1855,8 @@ msgid "The user's ID that archived this thread." msgstr "このスレッドをアーカイブしたユーザーのID。" #: ../../../discord/app_commands/models.py:docstring of discord.app_commands.models.AppCommandThread:87 -msgid "The duration in minutes until the thread is automatically archived due to inactivity. Usually a value of 60, 1440, 4320 and 10080." -msgstr "非アクティブのスレッドが自動的にアーカイブされるまでの分数です。通常は60、1440、4320、10080の値を設定します。" +msgid "The duration in minutes until the thread is automatically hidden from the channel list. Usually a value of 60, 1440, 4320 and 10080." +msgstr "" #: ../../../discord/app_commands/models.py:docstring of discord.app_commands.models.AppCommandThread:94 msgid "An aware timestamp of when the thread's archived status was last updated in UTC." @@ -2450,7 +2462,7 @@ msgid "This object must be inherited to create a UI within Discord." msgstr "Discord内でUIを作成するには、このオブジェクトを継承する必要があります。" #: ../../../discord/ui/view.py:docstring of discord.ui.view.View:7 -#: ../../../discord/ui/modal.py:docstring of discord.ui.modal.Modal:22 +#: ../../../discord/ui/modal.py:docstring of discord.ui.modal.Modal:23 msgid "Timeout in seconds from last interaction with the UI before no longer accepting input. If ``None`` then there is no timeout." msgstr "UIの最後のインタラクションから起算した、入力を受け付けなくなるまでの秒単位のタイムアウト。 ``None`` の場合タイムアウトはありません。" @@ -2677,19 +2689,19 @@ msgstr "Discord内でモーダルポップアップウィンドウを作成す msgid "Examples" msgstr "例" -#: ../../../discord/ui/modal.py:docstring of discord.ui.modal.Modal:20 +#: ../../../discord/ui/modal.py:docstring of discord.ui.modal.Modal:21 msgid "The title of the modal. Can only be up to 45 characters." msgstr "モーダルのタイトル。最大45文字までです。" -#: ../../../discord/ui/modal.py:docstring of discord.ui.modal.Modal:25 +#: ../../../discord/ui/modal.py:docstring of discord.ui.modal.Modal:26 msgid "The ID of the modal that gets received during an interaction. If not given then one is generated for you. Can only be up to 100 characters." msgstr "インタラクション中に受け取るモーダルID。 指定されていない場合は、自動で生成されます。最大 100 文字までしか使用できません。" -#: ../../../discord/ui/modal.py:docstring of discord.ui.modal.Modal:32 +#: ../../../discord/ui/modal.py:docstring of discord.ui.modal.Modal:33 msgid "The title of the modal." msgstr "モーダルのタイトル。" -#: ../../../discord/ui/modal.py:docstring of discord.ui.modal.Modal:38 +#: ../../../discord/ui/modal.py:docstring of discord.ui.modal.Modal:39 msgid "The ID of the modal that gets received during an interaction." msgstr "インタラクション中に受け取るモーダルID。" diff --git a/docs/locale/ja/LC_MESSAGES/intro.po b/docs/locale/ja/LC_MESSAGES/intro.po index 8dfc83ba5..93acdbe80 100644 --- a/docs/locale/ja/LC_MESSAGES/intro.po +++ b/docs/locale/ja/LC_MESSAGES/intro.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: discordpy\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-01-24 11:01+0000\n" -"PO-Revision-Date: 2023-01-30 13:38\n" +"POT-Creation-Date: 2023-06-21 01:17+0000\n" +"PO-Revision-Date: 2024-04-17 02:43\n" "Last-Translator: \n" "Language-Team: Japanese\n" "MIME-Version: 1.0\n" @@ -109,15 +109,19 @@ msgstr "いつものようにpipインストールを実行します。" msgid "Congratulations. You now have a virtual environment all set up." msgstr "おめでとうございます。これで仮想環境のセットアップができました。" -#: ../../intro.rst:92 +#: ../../intro.rst:93 +msgid "Scripts executed with ``py -3`` will ignore any currently active virtual environment, as the ``-3`` specifies a global scope." +msgstr "``py -3`` で実行されるスクリプトは、グローバルスコープを指定するため、現在アクティブな仮想環境を無視します。" + +#: ../../intro.rst:97 msgid "Basic Concepts" msgstr "基本概念" -#: ../../intro.rst:94 +#: ../../intro.rst:99 msgid "discord.py revolves around the concept of :ref:`events `. An event is something you listen to and then respond to. For example, when a message happens, you will receive an event about it that you can respond to." msgstr "discord.pyは :ref:`イベント ` の概念を中心としています。イベントは何かを受け取り、それに対する応答を行います。例えば、メッセージが発生すると、メッセージの発生に関連するイベントを受け取り、そのイベントに対して応答を返すことができます。" -#: ../../intro.rst:98 +#: ../../intro.rst:103 msgid "A quick example to showcase how events work:" msgstr "以下はイベントの仕組みを紹介する簡単な例です。" diff --git a/docs/locale/ja/LC_MESSAGES/logging.po b/docs/locale/ja/LC_MESSAGES/logging.po index 05023a574..d5a6b114d 100644 --- a/docs/locale/ja/LC_MESSAGES/logging.po +++ b/docs/locale/ja/LC_MESSAGES/logging.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: discordpy\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-01-24 11:01+0000\n" -"PO-Revision-Date: 2023-01-30 13:38\n" +"POT-Creation-Date: 2024-03-26 03:41+0000\n" +"PO-Revision-Date: 2024-04-17 02:43\n" "Last-Translator: \n" "Language-Team: Japanese\n" "MIME-Version: 1.0\n" @@ -46,18 +46,22 @@ msgid "This is recommended, especially at verbose levels such as ``DEBUG``, as t msgstr "特に、 ``DEBUG`` といった冗長なイベントレベルを設定している場合、プログラムの標準エラー出力をつまらせてしまう原因になるため、ファイルへの出力が推奨されます。" #: ../../logging.rst:46 +msgid "If you want the logging configuration the library provides to affect all loggers rather than just the ``discord`` logger, you can pass ``root_logger=True`` inside :meth:`Client.run`:" +msgstr "" + +#: ../../logging.rst:52 msgid "If you want to setup logging using the library provided configuration without using :meth:`Client.run`, you can use :func:`discord.utils.setup_logging`:" msgstr ":meth:`Client.run` を使用せずにライブラリ提供の構成を使用して logging を設定したい場合は、 :func:`discord.utils.setup_logging` を使用できます。" -#: ../../logging.rst:57 +#: ../../logging.rst:63 msgid "More advanced setups are possible with the :mod:`logging` module. The example below configures a rotating file handler that outputs DEBUG output for everything the library outputs, except for HTTP requests:" msgstr ":mod:`logging` モジュールを使用するとより高度なセットアップが行えます。以下の例では、HTTPリクエスト以外のすべてのライブラリの出力に対しDEBUG出力を使用するローテーションを行うファイルハンドラを構成します。" -#: ../../logging.rst:85 +#: ../../logging.rst:91 msgid "For more information, check the documentation and tutorial of the :mod:`logging` module." msgstr "詳細は、:mod:`logging` モジュールのドキュメントを参照してください。" -#: ../../logging.rst:89 +#: ../../logging.rst:95 msgid "The library now provides a default logging configuration." msgstr "ライブラリがデフォルト logging 構成を提供するようになりました。" diff --git a/docs/locale/ja/LC_MESSAGES/migrating.po b/docs/locale/ja/LC_MESSAGES/migrating.po index 13334deb1..9dad60007 100644 --- a/docs/locale/ja/LC_MESSAGES/migrating.po +++ b/docs/locale/ja/LC_MESSAGES/migrating.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: discordpy\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-01-24 11:01+0000\n" -"PO-Revision-Date: 2023-01-30 13:38\n" +"POT-Creation-Date: 2023-06-21 01:17+0000\n" +"PO-Revision-Date: 2024-04-17 02:43\n" "Last-Translator: \n" "Language-Team: Japanese\n" "MIME-Version: 1.0\n" @@ -42,7 +42,7 @@ msgid "Logging on with a user token is against the Discord `Terms of Service `_ に反しているため、ユーザーのみのエンドポイントに対するサポートはすべて削除されました。" #: ../../migrating.rst:24 -#: ../../migrating.rst:1151 +#: ../../migrating.rst:1165 msgid "The following have been removed:" msgstr "以下の機能は削除されました。" @@ -1221,8 +1221,8 @@ msgid "A guild member can react with an emoji to messages in a private thread if msgstr "ギルドメンバーは以下の場合にプライベートスレッドのメッセージにリアクションをつけることができます:" #: ../../migrating.rst:594 -#: ../../migrating.rst:1093 -#: ../../migrating.rst:1212 +#: ../../migrating.rst:1107 +#: ../../migrating.rst:1226 msgid "The following changes have been made:" msgstr "以下のように変更されました:" @@ -1619,7 +1619,7 @@ msgid "The following methods have been changed:" msgstr "以下のメソッドが変更されました:" #: ../../migrating.rst:918 -#: ../../migrating.rst:1033 +#: ../../migrating.rst:1047 msgid ":meth:`Message.add_reaction`" msgstr ":meth:`Message.add_reaction`" @@ -1688,7 +1688,7 @@ msgid ":meth:`WebhookMessage.delete`" msgstr ":meth:`WebhookMessage.delete`" #: ../../migrating.rst:935 -#: ../../migrating.rst:1040 +#: ../../migrating.rst:1054 msgid ":meth:`Webhook.delete_message`" msgstr ":meth:`Webhook.delete_message`" @@ -1753,7 +1753,7 @@ msgid "The library now provides a default logging configuration if using :meth:` msgstr "ライブラリは :meth:`Client.run` を使用した場合にデフォルトの logging 構成を提供するようになりました。無効化するには、 ``log_handler`` キーワード引数に ``None`` を渡してください。ライブラリがデフォルトでログ設定を提供するようになったため、次のメソッドは :data:`sys.stderr` に出力せず、ロガーを使用するように変更されました。" #: ../../migrating.rst:966 -#: ../../migrating.rst:1034 +#: ../../migrating.rst:1048 msgid ":meth:`Client.on_error`" msgstr ":meth:`Client.on_error`" @@ -1825,1088 +1825,1124 @@ msgstr "``Guild.bans`` エンドポイントの変更" msgid "Due to a breaking API change by Discord, :meth:`Guild.bans` no longer returns a list of every ban in the guild but instead is paginated using an asynchronous iterator." msgstr "Discord APIの破壊的変更のため、 :meth:`Guild.bans` はギルドのすべてのBANのリストを返さなくなり、代わりに非同期イテレータを用いてページ化されるようになりました。" -#: ../../migrating.rst:1011 -#: ../../migrating.rst:1364 +#: ../../migrating.rst:1010 +msgid "Flag classes now have a custom ``bool()`` implementation" +msgstr "フラグのクラスにカスタムの ``bool()`` 実装を追加" + +#: ../../migrating.rst:1012 +msgid "To allow library users to easily check whether an instance of a flag class has any flags enabled, using `bool` on them will now only return ``True`` if at least one flag is enabled." +msgstr "ライブラリユーザーがフラグクラスのインスタンスに何らかのフラグが立っているかどうかを確認しやすくするために、 ``bool`` を使用するとフラグが最低一個有効の場合に ``True`` を返すようにしました。" + +#: ../../migrating.rst:1015 +msgid "This means that evaluating instances of the following classes in a bool context (such as ``if obj:``) may no longer return ``True``:" +msgstr "つまり、 真偽値の文脈 (例えば ``if obj:``) で次のクラスのインスタンスを評価するとき、 ``True`` が返されない可能性があります:" + +#: ../../migrating.rst:1017 +msgid ":class:`Intents`" +msgstr ":class:`Intents`" + +#: ../../migrating.rst:1018 +msgid ":class:`MemberCacheFlags`" +msgstr ":class:`MemberCacheFlags`" + +#: ../../migrating.rst:1019 +msgid ":class:`MessageFlags`" +msgstr ":class:`MessageFlags`" + +#: ../../migrating.rst:1020 +msgid ":class:`Permissions`" +msgstr ":class:`Permissions`" + +#: ../../migrating.rst:1021 +msgid ":class:`PublicUserFlags`" +msgstr ":class:`PublicUserFlags`" + +#: ../../migrating.rst:1022 +msgid ":class:`SystemChannelFlags`" +msgstr ":class:`SystemChannelFlags`" + +#: ../../migrating.rst:1025 +#: ../../migrating.rst:1378 msgid "Function Signature Changes" msgstr "関数シグネチャの変更" -#: ../../migrating.rst:1013 -#: ../../migrating.rst:1366 +#: ../../migrating.rst:1027 +#: ../../migrating.rst:1380 msgid "Parameters in the following methods are now all positional-only:" msgstr "以下のメソッドの引数は全て位置限定になりました:" -#: ../../migrating.rst:1015 +#: ../../migrating.rst:1029 msgid ":meth:`AutoShardedClient.get_shard`" msgstr ":meth:`AutoShardedClient.get_shard`" -#: ../../migrating.rst:1016 +#: ../../migrating.rst:1030 msgid ":meth:`Client.get_channel`" msgstr ":meth:`Client.get_channel`" -#: ../../migrating.rst:1017 +#: ../../migrating.rst:1031 msgid ":meth:`Client.fetch_channel`" msgstr ":meth:`Client.fetch_channel`" -#: ../../migrating.rst:1018 +#: ../../migrating.rst:1032 msgid ":meth:`Guild.get_channel`" msgstr ":meth:`Guild.get_channel`" -#: ../../migrating.rst:1019 +#: ../../migrating.rst:1033 msgid ":meth:`Guild.fetch_channel`" msgstr ":meth:`Guild.fetch_channel`" -#: ../../migrating.rst:1020 +#: ../../migrating.rst:1034 msgid ":meth:`Client.get_emoji`" msgstr ":meth:`Client.get_emoji`" -#: ../../migrating.rst:1021 +#: ../../migrating.rst:1035 msgid ":meth:`Guild.fetch_emoji`" msgstr ":meth:`Guild.fetch_emoji`" -#: ../../migrating.rst:1022 +#: ../../migrating.rst:1036 msgid ":meth:`Client.get_guild`" msgstr ":meth:`Client.get_guild`" -#: ../../migrating.rst:1023 +#: ../../migrating.rst:1037 msgid ":meth:`Client.fetch_guild`" msgstr ":meth:`Client.fetch_guild`" -#: ../../migrating.rst:1024 +#: ../../migrating.rst:1038 msgid ":meth:`Client.delete_invite`" msgstr ":meth:`Client.delete_invite`" -#: ../../migrating.rst:1025 +#: ../../migrating.rst:1039 msgid ":meth:`Guild.get_member`" msgstr ":meth:`Guild.get_member`" -#: ../../migrating.rst:1026 +#: ../../migrating.rst:1040 msgid ":meth:`Guild.get_member_named`" msgstr ":meth:`Guild.get_member_named`" -#: ../../migrating.rst:1027 +#: ../../migrating.rst:1041 msgid ":meth:`Guild.fetch_member`" msgstr ":meth:`Guild.fetch_member`" -#: ../../migrating.rst:1028 +#: ../../migrating.rst:1042 msgid ":meth:`Client.get_user`" msgstr ":meth:`Client.get_user`" -#: ../../migrating.rst:1029 +#: ../../migrating.rst:1043 msgid ":meth:`Client.fetch_user`" msgstr ":meth:`Client.fetch_user`" -#: ../../migrating.rst:1030 +#: ../../migrating.rst:1044 msgid ":meth:`Guild.get_role`" msgstr ":meth:`Guild.get_role`" -#: ../../migrating.rst:1031 +#: ../../migrating.rst:1045 msgid ":meth:`Client.fetch_webhook`" msgstr ":meth:`Client.fetch_webhook`" -#: ../../migrating.rst:1032 +#: ../../migrating.rst:1046 msgid ":meth:`Client.fetch_widget`" msgstr ":meth:`Client.fetch_widget`" -#: ../../migrating.rst:1035 +#: ../../migrating.rst:1049 msgid ":meth:`abc.Messageable.fetch_message`" msgstr ":meth:`abc.Messageable.fetch_message`" -#: ../../migrating.rst:1036 +#: ../../migrating.rst:1050 msgid ":meth:`abc.GuildChannel.permissions_for`" msgstr ":meth:`abc.GuildChannel.permissions_for`" -#: ../../migrating.rst:1037 +#: ../../migrating.rst:1051 msgid ":meth:`DMChannel.get_partial_message`" msgstr ":meth:`DMChannel.get_partial_message`" -#: ../../migrating.rst:1038 +#: ../../migrating.rst:1052 msgid ":meth:`TextChannel.get_partial_message`" msgstr ":meth:`TextChannel.get_partial_message`" -#: ../../migrating.rst:1039 +#: ../../migrating.rst:1053 msgid ":meth:`TextChannel.delete_messages`" msgstr ":meth:`TextChannel.delete_messages`" -#: ../../migrating.rst:1041 +#: ../../migrating.rst:1055 msgid ":func:`utils.find`" msgstr ":func:`utils.find`" -#: ../../migrating.rst:1042 +#: ../../migrating.rst:1056 msgid ":func:`utils.snowflake_time`" msgstr ":func:`utils.snowflake_time`" -#: ../../migrating.rst:1044 -#: ../../migrating.rst:1410 +#: ../../migrating.rst:1058 +#: ../../migrating.rst:1424 msgid "The following parameters are now positional-only:" msgstr "以下の引数が位置限定になりました:" -#: ../../migrating.rst:1046 +#: ../../migrating.rst:1060 msgid "``iterable`` in :func:`utils.get`" msgstr ":func:`utils.get` の ``iterable``" -#: ../../migrating.rst:1047 +#: ../../migrating.rst:1061 msgid "``event_method`` in :meth:`Client.on_error`" msgstr ":meth:`Client.on_error` の ``event_method``" -#: ../../migrating.rst:1048 +#: ../../migrating.rst:1062 msgid "``event`` in :meth:`Client.wait_for`" msgstr ":meth:`Client.wait_for` の ``event``" -#: ../../migrating.rst:1049 +#: ../../migrating.rst:1063 msgid "``dt`` in :func:`utils.time_snowflake`" msgstr ":func:`utils.time_snowflake` の ``dt``" -#: ../../migrating.rst:1051 +#: ../../migrating.rst:1065 msgid "The following are now keyword-only:" msgstr "以下はキーワード限定になりました:" -#: ../../migrating.rst:1053 +#: ../../migrating.rst:1067 msgid "Parameters in :meth:`Reaction.users`" msgstr ":meth:`Reaction.users` の引数" -#: ../../migrating.rst:1054 +#: ../../migrating.rst:1068 msgid "Parameters in :meth:`Client.create_guild`" msgstr ":meth:`Client.create_guild` の引数" -#: ../../migrating.rst:1055 +#: ../../migrating.rst:1069 msgid "``permissions``, ``guild``, ``redirect_uri``, and ``scopes`` parameters in :func:`utils.oauth_url`" msgstr ":func:`utils.oauth_url` の ``permissions``, ``guild``, ``redirect_uri``, ``scopes`` 引数" -#: ../../migrating.rst:1056 +#: ../../migrating.rst:1070 msgid "``high`` in :func:`utils.snowflake_time`" msgstr ":func:`utils.snowflake_time` の ``high`` 引数" -#: ../../migrating.rst:1058 -#: ../../migrating.rst:1441 +#: ../../migrating.rst:1072 +#: ../../migrating.rst:1455 msgid "The library now less often uses ``None`` as the default value for function/method parameters." msgstr "ライブラリは関数/メソッドの引数のデフォルト値として ``None`` を使用することが少なくなりました。" -#: ../../migrating.rst:1060 -#: ../../migrating.rst:1443 +#: ../../migrating.rst:1074 +#: ../../migrating.rst:1457 msgid "As a result, these parameters can no longer be ``None``:" msgstr "その結果、 これらの引数は ``None`` にはならなくなりました:" -#: ../../migrating.rst:1062 +#: ../../migrating.rst:1076 msgid "``size``, ``format``, and ``static_format`` in :meth:`Asset.replace`" msgstr ":meth:`Asset.replace` の ``size``, ``format``, ``static_format``" -#: ../../migrating.rst:1063 +#: ../../migrating.rst:1077 msgid "``check`` in :meth:`TextChannel.purge`" msgstr ":meth:`TextChannel.purge` の ``check``" -#: ../../migrating.rst:1064 +#: ../../migrating.rst:1078 msgid "``icon`` and ``code`` in :meth:`Client.create_guild`" msgstr ":meth:`Client.create_guild` の ``icon`` と ``code``" -#: ../../migrating.rst:1065 +#: ../../migrating.rst:1079 msgid "``roles`` in :meth:`Emoji.edit`" msgstr ":meth:`Emoji.edit` の ``roles``" -#: ../../migrating.rst:1066 +#: ../../migrating.rst:1080 msgid "``topic``, ``position`` and ``overwrites`` in :meth:`Guild.create_text_channel`" msgstr ":meth:`Guild.create_text_channel` の ``topic``, ``position``, ``overwrites``" -#: ../../migrating.rst:1067 +#: ../../migrating.rst:1081 msgid "``position`` and ``overwrites`` in :meth:`Guild.create_voice_channel`" msgstr ":meth:`Guild.create_voice_channel` の ``position`` と ``overwrites``" -#: ../../migrating.rst:1068 +#: ../../migrating.rst:1082 msgid "``topic``, ``position`` and ``overwrites`` in :meth:`Guild.create_stage_channel`" msgstr ":meth:`Guild.create_voice_channel` の ``topic``, ``position``, ``overwrites``" -#: ../../migrating.rst:1069 +#: ../../migrating.rst:1083 msgid "``position`` and ``overwrites`` in :meth:`Guild.create_category`" msgstr ":meth:`Guild.create_category` の ``position`` と ``overwrites``" -#: ../../migrating.rst:1070 +#: ../../migrating.rst:1084 msgid "``roles`` in :meth:`Guild.prune_members`" msgstr ":meth:`Guild.prune_members` の ``roles``" -#: ../../migrating.rst:1071 +#: ../../migrating.rst:1085 msgid "``roles`` in :meth:`Guild.estimate_pruned_members`" msgstr ":meth:`Guild.estimate_pruned_members` の ``roles``" -#: ../../migrating.rst:1072 +#: ../../migrating.rst:1086 msgid "``description`` in :meth:`Guild.create_template`" msgstr ":meth:`Guild.create_template` の ``description``" -#: ../../migrating.rst:1073 +#: ../../migrating.rst:1087 msgid "``roles`` in :meth:`Guild.create_custom_emoji`" msgstr ":meth:`Guild.create_custom_emoji` の ``roles``" -#: ../../migrating.rst:1074 +#: ../../migrating.rst:1088 msgid "``before``, ``after``, ``oldest_first``, ``user``, and ``action`` in :meth:`Guild.audit_logs`" msgstr ":meth:`Guild.audit_logs` の ``before``, ``after``, ``oldest_first``, ``user``, ``action``" -#: ../../migrating.rst:1075 +#: ../../migrating.rst:1089 msgid "``enable_emoticons`` in :meth:`StreamIntegration.edit`" msgstr ":meth:`StreamIntegration.edit` の ``enable_emoticons``" -#: ../../migrating.rst:1076 +#: ../../migrating.rst:1090 msgid "``mute``, ``deafen``, ``suppress``, and ``roles`` in :meth:`Member.edit`" msgstr ":meth:`Member.edit` の ``mute``, ``deafen``, ``suppress``, ``roles``" -#: ../../migrating.rst:1077 +#: ../../migrating.rst:1091 msgid "``position`` in :meth:`Role.edit`" msgstr ":meth:`Role.edit` の ``position``" -#: ../../migrating.rst:1078 +#: ../../migrating.rst:1092 msgid "``icon`` in :meth:`Template.create_guild`" msgstr ":meth:`Template.create_guild` の ``icon``" -#: ../../migrating.rst:1079 +#: ../../migrating.rst:1093 msgid "``name`` in :meth:`Template.edit`" msgstr ":meth:`Template.edit` の ``name``" -#: ../../migrating.rst:1080 +#: ../../migrating.rst:1094 msgid "``permissions``, ``guild``, ``redirect_uri``, ``scopes`` in :meth:`utils.oauth_url`" msgstr ":meth:`utils.oauth_url` の ``permissions``, ``guild``, ``redirect_uri``, ``scopes``" -#: ../../migrating.rst:1081 +#: ../../migrating.rst:1095 msgid "``content``, ``username``, ``avatar_url``, ``tts``, ``file``, ``files``, ``embed``, ``embeds``, and ``allowed_mentions`` in :meth:`Webhook.send`" msgstr ":meth:`Webhook.send` の ``content``, ``username``, ``avatar_url``, ``tts``, ``file``, ``files``, ``embed``, ``embeds``, ``allowed_mentions``" -#: ../../migrating.rst:1083 +#: ../../migrating.rst:1097 msgid "Allowed types for the following parameters have been changed:" msgstr "以下の引数で受け取られる型が変更されました:" -#: ../../migrating.rst:1085 +#: ../../migrating.rst:1099 msgid "``rtc_region`` in :meth:`Guild.create_voice_channel` is now of type Optional[:class:`str`]." msgstr ":meth:`Guild.create_voice_channel` の ``rtc_region`` は Optional[:class:`str`] 型になりました。" -#: ../../migrating.rst:1086 +#: ../../migrating.rst:1100 msgid "``rtc_region`` in :meth:`StageChannel.edit` is now of type Optional[:class:`str`]." msgstr ":meth:`StageChannel.edit` の ``rtc_region`` は Optional[:class:`str`] 型になりました。" -#: ../../migrating.rst:1087 +#: ../../migrating.rst:1101 msgid "``rtc_region`` in :meth:`VoiceChannel.edit` is now of type Optional[:class:`str`]." msgstr ":meth:`VoiceChannel.edit` の ``rtc_region`` は Optional[:class:`str`] 型になりました。" -#: ../../migrating.rst:1088 +#: ../../migrating.rst:1102 msgid "``preferred_locale`` in :meth:`Guild.edit` is now of type :class:`Locale`." msgstr ":meth:`Guild.edit` の ``preferred_locale`` は :class:`Locale` 型になりました。" -#: ../../migrating.rst:1091 +#: ../../migrating.rst:1105 msgid "Attribute Type Changes" msgstr "属性の型の変更" -#: ../../migrating.rst:1095 +#: ../../migrating.rst:1109 msgid ":attr:`DMChannel.recipient` may now be ``None``." msgstr ":attr:`DMChannel.recipient` が ``None`` になりうるようになりました。" -#: ../../migrating.rst:1096 +#: ../../migrating.rst:1110 msgid ":meth:`Guild.vanity_invite` may now be ``None``. This has been done to fix an issue with the method returning a broken :class:`Invite` object." msgstr ":meth:`Guild.vanity_invite` が ``None`` を返しうるようになりました。これはこのメソッドが壊れた :class:`Invite` オブジェクトを返す問題を解決するために行われました。" -#: ../../migrating.rst:1097 +#: ../../migrating.rst:1111 msgid ":meth:`Widget.fetch_invite` may now be ``None``." msgstr ":meth:`Widget.fetch_invite` が ``None`` を返しうるようになりました。" -#: ../../migrating.rst:1098 +#: ../../migrating.rst:1112 msgid ":attr:`Guild.shard_id` is now ``0`` instead of ``None`` if :class:`AutoShardedClient` is not used." msgstr ":class:`AutoShardedClient` が使われていない場合、 :attr:`Guild.shard_id` は ``None`` の代わりに ``0`` になるようになりました。" -#: ../../migrating.rst:1099 +#: ../../migrating.rst:1113 msgid ":attr:`Guild.mfa_level` is now of type :class:`MFALevel`." msgstr ":attr:`Guild.mfa_level` は :class:`MFALevel` 型に変更されました。" -#: ../../migrating.rst:1100 +#: ../../migrating.rst:1114 msgid ":attr:`Guild.member_count` is now of type Optional[:class:`int`]." msgstr ":attr:`Guild.member_count` の型が Optional[:class:`int`] に変更されました。" -#: ../../migrating.rst:1101 +#: ../../migrating.rst:1115 msgid ":attr:`AuditLogDiff.mfa_level` is now of type :class:`MFALevel`." msgstr ":attr:`AuditLogDiff.mfa_level` は :class:`MFALevel` 型に変更されました。" -#: ../../migrating.rst:1102 +#: ../../migrating.rst:1116 msgid ":attr:`AuditLogDiff.rtc_region` is now of type :class:`str`." msgstr ":attr:`AuditLogDiff.rtc_region` は :class:`str` 型に変更されました。" -#: ../../migrating.rst:1103 +#: ../../migrating.rst:1117 msgid ":attr:`StageChannel.rtc_region` is now of type :class:`str`." msgstr ":attr:`StageChannel.rtc_region` は :class:`str` 型に変更されました。" -#: ../../migrating.rst:1104 +#: ../../migrating.rst:1118 msgid ":attr:`VoiceChannel.rtc_region` is now of type :class:`str`." msgstr ":attr:`VoiceChannel.rtc_region` は :class:`str` 型になりました。" -#: ../../migrating.rst:1105 +#: ../../migrating.rst:1119 msgid ":attr:`ClientUser.avatar` is now ``None`` when the default avatar is used." msgstr ":attr:`ClientUser.avatar` は、デフォルトのアバターが利用されている場合は ``None`` に変更されました。" -#: ../../migrating.rst:1107 +#: ../../migrating.rst:1121 msgid "If you want the avatar that a user has displayed, consider :attr:`ClientUser.display_avatar`." msgstr "ユーザーの表示されているアバターを取得したい場合は、 :attr:`ClientUser.display_avatar` を検討してください。" -#: ../../migrating.rst:1109 +#: ../../migrating.rst:1123 msgid ":attr:`Member.avatar` is now ``None`` when the default avatar is used." msgstr ":attr:`Member.avatar` は、デフォルトのアバターが使用されている場合は ``None`` に変更されました。" -#: ../../migrating.rst:1111 +#: ../../migrating.rst:1125 msgid "If you want the avatar that a member or user has displayed, consider :attr:`Member.display_avatar` or :attr:`User.display_avatar`." msgstr "メンバーやユーザーが表示しているアバターを取得したい場合は、 :attr:`Member.display_avatar` または :attr:`User.display_avatar` を検討してください。" -#: ../../migrating.rst:1114 +#: ../../migrating.rst:1128 msgid ":attr:`User.avatar` is now ``None`` when the default avatar is used." msgstr ":attr:`User.avatar` は、デフォルトのアバターが利用されている場合は ``None`` に変更されました。" -#: ../../migrating.rst:1116 +#: ../../migrating.rst:1130 msgid "If you want the avatar that a user has displayed, consider :attr:`User.display_avatar`." msgstr "ユーザーが表示しているアバターを取得したい場合は、 :attr:`User.display_avatar` を検討してください。" -#: ../../migrating.rst:1118 +#: ../../migrating.rst:1132 msgid ":attr:`Webhook.avatar` is now ``None`` when the default avatar is used." msgstr ":attr:`Webhook.avatar` は、デフォルトのアバターが利用されている場合は ``None`` に変更されました。" -#: ../../migrating.rst:1120 +#: ../../migrating.rst:1134 msgid "If you want the avatar that a webhook has displayed, consider :attr:`Webhook.display_avatar`." msgstr "Webhookに表示されるアバターを取得したい場合は、 :attr:`Webhook.display_avatar` を検討してください。" -#: ../../migrating.rst:1122 +#: ../../migrating.rst:1136 msgid ":attr:`AuditLogEntry.target` may now be a :class:`PartialMessageable`." msgstr ":attr:`AuditLogEntry.target` が :class:`PartialMessageable` になりうるようになりました。" -#: ../../migrating.rst:1123 +#: ../../migrating.rst:1137 msgid ":attr:`PartialMessage.channel` may now be a :class:`PartialMessageable`." msgstr ":attr:`PartialMessage.channel` が :class:`PartialMessageable` になりうるようになりました。" -#: ../../migrating.rst:1124 +#: ../../migrating.rst:1138 msgid ":attr:`Guild.preferred_locale` is now of type :class:`Locale`." msgstr ":attr:`Guild.preferred_locale` の型が :class:`Locale` になりました。" -#: ../../migrating.rst:1125 +#: ../../migrating.rst:1139 msgid ":attr:`abc.GuildChannel.overwrites` keys can now have :class:`Object` in them." msgstr ":attr:`abc.GuildChannel.overwrites` のキーが :class:`Object` になりうるようになりました。" -#: ../../migrating.rst:1128 -#: ../../migrating.rst:1454 +#: ../../migrating.rst:1142 +#: ../../migrating.rst:1468 msgid "Removals" msgstr "削除" -#: ../../migrating.rst:1130 +#: ../../migrating.rst:1144 msgid "The following deprecated functionality have been removed:" msgstr "以下の非推奨の機能は削除されました。" -#: ../../migrating.rst:1132 +#: ../../migrating.rst:1146 msgid "``Client.request_offline_members``" msgstr "``Client.request_offline_members``" -#: ../../migrating.rst:1134 -#: ../../migrating.rst:1138 +#: ../../migrating.rst:1148 +#: ../../migrating.rst:1152 msgid "Use :meth:`Guild.chunk` instead." msgstr "代わりに :meth:`Guild.chunk` を使用してください。" -#: ../../migrating.rst:1136 +#: ../../migrating.rst:1150 msgid "``AutoShardedClient.request_offline_members``" msgstr "``AutoShardedClient.request_offline_members``" -#: ../../migrating.rst:1140 +#: ../../migrating.rst:1154 msgid "``Client.logout``" msgstr "``Client.logout``" -#: ../../migrating.rst:1142 +#: ../../migrating.rst:1156 msgid "Use :meth:`Client.close` instead." msgstr "代わりに :meth:`Client.close` を使用してください。" -#: ../../migrating.rst:1144 +#: ../../migrating.rst:1158 msgid "``fetch_offline_members`` parameter from :class:`Client` constructor" msgstr ":class:`Client` コンストラクタの ``fetch_offline_members`` パラメータ" -#: ../../migrating.rst:1146 +#: ../../migrating.rst:1160 msgid "Use ``chunk_guild_at_startup`` instead." msgstr "代わりに ``chunk_guild_at_startup`` を使用してください。" -#: ../../migrating.rst:1149 +#: ../../migrating.rst:1163 msgid "``Permissions.use_slash_commands`` and ``PermissionOverwrite.use_slash_commands``" msgstr "``Permissions.use_slash_commands`` と ``PermissionOverwrite.use_slash_commands``" -#: ../../migrating.rst:1149 +#: ../../migrating.rst:1163 msgid "Use :attr:`Permissions.use_application_commands` and ``PermissionOverwrite.use_application_commands`` instead." msgstr "代わりに :attr:`Permissions.use_application_commands` と ``PermissionOverwrite.use_application_commands`` を使用してください。" -#: ../../migrating.rst:1153 +#: ../../migrating.rst:1167 msgid "``MemberCacheFlags.online``" msgstr "``MemberCacheFlags.online``" -#: ../../migrating.rst:1155 +#: ../../migrating.rst:1169 msgid "There is no replacement for this one. The current API version no longer provides enough data for this to be possible." msgstr "置換先はありません。現在のAPIバージョンは、このために必要な十分なデータを提供しません。" -#: ../../migrating.rst:1157 +#: ../../migrating.rst:1171 msgid "``AppInfo.summary``" msgstr "``AppInfo.summary``" -#: ../../migrating.rst:1159 +#: ../../migrating.rst:1173 msgid "There is no replacement for this one. The current API version no longer provides this field." msgstr "この項目を置き換えることはできません。現在の API バージョンではこの項目は提供されません。" -#: ../../migrating.rst:1161 +#: ../../migrating.rst:1175 msgid "``User.permissions_in`` and ``Member.permissions_in``" msgstr "``User.permissions_in`` と ``Member.permissions_in``" -#: ../../migrating.rst:1163 +#: ../../migrating.rst:1177 msgid "Use :meth:`abc.GuildChannel.permissions_for` instead." msgstr "代わりに :meth:`abc.GuildChannel.permissions_for` を使用してください。" -#: ../../migrating.rst:1165 +#: ../../migrating.rst:1179 msgid "``guild_subscriptions`` parameter from :class:`Client` constructor" msgstr ":class:`Client` コンストラクタの ``guild_subscriptions`` パラメータ" -#: ../../migrating.rst:1167 +#: ../../migrating.rst:1181 msgid "The current API version no longer provides this functionality. Use ``intents`` parameter instead." msgstr "現在の API バージョンはこの機能を提供していません。代わりに ``intents`` パラメーターを使用してください。" -#: ../../migrating.rst:1169 +#: ../../migrating.rst:1183 msgid ":class:`VerificationLevel` aliases:" msgstr ":class:`VerificationLevel` のエイリアス" -#: ../../migrating.rst:1171 +#: ../../migrating.rst:1185 msgid "``VerificationLevel.table_flip`` - use :attr:`VerificationLevel.high` instead." msgstr "``VerificationLevel.table_flip`` - 代わりに :attr:`VerificationLevel.high` を使用してください。" -#: ../../migrating.rst:1172 +#: ../../migrating.rst:1186 msgid "``VerificationLevel.extreme`` - use :attr:`VerificationLevel.highest` instead." msgstr "``VerificationLevel.extreme`` - 代わりに :attr:`VerificationLevel.highest` を使用してください。" -#: ../../migrating.rst:1173 +#: ../../migrating.rst:1187 msgid "``VerificationLevel.double_table_flip`` - use :attr:`VerificationLevel.highest` instead." msgstr "``VerificationLevel.double_table_flip`` - 代わりに :attr:`VerificationLevel.highest` を使用してください。" -#: ../../migrating.rst:1174 +#: ../../migrating.rst:1188 msgid "``VerificationLevel.very_high`` - use :attr:`VerificationLevel.highest` instead." msgstr "``VerificationLevel.very_high`` - 代わりに :attr:`VerificationLevel.highest` を利用してください。" -#: ../../migrating.rst:1176 +#: ../../migrating.rst:1190 msgid "``topic`` parameter from :meth:`StageChannel.edit`" msgstr ":meth:`StageChannel.edit` の ``topic`` パラメータ" -#: ../../migrating.rst:1178 +#: ../../migrating.rst:1192 msgid "The ``topic`` parameter must now be set via :meth:`StageChannel.create_instance`." msgstr "``topic`` パラメーターは :meth:`StageChannel.create_instance` で設定する必要があります。" -#: ../../migrating.rst:1180 +#: ../../migrating.rst:1194 msgid "``Reaction.custom_emoji``" msgstr "``Reaction.custom_emoji``" -#: ../../migrating.rst:1182 +#: ../../migrating.rst:1196 msgid "Use :meth:`Reaction.is_custom_emoji` instead." msgstr "代わりに :meth:`Reaction.is_custom_emoji` を使用してください。" -#: ../../migrating.rst:1184 +#: ../../migrating.rst:1198 msgid "``AuditLogDiff.region``" msgstr "``AuditLogDiff.region``" -#: ../../migrating.rst:1185 +#: ../../migrating.rst:1199 msgid "``Guild.region``" msgstr "``Guild.region``" -#: ../../migrating.rst:1186 +#: ../../migrating.rst:1200 msgid "``VoiceRegion``" msgstr "``VoiceRegion``" -#: ../../migrating.rst:1188 +#: ../../migrating.rst:1202 msgid "This has been marked deprecated by Discord and it was usually more or less out of date due to the pace they added them anyway." msgstr "これはDiscordより非推奨とされていて、また追加されたペースが速いため多くの場合で反映するのが遅れていました。" -#: ../../migrating.rst:1190 +#: ../../migrating.rst:1204 msgid "``region`` parameter from :meth:`Client.create_guild`" msgstr ":meth:`Client.create_guild` の ``region`` パラメータ" -#: ../../migrating.rst:1191 +#: ../../migrating.rst:1205 msgid "``region`` parameter from :meth:`Template.create_guild`" msgstr ":meth:`Template.create_guild` の ``region`` パラメータ" -#: ../../migrating.rst:1192 +#: ../../migrating.rst:1206 msgid "``region`` parameter from :meth:`Guild.edit`" msgstr ":meth:`Guild.edit` の ``region`` パラメータ" -#: ../../migrating.rst:1193 +#: ../../migrating.rst:1207 msgid "``on_private_channel_create`` event" msgstr "``on_private_channel_create`` イベント" -#: ../../migrating.rst:1195 -#: ../../migrating.rst:1199 +#: ../../migrating.rst:1209 +#: ../../migrating.rst:1213 msgid "Discord API no longer sends channel create event for DMs." msgstr "Discord APIは、DMにはチャンネル作成イベントを送信しないようになりました。" -#: ../../migrating.rst:1197 +#: ../../migrating.rst:1211 msgid "``on_private_channel_delete`` event" msgstr "``on_private_channel_delete`` イベント" -#: ../../migrating.rst:1201 +#: ../../migrating.rst:1215 msgid "The undocumented private ``on_socket_response`` event" msgstr "文書化されていないプライベートな ``on_socket_response`` イベント" -#: ../../migrating.rst:1203 +#: ../../migrating.rst:1217 msgid "Consider using the newer documented :func:`on_socket_event_type` event instead." msgstr "代わりに、より新しくドキュメント化された :func:`on_socket_event_type` イベントを使用することを検討してください。" -#: ../../migrating.rst:1205 +#: ../../migrating.rst:1219 msgid "``abc.Messageable.trigger_typing``" msgstr "``abc.Messageable.trigger_typing``" -#: ../../migrating.rst:1207 +#: ../../migrating.rst:1221 msgid "Use :meth:`abc.Messageable.typing` with ``await`` instead." msgstr "代わりに :meth:`abc.Messageable.typing` を ``await`` してください。" -#: ../../migrating.rst:1210 -#: ../../migrating.rst:1469 +#: ../../migrating.rst:1224 +#: ../../migrating.rst:1483 msgid "Miscellaneous Changes" msgstr "その他の変更点" -#: ../../migrating.rst:1214 +#: ../../migrating.rst:1228 msgid ":func:`on_socket_raw_receive` is now only called if ``enable_debug_events`` is set on :class:`Client`." msgstr ":class:`Client` に ``enable_debug_events`` が設定されている場合にのみ :func:`on_socket_raw_receive` が呼び出されるようになりました。" -#: ../../migrating.rst:1215 +#: ../../migrating.rst:1229 msgid ":func:`on_socket_raw_receive` is now only called once the **complete** message is received and decompressed. The passed ``msg`` parameter is now always :class:`str`." msgstr ":func:`on_socket_raw_receive` は **完全な** メッセージを受信して解凍した後にのみ呼び出されるようになりました。渡された ``msg`` パラメータは 常に :class:`str` になりました。" -#: ../../migrating.rst:1216 +#: ../../migrating.rst:1230 msgid ":func:`on_socket_raw_send` is now only called if ``enable_debug_events`` is set on :class:`Client`." msgstr ":class:`Client` に ``enable_debug_events`` が設定されている場合にのみ :func:`on_socket_raw_send` が呼び出されるようになりました。" -#: ../../migrating.rst:1217 +#: ../../migrating.rst:1231 msgid "The documented return type for :meth:`Guild.fetch_channels` changed to Sequence[:class:`abc.GuildChannel`]." msgstr "文書化された :meth:`Guild.fetch_channels` の戻り値の型は Sequence[:class:`abc.GuildChannel`] に変更されました。" -#: ../../migrating.rst:1218 +#: ../../migrating.rst:1232 msgid ":func:`utils.resolve_invite` now returns a :class:`ResolvedInvite` class." msgstr ":func:`utils.resolve_invite` が :class:`ResolvedInvite` クラスを返すようになりました。" -#: ../../migrating.rst:1219 +#: ../../migrating.rst:1233 msgid ":func:`utils.oauth_url` now defaults to ``bot`` and ``applications.commands`` scopes when not given instead of just ``bot``." msgstr ":func:`utils.oauth_url` のスコープが与えられていない場合の初期値が ``bot`` から ``bot`` と ``applications.commands`` に変更されました。" -#: ../../migrating.rst:1220 +#: ../../migrating.rst:1234 msgid ":meth:`abc.Messageable.typing` can no longer be used as a regular (non-async) context manager." msgstr ":meth:`abc.Messageable.typing` は、通常の(非同期でない)コンテキストマネージャーとしては使用できなくなりました。" -#: ../../migrating.rst:1221 +#: ../../migrating.rst:1235 msgid ":attr:`Intents.emojis` is now an alias of :attr:`Intents.emojis_and_stickers`." msgstr ":attr:`Intents.emojis` が :attr:`Intents.emojis_and_stickers` のエイリアスになりました。" -#: ../../migrating.rst:1223 +#: ../../migrating.rst:1237 msgid "This may affect code that iterates through ``(name, value)`` pairs in an instance of this class:" msgstr "これは、このクラスのインスタンス内の ``(name, value)`` ペアをイテレートするコードに影響を与える可能性があります。" -#: ../../migrating.rst:1245 +#: ../../migrating.rst:1259 msgid "``created_at`` is no longer part of :class:`abc.Snowflake`." msgstr "``created_at`` は :class:`abc.Snowflake` の一部ではなくなりました。" -#: ../../migrating.rst:1247 +#: ../../migrating.rst:1261 msgid "All of the existing classes still keep this attribute. It is just no longer part of this protocol. This has been done because Discord reuses IDs (snowflakes) of some models in other models. For example, if :class:`Thread` is created from a message, its :attr:`Thread.id` is equivalent to the ID of that message and as such it doesn't contain information about creation time of the thread and :attr:`Thread.created_at` cannot be based on it." msgstr "既存のすべてのクラスは、この属性を保持します。ただ、このプロトコルの一部ではなくなりました。これは、DiscordがいくつかのモデルのID(snowflake)を他のモデルで再利用するために行われました。例えば、あるメッセージから :class:`Thread` が作成された場合、その :attr:`Thread.id` はそのメッセージの ID と同じで、スレッドの作成時刻に関する情報は含まれず、 :attr:`Thread.created_at` に基づいて作成することはできません。" -#: ../../migrating.rst:1252 +#: ../../migrating.rst:1266 msgid ":class:`Embed`'s bool implementation now returns ``True`` when embed has any data set." msgstr ":class:`Embed` の bool 実装では、埋め込みに何らかのデータが設定されていれば ``True`` が返されるようになりました。" -#: ../../migrating.rst:1253 +#: ../../migrating.rst:1267 msgid "Calling :meth:`Emoji.edit` without ``roles`` argument no longer makes the emoji available to everyone." msgstr ":meth:`Emoji.edit` を ``roles`` 引数なしで呼び出したときに、絵文字が全員に利用できるようにならなくなりました。" -#: ../../migrating.rst:1255 +#: ../../migrating.rst:1269 msgid "To make the emoji available to everyone, pass an empty list to ``roles`` instead." msgstr "絵文字を誰でも利用できるようにするには、空のリストを ``roles`` に渡してください。" -#: ../../migrating.rst:1257 +#: ../../migrating.rst:1271 msgid "The old ``Colour.blurple`` has been renamed to :attr:`Colour.og_blurple`." msgstr "古い ``Colour.blurple`` は :attr:`Colour.og_blurple` に改名されました。" -#: ../../migrating.rst:1259 +#: ../../migrating.rst:1273 msgid ":attr:`Colour.blurple` refers to a different colour now." msgstr ":attr:`Colour.blurple` は別の色を参照するようになりました。" -#: ../../migrating.rst:1261 +#: ../../migrating.rst:1275 msgid ":attr:`Message.type` is now set to :attr:`MessageType.reply` when a message is a reply." msgstr "メッセージが返信である場合、 :attr:`Message.type` が :attr:`MessageType.reply` に設定されるようになりました。" -#: ../../migrating.rst:1263 +#: ../../migrating.rst:1277 msgid "This is caused by a difference in behavior in the current Discord API version." msgstr "これは、現在のDiscordAPIバージョンでの動作の違いによって引き起こされます。" -#: ../../migrating.rst:1265 +#: ../../migrating.rst:1279 msgid ":meth:`Message.edit` now merges object passed in ``allowed_mentions`` parameter with :attr:`Client.allowed_mentions`. If the parameter isn't provided, the defaults given by :attr:`Client.allowed_mentions` are used instead." msgstr ":meth:`Message.edit` は ``allowed_mentions`` パラメータで渡されたオブジェクトを :attr:`Client.allowed_mentions` と合わせるようになりました。このパラメータが指定されていない場合は、代わりに :attr:`Client.allowed_mentions` で指定されたデフォルト値が使用されます。" -#: ../../migrating.rst:1268 +#: ../../migrating.rst:1282 msgid ":meth:`Permissions.stage_moderator` now includes the :attr:`Permissions.manage_channels` permission and the :attr:`Permissions.request_to_speak` permission is no longer included." msgstr ":meth:`Permissions.stage_moderator` に :attr:`Permissions.manage_channels` 権限が含まれるようになり、 :attr:`Permissions.request_to_speak` 権限が含まれなくなりました。" -#: ../../migrating.rst:1270 +#: ../../migrating.rst:1284 msgid ":attr:`File.filename` will no longer be ``None``, in situations where previously this was the case the filename is set to ``'untitled'``." msgstr ":attr:`File.filename` が ``None`` になることはなくなりました。以前そうなった場合ではファイル名は ``'untitled'`` になります。" -#: ../../migrating.rst:1272 +#: ../../migrating.rst:1286 msgid ":attr:`Message.application` will no longer be a raw :class:`dict` of the API payload and now returns an instance of :class:`MessageApplication`." msgstr ":attr:`Message.application` が生のAPIペイロードの :class:`dict` ではなくなり、 :class:`MessageApplication` インスタンスを返すようになりました。" -#: ../../migrating.rst:1275 +#: ../../migrating.rst:1289 msgid ":meth:`VoiceProtocol.connect` signature changes." msgstr ":meth:`VoiceProtocol.connect` のシグネチャ変更" -#: ../../migrating.rst:1277 +#: ../../migrating.rst:1291 msgid ":meth:`VoiceProtocol.connect` will now be passed 2 keyword only arguments, ``self_deaf`` and ``self_mute``. These indicate whether or not the client should join the voice chat being deafened or muted." msgstr ":meth:`VoiceProtocol.connect` に、 ``self_deaf`` と ``self_mute`` キーワード引数が渡されるようになりました。これはクライアントがボイスチャットに参加する際にスピーカーミュートし、またはミュートすべきかを示します。" -#: ../../migrating.rst:1283 +#: ../../migrating.rst:1297 msgid "Command Extension Changes" msgstr "コマンド拡張の変更" -#: ../../migrating.rst:1288 +#: ../../migrating.rst:1302 msgid "Extension and Cog Loading / Unloading is Now Asynchronous" msgstr "エクステンションとコグの読み込み・解除の非同期化" -#: ../../migrating.rst:1290 +#: ../../migrating.rst:1304 msgid "As an extension to the :ref:`asyncio changes ` the loading and unloading of extensions and cogs is now asynchronous." msgstr ":ref:`asyncio の変更 ` に伴い、エクステンションとコグの読み込みや読み込み解除が非同期処理となりました。" -#: ../../migrating.rst:1292 +#: ../../migrating.rst:1306 msgid "To accommodate this, the following changes have been made:" msgstr "これに対応するために、以下のように変更が行われました:" -#: ../../migrating.rst:1294 +#: ../../migrating.rst:1308 msgid "The ``setup`` and ``teardown`` functions in extensions must now be coroutines." msgstr "エクステンションの ``setup`` と ``teardown`` 関数はコルーチンでないといけません。" -#: ../../migrating.rst:1295 +#: ../../migrating.rst:1309 msgid ":meth:`ext.commands.Bot.load_extension` must now be awaited." msgstr ":meth:`ext.commands.Bot.load_extension` を呼び出すときには await が必要です。" -#: ../../migrating.rst:1296 +#: ../../migrating.rst:1310 msgid ":meth:`ext.commands.Bot.unload_extension` must now be awaited." msgstr ":meth:`ext.commands.Bot.unload_extension` を呼び出すときには await が必要です。" -#: ../../migrating.rst:1297 +#: ../../migrating.rst:1311 msgid ":meth:`ext.commands.Bot.reload_extension` must now be awaited." msgstr ":meth:`ext.commands.Bot.reload_extension` を呼び出すときには await が必要です。" -#: ../../migrating.rst:1298 +#: ../../migrating.rst:1312 msgid ":meth:`ext.commands.Bot.add_cog` must now be awaited." msgstr ":meth:`ext.commands.Bot.add_cog` を呼び出すときには await が必要です。" -#: ../../migrating.rst:1299 +#: ../../migrating.rst:1313 msgid ":meth:`ext.commands.Bot.remove_cog` must now be awaited." msgstr ":meth:`ext.commands.Bot.remove_cog` を呼び出すときには await が必要です。" -#: ../../migrating.rst:1301 +#: ../../migrating.rst:1315 msgid "Quick example of an extension setup function:" msgstr "エクステンションのセットアップの簡単な例:" -#: ../../migrating.rst:1313 +#: ../../migrating.rst:1327 msgid "Quick example of loading an extension:" msgstr "エクステンションの読み込みの簡単な例:" -#: ../../migrating.rst:1335 +#: ../../migrating.rst:1349 msgid "Converters Are Now Generic Runtime Protocols" msgstr "コンバータの汎用ランタイムプロトコル化" -#: ../../migrating.rst:1337 +#: ../../migrating.rst:1351 msgid ":class:`~ext.commands.Converter` is now a :func:`runtime-checkable ` :class:`typing.Protocol`." msgstr ":class:`~ext.commands.Converter` が :func:`runtime-checkable ` な :class:`typing.Protocol` になりました。" -#: ../../migrating.rst:1339 +#: ../../migrating.rst:1353 msgid "This results in a change of the base metaclass used by these classes which may affect user-created classes that inherit from :class:`~ext.commands.Converter`." msgstr "その結果、これらのクラスで使用されるベースメタクラスが変更され、 :class:`~ext.commands.Converter` を継承するユーザーが作成したクラスに影響を与える可能性があります。" -#: ../../migrating.rst:1360 +#: ../../migrating.rst:1374 msgid "In addition, :class:`~ext.commands.Converter` is now a :class:`typing.Generic` which (optionally) allows the users to define their type hints more accurately." msgstr "さらに、 :class:`~ext.commands.Converter` は :class:`typing.Generic` になり、ユーザーは(自己判断で)型ヒントをより正確に定義することができます。" -#: ../../migrating.rst:1368 +#: ../../migrating.rst:1382 msgid ":meth:`ext.commands.when_mentioned`" msgstr ":meth:`ext.commands.when_mentioned`" -#: ../../migrating.rst:1369 +#: ../../migrating.rst:1383 msgid ":meth:`ext.commands.Bot.on_command_error`" msgstr ":meth:`ext.commands.Bot.on_command_error`" -#: ../../migrating.rst:1370 +#: ../../migrating.rst:1384 msgid ":meth:`ext.commands.Bot.check`" msgstr ":meth:`ext.commands.Bot.check`" -#: ../../migrating.rst:1371 +#: ../../migrating.rst:1385 msgid ":meth:`ext.commands.Bot.check_once`" msgstr ":meth:`ext.commands.Bot.check_once`" -#: ../../migrating.rst:1372 +#: ../../migrating.rst:1386 msgid ":meth:`ext.commands.Bot.is_owner`" msgstr ":meth:`ext.commands.Bot.is_owner`" -#: ../../migrating.rst:1373 +#: ../../migrating.rst:1387 msgid ":meth:`ext.commands.Bot.before_invoke`" msgstr ":meth:`ext.commands.Bot.before_invoke`" -#: ../../migrating.rst:1374 +#: ../../migrating.rst:1388 msgid ":meth:`ext.commands.Bot.after_invoke`" msgstr ":meth:`ext.commands.Bot.after_invoke`" -#: ../../migrating.rst:1375 +#: ../../migrating.rst:1389 msgid ":meth:`ext.commands.Bot.get_prefix`" msgstr ":meth:`ext.commands.Bot.get_prefix`" -#: ../../migrating.rst:1376 +#: ../../migrating.rst:1390 msgid ":meth:`ext.commands.Bot.invoke`" msgstr ":meth:`ext.commands.Bot.invoke`" -#: ../../migrating.rst:1377 +#: ../../migrating.rst:1391 msgid ":meth:`ext.commands.Bot.process_commands`" msgstr ":meth:`ext.commands.Bot.process_commands`" -#: ../../migrating.rst:1378 +#: ../../migrating.rst:1392 msgid ":meth:`ext.commands.Command.is_on_cooldown`" msgstr ":meth:`ext.commands.Command.is_on_cooldown`" -#: ../../migrating.rst:1379 +#: ../../migrating.rst:1393 msgid ":meth:`ext.commands.Command.reset_cooldown`" msgstr ":meth:`ext.commands.Command.reset_cooldown`" -#: ../../migrating.rst:1380 +#: ../../migrating.rst:1394 msgid ":meth:`ext.commands.Command.get_cooldown_retry_after`" msgstr ":meth:`ext.commands.Command.get_cooldown_retry_after`" -#: ../../migrating.rst:1381 +#: ../../migrating.rst:1395 msgid ":meth:`ext.commands.Command.error`" msgstr ":meth:`ext.commands.Command.error`" -#: ../../migrating.rst:1382 +#: ../../migrating.rst:1396 msgid ":meth:`ext.commands.Command.before_invoke`" msgstr ":meth:`ext.commands.Command.before_invoke`" -#: ../../migrating.rst:1383 +#: ../../migrating.rst:1397 msgid ":meth:`ext.commands.Command.after_invoke`" msgstr ":meth:`ext.commands.Command.after_invoke`" -#: ../../migrating.rst:1384 +#: ../../migrating.rst:1398 msgid ":meth:`ext.commands.Command.can_run`" msgstr ":meth:`ext.commands.Command.can_run`" -#: ../../migrating.rst:1385 +#: ../../migrating.rst:1399 msgid ":meth:`ext.commands.check`" msgstr ":meth:`ext.commands.check`" -#: ../../migrating.rst:1386 +#: ../../migrating.rst:1400 msgid ":meth:`ext.commands.has_role`" msgstr ":meth:`ext.commands.has_role`" -#: ../../migrating.rst:1387 +#: ../../migrating.rst:1401 msgid ":meth:`ext.commands.bot_has_role`" msgstr ":meth:`ext.commands.bot_has_role`" -#: ../../migrating.rst:1388 +#: ../../migrating.rst:1402 msgid ":meth:`ext.commands.before_invoke`" msgstr ":meth:`ext.commands.before_invoke`" -#: ../../migrating.rst:1389 +#: ../../migrating.rst:1403 msgid ":meth:`ext.commands.after_invoke`" msgstr ":meth:`ext.commands.after_invoke`" -#: ../../migrating.rst:1390 +#: ../../migrating.rst:1404 msgid ":meth:`ext.commands.HelpCommand.get_command_signature`" msgstr ":meth:`ext.commands.HelpCommand.get_command_signature`" -#: ../../migrating.rst:1391 +#: ../../migrating.rst:1405 msgid ":meth:`ext.commands.HelpCommand.remove_mentions`" msgstr ":meth:`ext.commands.HelpCommand.remove_mentions`" -#: ../../migrating.rst:1392 +#: ../../migrating.rst:1406 msgid ":meth:`ext.commands.HelpCommand.command_not_found`" msgstr ":meth:`ext.commands.HelpCommand.command_not_found`" -#: ../../migrating.rst:1393 +#: ../../migrating.rst:1407 msgid ":meth:`ext.commands.HelpCommand.subcommand_not_found`" msgstr ":meth:`ext.commands.HelpCommand.subcommand_not_found`" -#: ../../migrating.rst:1394 +#: ../../migrating.rst:1408 msgid ":meth:`ext.commands.HelpCommand.get_max_size`" msgstr ":meth:`ext.commands.HelpCommand.get_max_size`" -#: ../../migrating.rst:1395 +#: ../../migrating.rst:1409 msgid ":meth:`ext.commands.HelpCommand.send_error_message`" msgstr ":meth:`ext.commands.HelpCommand.send_error_message`" -#: ../../migrating.rst:1396 +#: ../../migrating.rst:1410 msgid ":meth:`ext.commands.HelpCommand.on_help_command_error`" msgstr ":meth:`ext.commands.HelpCommand.on_help_command_error`" -#: ../../migrating.rst:1397 +#: ../../migrating.rst:1411 msgid ":meth:`ext.commands.HelpCommand.send_bot_help`" msgstr ":meth:`ext.commands.HelpCommand.send_bot_help`" -#: ../../migrating.rst:1398 +#: ../../migrating.rst:1412 msgid ":meth:`ext.commands.HelpCommand.send_cog_help`" msgstr ":meth:`ext.commands.HelpCommand.send_cog_help`" -#: ../../migrating.rst:1399 +#: ../../migrating.rst:1413 msgid ":meth:`ext.commands.HelpCommand.send_group_help`" msgstr ":meth:`ext.commands.HelpCommand.send_group_help`" -#: ../../migrating.rst:1400 +#: ../../migrating.rst:1414 msgid ":meth:`ext.commands.HelpCommand.send_command_help`" msgstr ":meth:`ext.commands.HelpCommand.send_command_help`" -#: ../../migrating.rst:1401 +#: ../../migrating.rst:1415 msgid ":meth:`ext.commands.HelpCommand.prepare_help_command`" msgstr ":meth:`ext.commands.HelpCommand.prepare_help_command`" -#: ../../migrating.rst:1402 +#: ../../migrating.rst:1416 msgid ":meth:`ext.commands.DefaultHelpCommand.shorten_text`" msgstr ":meth:`ext.commands.DefaultHelpCommand.shorten_text`" -#: ../../migrating.rst:1403 +#: ../../migrating.rst:1417 msgid ":meth:`ext.commands.DefaultHelpCommand.add_command_formatting`" msgstr ":meth:`ext.commands.DefaultHelpCommand.add_command_formatting`" -#: ../../migrating.rst:1404 +#: ../../migrating.rst:1418 msgid ":meth:`ext.commands.MinimalHelpCommand.get_command_signature`" msgstr ":meth:`ext.commands.MinimalHelpCommand.get_command_signature`" -#: ../../migrating.rst:1405 +#: ../../migrating.rst:1419 msgid ":meth:`ext.commands.MinimalHelpCommand.add_bot_commands_formatting`" msgstr ":meth:`ext.commands.MinimalHelpCommand.add_bot_commands_formatting`" -#: ../../migrating.rst:1406 +#: ../../migrating.rst:1420 msgid ":meth:`ext.commands.MinimalHelpCommand.add_subcommand_formatting`" msgstr ":meth:`ext.commands.MinimalHelpCommand.add_subcommand_formatting`" -#: ../../migrating.rst:1407 +#: ../../migrating.rst:1421 msgid ":meth:`ext.commands.MinimalHelpCommand.add_aliases_formatting`" msgstr ":meth:`ext.commands.MinimalHelpCommand.add_aliases_formatting`" -#: ../../migrating.rst:1408 +#: ../../migrating.rst:1422 msgid ":meth:`ext.commands.MinimalHelpCommand.add_command_formatting`" msgstr ":meth:`ext.commands.MinimalHelpCommand.add_command_formatting`" -#: ../../migrating.rst:1412 +#: ../../migrating.rst:1426 msgid "``func`` in :meth:`ext.commands.Bot.check`" msgstr ":meth:`ext.commands.Bot.check` の ``func``" -#: ../../migrating.rst:1413 +#: ../../migrating.rst:1427 msgid "``func`` in :meth:`ext.commands.Bot.add_check`" msgstr ":meth:`ext.commands.Bot.add_check` の ``func``" -#: ../../migrating.rst:1414 +#: ../../migrating.rst:1428 msgid "``func`` in :meth:`ext.commands.Bot.remove_check`" msgstr ":meth:`ext.commands.Bot.remove_check` の ``func``" -#: ../../migrating.rst:1415 +#: ../../migrating.rst:1429 msgid "``func`` in :meth:`ext.commands.Bot.check_once`" msgstr ":meth:`ext.commands.Bot.check_once` の ``func``" -#: ../../migrating.rst:1416 +#: ../../migrating.rst:1430 msgid "``func`` in :meth:`ext.commands.Bot.add_listener`" msgstr ":meth:`ext.commands.Bot.add_listener` の ``func``" -#: ../../migrating.rst:1417 +#: ../../migrating.rst:1431 msgid "``func`` in :meth:`ext.commands.Bot.remove_listener`" msgstr ":meth:`ext.commands.Bot.remove_listener` の ``func``" -#: ../../migrating.rst:1418 +#: ../../migrating.rst:1432 msgid "``message`` in :meth:`ext.commands.Bot.get_context`" msgstr ":meth:`ext.commands.Bot.get_context` の ``message``" -#: ../../migrating.rst:1419 +#: ../../migrating.rst:1433 msgid "``func`` in :meth:`ext.commands.Command.add_check`" msgstr ":meth:`ext.commands.Command.add_check` の ``func``" -#: ../../migrating.rst:1420 +#: ../../migrating.rst:1434 msgid "``func`` in :meth:`ext.commands.Command.remove_check`" msgstr ":meth:`ext.commands.Command.remove_check` の ``func``" -#: ../../migrating.rst:1421 +#: ../../migrating.rst:1435 msgid "``context`` in :meth:`ext.commands.Command.__call__`" msgstr ":meth:`ext.commands.Command.__call__` の ``context``" -#: ../../migrating.rst:1422 +#: ../../migrating.rst:1436 msgid "``commands`` in :meth:`ext.commands.HelpCommand.filter_commands`" msgstr ":meth:`ext.commands.HelpCommand.filter_commands` の ``commands``" -#: ../../migrating.rst:1423 +#: ../../migrating.rst:1437 msgid "``ctx`` in :meth:`ext.commands.HelpCommand.command_callback`" msgstr ":meth:`ext.commands.HelpCommand.command_callback` の ``ctx``" -#: ../../migrating.rst:1424 +#: ../../migrating.rst:1438 msgid "``func`` in :meth:`ext.commands.HelpCommand.add_check`" msgstr ":meth:`ext.commands.HelpCommand.add_check` の ``func``" -#: ../../migrating.rst:1425 +#: ../../migrating.rst:1439 msgid "``func`` in :meth:`ext.commands.HelpCommand.remove_check`" msgstr ":meth:`ext.commands.HelpCommand.remove_check` の ``func``" -#: ../../migrating.rst:1426 +#: ../../migrating.rst:1440 msgid "``commands`` in :meth:`ext.commands.DefaultHelpCommand.add_indented_commands`" msgstr ":meth:`ext.commands.DefaultHelpCommand.add_indented_commands` の ``commands``" -#: ../../migrating.rst:1427 +#: ../../migrating.rst:1441 msgid "``cog`` in :meth:`ext.commands.Bot.add_cog`" msgstr ":meth:`ext.commands.Bot.add_cog` の ``cog``" -#: ../../migrating.rst:1428 +#: ../../migrating.rst:1442 msgid "``name`` in :meth:`ext.commands.Bot.get_cog`" msgstr ":meth:`ext.commands.Bot.get_cog` の ``name``" -#: ../../migrating.rst:1429 +#: ../../migrating.rst:1443 msgid "``name`` in :meth:`ext.commands.Bot.remove_cog`" msgstr ":meth:`ext.commands.Bot.remove_cog` の ``name``" -#: ../../migrating.rst:1430 +#: ../../migrating.rst:1444 msgid "``command`` in :meth:`ext.commands.Context.invoke`" msgstr ":meth:`ext.commands.Context.invoke` の ``command``" -#: ../../migrating.rst:1431 +#: ../../migrating.rst:1445 msgid "``command`` in :meth:`ext.commands.GroupMixin.add_command`" msgstr ":meth:`ext.commands.GroupMixin.add_command` の ``command``" -#: ../../migrating.rst:1432 +#: ../../migrating.rst:1446 msgid "``name`` in :meth:`ext.commands.GroupMixin.get_command`" msgstr ":meth:`ext.commands.GroupMixin.get_command` の ``name``" -#: ../../migrating.rst:1433 +#: ../../migrating.rst:1447 msgid "``name`` in :meth:`ext.commands.GroupMixin.remove_command`" msgstr ":meth:`ext.commands.GroupMixin.remove_command` の ``name``" -#: ../../migrating.rst:1435 +#: ../../migrating.rst:1449 msgid "The following parameters have been removed:" msgstr "以下のパラメータは削除されました。" -#: ../../migrating.rst:1437 +#: ../../migrating.rst:1451 msgid "``self_bot`` from :class:`~ext.commands.Bot`" msgstr ":class:`~ext.commands.Bot` の ``self_bot``" -#: ../../migrating.rst:1439 +#: ../../migrating.rst:1453 msgid "This has been done due to the :ref:`migrating_2_0_userbot_removal` changes." msgstr "これは :ref:`migrating_2_0_userbot_removal` の変更によって行われました。" -#: ../../migrating.rst:1445 +#: ../../migrating.rst:1459 msgid "``name`` in :meth:`ext.commands.Bot.add_listener`" msgstr ":meth:`ext.commands.Bot.add_listener` の ``name``" -#: ../../migrating.rst:1446 +#: ../../migrating.rst:1460 msgid "``name`` in :meth:`ext.commands.Bot.remove_listener`" msgstr ":meth:`ext.commands.Bot.remove_listener` の ``name``" -#: ../../migrating.rst:1447 +#: ../../migrating.rst:1461 msgid "``name`` in :meth:`ext.commands.Bot.listen`" msgstr ":meth:`ext.commands.Bot.listen` の ``name``" -#: ../../migrating.rst:1448 +#: ../../migrating.rst:1462 msgid "``name`` in :meth:`ext.commands.Cog.listener`" msgstr ":meth:`ext.commands.Cog.listener` の ``name``" -#: ../../migrating.rst:1449 +#: ../../migrating.rst:1463 msgid "``name`` in :meth:`ext.commands.Command`" msgstr ":meth:`ext.commands.Command` の ``name``" -#: ../../migrating.rst:1450 +#: ../../migrating.rst:1464 msgid "``name`` and ``cls`` in :meth:`ext.commands.command`" msgstr ":meth:`ext.commands.command` の ``name`` と ``cls``" -#: ../../migrating.rst:1451 +#: ../../migrating.rst:1465 msgid "``name`` and ``cls`` in :meth:`ext.commands.group`" msgstr ":meth:`ext.commands.group` の ``name`` と ``cls``" -#: ../../migrating.rst:1456 +#: ../../migrating.rst:1470 msgid "The following attributes have been removed:" msgstr "以下の属性が削除されました:" -#: ../../migrating.rst:1458 +#: ../../migrating.rst:1472 msgid "``original`` from the :exc:`~ext.commands.ExtensionNotFound`" msgstr ":exc:`~ext.commands.ExtensionNotFound` の ``original``" -#: ../../migrating.rst:1459 +#: ../../migrating.rst:1473 msgid "``type`` from the :class:`~ext.commands.Cooldown` class that was provided by the :attr:`ext.commands.CommandOnCooldown.cooldown` attribute" msgstr ":attr:`ext.commands.CommandOnCooldown.cooldown` から提供されていた :class:`~ext.commands.Cooldown` クラスの ``type``" -#: ../../migrating.rst:1462 +#: ../../migrating.rst:1476 msgid "Use :attr:`ext.commands.CommandOnCooldown.type` instead." msgstr "代わりに :attr:`ext.commands.CommandOnCooldown.type` を使用してください。" -#: ../../migrating.rst:1464 +#: ../../migrating.rst:1478 msgid "``clean_prefix`` from the :class:`~ext.commands.HelpCommand`" msgstr ":class:`~ext.commands.HelpCommand` の ``clean_prefix``" -#: ../../migrating.rst:1466 +#: ../../migrating.rst:1480 msgid "Use :attr:`ext.commands.Context.clean_prefix` instead." msgstr "代わりに :attr:`ext.commands.Context.clean_prefix` を使用してください。" -#: ../../migrating.rst:1471 +#: ../../migrating.rst:1485 msgid ":meth:`ext.commands.Bot.add_cog` is now raising :exc:`ClientException` when a cog with the same name is already loaded." msgstr ":meth:`ext.commands.Bot.add_cog` は、同名のコグがすでに読み込まれている場合には :exc:`ClientException` を送出するようになりました。" -#: ../../migrating.rst:1473 +#: ../../migrating.rst:1487 msgid "To override a cog, the new ``override`` parameter can be used." msgstr "コグを上書きするには、新しい ``override`` パラメータが使用できます。" -#: ../../migrating.rst:1475 +#: ../../migrating.rst:1489 msgid "When passing a callable to ``type`` argument of :meth:`~ext.commands.cooldown`, it now needs to accept :class:`~ext.commands.Context` rather than :class:`Message` as its only argument." msgstr ":meth:`~ext.commands.cooldown` の ``type`` 引数に呼び出し可能な引数を渡す場合、 :class:`Message` ではなく、 :class:`~ext.commands.Context` を引数として受け付けないといけないようになりました。" -#: ../../migrating.rst:1477 +#: ../../migrating.rst:1491 msgid "Metaclass of :class:`~ext.commands.Context` changed from :class:`abc.ABCMeta` to :class:`type`." msgstr ":class:`~ext.commands.Context` のメタクラスが :class:`abc.ABCMeta` から :class:`type` へと変更されました。" -#: ../../migrating.rst:1478 +#: ../../migrating.rst:1492 msgid "Changed type of :attr:`ext.commands.Command.clean_params` from :class:`collections.OrderedDict` to :class:`dict`. As the latter is guaranteed to preserve insertion order since Python 3.7." msgstr ":attr:`ext.commands.Command.clean_params` の型を :class:`collections.OrderedDict` からPython 3.7以降で追加順が必ず保持される :class:`dict` に変更しました。" -#: ../../migrating.rst:1480 +#: ../../migrating.rst:1494 msgid ":attr:`ext.commands.ChannelNotReadable.argument` may now be a :class:`Thread` due to the :ref:`migrating_2_0_thread_support` changes." msgstr ":ref:`migrating_2_0_thread_support` の変更により、 :attr:`ext.commands.ChannelNotReadable.argument` は :class:`Thread` になる可能性があります。" -#: ../../migrating.rst:1481 +#: ../../migrating.rst:1495 msgid ":attr:`ext.commands.NSFWChannelRequired.channel` may now be a :class:`Thread` due to the :ref:`migrating_2_0_thread_support` changes." msgstr ":ref:`migrating_2_0_thread_support` の変更により、 :attr:`ext.commands.NSFWChannelRequired.channel` は :class:`Thread` になる可能性があります。" -#: ../../migrating.rst:1482 +#: ../../migrating.rst:1496 msgid ":attr:`ext.commands.Context.channel` may now be a :class:`Thread` due to the :ref:`migrating_2_0_thread_support` changes." msgstr ":ref:`migrating_2_0_thread_support` の変更により、 :attr:`ext.commands.Context.channel` は :class:`Thread` になる可能性があります。" -#: ../../migrating.rst:1483 +#: ../../migrating.rst:1497 msgid ":attr:`ext.commands.Context.channel` may now be a :class:`PartialMessageable`." msgstr ":attr:`ext.commands.Context.channel` が :class:`PartialMessageable` になりうるようになりました。" -#: ../../migrating.rst:1484 +#: ../../migrating.rst:1498 msgid "``MissingPermissions.missing_perms`` has been renamed to :attr:`ext.commands.MissingPermissions.missing_permissions`." msgstr "``MissingPermissions.missing_perms`` は :attr:`ext.commands.MissingPermissions.missing_permissions` へと名前が変更されました。" -#: ../../migrating.rst:1485 +#: ../../migrating.rst:1499 msgid "``BotMissingPermissions.missing_perms`` has been renamed to :attr:`ext.commands.BotMissingPermissions.missing_permissions`." msgstr "``BotMissingPermissions.missing_perms`` は :attr:`ext.commands.BotMissingPermissions.missing_permissions` へと名前が変更されました。" -#: ../../migrating.rst:1486 +#: ../../migrating.rst:1500 msgid ":meth:`ext.commands.Cog.cog_load` has been added as part of the :ref:`migrating_2_0_commands_extension_cog_async` changes." msgstr ":meth:`ext.commands.Cog.cog_load` が、 :ref:`migrating_2_0_commands_extension_cog_async` の変更に伴って追加されました。" -#: ../../migrating.rst:1487 +#: ../../migrating.rst:1501 msgid ":meth:`ext.commands.Cog.cog_unload` may now be a :term:`coroutine` due to the :ref:`migrating_2_0_commands_extension_cog_async` changes." msgstr ":meth:`ext.commands.Cog.cog_unload` は、 :ref:`migrating_2_0_commands_extension_cog_async` のため :term:`coroutine` になることができるようになりました。" -#: ../../migrating.rst:1488 +#: ../../migrating.rst:1502 msgid ":attr:`ext.commands.Command.clean_params` type now uses a custom :class:`inspect.Parameter` to handle defaults." msgstr ":attr:`ext.commands.Command.clean_params` 型は既定値を扱うためカスタム :class:`inspect.Parameter` を使用するようになりました。" -#: ../../migrating.rst:1493 +#: ../../migrating.rst:1507 msgid "Tasks Extension Changes" msgstr "タスク拡張機能の変更" -#: ../../migrating.rst:1495 +#: ../../migrating.rst:1509 msgid "Calling :meth:`ext.tasks.Loop.stop` in :meth:`~ext.tasks.Loop.before_loop` now stops the first iteration from running." msgstr ":meth:`~ext.tasks.Loop.stop` を :meth:`~ext.tasks.Loop.before_loop` で呼び出すと、最初のイテレーションが実行されなくなりました。" -#: ../../migrating.rst:1496 +#: ../../migrating.rst:1510 msgid "Calling :meth:`ext.tasks.Loop.change_interval` now changes the interval for the sleep time right away, rather than on the next loop iteration." msgstr ":meth:`ext.tasks.Loop.change_interval` を呼び出すと、次のループの繰り返しの時ではなく、直ちにスリープ時間の間隔が変更されるようになりました。" -#: ../../migrating.rst:1498 +#: ../../migrating.rst:1512 msgid "``loop`` parameter in :func:`ext.tasks.loop` can no longer be ``None``." msgstr ":func:`ext.tasks.loop` のパラメーター ``loop`` に ``None`` を渡せなくなりました。" -#: ../../migrating.rst:1501 +#: ../../migrating.rst:1515 msgid "Migrating to v1.0" msgstr "v1.0への移行" -#: ../../migrating.rst:1503 +#: ../../migrating.rst:1517 msgid "The contents of that migration has been moved to :ref:`migrating_1_0`." msgstr "この移行に関する内容は :ref:`migrating_1_0` に移動されました。" diff --git a/docs/locale/ja/LC_MESSAGES/migrating_to_async.po b/docs/locale/ja/LC_MESSAGES/migrating_to_async.po index 45ad1ebd7..cce2227c2 100644 --- a/docs/locale/ja/LC_MESSAGES/migrating_to_async.po +++ b/docs/locale/ja/LC_MESSAGES/migrating_to_async.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: discordpy\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-01-24 11:01+0000\n" -"PO-Revision-Date: 2023-01-30 13:38\n" +"POT-Creation-Date: 2023-06-21 01:17+0000\n" +"PO-Revision-Date: 2024-04-17 02:43\n" "Last-Translator: \n" "Language-Team: Japanese\n" "MIME-Version: 1.0\n" diff --git a/docs/locale/ja/LC_MESSAGES/migrating_to_v1.po b/docs/locale/ja/LC_MESSAGES/migrating_to_v1.po index acda9dd6d..64c88c2cb 100644 --- a/docs/locale/ja/LC_MESSAGES/migrating_to_v1.po +++ b/docs/locale/ja/LC_MESSAGES/migrating_to_v1.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: discordpy\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-01-24 11:01+0000\n" -"PO-Revision-Date: 2023-01-30 13:38\n" +"POT-Creation-Date: 2023-06-21 01:17+0000\n" +"PO-Revision-Date: 2024-04-17 02:43\n" "Last-Translator: \n" "Language-Team: Japanese\n" "MIME-Version: 1.0\n" diff --git a/docs/locale/ja/LC_MESSAGES/quickstart.po b/docs/locale/ja/LC_MESSAGES/quickstart.po index a63227afa..6befcd7c0 100644 --- a/docs/locale/ja/LC_MESSAGES/quickstart.po +++ b/docs/locale/ja/LC_MESSAGES/quickstart.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: discordpy\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-01-24 11:01+0000\n" -"PO-Revision-Date: 2023-01-30 13:38\n" +"POT-Creation-Date: 2023-06-21 01:17+0000\n" +"PO-Revision-Date: 2024-04-17 02:43\n" "Last-Translator: \n" "Language-Team: Japanese\n" "MIME-Version: 1.0\n" diff --git a/docs/locale/ja/LC_MESSAGES/sphinx.po b/docs/locale/ja/LC_MESSAGES/sphinx.po index 6a261e649..eceded7f8 100644 --- a/docs/locale/ja/LC_MESSAGES/sphinx.po +++ b/docs/locale/ja/LC_MESSAGES/sphinx.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: discordpy\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-01-24 11:01+0000\n" -"PO-Revision-Date: 2023-01-30 13:38\n" +"POT-Creation-Date: 2023-06-21 01:17+0000\n" +"PO-Revision-Date: 2024-04-17 02:43\n" "Last-Translator: \n" "Language-Team: Japanese\n" "MIME-Version: 1.0\n" diff --git a/docs/locale/ja/LC_MESSAGES/version_guarantees.po b/docs/locale/ja/LC_MESSAGES/version_guarantees.po index 0b57fbee8..a0e5bc097 100644 --- a/docs/locale/ja/LC_MESSAGES/version_guarantees.po +++ b/docs/locale/ja/LC_MESSAGES/version_guarantees.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: discordpy\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-01-24 11:01+0000\n" -"PO-Revision-Date: 2023-01-30 13:38\n" +"POT-Creation-Date: 2023-06-21 01:17+0000\n" +"PO-Revision-Date: 2024-04-17 02:43\n" "Last-Translator: \n" "Language-Team: Japanese\n" "MIME-Version: 1.0\n" diff --git a/docs/locale/ja/LC_MESSAGES/whats_new.po b/docs/locale/ja/LC_MESSAGES/whats_new.po index ec2360a1d..0a75ffd17 100644 --- a/docs/locale/ja/LC_MESSAGES/whats_new.po +++ b/docs/locale/ja/LC_MESSAGES/whats_new.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: discordpy\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-01-24 11:01+0000\n" -"PO-Revision-Date: 2023-01-30 13:38\n" +"POT-Creation-Date: 2024-03-26 03:41+0000\n" +"PO-Revision-Date: 2024-04-17 02:43\n" "Last-Translator: \n" "Language-Team: Japanese\n" "MIME-Version: 1.0\n" @@ -26,2923 +26,3471 @@ msgid "This page keeps a detailed human friendly rendering of what's new and cha msgstr "このページでは、特定のバージョンの新機能や変更された機能をわかりやすい形で詳細に記載しています。" #: ../../whats_new.rst:17 -msgid "v2.1.0" -msgstr "v2.1.0" +msgid "v2.3.2" +msgstr "" #: ../../whats_new.rst:20 -#: ../../whats_new.rst:141 -#: ../../whats_new.rst:220 -#: ../../whats_new.rst:323 -#: ../../whats_new.rst:417 +#: ../../whats_new.rst:37 +#: ../../whats_new.rst:91 +#: ../../whats_new.rst:125 +#: ../../whats_new.rst:139 +msgid "Bug Fixes" +msgstr "バグ修正" + +#: ../../whats_new.rst:22 +msgid "Fix the ``name`` parameter not being respected when sending a :class:`CustomActivity`." +msgstr "" + +#: ../../whats_new.rst:23 +msgid "Fix :attr:`Intents.emoji` and :attr:`Intents.emojis_and_stickers` having swapped alias values (:issue:`9471`)." +msgstr "" + +#: ../../whats_new.rst:24 +msgid "Fix ``NameError`` when using :meth:`abc.GuildChannel.create_invite` (:issue:`9505`)." +msgstr "" + +#: ../../whats_new.rst:25 +msgid "Fix crash when disconnecting during the middle of a ``HELLO`` packet when using :class:`AutoShardedClient`." +msgstr "" + +#: ../../whats_new.rst:26 +msgid "Fix overly eager escape behaviour for lists and header markdown in :func:`utils.escape_markdown` (:issue:`9516`)." +msgstr "" + +#: ../../whats_new.rst:27 +msgid "Fix voice websocket not being closed before being replaced by a new one (:issue:`9518`)." +msgstr "" + +#: ../../whats_new.rst:28 +msgid "|commands| Fix the wrong :meth:`~ext.commands.HelpCommand.on_help_command_error` being called when ejected from a cog." +msgstr "" + +#: ../../whats_new.rst:29 +msgid "|commands| Fix ``=None`` being displayed in :attr:`~ext.commands.Command.signature`." +msgstr "" + +#: ../../whats_new.rst:34 +msgid "v2.3.1" +msgstr "" + +#: ../../whats_new.rst:39 +msgid "Fix username lookup in :meth:`Guild.get_member_named` (:issue:`9451`)." +msgstr "" + +#: ../../whats_new.rst:41 +msgid "Use cache data first for :attr:`Interaction.channel` instead of API data." +msgstr "" + +#: ../../whats_new.rst:41 +msgid "This bug usually manifested in incomplete channel objects (e.g. no ``overwrites``) because Discord does not provide this data." +msgstr "" + +#: ../../whats_new.rst:43 +msgid "Fix false positives in :meth:`PartialEmoji.from_str` inappropriately setting ``animated`` to ``True`` (:issue:`9456`, :issue:`9457`)." +msgstr "" + +#: ../../whats_new.rst:44 +msgid "Fix certain select types not appearing in :attr:`Message.components` (:issue:`9462`)." +msgstr "" + +#: ../../whats_new.rst:45 +msgid "|commands| Change lookup order for :class:`~ext.commands.MemberConverter` and :class:`~ext.commands.UserConverter` to prioritise usernames instead of nicknames." +msgstr "" + +#: ../../whats_new.rst:50 +msgid "v2.3.0" +msgstr "v2.3.0" + +#: ../../whats_new.rst:53 +#: ../../whats_new.rst:149 +#: ../../whats_new.rst:239 +#: ../../whats_new.rst:360 +#: ../../whats_new.rst:439 msgid "New Features" msgstr "新機能" -#: ../../whats_new.rst:22 +#: ../../whats_new.rst:61 +msgid "Add support for the new username system (also known as \"pomelo\")." +msgstr "新しいユーザー名システム (\"pomelo\"とも呼ばれます) のサポートを追加しました。" + +#: ../../whats_new.rst:56 +msgid "Add :attr:`User.global_name` to get their global nickname or \"display name\"." +msgstr "グローバルのニックネーム、つまり「表示名」を取得する :attr:`User.global_name` を追加しました。" + +#: ../../whats_new.rst:57 +msgid "Update :attr:`User.display_name` and :attr:`Member.display_name` to understand global nicknames." +msgstr ":attr:`User.display_name` と :attr:`Member.display_name` を、グローバルのニックネームを使用するように変更しました。" + +#: ../../whats_new.rst:58 +msgid "Update ``__str__`` for :class:`User` to drop discriminators if the user has been migrated." +msgstr ":class:`User` の ``__str__`` が、移行したユーザーのタグを含まないよう、変更しました。" + +#: ../../whats_new.rst:59 +msgid "Update :meth:`Guild.get_member_named` to work with migrated users." +msgstr "移行したユーザーでも動くよう :meth:`Guild.get_member_named` を変更しました。" + +#: ../../whats_new.rst:60 +msgid "Update :attr:`User.default_avatar` to work with migrated users." +msgstr "移行したユーザーでも動くよう :attr:`User.default_avatar` を変更しました。" + +#: ../../whats_new.rst:61 +msgid "|commands| Update user and member converters to understand migrated users." +msgstr "|commands| 移行したユーザーを解釈するよう、ユーザーとメンバーコンバータを変更しました。" + +#: ../../whats_new.rst:63 +msgid "Add :attr:`DefaultAvatar.pink` for new pink default avatars." +msgstr "新しいピンクのデフォルトアバタ―用の :attr:`DefaultAvatar.pink` を追加しました。" + +#: ../../whats_new.rst:64 +msgid "Add :meth:`Colour.pink` to get the pink default avatar colour." +msgstr "ピンクのデフォルトアバターの色を取得する :meth:`Colour.pink` を追加しました。" + +#: ../../whats_new.rst:69 +msgid "Add support for voice messages (:issue:`9358`)" +msgstr "ボイスメッセージのサポートを追加しました。 (:issue:`9358`)" + +#: ../../whats_new.rst:66 +msgid "Add :attr:`MessageFlags.voice`" +msgstr ":attr:`MessageFlags.voice` を追加しました。" + +#: ../../whats_new.rst:67 +msgid "Add :attr:`Attachment.duration` and :attr:`Attachment.waveform`" +msgstr ":attr:`Attachment.duration` と :attr:`Attachment.waveform` を追加しました。" + +#: ../../whats_new.rst:68 +msgid "Add :meth:`Attachment.is_voice_message`" +msgstr ":meth:`Attachment.is_voice_message` を追加しました。" + +#: ../../whats_new.rst:69 +msgid "This does not support *sending* voice messages because this is currently unsupported by the API." +msgstr "ボイスメッセージの *送信* は現在APIで対応していないため、サポートされていません。" + +#: ../../whats_new.rst:71 +msgid "Add support for new :attr:`Interaction.channel` attribute from the API update (:issue:`9339`)." +msgstr "API更新で追加された :attr:`Interaction.channel` 属性のサポートを追加しました。 (:issue:`9339`)" + +#: ../../whats_new.rst:72 +msgid "Add support for :attr:`TextChannel.default_thread_slowmode_delay` (:issue:`9291`)." +msgstr ":attr:`TextChannel.default_thread_slowmode_delay` のサポートを追加しました。 (:issue:`9291`)" + +#: ../../whats_new.rst:73 +msgid "Add support for :attr:`ForumChannel.default_sort_order` (:issue:`9290`)." +msgstr ":attr:`ForumChannel.default_sort_order` のサポートを追加しました。 (:issue:`9290`)" + +#: ../../whats_new.rst:74 +msgid "Add support for ``default_reaction_emoji`` and ``default_forum_layout`` in :meth:`Guild.create_forum` (:issue:`9300`)." +msgstr ":meth:`Guild.create_forum` にて、 ``default_reaction_emoji`` と ``default_forum_layout`` のサポートを追加しました。 (:issue:`9300`)" + +#: ../../whats_new.rst:75 +msgid "Add support for ``widget_channel``, ``widget_enabled``, and ``mfa_level`` in :meth:`Guild.edit` (:issue:`9302`, :issue:`9303`)." +msgstr ":meth:`Guild.edit` にて、 ``widget_channel`` 、 ``widget_enabled`` 、 ``mfa_level`` のサポートを追加しました。(:issue:`9302` 、 :issue:`9303`)" + +#: ../../whats_new.rst:78 +msgid "Add various new :class:`Permissions` and changes (:issue:`9312`, :issue:`9325`, :issue:`9358`, :issue:`9378`)" +msgstr "新しい :class:`Permissions` を追加しました。 (:issue:`9312` 、 :issue:`9325` 、 :issue:`9358` 、 :issue:`9378`)" + +#: ../../whats_new.rst:77 +msgid "Add new :attr:`~Permissions.manage_expressions`, :attr:`~Permissions.use_external_sounds`, :attr:`~Permissions.use_soundboard`, :attr:`~Permissions.send_voice_messages`, :attr:`~Permissions.create_expressions` permissions." +msgstr "新しい :attr:`~Permissions.manage_expressions` 、 :attr:`~Permissions.use_external_sounds` 、 :attr:`~Permissions.use_soundboard` 、 :attr:`~Permissions.send_voice_messages` 、 :attr:`~Permissions.create_expressions` 権限を追加しました。" + +#: ../../whats_new.rst:78 +msgid "Change :attr:`Permissions.manage_emojis` to be an alias of :attr:`~Permissions.manage_expressions`." +msgstr ":attr:`Permissions.manage_emojis` を :attr:`~Permissions.manage_expressions` のエイリアスに変更しました。" + +#: ../../whats_new.rst:80 +msgid "Add various new properties to :class:`PartialAppInfo` and :class:`AppInfo` (:issue:`9298`)." +msgstr ":class:`PartialAppInfo` と :class:`AppInfo` にさまざまな新しいプロパティを追加しました。 (:issue:`9298`)" + +#: ../../whats_new.rst:81 +msgid "Add support for ``with_counts`` parameter to :meth:`Client.fetch_guilds` (:issue:`9369`)." +msgstr ":meth:`Client.fetch_guilds` に ``with_counts`` 引数のサポートを追加しました。 (:issue:`9369`)" + +#: ../../whats_new.rst:82 +msgid "Add new :meth:`Guild.get_emoji` helper (:issue:`9296`)." +msgstr "新しく :meth:`Guild.get_emoji` ヘルパーを追加しました。 (:issue:`9296`)" + +#: ../../whats_new.rst:83 +msgid "Add :attr:`ApplicationFlags.auto_mod_badge` (:issue:`9313`)." +msgstr ":attr:`ApplicationFlags.auto_mod_badge` を追加しました。 (:issue:`9313`)" + +#: ../../whats_new.rst:84 +msgid "Add :attr:`Guild.max_stage_video_users` and :attr:`Guild.safety_alerts_channel` (:issue:`9318`)." +msgstr ":attr:`Guild.max_stage_video_users` と :attr:`Guild.safety_alerts_channel` を追加しました。 (:issue:`9318`)" + +#: ../../whats_new.rst:85 +msgid "Add support for ``raid_alerts_disabled`` and ``safety_alerts_channel`` in :meth:`Guild.edit` (:issue:`9318`)." +msgstr ":meth:`Guild.edit` にて ``raid_alerts_disabled`` と ``safety_alerts_channel`` のサポートを追加しました。 (:issue:`9318`)" + +#: ../../whats_new.rst:86 +msgid "|commands| Add :attr:`BadLiteralArgument.argument ` to get the failed argument's value (:issue:`9283`)." +msgstr "|commands| 失敗した引数の値を取得するための :attr:`BadLiteralArgument.argument ` を追加しました。 (:issue:`9283`)" + +#: ../../whats_new.rst:87 +msgid "|commands| Add :attr:`Context.filesize_limit ` property (:issue:`9416`)." +msgstr "|commands| :attr:`Context.filesize_limit ` 属性を追加しました。 (:issue:`9416`)" + +#: ../../whats_new.rst:88 +msgid "|commands| Add support for :attr:`Parameter.displayed_name ` (:issue:`9427`)." +msgstr "|commands| :attr:`Parameter.displayed_name ` のサポートを追加しました。 (:issue:`9427`)" + +#: ../../whats_new.rst:94 +msgid "Fix ``FileHandler`` handlers being written ANSI characters when the bot is executed inside PyCharm." +msgstr "PyCharm 内でボットが実行された場合、 ``FileHandler`` ハンドラにANSI 文字が出力されるのを修正しました。" + +#: ../../whats_new.rst:94 +msgid "This has the side effect of removing coloured logs from the PyCharm terminal due an upstream bug involving TTY detection. This issue is tracked under `PY-43798 `_." +msgstr "PyCharmのTTY検出のバグの影響により、PyCharm ターミナル内でログに色が付かなくなる副作用があります。このバグは `PY-43798 `_ で追跡されています。" + +#: ../../whats_new.rst:96 +msgid "Fix channel edits with :meth:`Webhook.edit` sending two requests instead of one." +msgstr ":meth:`Webhook.edit` でチャンネルを編集するときに2回リクエストが行われるバグを修正しました。" + +#: ../../whats_new.rst:97 +msgid "Fix :attr:`StageChannel.last_message_id` always being ``None`` (:issue:`9422`)." +msgstr ":attr:`StageChannel.last_message_id` が常に ``None`` となるのを修正しました。 (:issue:`9422`)" + +#: ../../whats_new.rst:98 +msgid "Fix piped audio input ending prematurely (:issue:`9001`, :issue:`9380`)." +msgstr "パイプによるオーディオ入力が終了するのが早すぎる問題を修正しました。 (:issue:`9001` 、 :issue:`9380`)" + +#: ../../whats_new.rst:99 +msgid "Fix persistent detection for :class:`ui.TextInput` being incorrect if the ``custom_id`` is set later (:issue:`9438`)." +msgstr "``custom_id`` が後で設定された場合、 :class:`ui.TextInput` の永続的な検出が正しくない問題を修正しました。 (:issue:`9438`)" + +#: ../../whats_new.rst:100 +msgid "Fix custom attributes not being copied over when inheriting from :class:`app_commands.Group` (:issue:`9383`)." +msgstr ":class:`app_commands.Group` から継承するときにカスタム属性がコピーされない問題を修正しました。 (:issue:`9383`)" + +#: ../../whats_new.rst:101 +msgid "Fix AutoMod audit log entry error due to empty channel_id (:issue:`9384`)." +msgstr "空の channel_id により自動管理の監査ログ項目でエラーが発生するのを修正しました。 (:issue:`9384`)" + +#: ../../whats_new.rst:102 +msgid "Fix handling of ``around`` parameter in :meth:`abc.Messageable.history` (:issue:`9388`)." +msgstr ":meth:`abc.Messageable.history` の ``around`` 引数の扱いを修正しました。 (:issue:`9388`)" + +#: ../../whats_new.rst:103 +msgid "Fix occasional :exc:`AttributeError` when accessing the :attr:`ClientUser.mutual_guilds` property (:issue:`9387`)." +msgstr ":attr:`ClientUser.mutual_guilds` プロパティにアクセスするとき時々 :exc:`AttributeError` が発生する問題を修正しました。 (:issue:`9387`)" + +#: ../../whats_new.rst:104 +msgid "Fix :func:`utils.escape_markdown` not escaping the new markdown (:issue:`9361`)." +msgstr ":func:`utils.escape_markdown` が新しいマークダウンを正しくエスケープしない問題を修正しました。 (:issue:`9361`)" + +#: ../../whats_new.rst:105 +msgid "Fix webhook targets not being converted in audit logs (:issue:`9332`)." +msgstr "監査ログでWebhookターゲットが変換されない問題を修正しました。 (:issue:`9332`)" + +#: ../../whats_new.rst:106 +msgid "Fix error when not passing ``enabled`` in :meth:`Guild.create_automod_rule` (:issue:`9292`)." +msgstr ":meth:`Guild.create_automod_rule` で ``enabled`` を渡さないときに生じるエラーを修正しました。 (:issue:`9292`)" + +#: ../../whats_new.rst:107 +msgid "Fix how various parameters are handled in :meth:`Guild.create_scheduled_event` (:issue:`9275`)." +msgstr ":meth:`Guild.create_scheduled_event` のパラメータの扱いを修正しました。 (:issue:`9275`)" + +#: ../../whats_new.rst:108 +msgid "Fix not sending the ``ssrc`` parameter when sending the SPEAKING payload (:issue:`9301`)." +msgstr "SPEAKING ペイロードの送信時に ``ssrc`` パラメータを送信しない問題を修正しました。 (:issue:`9301`)" + +#: ../../whats_new.rst:109 +msgid "Fix :attr:`Message.guild` being ``None`` sometimes when received via an interaction." +msgstr "インタラクションで受け取った :attr:`Message.guild` が時々 ``None`` になる問題を修正しました。" + +#: ../../whats_new.rst:110 +msgid "Fix :attr:`Message.system_content` for :attr:`MessageType.channel_icon_change` (:issue:`9410`)." +msgstr ":attr:`MessageType.channel_icon_change` の :attr:`Message.system_content` を修正しました。 (:issue:`9410`)" + +#: ../../whats_new.rst:113 +#: ../../whats_new.rst:213 +#: ../../whats_new.rst:283 +#: ../../whats_new.rst:425 +#: ../../whats_new.rst:492 +msgid "Miscellaneous" +msgstr "その他" + +#: ../../whats_new.rst:115 +msgid "Update the base :attr:`Guild.filesize_limit` to 25MiB (:issue:`9353`)." +msgstr "基本の :attr:`Guild.filesize_limit` を 25MiB に更新しました。 (:issue:`9353`)" + +#: ../../whats_new.rst:116 +msgid "Allow Interaction webhook URLs to be used in :meth:`Webhook.from_url`." +msgstr ":meth:`Webhook.from_url` でインタラクション Webhook URLを使用できるようになりました。" + +#: ../../whats_new.rst:117 +msgid "Set the socket family of internal connector to ``AF_INET`` to prevent IPv6 connections (:issue:`9442`, :issue:`9443`)." +msgstr "IPv6 接続を防ぐために、内部コネクタのソケットファミリを ``AF_INET`` に設定するようにしました。 (:issue:`9442` 、 :issue:`9443`)" + +#: ../../whats_new.rst:122 +msgid "v2.2.3" +msgstr "v2.2.3" + +#: ../../whats_new.rst:127 +msgid "Fix crash from Discord sending null ``channel_id`` for automod audit logs." +msgstr "Discordが自動管理の監査ログに関し null の ``channel_id`` を送ることによって生じたクラッシュを修正しました。" + +#: ../../whats_new.rst:128 +msgid "Fix ``channel`` edits when using :meth:`Webhook.edit` sending two requests." +msgstr ":meth:`Webhook.edit` を使用して ``channel`` を変更するときに2回リクエストが送信されるバグを修正しました。" + +#: ../../whats_new.rst:129 +msgid "Fix :attr:`AuditLogEntry.target` being ``None`` for invites (:issue:`9336`)." +msgstr "招待に関し :attr:`AuditLogEntry.target` が ``None`` となるのを修正しました。 (:issue:`9336`)" + +#: ../../whats_new.rst:130 +msgid "Fix :exc:`KeyError` when accessing data for :class:`GuildSticker` (:issue:`9324`)." +msgstr ":class:`GuildSticker` のデータにアクセスするときの :exc:`KeyError` を修正しました。 (:issue:`9324`)" + +#: ../../whats_new.rst:136 +msgid "v2.2.2" +msgstr "v2.2.2" + +#: ../../whats_new.rst:141 +msgid "Fix UDP discovery in voice not using new 74 byte layout which caused voice to break (:issue:`9277`, :issue:`9278`)" +msgstr "ボイスのUDP検出が、新しい74バイトレイアウトを使用していないため、ボイスが使用できない問題を修正しました。 (:issue:`9277` 、 :issue:`9278`)" + +#: ../../whats_new.rst:146 +msgid "v2.2.0" +msgstr "v2.2.0" + +#: ../../whats_new.rst:151 +msgid "Add support for new :func:`on_audit_log_entry_create` event" +msgstr "新しい :func:`on_audit_log_entry_create` イベントのサポートを追加しました。" + +#: ../../whats_new.rst:153 +msgid "Add support for silent messages via ``silent`` parameter in :meth:`abc.Messageable.send`" +msgstr "サイレントメッセージを送信する :meth:`abc.Messageable.send` の ``silent`` パラメータのサポートを追加しました。" + +#: ../../whats_new.rst:153 +msgid "This is queryable via :attr:`MessageFlags.suppress_notifications`" +msgstr "これは :attr:`MessageFlags.suppress_notifications` から確認できます。" + +#: ../../whats_new.rst:155 +msgid "Implement :class:`abc.Messageable` for :class:`StageChannel` (:issue:`9248`)" +msgstr ":class:`StageChannel` が :class:`abc.Messageable` を実装するようにしました。 (:issue:`9248`)" + +#: ../../whats_new.rst:156 +msgid "Add setter for :attr:`discord.ui.ChannelSelect.channel_types` (:issue:`9068`)" +msgstr ":attr:`discord.ui.ChannelSelect.channel_types` のセッターを追加しました。 (:issue:`9068`)" + +#: ../../whats_new.rst:157 +msgid "Add support for custom messages in automod via :attr:`AutoModRuleAction.custom_message` (:issue:`9267`)" +msgstr ":attr:`AutoModRuleAction.custom_message` で、自動管理のカスタムメッセージのサポートを追加しました。 (:issue:`9267`)" + +#: ../../whats_new.rst:158 +msgid "Add :meth:`ForumChannel.get_thread` (:issue:`9106`)" +msgstr ":meth:`ForumChannel.get_thread` を追加しました。 (:issue:`9106`)" + +#: ../../whats_new.rst:159 +msgid "Add :attr:`StageChannel.slowmode_delay` and :attr:`VoiceChannel.slowmode_delay` (:issue:`9111`)" +msgstr ":attr:`StageChannel.slowmode_delay` と :attr:`VoiceChannel.slowmode_delay` を追加しました。 (:issue:`9111`)" + +#: ../../whats_new.rst:160 +msgid "Add support for editing the slowmode for :class:`StageChannel` and :class:`VoiceChannel` (:issue:`9111`)" +msgstr ":class:`StageChannel` と :class:`VoiceChannel` の低速モードの変更のサポートを追加しました。 (:issue:`9111`)" + +#: ../../whats_new.rst:161 +msgid "Add :attr:`Locale.indonesian`" +msgstr ":attr:`Locale.indonesian` を追加しました。" + +#: ../../whats_new.rst:162 +msgid "Add ``delete_after`` keyword argument to :meth:`Interaction.edit_message` (:issue:`9415`)" +msgstr ":meth:`Interaction.edit_message` に ``delete_after`` キーワード引数を追加しました。 (:issue:`9415`)" + +#: ../../whats_new.rst:163 +msgid "Add ``delete_after`` keyword argument to :meth:`InteractionMessage.edit` (:issue:`9206`)" +msgstr ":meth:`InteractionMessage.edit` に ``delete_after`` キーワード引数を追加しました。 (:issue:`9206`)" + +#: ../../whats_new.rst:166 +msgid "Add support for member flags (:issue:`9204`)" +msgstr "メンバーフラグのサポートを追加しました。 (:issue:`9204`)" + +#: ../../whats_new.rst:165 +msgid "Accessible via :attr:`Member.flags` and has a type of :class:`MemberFlags`" +msgstr ":attr:`Member.flags` でアクセスでき、型は :class:`MemberFlags` です。" + +#: ../../whats_new.rst:166 +msgid "Support ``bypass_verification`` within :meth:`Member.edit`" +msgstr ":meth:`Member.edit` にて ``bypass_verification`` のサポートを追加しました。" + +#: ../../whats_new.rst:169 +msgid "Add support for passing a client to :meth:`Webhook.from_url` and :meth:`Webhook.partial`" +msgstr ":meth:`Webhook.from_url` と :meth:`Webhook.partial` にクライアントを渡せるようにしました。" + +#: ../../whats_new.rst:169 +msgid "This allows them to use views (assuming they are \"bot owned\" webhooks)" +msgstr "これにより、ビューを使用することができます (「ボット所有」Webhookである場合は)。" + +#: ../../whats_new.rst:171 +msgid "Add :meth:`Colour.dark_embed` and :meth:`Colour.light_embed` (:issue:`9219`)" +msgstr ":meth:`Colour.dark_embed` と :meth:`Colour.light_embed` を追加しました。 (:issue:`9219`)" + +#: ../../whats_new.rst:172 +msgid "Add support for many more parameters within :meth:`Guild.create_stage_channel` (:issue:`9245`)" +msgstr ":meth:`Guild.create_stage_channel` で対応するパラメータを追加しました。 (:issue:`9245`)" + +#: ../../whats_new.rst:173 +msgid "Add :attr:`AppInfo.role_connections_verification_url`" +msgstr ":attr:`AppInfo.role_connections_verification_url` を追加しました。" + +#: ../../whats_new.rst:174 +msgid "Add support for :attr:`ForumChannel.default_layout`" +msgstr ":attr:`ForumChannel.default_layout` のサポートを追加しました。" + +#: ../../whats_new.rst:175 +msgid "Add various new :class:`MessageType` values such as ones related to stage channel and role subscriptions" +msgstr "ステージチャンネルやロールサブスクリプションに関連するものなど、新しい :class:`MessageType` 値を追加しました。" + +#: ../../whats_new.rst:182 +msgid "Add support for role subscription related attributes" +msgstr "ロールサブスクリプション関連属性のサポートを追加しました。" + +#: ../../whats_new.rst:177 +msgid ":class:`RoleSubscriptionInfo` within :attr:`Message.role_subscription`" +msgstr ":attr:`Message.role_subscription` と :class:`RoleSubscriptionInfo` 。" + +#: ../../whats_new.rst:178 +msgid ":attr:`MessageType.role_subscription_purchase`" +msgstr ":attr:`MessageType.role_subscription_purchase`" + +#: ../../whats_new.rst:179 +msgid ":attr:`SystemChannelFlags.role_subscription_purchase_notifications`" +msgstr ":attr:`SystemChannelFlags.role_subscription_purchase_notifications`" + +#: ../../whats_new.rst:180 +msgid ":attr:`SystemChannelFlags.role_subscription_purchase_notification_replies`" +msgstr ":attr:`SystemChannelFlags.role_subscription_purchase_notification_replies`" + +#: ../../whats_new.rst:181 +msgid ":attr:`RoleTags.subscription_listing_id`" +msgstr ":attr:`RoleTags.subscription_listing_id`" + +#: ../../whats_new.rst:182 +msgid ":meth:`RoleTags.is_available_for_purchase`" +msgstr ":meth:`RoleTags.is_available_for_purchase`" + +#: ../../whats_new.rst:184 +msgid "Add support for checking if a role is a linked role under :meth:`RoleTags.is_guild_connection`" +msgstr ":meth:`RoleTags.is_guild_connection` で、ロールが紐づいたロールかの確認のサポートを追加しました。" + +#: ../../whats_new.rst:185 +msgid "Add support for GIF sticker type" +msgstr "GIFスタンプタイプのサポートを追加しました。" + +#: ../../whats_new.rst:186 +msgid "Add support for :attr:`Message.application_id` and :attr:`Message.position`" +msgstr ":attr:`Message.application_id` と :attr:`Message.position` のサポートを追加しました。" + +#: ../../whats_new.rst:187 +msgid "Add :func:`utils.maybe_coroutine` helper" +msgstr ":func:`utils.maybe_coroutine` ヘルパーを追加しました。" + +#: ../../whats_new.rst:188 +msgid "Add :attr:`ScheduledEvent.creator_id` attribute" +msgstr ":attr:`ScheduledEvent.creator_id` 属性を追加しました。" + +#: ../../whats_new.rst:189 +msgid "|commands| Add support for :meth:`~ext.commands.Cog.interaction_check` for :class:`~ext.commands.GroupCog` (:issue:`9189`)" +msgstr "|commands| :class:`~ext.commands.GroupCog` にて :meth:`~ext.commands.Cog.interaction_check` のサポートを追加しました。 (:issue:`9189`)" + +#: ../../whats_new.rst:194 +msgid "Fix views not being removed from message store backing leading to a memory leak when used from an application command context" +msgstr "アプリケーションコマンドから使用されたビューがメッセージストアから除去されず、メモリリークを引き起こすバグを修正しました。" + +#: ../../whats_new.rst:195 +msgid "Fix async iterators requesting past their bounds when using ``oldest_first`` and ``after`` or ``before`` (:issue:`9093`)" +msgstr "非同期イテレータが ``oldest_first`` と ``after`` または ``before`` を指定した場合に境界を越えてリクエストをするのを修正しました。 (:issue:`9093`)" + +#: ../../whats_new.rst:196 +msgid "Fix :meth:`Guild.audit_logs` pagination logic being buggy when using ``after`` (:issue:`9269`)" +msgstr ":meth:`Guild.audit_logs` にて、 ``after`` を使用したときにページネーションで発生するバグを修正しました。 (:issue:`9269`)" + +#: ../../whats_new.rst:197 +msgid "Fix :attr:`Message.channel` sometimes being :class:`Object` instead of :class:`PartialMessageable`" +msgstr ":attr:`Message.channel` が時々 :class:`PartialMessageable` ではなく :class:`Object` となるバグを修正しました。" + +#: ../../whats_new.rst:198 +msgid "Fix :class:`ui.View` not properly calling ``super().__init_subclass__`` (:issue:`9231`)" +msgstr ":class:`ui.View` が ``super().__init_subclass__`` を適切に呼び出さないのを修正しました。 (:issue:`9231`)" + +#: ../../whats_new.rst:199 +msgid "Fix ``available_tags`` and ``default_thread_slowmode_delay`` not being respected in :meth:`Guild.create_forum`" +msgstr ":meth:`Guild.create_forum` で渡された ``available_tags`` と ``default_thread_slowmode_delay`` が使用されない問題を修正しました。" + +#: ../../whats_new.rst:200 +msgid "Fix :class:`AutoModTrigger` ignoring ``allow_list`` with type keyword (:issue:`9107`)" +msgstr ":class:`AutoModTrigger` が type キーワードのある ``allow_list`` を無視するバグを修正しました。 (:issue:`9107`)" + +#: ../../whats_new.rst:201 +msgid "Fix implicit permission resolution for :class:`Thread` (:issue:`9153`)" +msgstr ":class:`Thread` の暗黙的な権限の解決を修正しました。 (:issue:`9153`)" + +#: ../../whats_new.rst:202 +msgid "Fix :meth:`AutoModRule.edit` to work with actual snowflake types such as :class:`Object` (:issue:`9159`)" +msgstr ":meth:`AutoModRule.edit` を、 :class:`Object` のようなスノウフレーク型で動くよう修正しました。 (:issue:`9159`)" + +#: ../../whats_new.rst:203 +msgid "Fix :meth:`Webhook.send` returning :class:`ForumChannel` for :attr:`WebhookMessage.channel`" +msgstr ":meth:`Webhook.send` が :attr:`WebhookMessage.channel` に関し :class:`ForumChannel` を返すのを修正しました。" + +#: ../../whats_new.rst:204 +msgid "When a lookup for :attr:`AuditLogEntry.target` fails, it will fallback to :class:`Object` with the appropriate :attr:`Object.type` (:issue:`9171`)" +msgstr ":attr:`AuditLogEntry.target` の検索が失敗したとき、適切な :attr:`Object.type` をもつ :class:`Object` にフォールバックするようにしました。 (:issue:`9171`)" + +#: ../../whats_new.rst:205 +msgid "Fix :attr:`AuditLogDiff.type` for integrations returning :class:`ChannelType` instead of :class:`str` (:issue:`9200`)" +msgstr "インテグレーションの :attr:`AuditLogDiff.type` が :class:`str` ではなく :class:`ChannelType` を返すのを修正しました。 (:issue:`9200`)" + +#: ../../whats_new.rst:206 +msgid "Fix :attr:`AuditLogDiff.type` for webhooks returning :class:`ChannelType` instead of :class:`WebhookType` (:issue:`9251`)" +msgstr "Webhookの :attr:`AuditLogDiff.type` が :class:`WebhookType` ではなく :class:`ChannelType` を返すのを修正しました。 (:issue:`9251`)" + +#: ../../whats_new.rst:207 +msgid "Fix webhooks and interactions not properly closing files after the request has completed" +msgstr "Webhookとインタラクションが、リクエストが完了した後にファイルを正しく閉じないバグを修正しました。" + +#: ../../whats_new.rst:208 +msgid "Fix :exc:`NameError` in audit log target for app commands" +msgstr "アプリケーションコマンドの監査ログターゲットでの :exc:`NameError` を修正しました。" + +#: ../../whats_new.rst:209 +msgid "Fix :meth:`ScheduledEvent.edit` requiring some arguments to be passed in when unnecessary (:issue:`9261`, :issue:`9268`)" +msgstr ":meth:`ScheduledEvent.edit` にて不必要な引数が必須とされるバグを修正しました。 (:issue:`9261` 、 :issue:`9268`)" + +#: ../../whats_new.rst:210 +msgid "|commands| Explicit set a traceback for hybrid command invocations (:issue:`9205`)" +msgstr "|commands| ハイブリッドコマンドを呼び出すとき、明示的にトレースバックを設定するようにしました。 (:issue:`9205`)" + +#: ../../whats_new.rst:215 +msgid "Add colour preview for the colours predefined in :class:`Colour`" +msgstr ":class:`Colour` で定義された色のプレビューを追加しました。" + +#: ../../whats_new.rst:216 +msgid "Finished views are no longer stored by the library when sending them (:issue:`9235`)" +msgstr "終了したビューは送信時にライブラリで保管されないようになりました。 (:issue:`9235`)" + +#: ../../whats_new.rst:217 +msgid "Force enable colour logging for the default logging handler when run under Docker." +msgstr "Docker下で実行するときに、デフォルトの logging ハンドラで色のついたログを常に有効にするようにしました。" + +#: ../../whats_new.rst:218 +msgid "Add various overloads for :meth:`Client.wait_for` to aid in static analysis (:issue:`9184`)" +msgstr "静的解析のために、 :meth:`Client.wait_for` のオーバーロードを追加しました。 (:issue:`9184`)" + +#: ../../whats_new.rst:219 +msgid ":class:`Interaction` can now optionally take a generic parameter, ``ClientT`` to represent the type for :attr:`Interaction.client`" +msgstr ":class:`Interaction` は、オプションでジェネリックのパラメータ ``ClientT`` をとり、 :attr:`Interaction.client` の型を指定できるようになりました。" + +#: ../../whats_new.rst:220 +msgid "|commands| Respect :attr:`~ext.commands.Command.ignore_extra` for :class:`~discord.ext.commands.FlagConverter` keyword-only parameters" +msgstr "|commands| :class:`~discord.ext.commands.FlagConverter` キーワードのみのパラメータでも、 :attr:`~ext.commands.Command.ignore_extra` に従うようにしました。" + +#: ../../whats_new.rst:221 +msgid "|commands| Change :attr:`Paginator.pages ` to not prematurely close (:issue:`9257`)" +msgstr "|commands| :attr:`Paginator.pages ` を早期に閉じないように変更しました。 (:issue:`9257`)" + +#: ../../whats_new.rst:226 +msgid "v2.1.1" +msgstr "v2.1.1" + +#: ../../whats_new.rst:231 +msgid "Fix crash involving GIF stickers when looking up their filename extension." +msgstr "GIF スタンプのファイル名の拡張子を検索するときのクラッシュを修正しました。" + +#: ../../whats_new.rst:236 +msgid "v2.1.0" +msgstr "v2.1.0" + +#: ../../whats_new.rst:241 msgid "Add support for ``delete_message_seconds`` in :meth:`Guild.ban` (:issue:`8391`)" msgstr ":meth:`Guild.ban` に ``delete_message_seconds`` へのサポートを追加しました。 (:issue:`8391`)" -#: ../../whats_new.rst:23 +#: ../../whats_new.rst:242 msgid "Add support for automod related audit log actions (:issue:`8389`)" msgstr "AutoMod関連の監査ログアクションのサポートを追加しました。 (:issue:`8389`)" -#: ../../whats_new.rst:24 +#: ../../whats_new.rst:243 msgid "Add support for :class:`ForumChannel` annotations in app commands" msgstr "アプリケーションコマンドで :class:`ForumChannel` アノテーションのサポートを追加しました。" -#: ../../whats_new.rst:25 +#: ../../whats_new.rst:244 msgid "Add support for :attr:`ForumChannel.default_thread_slowmode_delay`." msgstr ":attr:`ForumChannel.default_thread_slowmode_delay` のサポートを追加しました。" -#: ../../whats_new.rst:26 +#: ../../whats_new.rst:245 msgid "Add support for :attr:`ForumChannel.default_reaction_emoji`." msgstr ":attr:`ForumChannel.default_reaction_emoji` のサポートを追加しました。" -#: ../../whats_new.rst:29 +#: ../../whats_new.rst:248 msgid "Add support for forum tags under :class:`ForumTag`." msgstr ":class:`ForumTag` にて、フォーラムタグのサポートを追加しました。" -#: ../../whats_new.rst:28 +#: ../../whats_new.rst:247 msgid "Tags can be obtained using :attr:`ForumChannel.available_tags` or :meth:`ForumChannel.get_tag`." msgstr "タグは :attr:`ForumChannel.available_tags` または :meth:`ForumChannel.get_tag` で取得できます。" -#: ../../whats_new.rst:29 +#: ../../whats_new.rst:248 msgid "See :meth:`Thread.edit` and :meth:`ForumChannel.edit` for modifying tags and their usage." msgstr "タグの変更方法や使い方については :meth:`Thread.edit` と :meth:`ForumChannel.edit` を参照してください。" -#: ../../whats_new.rst:33 +#: ../../whats_new.rst:252 msgid "Add support for new select types (:issue:`9013`, :issue:`9003`)." msgstr "新しい選択メニューの種類のサポートを追加しました。 (:issue:`9013`, :issue:`9003`)" -#: ../../whats_new.rst:32 +#: ../../whats_new.rst:251 msgid "These are split into separate classes, :class:`~discord.ui.ChannelSelect`, :class:`~discord.ui.RoleSelect`, :class:`~discord.ui.UserSelect`, :class:`~discord.ui.MentionableSelect`." msgstr "これらは、 :class:`~discord.ui.ChannelSelect` 、 :class:`~discord.ui.RoleSelect` 、 :class:`~discord.ui.UserSelect` 、 :class:`~discord.ui.MentionableSelect` に分割されています。" -#: ../../whats_new.rst:33 +#: ../../whats_new.rst:252 msgid "The decorator still uses a single function, :meth:`~discord.ui.select`. Changing the select type is done by the ``cls`` keyword parameter." msgstr "デコレータはこれまで通り単一の関数 :meth:`~discord.ui.select` を使用しています。選択メニューの種類の変更は ``cls`` キーワード引数によって行われます。" -#: ../../whats_new.rst:35 +#: ../../whats_new.rst:254 msgid "Add support for toggling discoverable and invites_disabled features in :meth:`Guild.edit` (:issue:`8390`)." msgstr ":meth:`Guild.edit` で、discoverable と invites_disabled 機能を切り替えるためのサポートを追加しました。 (:issue:`8390`)" -#: ../../whats_new.rst:36 +#: ../../whats_new.rst:255 msgid "Add :meth:`Interaction.translate` helper method (:issue:`8425`)." msgstr ":meth:`Interaction.translate` ヘルパーメソッドを追加しました。 (:issue:`8425`)" -#: ../../whats_new.rst:37 +#: ../../whats_new.rst:256 msgid "Add :meth:`Forum.archived_threads` (:issue:`8476`)." msgstr ":meth:`Forum.archived_threads` を追加しました。 (:issue:`8476`)" -#: ../../whats_new.rst:38 +#: ../../whats_new.rst:257 msgid "Add :attr:`ApplicationFlags.active`, :attr:`UserFlags.active_developer`, and :attr:`PublicUserFlags.active_developer`." msgstr ":attr:`ApplicationFlags.active` 、 :attr:`UserFlags.active_developer` 、および :attr:`PublicUserFlags.active_developer` を追加しました。" -#: ../../whats_new.rst:39 +#: ../../whats_new.rst:258 msgid "Add ``delete_after`` to :meth:`InteractionResponse.send_message` (:issue:`9022`)." msgstr ":meth:`InteractionResponse.send_message` に ``delete_after`` を追加しました。 (:issue:`9022`)" -#: ../../whats_new.rst:40 +#: ../../whats_new.rst:259 msgid "Add support for :attr:`AutoModTrigger.regex_patterns`." msgstr ":attr:`AutoModTrigger.regex_patterns` のサポートを追加しました。" -#: ../../whats_new.rst:41 +#: ../../whats_new.rst:260 msgid "|commands| Add :attr:`GroupCog.group_extras ` to set :attr:`app_commands.Group.extras` (:issue:`8405`)." msgstr "|commands| :attr:`app_commands.Group.extras` を設定できる :attr:`GroupCog.group_extras ` を追加しました。 (:issue:`8405`)" -#: ../../whats_new.rst:42 +#: ../../whats_new.rst:261 msgid "|commands| Add support for NumPy style docstrings for regular commands to set parameter descriptions." msgstr "|commands| 通常のコマンドでパラメータの説明を設定するのに NumPy スタイルの docstring が利用できるようになりました。" -#: ../../whats_new.rst:43 +#: ../../whats_new.rst:262 msgid "|commands| Allow :class:`~discord.ext.commands.Greedy` to potentially maintain state between calls." msgstr "|commands| :class:`~discord.ext.commands.Greedy` が呼び出し間で状態を維持できるようにしました。" -#: ../../whats_new.rst:44 +#: ../../whats_new.rst:263 msgid "|commands| Add :meth:`Cog.has_app_command_error_handler ` (:issue:`8991`)." msgstr "|commands| :meth:`Cog.has_app_command_error_handler ` を追加しました。 (:issue:`8991`)" -#: ../../whats_new.rst:45 +#: ../../whats_new.rst:264 msgid "|commands| Allow ``delete_after`` in :meth:`Context.send ` on ephemeral messages (:issue:`9021`)." msgstr "|commands| :meth:`Context.send ` で ``delete_after`` が利用できるようになりました。 (:issue:`9021`)" -#: ../../whats_new.rst:48 -#: ../../whats_new.rst:77 -#: ../../whats_new.rst:102 -#: ../../whats_new.rst:113 -#: ../../whats_new.rst:125 -msgid "Bug Fixes" -msgstr "バグ修正" - -#: ../../whats_new.rst:50 +#: ../../whats_new.rst:269 msgid "Fix an :exc:`KeyError` being raised when constructing :class:`app_commands.Group` with no module (:issue:`8411`)." msgstr ":exc:`app_commands.Group` をモジュールなしで構築したときに :class:`KeyError` が発生する問題を修正しました。 (:issue:`8411`)" -#: ../../whats_new.rst:51 +#: ../../whats_new.rst:270 msgid "Fix unescaped period in webhook URL regex (:issue:`8443`)." msgstr "Webhook URLの正規表現でピリオドがエスケープされていない問題を修正しました。 (:issue:`8443`)" -#: ../../whats_new.rst:52 +#: ../../whats_new.rst:271 msgid "Fix :exc:`app_commands.CommandSyncFailure` raising for other 400 status code errors." msgstr "他の400ステータスコードエラーで :exc:`app_commands.CommandSyncFailure` が送出される問題を修正しました。" -#: ../../whats_new.rst:53 +#: ../../whats_new.rst:272 msgid "Fix potential formatting issues showing `_errors` in :exc:`app_commands.CommandSyncFailure`." msgstr ":exc:`app_commands.CommandSyncFailure` で ``_errors`` を表示するかもしれないフォーマットの問題を修正しました。" -#: ../../whats_new.rst:54 +#: ../../whats_new.rst:273 msgid "Fix :attr:`Guild.stage_instances` and :attr:`Guild.schedule_events` clearing on ``GUILD_UPDATE``." msgstr ":attr:`Guild.stage_instances` と :attr:`Guild.schedule_events` が ``GUILD_UPDATE`` 時に空になる問題を修正しました。" -#: ../../whats_new.rst:55 +#: ../../whats_new.rst:274 msgid "Fix detection of overriden :meth:`app_commands.Group.on_error`" msgstr "オーバーライドされた :meth:`app_commands.Group.on_error` の検出を修正しました。" -#: ../../whats_new.rst:56 +#: ../../whats_new.rst:275 msgid "Fix :meth:`app_commands.CommandTree.on_error` still being called when a bound error handler is set." msgstr "エラーハンドラが設定されている場合にも、 :meth:`app_commands.CommandTree.on_error` が呼び出されているのを修正しました。" -#: ../../whats_new.rst:57 +#: ../../whats_new.rst:276 msgid "Fix thread permissions being set to ``True`` in :meth:`DMChannel.permissions_for` (:issue:`8965`)." msgstr ":meth:`DMChannel.permissions_for` でスレッドの権限が ``True`` に設定されている問題を修正しました。 (:issue:`8965`)" -#: ../../whats_new.rst:58 +#: ../../whats_new.rst:277 msgid "Fix ``on_scheduled_event_delete`` occasionally dispatching with too many parameters (:issue:`9019`)." msgstr "``on_scheduled_event_delete`` に渡されるパラメータが多すぎる場合がある問題を修正しました。 (:issue:`9019`)" -#: ../../whats_new.rst:59 +#: ../../whats_new.rst:278 msgid "|commands| Fix :meth:`Context.from_interaction ` ignoring :attr:`~discord.ext.commands.Context.command_failed`." msgstr "|commands| :meth:`Context.from_interaction ` が :attr:`~discord.ext.commands.Context.command_failed` を無視する問題を修正しました。" -#: ../../whats_new.rst:60 +#: ../../whats_new.rst:279 msgid "|commands| Fix :class:`~discord.ext.commands.Range` to allow 3.10 Union syntax (:issue:`8446`)." msgstr "|commands| :class:`~discord.ext.commands.Range` で 3.10 Union 構文が利用できるようになりました。 (:issue:`8446`)." -#: ../../whats_new.rst:61 +#: ../../whats_new.rst:280 msgid "|commands| Fix ``before_invoke`` not triggering for fallback commands in a hybrid group command (:issue:`8461`, :issue:`8462`)." msgstr "|commands| HybridGroupコマンドでfallbackコマンドがトリガーされない問題を修正しました(:issue:`8461`, :issue:`8462`)。" -#: ../../whats_new.rst:64 -#: ../../whats_new.rst:206 -#: ../../whats_new.rst:273 -#: ../../whats_new.rst:302 -#: ../../whats_new.rst:358 -msgid "Miscellaneous" -msgstr "その他" - -#: ../../whats_new.rst:66 +#: ../../whats_new.rst:285 msgid "Change error message for unbound callbacks in :class:`app_commands.ContextMenu` to make it clearer that bound methods are not allowed." msgstr ":class:`app_commands.ContextMenu` でバインドされていないコールバックのエラーメッセージを変更し、バインドされているメソッドは使用できないことを明確にしました。" -#: ../../whats_new.rst:67 +#: ../../whats_new.rst:286 msgid "Normalize type formatting in TypeError exceptions (:issue:`8453`)." msgstr "TypeError 例外で型のフォーマットを標準化しました。 (:issue:`8453`)" -#: ../../whats_new.rst:68 +#: ../../whats_new.rst:287 msgid "Change :meth:`VoiceProtocol.on_voice_state_update` and :meth:`VoiceProtocol.on_voice_server_update` parameters to be positional only (:issue:`8463`)." msgstr ":meth:`VoiceProtocol.on_voice_state_update` と :meth:`VoiceProtocol.on_voice_server_update` パラメータを位置指定専用に変更しました。 (:issue:`8463` )" -#: ../../whats_new.rst:69 +#: ../../whats_new.rst:288 msgid "Add support for PyCharm when using the default coloured logger (:issue:`9015`)." msgstr "デフォルトの色付きロガーに、 PyCharm 対応を追加しました。 (:issue:`9015`)" -#: ../../whats_new.rst:74 +#: ../../whats_new.rst:293 msgid "v2.0.1" msgstr "v2.0.1" -#: ../../whats_new.rst:79 +#: ../../whats_new.rst:298 msgid "Fix ``cchardet`` being installed on Python >=3.10 when using the ``speed`` extras." msgstr "Python 3.10 以降で ``speed`` extrasを使用した場合に ``cchardet`` がインストールされる問題を修正しました。" -#: ../../whats_new.rst:80 +#: ../../whats_new.rst:299 msgid "Fix :class:`ui.View` timeout updating when the :meth:`ui.View.interaction_check` failed." msgstr ":meth:`ui.View.interaction_check` に失敗したときにも :class:`ui.View` のタイムアウトが更新される問題を修正しました。" -#: ../../whats_new.rst:81 +#: ../../whats_new.rst:300 msgid "Fix :meth:`app_commands.CommandTree.on_error` not triggering if :meth:`~app_commands.CommandTree.interaction_check` raises." msgstr ":meth:`~app_commands.CommandTree.interaction_check` が例外を送出したときに :meth:`app_commands.CommandTree.on_error` が実行されない問題を修正しました。" -#: ../../whats_new.rst:82 +#: ../../whats_new.rst:301 msgid "Fix ``__main__`` script to use ``importlib.metadata`` instead of the deprecated ``pkg_resources``." msgstr "非推奨の ``pkg_resources`` の代わりに ``importlib.metadata`` を使用するよう ``__main__`` スクリプトを修正しました。" -#: ../../whats_new.rst:84 +#: ../../whats_new.rst:303 msgid "Fix library callbacks triggering a type checking error if the parameter names were different." msgstr "ライブラリコールバックのパラメータ名が異なる場合に型チェックエラーが検出される問題を修正しました。" -#: ../../whats_new.rst:84 +#: ../../whats_new.rst:303 msgid "This required a change in the :ref:`version_guarantees`" msgstr "これに伴い :ref:`version_guarantees` が改訂されました。" -#: ../../whats_new.rst:86 +#: ../../whats_new.rst:305 msgid "|commands| Fix Python 3.10 union types not working with :class:`commands.Greedy `." msgstr "|commands| Python 3.10 のユニオン型が :class:`commands.Greedy ` で動作しない問題を修正しました。" -#: ../../whats_new.rst:91 +#: ../../whats_new.rst:310 msgid "v2.0.0" msgstr "v2.0.0" -#: ../../whats_new.rst:93 +#: ../../whats_new.rst:312 msgid "The changeset for this version are too big to be listed here, for more information please see :ref:`the migrating page `." msgstr "このバージョンの変更は大きすぎるため、この場所に収まりきりません。詳細については :ref:`移行についてのページ ` を参照してください。" -#: ../../whats_new.rst:99 +#: ../../whats_new.rst:318 msgid "v1.7.3" msgstr "v1.7.3" -#: ../../whats_new.rst:104 +#: ../../whats_new.rst:323 msgid "Fix a crash involving guild uploaded stickers" msgstr "ギルドでアップロードされたスタンプに関するクラッシュを修正しました。" -#: ../../whats_new.rst:105 +#: ../../whats_new.rst:324 msgid "Fix :meth:`DMChannel.permissions_for` not having :attr:`Permissions.read_messages` set." msgstr ":meth:`DMChannel.permissions_for` に :attr:`Permissions.read_messages` が設定されていない問題を修正しました。" -#: ../../whats_new.rst:110 +#: ../../whats_new.rst:329 msgid "v1.7.2" msgstr "v1.7.2" -#: ../../whats_new.rst:115 +#: ../../whats_new.rst:334 msgid "Fix ``fail_if_not_exists`` causing certain message references to not be usable within :meth:`abc.Messageable.send` and :meth:`Message.reply` (:issue:`6726`)" msgstr "``fail_if_not_exists`` により、特定のメッセージ参照が :meth:`abc.Messageable.send` および :meth:`Message.reply` 内で使用できない問題を修正しました。 (:issue:`6726`)" -#: ../../whats_new.rst:116 +#: ../../whats_new.rst:335 msgid "Fix :meth:`Guild.chunk` hanging when the user left the guild. (:issue:`6730`)" msgstr "ギルドからユーザーが脱退した際に :meth:`Guild.chunk` がハングするのを修正しました。 (:issue:`6730`)" -#: ../../whats_new.rst:117 +#: ../../whats_new.rst:336 msgid "Fix loop sleeping after final iteration rather than before (:issue:`6744`)" msgstr "最終反復の前ではなく後にループがスリープするのを修正しました。 (:issue:`6744`)" -#: ../../whats_new.rst:122 +#: ../../whats_new.rst:341 msgid "v1.7.1" msgstr "v1.7.1" -#: ../../whats_new.rst:127 +#: ../../whats_new.rst:346 msgid "|commands| Fix :meth:`Cog.has_error_handler ` not working as intended." msgstr "|commands| :meth:`Cog.has_error_handler ` が正常に動作しない問題を修正しました。" -#: ../../whats_new.rst:132 +#: ../../whats_new.rst:351 msgid "v1.7.0" msgstr "v1.7.0" -#: ../../whats_new.rst:134 +#: ../../whats_new.rst:353 msgid "This version is mainly for improvements and bug fixes. This is more than likely the last major version in the 1.x series. Work after this will be spent on v2.0. As a result, **this is the last version to support Python 3.5**. Likewise, **this is the last version to support user bots**." msgstr "このバージョンは、主にバグ修正と、機能改善が含まれています。 おそらくこのバージョンが、1.xシリーズの最後のメジャーバージョンとなる予定です。これ以降の作業は、主にv2.0に費やされます。 結果として、**このバージョンが、Python 3.5をサポートする最後のバージョンになります**。 同様に、**このバージョンがユーザーボットをサポートする最後のバージョンです**。" -#: ../../whats_new.rst:138 +#: ../../whats_new.rst:357 msgid "Development of v2.0 will have breaking changes and support for newer API features." msgstr "v2.0の開発には、破壊的更新と、新しいAPI機能の変更が含まれるでしょう。" -#: ../../whats_new.rst:143 +#: ../../whats_new.rst:362 msgid "Add support for stage channels via :class:`StageChannel` (:issue:`6602`, :issue:`6608`)" msgstr ":class:`StageChannel` のサポートを追加しました。 (:issue:`6602`, :issue:`6608`)" -#: ../../whats_new.rst:146 +#: ../../whats_new.rst:365 msgid "Add support for :attr:`MessageReference.fail_if_not_exists` (:issue:`6484`)" msgstr ":attr:`MessageReference.fail_if_not_exists` のサポートを追加しました。 (:issue:`6484`)" -#: ../../whats_new.rst:145 +#: ../../whats_new.rst:364 msgid "By default, if the message you're replying to doesn't exist then the API errors out. This attribute tells the Discord API that it's okay for that message to be missing." msgstr "デフォルトでは、もし返信するメッセージが存在しない場合、APIエラーが発生します。この属性は、Discord APIにメッセージが存在していない場合でも、問題がないことを伝えます。" -#: ../../whats_new.rst:148 +#: ../../whats_new.rst:367 msgid "Add support for Discord's new permission serialisation scheme." msgstr "Discordの新しい権限シリアライゼーションスキームのサポートを追加しました。" -#: ../../whats_new.rst:149 +#: ../../whats_new.rst:368 msgid "Add an easier way to move channels using :meth:`abc.GuildChannel.move`" msgstr "簡単にチャンネルの移動をする :meth:`abc.GuildChannel.move` を追加しました。" -#: ../../whats_new.rst:150 +#: ../../whats_new.rst:369 msgid "Add :attr:`Permissions.use_slash_commands`" msgstr "新しい権限 :attr:`Permissions.use_slash_commands` を追加しました。" -#: ../../whats_new.rst:151 +#: ../../whats_new.rst:370 msgid "Add :attr:`Permissions.request_to_speak`" msgstr "新しい権限 :attr:`Permissions.request_to_speak` を追加しました。" -#: ../../whats_new.rst:152 +#: ../../whats_new.rst:371 msgid "Add support for voice regions in voice channels via :attr:`VoiceChannel.rtc_region` (:issue:`6606`)" msgstr ":attr:`VoiceChannel.rtc_region` によるボイスチャンネルの、ボイスリージョンのサポートを追加しました。 (:issue:`6606`)" -#: ../../whats_new.rst:153 +#: ../../whats_new.rst:372 msgid "Add support for :meth:`PartialEmoji.url_as` (:issue:`6341`)" msgstr ":meth:`PartialEmoji.url_as` のサポートを追加しました。 (:issue:`6341`)" -#: ../../whats_new.rst:154 +#: ../../whats_new.rst:373 msgid "Add :attr:`MessageReference.jump_url` (:issue:`6318`)" msgstr ":attr:`MessageReference.jump_url` を追加しました。(:issue:`6318`)" -#: ../../whats_new.rst:155 +#: ../../whats_new.rst:374 msgid "Add :attr:`File.spoiler` (:issue:`6317`)" msgstr ":attr:`File.spoiler` を追加しました。 (:issue:`6317`)" -#: ../../whats_new.rst:156 +#: ../../whats_new.rst:375 msgid "Add support for passing ``roles`` to :meth:`Guild.estimate_pruned_members` (:issue:`6538`)" msgstr "``roles`` を :meth:`Guild.estimate_pruned_members` に渡すことができるようになりました。(:issue:`6538`)" -#: ../../whats_new.rst:157 +#: ../../whats_new.rst:376 msgid "Allow callable class factories to be used in :meth:`abc.Connectable.connect` (:issue:`6478`)" msgstr ":meth:`abc.Connectable.connect` において、クラスを生成する呼び出し可能オブジェクトを使用できるようになりました。( :issue:`6478` )" -#: ../../whats_new.rst:158 +#: ../../whats_new.rst:377 msgid "Add a way to get mutual guilds from the client's cache via :attr:`User.mutual_guilds` (:issue:`2539`, :issue:`6444`)" msgstr "クライアントのキャッシュから共通のギルドを取得する :attr:`User.mutual_guilds` を追加しました。 (:issue:`2539`, :issue:`6444`)" -#: ../../whats_new.rst:159 +#: ../../whats_new.rst:378 msgid ":meth:`PartialMessage.edit` now returns a full :class:`Message` upon success (:issue:`6309`)" msgstr ":meth:`PartialMessage.edit` が成功時に完全な :class:`Message` を返すようになりました。 (:issue:`6309`)" -#: ../../whats_new.rst:160 +#: ../../whats_new.rst:379 msgid "Add :attr:`RawMessageUpdateEvent.guild_id` (:issue:`6489`)" msgstr ":attr:`RawMessageUpdateEvent.guild_id` を追加しました。(:issue:`6489`)" -#: ../../whats_new.rst:161 +#: ../../whats_new.rst:380 msgid ":class:`AuditLogEntry` is now hashable (:issue:`6495`)" msgstr ":class:`AuditLogEntry` がハッシュ可能になりました。 (:issue:`6495`)" -#: ../../whats_new.rst:162 +#: ../../whats_new.rst:381 msgid ":class:`Attachment` is now hashable" msgstr ":class:`Attachment` がハッシュ可能になりました。" -#: ../../whats_new.rst:163 +#: ../../whats_new.rst:382 msgid "Add :attr:`Attachment.content_type` attribute (:issue:`6618`)" msgstr ":attr:`Attachment.content_type` 属性を追加しました。 (:issue:`6618`)" -#: ../../whats_new.rst:164 +#: ../../whats_new.rst:383 msgid "Add support for casting :class:`Attachment` to :class:`str` to get the URL." msgstr "URLを取得するために :class:`Atachment` を :class:`str` へキャストできるようになりました。" -#: ../../whats_new.rst:166 +#: ../../whats_new.rst:385 msgid "Add ``seed`` parameter for :class:`Colour.random` (:issue:`6562`)" msgstr ":class:`Colour.random` に ``seed`` パラメータを追加しました。 (:issue:`6562`)" -#: ../../whats_new.rst:166 +#: ../../whats_new.rst:385 msgid "This only seeds it for one call. If seeding for multiple calls is desirable, use :func:`random.seed`." msgstr "これは1つの呼び出しに対してのみシードします。複数の呼び出しに対するシードが望ましい場合は、 :func:`random.seed` を使用してください。" -#: ../../whats_new.rst:168 +#: ../../whats_new.rst:387 msgid "Add a :func:`utils.remove_markdown` helper function (:issue:`6573`)" msgstr ":func:`utils.remove_markdown` ヘルパー関数を追加しました。 (:issue:`6573`)" -#: ../../whats_new.rst:169 +#: ../../whats_new.rst:388 msgid "Add support for passing scopes to :func:`utils.oauth_url` (:issue:`6568`)" msgstr ":func:`utils.oauth_url` にスコープを渡すことが可能になりました。 (:issue:`6568`)" -#: ../../whats_new.rst:170 +#: ../../whats_new.rst:389 msgid "|commands| Add support for ``rgb`` CSS function as a parameter to :class:`ColourConverter ` (:issue:`6374`)" msgstr "|commands| :class:`ColourConverter ` において、 ``rgb`` CSS関数の文字列を変換できるようになりました。 (:issue:`6374`)" -#: ../../whats_new.rst:171 +#: ../../whats_new.rst:390 msgid "|commands| Add support for converting :class:`StoreChannel` via :class:`StoreChannelConverter ` (:issue:`6603`)" msgstr "|commands| :class:`StoreChannel` を :class:`StoreChannelConverter ` によって変換できるようになりました。 (:issue:`6603`)" -#: ../../whats_new.rst:172 +#: ../../whats_new.rst:391 msgid "|commands| Add support for stripping whitespace after the prefix is encountered using the ``strip_after_prefix`` :class:`~ext.commands.Bot` constructor parameter." msgstr "|commands| :class:`~ext.commands.Bot` の ``strip_after_prefix`` 初期化パラメーターを指定することで、プレフィックスのあとの空白を取り除けるようになりました。" -#: ../../whats_new.rst:173 +#: ../../whats_new.rst:392 msgid "|commands| Add :attr:`Context.invoked_parents ` to get the aliases a command's parent was invoked with (:issue:`1874`, :issue:`6462`)" msgstr "|commands| :attr:`Context.invoked_parents ` で、呼び出されたときのコマンドの親のエイリアスを取得できるようになりました。 (:issue:`1874`, :issue:`6462`)" -#: ../../whats_new.rst:174 +#: ../../whats_new.rst:393 msgid "|commands| Add a converter for :class:`PartialMessage` under :class:`ext.commands.PartialMessageConverter` (:issue:`6308`)" msgstr "|commands| :class:`PartialMessage` 用のコンバーター :class:`ext.commands.PartialMessageConverter` を追加しました。 (:issue:`6308`)" -#: ../../whats_new.rst:175 +#: ../../whats_new.rst:394 msgid "|commands| Add a converter for :class:`Guild` under :class:`ext.commands.GuildConverter` (:issue:`6016`, :issue:`6365`)" msgstr "|commands| :class:`Guild` 用のコンバーター :class:`ext.commands.GuildConverter` を追加しました。 (:issue:`6016`, :issue:`6365`)" -#: ../../whats_new.rst:176 +#: ../../whats_new.rst:395 msgid "|commands| Add :meth:`Command.has_error_handler `" msgstr "|commands| :meth:`Command.has_error_handler ` を追加しました。" -#: ../../whats_new.rst:177 +#: ../../whats_new.rst:396 msgid "This is also adds :meth:`Cog.has_error_handler `" msgstr ":meth:`Cog.has_error_handler ` も追加されました。" -#: ../../whats_new.rst:178 +#: ../../whats_new.rst:397 msgid "|commands| Allow callable types to act as a bucket key for cooldowns (:issue:`6563`)" msgstr "|commands| 呼び出し可能オブジェクトをクールダウンのバケットキーとして使用できるようになりました。 (:issue:`6563`)" -#: ../../whats_new.rst:179 +#: ../../whats_new.rst:398 msgid "|commands| Add ``linesep`` keyword argument to :class:`Paginator ` (:issue:`5975`)" msgstr "|commands| :class:`Paginator ` に ``linesep`` キーワード引数を追加しました。 (:issue:`5975`)" -#: ../../whats_new.rst:180 +#: ../../whats_new.rst:399 msgid "|commands| Allow ``None`` to be passed to :attr:`HelpCommand.verify_checks ` to only verify in a guild context (:issue:`2008`, :issue:`6446`)" msgstr "|commands| :attr:`HelpCommand.verify_checks ` に ``None`` を指定することで、サーバー内でのみチェックを確認するようにできるようにしました。 (:issue:`2008`, :issue:`6446`)" -#: ../../whats_new.rst:181 +#: ../../whats_new.rst:400 msgid "|commands| Allow relative paths when loading extensions via a ``package`` keyword argument (:issue:`2465`, :issue:`6445`)" msgstr "|commands| ``package`` キーワード引数で、エクステンションをロードするときの相対パスを指定できるようになりました。 (:issue:`2465`, :issue:`6445`)" -#: ../../whats_new.rst:186 +#: ../../whats_new.rst:405 msgid "Fix mentions not working if ``mention_author`` is passed in :meth:`abc.Messageable.send` without :attr:`Client.allowed_mentions` set (:issue:`6192`, :issue:`6458`)" msgstr ":attr:`Client.allowed_mentions` が設定されていないときに、 :meth:`abc.Messageable.send` で ``mention_author`` が渡されてもメンションしない問題を修正しました。 (:issue:`6192`, :issue:`6458`)" -#: ../../whats_new.rst:187 +#: ../../whats_new.rst:406 msgid "Fix user created instances of :class:`CustomActivity` triggering an error (:issue:`4049`)" msgstr "ユーザーが作成した :class:`CustomActivity` インスタンスがエラーを引き起こす問題を修正しました。 (:issue:`4049`)" -#: ../../whats_new.rst:188 +#: ../../whats_new.rst:407 msgid "Note that currently, bot users still cannot set a custom activity due to a Discord limitation." msgstr "現在、Discordの制限により、Botはまだカスタムアクティビティを設定できません。" -#: ../../whats_new.rst:189 +#: ../../whats_new.rst:408 msgid "Fix :exc:`ZeroDivisionError` being raised from :attr:`VoiceClient.average_latency` (:issue:`6430`, :issue:`6436`)" msgstr ":attr:`VoiceClient.average_latency` にて :exc:`ZeroDivisionError` が発生する問題を修正しました。 (:issue:`6430`, :issue:`6436`)" -#: ../../whats_new.rst:190 +#: ../../whats_new.rst:409 msgid "Fix :attr:`User.public_flags` not updating upon edit (:issue:`6315`)" msgstr ":attr:`User.public_flags` が編集時に更新されない問題を修正しました。 (:issue:`6315`)" -#: ../../whats_new.rst:191 +#: ../../whats_new.rst:410 msgid "Fix :attr:`Message.call` sometimes causing attribute errors (:issue:`6390`)" msgstr ":attr:`Message.call` が時々AttributeErrorを送出する問題を修正しました。 (:issue:`6390`)" -#: ../../whats_new.rst:192 +#: ../../whats_new.rst:411 msgid "Fix issue resending a file during request retries on newer versions of ``aiohttp`` (:issue:`6531`)" msgstr "新しいバージョンの ``aiohttp`` で、リクエストの再試行中にファイルを再送するときに発生する問題を修正しました。 (:issue:`6531`)" -#: ../../whats_new.rst:193 +#: ../../whats_new.rst:412 msgid "Raise an error when ``user_ids`` is empty in :meth:`Guild.query_members`" msgstr ":meth:`Guild.query_members` を呼び出す際、 ``user_ids`` に空のリストが指定された際にエラーが発生するようになりました。" -#: ../../whats_new.rst:194 +#: ../../whats_new.rst:413 msgid "Fix ``__str__`` magic method raising when a :class:`Guild` is unavailable." msgstr ":class:`Guild` が利用不可能なときに ``__str__`` メソッドがエラーを出す問題を修正しました。" -#: ../../whats_new.rst:195 +#: ../../whats_new.rst:414 msgid "Fix potential :exc:`AttributeError` when accessing :attr:`VoiceChannel.members` (:issue:`6602`)" msgstr ":attr:`VoiceChannel.members` にアクセスする時に :exc:`AttributeError` が発生する潜在的なバグを修正しました。(:issue:`6602`)" -#: ../../whats_new.rst:196 +#: ../../whats_new.rst:415 msgid ":class:`Embed` constructor parameters now implicitly convert to :class:`str` (:issue:`6574`)" msgstr ":class:`Embed` の初期化時に指定された引数は暗黙的に :class:`str` へ変換されるようになりました。 (:issue:`6574`)" -#: ../../whats_new.rst:197 +#: ../../whats_new.rst:416 msgid "Ensure ``discord`` package is only run if executed as a script (:issue:`6483`)" msgstr "``discord`` パッケージがスクリプトとして実行された場合のみ実行されるようになりました。 (:issue:`6483`)" -#: ../../whats_new.rst:198 +#: ../../whats_new.rst:417 msgid "|commands| Fix irrelevant commands potentially being unloaded during cog unload due to failure." msgstr "|commands| コグのアンロード中、失敗することにより無関係なコマンドがアンロードされる可能性がある問題を修正しました。" -#: ../../whats_new.rst:199 +#: ../../whats_new.rst:418 msgid "|commands| Fix attribute errors when setting a cog to :class:`~.ext.commands.HelpCommand` (:issue:`5154`)" msgstr "|commands| コグを :class:`~.ext.commands.HelpCommand` に設定した際にAttributeErrorが出る問題を修正しました。 (:issue:`5154`)" -#: ../../whats_new.rst:200 +#: ../../whats_new.rst:419 msgid "|commands| Fix :attr:`Context.invoked_with ` being improperly reassigned during a :meth:`~ext.commands.Context.reinvoke` (:issue:`6451`, :issue:`6462`)" msgstr "|commands| :meth:`~ext.commands.Context.reinvoke` 中に :attr:`Context.invoked_with ` が不適切に再割り当てされる問題を修正しました。 (:issue:`6451`, :issue:`6462`)" -#: ../../whats_new.rst:201 +#: ../../whats_new.rst:420 msgid "|commands| Remove duplicates from :meth:`HelpCommand.get_bot_mapping ` (:issue:`6316`)" msgstr "|commands| :meth:`HelpCommand.get_bot_mapping ` で、コマンドが重複する問題を修正しました。 (:issue:`6316`)" -#: ../../whats_new.rst:202 +#: ../../whats_new.rst:421 msgid "|commands| Properly handle positional-only parameters in bot command signatures (:issue:`6431`)" msgstr "|commands| Botのコマンドシグネチャーで、位置限定引数を適切に処理するようになりました。 (:issue:`6431`)" -#: ../../whats_new.rst:203 +#: ../../whats_new.rst:422 msgid "|commands| Group signatures now properly show up in :attr:`Command.signature ` (:issue:`6529`, :issue:`6530`)" msgstr "|commands| グループのシグネチャーが :attr:`Command.signature ` に正しく表示されるようになりました。 (:issue:`6529`, :issue:`6530`)" -#: ../../whats_new.rst:208 +#: ../../whats_new.rst:427 msgid "User endpoints and all userbot related functionality has been deprecated and will be removed in the next major version of the library." msgstr "ユーザー用エンドポイントとユーザーボットの関連機能は非推奨になり、次のライブラリのメジャーバージョンで削除されます。" -#: ../../whats_new.rst:209 +#: ../../whats_new.rst:428 msgid ":class:`Permission` class methods were updated to match the UI of the Discord client (:issue:`6476`)" msgstr ":class:`Permission` のクラスメソッドがDiscordクライアントのUIと一致するように更新されました。 (:issue:`6476`)" -#: ../../whats_new.rst:210 +#: ../../whats_new.rst:429 msgid "``_`` and ``-`` characters are now stripped when making a new cog using the ``discord`` package (:issue:`6313`)" msgstr "``_`` と ``-`` の文字が ``discord`` パッケージを使用して新しいコグを作成するときに取り除かれるようになりました。 (:issue:`6313`)" -#: ../../whats_new.rst:215 +#: ../../whats_new.rst:434 msgid "v1.6.0" msgstr "v1.6.0" -#: ../../whats_new.rst:217 +#: ../../whats_new.rst:436 msgid "This version comes with support for replies and stickers." msgstr "このバージョンでは、返信機能とスタンプ機能がサポートされるようになりました。" -#: ../../whats_new.rst:222 +#: ../../whats_new.rst:441 msgid "An entirely redesigned documentation. This was the cumulation of multiple months of effort." msgstr "完全に再設計されたドキュメント。 これは何ヶ月もの努力の積み重ねで作られました。" -#: ../../whats_new.rst:223 +#: ../../whats_new.rst:442 msgid "There's now a dark theme, feel free to navigate to the cog on the screen to change your setting, though this should be automatic." msgstr "ダークテーマが実装されました。変更するには、画面上の歯車から設定をしてください。これは自動的に行われます。" -#: ../../whats_new.rst:224 +#: ../../whats_new.rst:443 msgid "Add support for :meth:`AppInfo.icon_url_as` and :meth:`AppInfo.cover_image_url_as` (:issue:`5888`)" msgstr ":meth:`AppInfo.icon_url_as` と :meth:`AppInfo.cover_image_url_as` が追加されました。 (:issue:`5888`)" -#: ../../whats_new.rst:225 +#: ../../whats_new.rst:444 msgid "Add :meth:`Colour.random` to get a random colour (:issue:`6067`)" msgstr "ランダムな色が得られる、 :meth:`Colour.random` が追加されました。 (:issue:`6067`)" -#: ../../whats_new.rst:226 +#: ../../whats_new.rst:445 msgid "Add support for stickers via :class:`Sticker` (:issue:`5946`)" msgstr ":class:`Sticker` によってスタンプがサポートされました。 (:issue:`5946`)" -#: ../../whats_new.rst:230 +#: ../../whats_new.rst:449 msgid "Add support for replying via :meth:`Message.reply` (:issue:`6061`)" msgstr ":meth:`Message.reply` で返信ができるようになりました。 (:issue:`6061`)" -#: ../../whats_new.rst:228 +#: ../../whats_new.rst:447 msgid "This also comes with the :attr:`AllowedMentions.replied_user` setting." msgstr "これには :attr:`AllowedMentions.replied_user` の設定も含まれます。" -#: ../../whats_new.rst:229 +#: ../../whats_new.rst:448 msgid ":meth:`abc.Messageable.send` can now accept a :class:`MessageReference`." msgstr ":meth:`abc.Messageable.send` が :class:`MessageReference` を受け付けるようになりました。" -#: ../../whats_new.rst:230 +#: ../../whats_new.rst:449 msgid ":class:`MessageReference` can now be constructed by users." msgstr ":class:`MessageReference` がユーザーによって生成できるようになりました。" -#: ../../whats_new.rst:231 +#: ../../whats_new.rst:450 msgid ":meth:`Message.to_reference` can now convert a message to a :class:`MessageReference`." msgstr ":meth:`Message.to_reference` によってMessageオブジェクトを :class:`MessageReference` に変換できるようになりました。" -#: ../../whats_new.rst:232 +#: ../../whats_new.rst:451 msgid "Add support for getting the replied to resolved message through :attr:`MessageReference.resolved`." msgstr ":attr:`MessageReference.resolved` で解決済みメッセージを得ることができます。" -#: ../../whats_new.rst:238 +#: ../../whats_new.rst:457 msgid "Add support for role tags." msgstr "ロールのタグがサポートされました。" -#: ../../whats_new.rst:234 +#: ../../whats_new.rst:453 msgid ":attr:`Guild.premium_subscriber_role` to get the \"Nitro Booster\" role (if available)." msgstr ":attr:`Guild.premium_subscriber_role` で ニトロブースターロールを取得できます(利用可能な場合)。" -#: ../../whats_new.rst:235 +#: ../../whats_new.rst:454 msgid ":attr:`Guild.self_role` to get the bot's own role (if available)." msgstr ":attr:`Guild.self_role` でサーバー内のBot自身のロールを取得できます(利用可能な場合)。" -#: ../../whats_new.rst:236 +#: ../../whats_new.rst:455 msgid ":attr:`Role.tags` to get the role's tags." msgstr ":attr:`Role.tags` でロールのタグを取得できます。" -#: ../../whats_new.rst:237 +#: ../../whats_new.rst:456 msgid ":meth:`Role.is_premium_subscriber` to check if a role is the \"Nitro Booster\" role." msgstr ":meth:`Role.is_premium_subscriber` でロールがニトロブースターロールであるかを確認できます。" -#: ../../whats_new.rst:238 +#: ../../whats_new.rst:457 msgid ":meth:`Role.is_bot_managed` to check if a role is a bot role (i.e. the automatically created role for bots)." msgstr ":meth:`Role.is_bot_managed` でロールがボットロール(自動的に作られたBot用ロール)であるかを確認できます。" -#: ../../whats_new.rst:239 +#: ../../whats_new.rst:458 msgid ":meth:`Role.is_integration` to check if a role is role created by an integration." msgstr ":meth:`Role.is_integration` でインテグレーションによって作成されたロールかどうか確認できます。" -#: ../../whats_new.rst:240 +#: ../../whats_new.rst:459 msgid "Add :meth:`Client.is_ws_ratelimited` to check if the websocket is rate limited." msgstr ":meth:`Client.is_ws_ratelimited` でWebSocketのレート制限がされているかどうか確認できるようになりました。" -#: ../../whats_new.rst:241 +#: ../../whats_new.rst:460 msgid ":meth:`ShardInfo.is_ws_ratelimited` is the equivalent for checking a specific shard." msgstr ":meth:`ShardInfo.is_ws_ratelimited` は特定のシャードのWebSocketレート制限をチェックします。" -#: ../../whats_new.rst:242 +#: ../../whats_new.rst:461 msgid "Add support for chunking an :class:`AsyncIterator` through :meth:`AsyncIterator.chunk` (:issue:`6100`, :issue:`6082`)" msgstr ":class:`AsyncIterator` を :meth:`AsyncIterator.chunk` を通してチャンク化できるようになりました。 (:issue:`6100`, :issue:`6082`)" -#: ../../whats_new.rst:243 +#: ../../whats_new.rst:462 msgid "Add :attr:`PartialEmoji.created_at` (:issue:`6128`)" msgstr ":attr:`PartialEmoji.created_at` を追加しました。 (:issue:`6128`)" -#: ../../whats_new.rst:244 +#: ../../whats_new.rst:463 msgid "Add support for editing and deleting webhook sent messages (:issue:`6058`)" msgstr "Webhookで送信したメッセージの編集と削除をサポートしました。 (:issue:`6058`)" -#: ../../whats_new.rst:245 +#: ../../whats_new.rst:464 msgid "This adds :class:`WebhookMessage` as well to power this behaviour." msgstr "この機能のために :class:`WebhookMessage` が追加されました。" -#: ../../whats_new.rst:246 +#: ../../whats_new.rst:465 msgid "Add :class:`PartialMessage` to allow working with a message via channel objects and just a message_id (:issue:`5905`)" msgstr "チャンネルオブジェクトとメッセージIDのみでメッセージを操作できるようにするために、 :class:`PartialMessage` を追加しました。 (:issue:`5905`)" -#: ../../whats_new.rst:247 +#: ../../whats_new.rst:466 msgid "This is useful if you don't want to incur an extra API call to fetch the message." msgstr "これはメッセージを取得するために追加のAPI呼び出しをしたくないときに便利です。" -#: ../../whats_new.rst:248 +#: ../../whats_new.rst:467 msgid "Add :meth:`Emoji.url_as` (:issue:`6162`)" msgstr ":meth:`Emoji.url_as` を追加しました。 (:issue:`6162`)" -#: ../../whats_new.rst:249 +#: ../../whats_new.rst:468 msgid "Add support for :attr:`Member.pending` for the membership gating feature." msgstr "メンバーシップゲート機能用に :attr:`Member.pending` のサポートを追加しました。" -#: ../../whats_new.rst:250 +#: ../../whats_new.rst:469 msgid "Allow ``colour`` parameter to take ``int`` in :meth:`Guild.create_role` (:issue:`6195`)" msgstr ":meth:`Guild.create_role` で ``colour`` パラメータに ``int`` 型を渡すことができるようになりました。 (:issue:`6195`)" -#: ../../whats_new.rst:251 +#: ../../whats_new.rst:470 msgid "Add support for ``presences`` in :meth:`Guild.query_members` (:issue:`2354`)" msgstr ":meth:`Guild.query_members` で、 ``presences`` 引数が使えるようになりました。 (:issue:`2354`)" -#: ../../whats_new.rst:252 +#: ../../whats_new.rst:471 msgid "|commands| Add support for ``description`` keyword argument in :class:`commands.Cog ` (:issue:`6028`)" msgstr "|commands| :class:`commands.Cog ` において、 ``description`` キーワード引数が使えるようになりました。 (:issue:`6028`)" -#: ../../whats_new.rst:253 +#: ../../whats_new.rst:472 msgid "|tasks| Add support for calling the wrapped coroutine as a function via ``__call__``." msgstr "|tasks| ``__call__`` を使うことによってラップされたコルーチン関数を呼び出せるようになりました。" -#: ../../whats_new.rst:259 +#: ../../whats_new.rst:478 msgid "Raise :exc:`DiscordServerError` when reaching 503s repeatedly (:issue:`6044`)" msgstr "HTTPリクエスト時にステータス503が繰り返し返されたとき、 :exc:`DiscordServerError` が出るようになりました。 (:issue:`6044`)" -#: ../../whats_new.rst:260 +#: ../../whats_new.rst:479 msgid "Fix :exc:`AttributeError` when :meth:`Client.fetch_template` is called (:issue:`5986`)" msgstr ":meth:`Client.fetch_template` が呼び出されたとき :exc:`AttributeError` が出る問題を修正しました。 (:issue:`5986`)" -#: ../../whats_new.rst:261 +#: ../../whats_new.rst:480 msgid "Fix errors when playing audio and moving to another channel (:issue:`5953`)" msgstr "音声を再生するときと別のボイスチャンネルへ移動するときに発生するエラーを修正しました。 (:issue:`5953`)" -#: ../../whats_new.rst:262 +#: ../../whats_new.rst:481 msgid "Fix :exc:`AttributeError` when voice channels disconnect too fast (:issue:`6039`)" msgstr "ボイスチャンネルから切断するのが速すぎるときに発生する :exc:`AttributeError` を修正しました。 (:issue:`6039`)" -#: ../../whats_new.rst:263 +#: ../../whats_new.rst:482 msgid "Fix stale :class:`User` references when the members intent is off." msgstr "memberインテントがオフの場合に :class:`User` の参照が古くなってしまう問題を修正しました。" -#: ../../whats_new.rst:264 +#: ../../whats_new.rst:483 msgid "Fix :func:`on_user_update` not dispatching in certain cases when a member is not cached but the user somehow is." msgstr "memberがキャッシュされておらず、userが何らかの形でキャッシュされている場合に :func:`on_user_update` が発火されない問題を修正しました。" -#: ../../whats_new.rst:265 +#: ../../whats_new.rst:484 msgid "Fix :attr:`Message.author` being overwritten in certain cases during message update." msgstr "メッセージが更新されているとき、特定のケースで :attr:`Message.author` が上書きされてしまう問題を修正しました。" -#: ../../whats_new.rst:266 +#: ../../whats_new.rst:485 msgid "This would previously make it so :attr:`Message.author` is a :class:`User`." msgstr "これにより、 :attr:`Message.author` が :class:`User` になるようになりました。" -#: ../../whats_new.rst:267 +#: ../../whats_new.rst:486 msgid "Fix :exc:`UnboundLocalError` for editing ``public_updates_channel`` in :meth:`Guild.edit` (:issue:`6093`)" msgstr ":meth:`Guild.edit` で ``public_updates_channel`` を変更する際に :exc:`UnboundLocalError` が発生する問題を修正しました。 (:issue:`6093`)" -#: ../../whats_new.rst:268 +#: ../../whats_new.rst:487 msgid "Fix uninitialised :attr:`CustomActivity.created_at` (:issue:`6095`)" msgstr ":attr:`CustomActivity.created_at` が初期化されない問題を修正しました。 (:issue:`6095`)" -#: ../../whats_new.rst:269 +#: ../../whats_new.rst:488 msgid "|commands| Errors during cog unload no longer stops module cleanup (:issue:`6113`)" msgstr "|commands| コグのアンロード中に起きたエラーがモジュールのcleanupを止めないようになりました。 (:issue:`6113`)" -#: ../../whats_new.rst:270 +#: ../../whats_new.rst:489 msgid "|commands| Properly cleanup lingering commands when a conflicting alias is found when adding commands (:issue:`6217`)" msgstr "|commands| コマンドを追加する際、エイリアスが競合したときに残ってしまうエイリアスを適切にクリーンアップするようになりました。 (:issue:`6217`)" -#: ../../whats_new.rst:275 +#: ../../whats_new.rst:494 msgid "``ffmpeg`` spawned processes no longer open a window in Windows (:issue:`6038`)" msgstr "Windowsにおいて呼び出された ``ffmpeg`` がウィンドウを開かないようになりました。 (:issue:`6038`)" -#: ../../whats_new.rst:276 +#: ../../whats_new.rst:495 msgid "Update dependencies to allow the library to work on Python 3.9+ without requiring build tools. (:issue:`5984`, :issue:`5970`)" msgstr "ライブラリがビルドツールなしでPython3.9以上で動作するよう、依存関係を変更しました。 (:issue:`5984`, :issue:`5970`)" -#: ../../whats_new.rst:277 +#: ../../whats_new.rst:496 msgid "Fix docstring issue leading to a SyntaxError in 3.9 (:issue:`6153`)" msgstr "Python3.9においてSyntaxErrorになるdocstringの問題を修正しました。 (:issue:`6153`)" -#: ../../whats_new.rst:278 +#: ../../whats_new.rst:497 msgid "Update Windows opus binaries from 1.2.1 to 1.3.1 (:issue:`6161`)" msgstr "Windows用のopusバイナリをバージョン1.2.1から1.3.1に更新しました。 (:issue:`6161`)" -#: ../../whats_new.rst:279 +#: ../../whats_new.rst:498 msgid "Allow :meth:`Guild.create_role` to accept :class:`int` as the ``colour`` parameter (:issue:`6195`)" msgstr ":meth:`Guild.create_role` の ``colour`` 引数で :class:`int` 型が使えるようになりました。\n" "(:issue:`6195`)" -#: ../../whats_new.rst:280 +#: ../../whats_new.rst:499 msgid "|commands| :class:`MessageConverter ` regex got updated to support ``www.`` prefixes (:issue:`6002`)" msgstr "|commands| :class:`MessageConverter ` のregexが ``www.`` プレフィックスをサポートするように更新されました。 (:issue:`6002`)" -#: ../../whats_new.rst:281 +#: ../../whats_new.rst:500 msgid "|commands| :class:`UserConverter ` now fetches the API if an ID is passed and the user is not cached." msgstr "|commands| :class:`UserConverter ` は、IDが渡され、そのユーザーがキャッシュされていない場合にAPIからデータを取得するようになりました。" -#: ../../whats_new.rst:282 +#: ../../whats_new.rst:501 msgid "|commands| :func:`max_concurrency ` is now called before cooldowns (:issue:`6172`)" msgstr "|commands| :func:`max_concurrency ` がクールダウンの前に呼び出されるようになりました。 (:issue:`6172`)" -#: ../../whats_new.rst:287 +#: ../../whats_new.rst:506 msgid "v1.5.1" msgstr "v1.5.1" -#: ../../whats_new.rst:292 +#: ../../whats_new.rst:511 msgid "Fix :func:`utils.escape_markdown` not escaping quotes properly (:issue:`5897`)" msgstr ":func:`utils.escape_markdown` が引用符を正しくエスケープしない問題を修正しました。 (:issue:`5897`)" -#: ../../whats_new.rst:293 +#: ../../whats_new.rst:512 msgid "Fix :class:`Message` not being hashable (:issue:`5901`, :issue:`5866`)" msgstr ":class:`Message` がハッシュ可能でない問題を修正しました。 (:issue:`5901`, :issue:`5866`)" -#: ../../whats_new.rst:294 +#: ../../whats_new.rst:513 msgid "Fix moving channels to the end of the channel list (:issue:`5923`)" msgstr "チャンネルをチャンネルリストの最後まで移動する際の問題を修正しました。 (:issue:`5923`)" -#: ../../whats_new.rst:295 +#: ../../whats_new.rst:514 msgid "Fix seemingly strange behaviour in ``__eq__`` for :class:`PermissionOverwrite` (:issue:`5929`)" msgstr ":class:`PermissionOverwrite` における ``__eq__`` のおかしい挙動を修正しました。 (:issue:`5929`)" -#: ../../whats_new.rst:296 +#: ../../whats_new.rst:515 msgid "Fix aliases showing up in ``__iter__`` for :class:`Intents` (:issue:`5945`)" msgstr ":class:`Intents` の ``__iter__`` におけるエイリアスの表示の問題を修正しました。 (:issue:`5945`)" -#: ../../whats_new.rst:297 +#: ../../whats_new.rst:516 msgid "Fix the bot disconnecting from voice when moving them to another channel (:issue:`5904`)" msgstr "別のボイスチャンネルに移動する時にBotがボイスチャンネルから切断されてしまう問題を修正しました。 (:issue:`5945`)" -#: ../../whats_new.rst:298 +#: ../../whats_new.rst:517 msgid "Fix attribute errors when chunking times out sometimes during delayed on_ready dispatching." msgstr "遅延on_readyディスパッチ中にチャンキングがタイムアウトする場合の属性エラーを修正しました。" -#: ../../whats_new.rst:299 +#: ../../whats_new.rst:518 msgid "Ensure that the bot's own member is not evicted from the cache (:issue:`5949`)" msgstr "Bot自身のmemberオブジェクトがキャッシュから削除されないことが保証されるようになりました。 (:issue:`5949`)" -#: ../../whats_new.rst:304 +#: ../../whats_new.rst:523 msgid "Members are now loaded during ``GUILD_MEMBER_UPDATE`` events if :attr:`MemberCacheFlags.joined` is set. (:issue:`5930`)" msgstr ":attr:`MemberCacheFlags.joined` が設定されている場合、memberが ``GUILD_MEMBER_UPDATE`` イベントでロードされるようになりました。 (:issue:`5930`)" -#: ../../whats_new.rst:305 +#: ../../whats_new.rst:524 msgid "|commands| :class:`MemberConverter ` now properly lazily fetches members if not available from cache." msgstr "|commands| :class:`MemberConverter ` は、memberがキャッシュから利用できない場合に遅延ロードでmemberを取得するようになりました。" -#: ../../whats_new.rst:306 +#: ../../whats_new.rst:525 msgid "This is the same as having ``discord.Member`` as the type-hint." msgstr "これは ``discord.Member`` を型ヒントとして使うのと同じです。" -#: ../../whats_new.rst:307 +#: ../../whats_new.rst:526 msgid ":meth:`Guild.chunk` now allows concurrent calls without spamming the gateway with requests." msgstr ":meth:`Guild.chunk` によって、Gatewayに負荷をかけずに同時呼び出しができるようになりました。" -#: ../../whats_new.rst:312 +#: ../../whats_new.rst:531 msgid "v1.5.0" msgstr "v1.5.0" -#: ../../whats_new.rst:314 +#: ../../whats_new.rst:533 msgid "This version came with forced breaking changes that Discord is requiring all bots to go through on October 7th. It is highly recommended to read the documentation on intents, :ref:`intents_primer`." msgstr "このバージョンでは、Discordが10月7日に行う、すべてのBotに要求している強制的な破壊的変更が含まれています。Intentsに関するドキュメント :ref:`intents_primer` を読むことを強くおすすめします。" -#: ../../whats_new.rst:317 +#: ../../whats_new.rst:536 msgid "API Changes" msgstr "APIの変更" -#: ../../whats_new.rst:319 +#: ../../whats_new.rst:538 msgid "Members and presences will no longer be retrieved due to an API change. See :ref:`privileged_intents` for more info." msgstr "APIの変更により、memberとpresenceの情報は取得されなくなります。 詳細は :ref:`privileged_intents` を参照してください。" -#: ../../whats_new.rst:320 +#: ../../whats_new.rst:539 msgid "As a consequence, fetching offline members is disabled if the members intent is not enabled." msgstr "結果として、 memberインテントが有効でない場合、オフラインメンバーの取得が無効になります。" -#: ../../whats_new.rst:325 +#: ../../whats_new.rst:544 msgid "Support for gateway intents, passed via ``intents`` in :class:`Client` using :class:`Intents`." msgstr ":class:`Client` において、 ``intents`` 引数に :class:`Intents` を渡すことでゲートウェイインテントがサポートされるようになりました。" -#: ../../whats_new.rst:326 +#: ../../whats_new.rst:545 msgid "Add :attr:`VoiceRegion.south_korea` (:issue:`5233`)" msgstr ":attr:`VoiceRegion.south_korea` が追加されました。 (:issue:`5233`)" -#: ../../whats_new.rst:327 +#: ../../whats_new.rst:546 msgid "Add support for ``__eq__`` for :class:`Message` (:issue:`5789`)" msgstr ":class:`Message` において、 ``__eq__`` がサポートされました。 (:issue:`5789`)" -#: ../../whats_new.rst:328 +#: ../../whats_new.rst:547 msgid "Add :meth:`Colour.dark_theme` factory method (:issue:`1584`)" msgstr ":meth:`Colour.dark_theme` クラスメソッドが追加されました。 (:issue:`1584`)" -#: ../../whats_new.rst:329 +#: ../../whats_new.rst:548 msgid "Add :meth:`AllowedMentions.none` and :meth:`AllowedMentions.all` (:issue:`5785`)" msgstr ":meth:`AllowedMentions.none` と :meth:`AllowedMentions.all` が追加されました。 (:issue:`5785`)" -#: ../../whats_new.rst:330 +#: ../../whats_new.rst:549 msgid "Add more concrete exceptions for 500 class errors under :class:`DiscordServerError` (:issue:`5797`)" msgstr ":class:`DiscordServerError` のサブクラスとして、 ステータス500エラーの具体的な例外を追加しました。 (:issue:`5797`)" -#: ../../whats_new.rst:331 +#: ../../whats_new.rst:550 msgid "Implement :class:`VoiceProtocol` to better intersect the voice flow." msgstr "音声フローをより良く交差させるため、 :class:`VoiceProtocol` を実装しました。" -#: ../../whats_new.rst:332 +#: ../../whats_new.rst:551 msgid "Add :meth:`Guild.chunk` to fully chunk a guild." msgstr "ギルドをチャンク化して取得する :meth:`Guild.chunk` を追加しました。" -#: ../../whats_new.rst:333 +#: ../../whats_new.rst:552 msgid "Add :class:`MemberCacheFlags` to better control member cache. See :ref:`intents_member_cache` for more info." msgstr "メンバーキャッシュをより適切に制御するために :class:`MemberCacheFlags` を追加しました。詳細は :ref:`intents_member_cache` を参照してください。" -#: ../../whats_new.rst:335 +#: ../../whats_new.rst:554 msgid "Add support for :attr:`ActivityType.competing` (:issue:`5823`)" msgstr ":attr:`ActivityType.competing` のサポートを追加しました。 (:issue:`5823`)" -#: ../../whats_new.rst:335 +#: ../../whats_new.rst:554 msgid "This seems currently unused API wise." msgstr "これはAPIとしては現在未使用のようです。" -#: ../../whats_new.rst:337 +#: ../../whats_new.rst:556 msgid "Add support for message references, :attr:`Message.reference` (:issue:`5754`, :issue:`5832`)" msgstr "メッセージ参照のサポート、 :attr:`Message.reference` を追加しました。 (:issue:`5754`, :issue:`5832`)" -#: ../../whats_new.rst:338 +#: ../../whats_new.rst:557 msgid "Add alias for :class:`ColourConverter` under ``ColorConverter`` (:issue:`5773`)" msgstr ":class:`ColourConverter` のエイリアス ``ColorConverter`` を追加しました。 (:issue:`5773`)" -#: ../../whats_new.rst:339 +#: ../../whats_new.rst:558 msgid "Add alias for :attr:`PublicUserFlags.verified_bot_developer` under :attr:`PublicUserFlags.early_verified_bot_developer` (:issue:`5849`)" msgstr ":attr:`PublicUserFlags.verified_bot_developer` のエイリアスを :attr:`PublicUserFlags.early_verified_bot_developer` の下に追加しました。(:issue:`5849`)" -#: ../../whats_new.rst:340 +#: ../../whats_new.rst:559 msgid "|commands| Add support for ``require_var_positional`` for :class:`Command` (:issue:`5793`)" msgstr "|commands| :class:`Command` に ``require_var_positional`` のサポートを追加しました。 (:issue:`5793`)" -#: ../../whats_new.rst:345 -#: ../../whats_new.rst:379 +#: ../../whats_new.rst:564 +#: ../../whats_new.rst:598 msgid "Fix issue with :meth:`Guild.by_category` not showing certain channels." msgstr ":meth:`Guild.by_category` がいくつかのチャンネルを表示しない問題を修正しました。" -#: ../../whats_new.rst:346 -#: ../../whats_new.rst:380 +#: ../../whats_new.rst:565 +#: ../../whats_new.rst:599 msgid "Fix :attr:`abc.GuildChannel.permissions_synced` always being ``False`` (:issue:`5772`)" msgstr ":attr:`abc.GuildChannel.permissions_synced` が常に ``False`` になる問題を修正しました。 (:issue:`5772`)" -#: ../../whats_new.rst:347 -#: ../../whats_new.rst:381 +#: ../../whats_new.rst:566 +#: ../../whats_new.rst:600 msgid "Fix handling of cloudflare bans on webhook related requests (:issue:`5221`)" msgstr "Webhook関連のリクエストでcloudflareにBANされた際の処理に発生するバグを修正しました。(:issue:`5221`)" -#: ../../whats_new.rst:348 -#: ../../whats_new.rst:382 +#: ../../whats_new.rst:567 +#: ../../whats_new.rst:601 msgid "Fix cases where a keep-alive thread would ack despite already dying (:issue:`5800`)" msgstr "キープライブスレッドが既に死んでいるにも関わらずackをするのを修正しました。(:issue:`5800`)" -#: ../../whats_new.rst:349 -#: ../../whats_new.rst:383 +#: ../../whats_new.rst:568 +#: ../../whats_new.rst:602 msgid "Fix cases where a :class:`Member` reference would be stale when cache is disabled in message events (:issue:`5819`)" msgstr "メッセージイベントでキャッシュが無効になった際に、 :class:`Member` の参照が古くなる問題を修正しました。 (:issue:`5819`)" -#: ../../whats_new.rst:350 -#: ../../whats_new.rst:384 +#: ../../whats_new.rst:569 +#: ../../whats_new.rst:603 msgid "Fix ``allowed_mentions`` not being sent when sending a single file (:issue:`5835`)" msgstr "単一のファイルを送信したときに ``allowed_mentions`` が送信されない問題を修正しました。 (:issue:`5835`)" -#: ../../whats_new.rst:351 -#: ../../whats_new.rst:385 +#: ../../whats_new.rst:570 +#: ../../whats_new.rst:604 msgid "Fix ``overwrites`` being ignored in :meth:`abc.GuildChannel.edit` if ``{}`` is passed (:issue:`5756`, :issue:`5757`)" msgstr "``{}`` が渡された場合、 :meth:`abc.GuildChannel.edit` で ``overwrites`` が無視されるのを修正しました。(:issue:`5756`, :issue:`5757`)" -#: ../../whats_new.rst:352 -#: ../../whats_new.rst:386 +#: ../../whats_new.rst:571 +#: ../../whats_new.rst:605 msgid "|commands| Fix exceptions being raised improperly in command invoke hooks (:issue:`5799`)" msgstr "|commands| コマンド呼び出しフックでの例外が正しく送出されない問題を修正しました。 (:issue:`5799`)" -#: ../../whats_new.rst:353 -#: ../../whats_new.rst:387 +#: ../../whats_new.rst:572 +#: ../../whats_new.rst:606 msgid "|commands| Fix commands not being properly ejected during errors in a cog injection (:issue:`5804`)" msgstr "|commands| コグを追加するときにエラーが発生した場合にコマンドが正しく除去されない問題を修正しました。 (:issue:`5804`)" -#: ../../whats_new.rst:354 -#: ../../whats_new.rst:388 +#: ../../whats_new.rst:573 +#: ../../whats_new.rst:607 msgid "|commands| Fix cooldown timing ignoring edited timestamps." msgstr "|commands| クールダウンのタイミングが編集のタイムスタンプを無視していたのを修正しました。" -#: ../../whats_new.rst:355 -#: ../../whats_new.rst:389 +#: ../../whats_new.rst:574 +#: ../../whats_new.rst:608 msgid "|tasks| Fix tasks extending the next iteration on handled exceptions (:issue:`5762`, :issue:`5763`)" msgstr "|tasks| 例外処理後のイテレーションでの問題を修正しました。 (:issue:`5762`, :issue:`5763`)" -#: ../../whats_new.rst:360 +#: ../../whats_new.rst:579 msgid "Webhook requests are now logged (:issue:`5798`)" msgstr "Webhookリクエストをログに記録するように変更しました。 (:issue:`5798`)" -#: ../../whats_new.rst:361 -#: ../../whats_new.rst:394 +#: ../../whats_new.rst:580 +#: ../../whats_new.rst:613 msgid "Remove caching layer from :attr:`AutoShardedClient.shards`. This was causing issues if queried before launching shards." msgstr ":attr:`AutoShardedClient.shards` からキャッシュレイヤーを削除しました。これは、シャードを起動する前にクエリを実行すると問題が発生するためです。" -#: ../../whats_new.rst:362 +#: ../../whats_new.rst:581 msgid "Gateway rate limits are now handled." msgstr "ゲートウェイレート制限の処理が行われるようになりました。" -#: ../../whats_new.rst:363 +#: ../../whats_new.rst:582 msgid "Warnings logged due to missed caches are now changed to DEBUG log level." msgstr "ミスキャッシュによる警告レベルのログがDEBUGレベルのログに変更されました。" -#: ../../whats_new.rst:364 +#: ../../whats_new.rst:583 msgid "Some strings are now explicitly interned to reduce memory usage." msgstr "一部の文字列は、メモリ使用量を削減するために明示的にインターンされるようになりました。" -#: ../../whats_new.rst:365 +#: ../../whats_new.rst:584 msgid "Usage of namedtuples has been reduced to avoid potential breaking changes in the future (:issue:`5834`)" msgstr "将来的に壊れる可能性のある変更を避けるために、namedtuplesの使用が削減されました。(:issue:`5834`)" -#: ../../whats_new.rst:366 +#: ../../whats_new.rst:585 msgid "|commands| All :class:`BadArgument` exceptions from the built-in converters now raise concrete exceptions to better tell them apart (:issue:`5748`)" msgstr "|commands| ビルトインコンバータから送出されていた全ての :class:`BadArgument` 例外は、判別しやすいよう具体的な例外を発生させるようになりました。 (:issue:`5748`)" -#: ../../whats_new.rst:367 -#: ../../whats_new.rst:395 +#: ../../whats_new.rst:586 +#: ../../whats_new.rst:614 msgid "|tasks| Lazily fetch the event loop to prevent surprises when changing event loop policy (:issue:`5808`)" msgstr "|tasks| Lazily fetch event loop to prevent surprises when changing event loop policy (:issue:`5808`)" -#: ../../whats_new.rst:372 +#: ../../whats_new.rst:591 msgid "v1.4.2" msgstr "v1.4.2" -#: ../../whats_new.rst:374 +#: ../../whats_new.rst:593 msgid "This is a maintenance release with backports from :ref:`vp1p5p0`." msgstr "これは :ref:`vp1p5p0` からのバックポートによるメンテナンスリリースです。" -#: ../../whats_new.rst:400 +#: ../../whats_new.rst:619 msgid "v1.4.1" msgstr "v1.4.1" -#: ../../whats_new.rst:405 +#: ../../whats_new.rst:624 msgid "Properly terminate the connection when :meth:`Client.close` is called (:issue:`5207`)" msgstr ":meth:`Client.close` が呼び出されたときに正常に接続を終了するようにしました。 (:issue:`5207`)" -#: ../../whats_new.rst:406 +#: ../../whats_new.rst:625 msgid "Fix error being raised when clearing embed author or image when it was already cleared (:issue:`5210`, :issue:`5212`)" msgstr "埋め込みの作者や画像がすでにクリアされているときにクリアしようとするとエラーが発生するのを修正しました。 (:issue:`5210`, :issue:`5212`)" -#: ../../whats_new.rst:407 +#: ../../whats_new.rst:626 msgid "Fix ``__path__`` to allow editable extensions (:issue:`5213`)" msgstr "編集可能なエクステンションを利用できるように ``__path__`` を修正しました。 (:issue:`5213`)" -#: ../../whats_new.rst:412 +#: ../../whats_new.rst:631 msgid "v1.4.0" msgstr "v1.4.0" -#: ../../whats_new.rst:414 +#: ../../whats_new.rst:633 msgid "Another version with a long development time. Features like Intents are slated to be released in a v1.5 release. Thank you for your patience!" msgstr "長い開発時間を持つ別のバージョンです。Intentsのような機能はv1.5リリースでリリースされる予定です。ご理解いただきありがとうございます!" -#: ../../whats_new.rst:421 +#: ../../whats_new.rst:640 msgid "Add support for :class:`AllowedMentions` to have more control over what gets mentioned." msgstr "メンションの動作を制御する :class:`AllowedMentions` を追加しました。" -#: ../../whats_new.rst:420 +#: ../../whats_new.rst:639 msgid "This can be set globally through :attr:`Client.allowed_mentions`" msgstr "これは :attr:`Client.allowed_mentions` から設定することができます。" -#: ../../whats_new.rst:421 +#: ../../whats_new.rst:640 msgid "This can also be set on a per message basis via :meth:`abc.Messageable.send`" msgstr ":meth:`abc.Messageable.send` を介してメッセージごとに設定することもできます。" -#: ../../whats_new.rst:429 +#: ../../whats_new.rst:648 msgid ":class:`AutoShardedClient` has been completely redesigned from the ground up to better suit multi-process clusters (:issue:`2654`)" msgstr ":class:`AutoShardedClient` は、マルチプロセスクラスタに適した設計に完全に変更されました。(:issue:`2654`)" -#: ../../whats_new.rst:424 +#: ../../whats_new.rst:643 msgid "Add :class:`ShardInfo` which allows fetching specific information about a shard." msgstr "シャードに関する情報を取得するために :class:`ShardInfo` を追加しました。" -#: ../../whats_new.rst:425 +#: ../../whats_new.rst:644 msgid "The :class:`ShardInfo` allows for reconnecting and disconnecting of a specific shard as well." msgstr ":class:`ShardInfo` では、特定のシャードの再接続と切断も可能です。" -#: ../../whats_new.rst:426 +#: ../../whats_new.rst:645 msgid "Add :meth:`AutoShardedClient.get_shard` and :attr:`AutoShardedClient.shards` to get information about shards." msgstr "シャードに関する情報を取得するための :meth:`AutoShardedClient.get_shard` と :attr:`AutoShardedClient.shards` を追加しました。" -#: ../../whats_new.rst:427 +#: ../../whats_new.rst:646 msgid "Rework the entire connection flow to better facilitate the ``IDENTIFY`` rate limits." msgstr "接続フロー全体をリワークして、``IDENTIFY`` レート制限の対応を改善しました。" -#: ../../whats_new.rst:428 +#: ../../whats_new.rst:647 msgid "Add a hook :meth:`Client.before_identify_hook` to have better control over what happens before an ``IDENTIFY`` is done." msgstr "``IDENTIFY`` が完了する前に何を行うべきかをよりよく制御できる :meth:`Client.before_identify_hook` を追加しました。" -#: ../../whats_new.rst:429 +#: ../../whats_new.rst:648 msgid "Add more shard related events such as :func:`on_shard_connect`, :func:`on_shard_disconnect` and :func:`on_shard_resumed`." msgstr ":func:`on_shard_connect` 、 :func:`on_shard_disconnect` 、 :func:`on_shard_resumed` などのシャード関連イベントを追加しました。" -#: ../../whats_new.rst:435 +#: ../../whats_new.rst:654 msgid "Add support for guild templates (:issue:`2652`)" msgstr "サーバーテンプレートのサポートを追加しました。 (:issue:`2652`)" -#: ../../whats_new.rst:432 +#: ../../whats_new.rst:651 msgid "This adds :class:`Template` to read a template's information." msgstr "テンプレートの情報を読むために :class:`Template` を追加しました。" -#: ../../whats_new.rst:433 +#: ../../whats_new.rst:652 msgid ":meth:`Client.fetch_template` can be used to fetch a template's information from the API." msgstr "テンプレートの情報を API から取得するには :meth:`Client.fetch_template` が使用できます。" -#: ../../whats_new.rst:434 +#: ../../whats_new.rst:653 msgid ":meth:`Client.create_guild` can now take an optional template to base the creation from." msgstr ":meth:`Client.create_guild` は任意で作成元のテンプレートを取ることができます。" -#: ../../whats_new.rst:435 +#: ../../whats_new.rst:654 msgid "Note that fetching a guild's template is currently restricted for bot accounts." msgstr "Botアカウントでは、ギルドのテンプレートの取得は現在制限されていることに注意してください。" -#: ../../whats_new.rst:445 +#: ../../whats_new.rst:664 msgid "Add support for guild integrations (:issue:`2051`, :issue:`1083`)" msgstr "ギルドインテグレーションのサポートを追加しました。 (:issue:`2051`, :issue:`1083`)" -#: ../../whats_new.rst:438 +#: ../../whats_new.rst:657 msgid ":class:`Integration` is used to read integration information." msgstr ":class:`Integration` はインテグレーション情報の読み取りに使用されます。" -#: ../../whats_new.rst:439 +#: ../../whats_new.rst:658 msgid ":class:`IntegrationAccount` is used to read integration account information." msgstr ":class:`IntegrationAccount` はインテグレーションアカウント情報の読み取りに使用されます。" -#: ../../whats_new.rst:440 +#: ../../whats_new.rst:659 msgid ":meth:`Guild.integrations` will fetch all integrations in a guild." msgstr ":meth:`Guild.integrations` はギルド内の全てのインテグレーションを取得します。" -#: ../../whats_new.rst:441 +#: ../../whats_new.rst:660 msgid ":meth:`Guild.create_integration` will create an integration." msgstr ":meth:`Guild.create_integration` はインテグレーションを作成します。" -#: ../../whats_new.rst:442 +#: ../../whats_new.rst:661 msgid ":meth:`Integration.edit` will edit an existing integration." msgstr ":meth:`Integration.edit` は既存のインテグレーションを編集します。" -#: ../../whats_new.rst:443 +#: ../../whats_new.rst:662 msgid ":meth:`Integration.delete` will delete an integration." msgstr ":meth:`Integration.delete` はインテグレーションを削除します。" -#: ../../whats_new.rst:444 +#: ../../whats_new.rst:663 msgid ":meth:`Integration.sync` will sync an integration." msgstr ":meth:`Integration.sync` はインテグレーションを同期します。" -#: ../../whats_new.rst:445 +#: ../../whats_new.rst:664 msgid "There is currently no support in the audit log for this." msgstr "これには現時点で監査ログのサポートはありません。" -#: ../../whats_new.rst:447 +#: ../../whats_new.rst:666 msgid "Add an alias for :attr:`VerificationLevel.extreme` under :attr:`VerificationLevel.very_high` (:issue:`2650`)" msgstr ":attr:`VerificationLevel.extreme` の別名を :attr:`VerificationLevel.very_high` の下に追加しました (:issue:`2650`)" -#: ../../whats_new.rst:448 +#: ../../whats_new.rst:667 msgid "Add various grey to gray aliases for :class:`Colour` (:issue:`5130`)" msgstr ":class:`Colour` に「グレー」の綴り違いのエイリアスを追加しました。 (:issue:`5130`)" -#: ../../whats_new.rst:449 +#: ../../whats_new.rst:668 msgid "Added :attr:`VoiceClient.latency` and :attr:`VoiceClient.average_latency` (:issue:`2535`)" msgstr ":attr:`VoiceClient.latency` と :attr:`VoiceClient.average_latency` を追加しました。 (:issue:`2535`)" -#: ../../whats_new.rst:450 +#: ../../whats_new.rst:669 msgid "Add ``use_cached`` and ``spoiler`` parameters to :meth:`Attachment.to_file` (:issue:`2577`, :issue:`4095`)" msgstr ":meth:`Attachment.to_file` にパラメータ ``use_cached`` と ``spoiler`` を追加しました。 (:issue:`2577`, :issue:`4095`)" -#: ../../whats_new.rst:451 +#: ../../whats_new.rst:670 msgid "Add ``position`` parameter support to :meth:`Guild.create_category` (:issue:`2623`)" msgstr ":meth:`Guild.create_category` にて ``position`` パラメータのサポートを追加しました。 (:issue:`2623`)" -#: ../../whats_new.rst:452 +#: ../../whats_new.rst:671 msgid "Allow passing ``int`` for the colour in :meth:`Role.edit` (:issue:`4057`)" msgstr ":meth:`Role.edit` のロールカラーに ``int`` が渡せるようになりました。 (:issue:`4057`)" -#: ../../whats_new.rst:453 +#: ../../whats_new.rst:672 msgid "Add :meth:`Embed.remove_author` to clear author information from an embed (:issue:`4068`)" msgstr "埋め込みの作者を削除する :meth:`Embed.remove_author` が追加されました。 (:issue:`4068`)" -#: ../../whats_new.rst:454 +#: ../../whats_new.rst:673 msgid "Add the ability to clear images and thumbnails in embeds using :attr:`Embed.Empty` (:issue:`4053`)" msgstr ":attr:`Embed.Empty` を使用してEmbed内のサムネイルと画像をクリアできるようになりました。 (:issue:`4053`)" -#: ../../whats_new.rst:455 +#: ../../whats_new.rst:674 msgid "Add :attr:`Guild.max_video_channel_users` (:issue:`4120`)" msgstr ":attr:`Guild.max_video_channel_users` を追加。( :issue:`4120` )" -#: ../../whats_new.rst:456 +#: ../../whats_new.rst:675 msgid "Add :attr:`Guild.public_updates_channel` (:issue:`4120`)" msgstr ":attr:`Guild.public_updates_channel` を追加。( :issue:`4120` )" -#: ../../whats_new.rst:457 +#: ../../whats_new.rst:676 msgid "Add ``guild_ready_timeout`` parameter to :class:`Client` and subclasses to control timeouts when the ``GUILD_CREATE`` stream takes too long (:issue:`4112`)" msgstr "``GUILD_CREATE`` に時間がかかりすぎるとき、タイムアウトをコントロールできように ``guild_ready_timeout`` パラメータを :class:`Client` に追加しました。 (:issue:`4112`)" -#: ../../whats_new.rst:458 +#: ../../whats_new.rst:677 msgid "Add support for public user flags via :attr:`User.public_flags` and :class:`PublicUserFlags` (:issue:`3999`)" msgstr ":attr:`User.public_flags` と :class:`PublicUserFlags` を介しユーザーフラグのサポートを追加しました。 (:issue:`3999`)" -#: ../../whats_new.rst:459 +#: ../../whats_new.rst:678 msgid "Allow changing of channel types via :meth:`TextChannel.edit` to and from a news channel (:issue:`4121`)" msgstr ":meth:`TextChannel.edit` を介してニュースチャンネルの種類を変更することができるようにしました。(:issue:`4121` )" -#: ../../whats_new.rst:460 +#: ../../whats_new.rst:679 msgid "Add :meth:`Guild.edit_role_positions` to bulk edit role positions in a single API call (:issue:`2501`, :issue:`2143`)" msgstr "一回のAPI呼び出しでロールの位置を一括変更できる :meth:`Guild.edit_role_positions` を追加しました。 (:issue:`2501`, :issue:`2143`)" -#: ../../whats_new.rst:461 +#: ../../whats_new.rst:680 msgid "Add :meth:`Guild.change_voice_state` to change your voice state in a guild (:issue:`5088`)" msgstr "ギルド内のボイスステートを変更する :meth:`Guild.change_voice_state` を追加しました。 (:issue:`5088`)" -#: ../../whats_new.rst:462 +#: ../../whats_new.rst:681 msgid "Add :meth:`PartialInviteGuild.is_icon_animated` for checking if the invite guild has animated icon (:issue:`4180`, :issue:`4181`)" msgstr "ギルドにアニメーションアイコンがあるか判断する :meth:`PartialInviteGuild.is_icon_animated` を追加しました。 (:issue:`4180`, :issue:`4181`)" -#: ../../whats_new.rst:463 +#: ../../whats_new.rst:682 msgid "Add :meth:`PartialInviteGuild.icon_url_as` now supports ``static_format`` for consistency (:issue:`4180`, :issue:`4181`)" msgstr "``static_format`` が :meth:`PartialInviteGuild.icon_url_as` に追加されました (:issue:`4180`, :issue:`4181`)" -#: ../../whats_new.rst:464 +#: ../../whats_new.rst:683 msgid "Add support for ``user_ids`` in :meth:`Guild.query_members`" msgstr ":meth:`Guild.query_members` で、 ``user_ids`` 引数が使えるようになりました。" -#: ../../whats_new.rst:465 +#: ../../whats_new.rst:684 msgid "Add support for pruning members by roles in :meth:`Guild.prune_members` (:issue:`4043`)" msgstr ":meth:`Guild.prune_members` でメンバーをロールにより一括キックできるようになりました。 (:issue:`4043`)" -#: ../../whats_new.rst:466 +#: ../../whats_new.rst:685 msgid "|commands| Implement :func:`~ext.commands.before_invoke` and :func:`~ext.commands.after_invoke` decorators (:issue:`1986`, :issue:`2502`)" msgstr "|commands| :func:`~ext.commands.before_invoke` と :func:`~ext.commands.after_invoke` デコレーターを実装。 ( :issue:`1986`, :issue:`2502` )" -#: ../../whats_new.rst:467 +#: ../../whats_new.rst:686 msgid "|commands| Add a way to retrieve ``retry_after`` from a cooldown in a command via :meth:`Command.get_cooldown_retry_after <.ext.commands.Command.get_cooldown_retry_after>` (:issue:`5195`)" msgstr "|commands| :meth:`Command.get_cooldown_retry_after <.ext.commands.Command.get_cooldown_retry_after>` によってコマンド中のクールダウンから ``retry_after`` を取得する方法を追加しました (:issue:`5195`)" -#: ../../whats_new.rst:468 +#: ../../whats_new.rst:687 msgid "|commands| Add a way to dynamically add and remove checks from a :class:`HelpCommand <.ext.commands.HelpCommand>` (:issue:`5197`)" msgstr "|commands| :class:`HelpCommand <.ext.commands.HelpCommand>` から動的にチェックを追加したり削除したりする方法を追加しました (:issue:`5197`)" -#: ../../whats_new.rst:469 +#: ../../whats_new.rst:688 msgid "|tasks| Add :meth:`Loop.is_running <.ext.tasks.Loop.is_running>` method to the task objects (:issue:`2540`)" msgstr "|tasks| タスクオブジェクトに :meth:`Loop.is_running <.ext.tasks.Loop.is_running>` メソッドを追加しました (:issue:`2540`)" -#: ../../whats_new.rst:470 +#: ../../whats_new.rst:689 msgid "|tasks| Allow usage of custom error handlers similar to the command extensions to tasks using :meth:`Loop.error <.ext.tasks.Loop.error>` decorator (:issue:`2621`)" msgstr "|tasks| :meth:`Loop.error <.ext.tasks.Loop.error>` デコレーターを用いたタスクに対するコマンド拡張と同様のカスタムエラーハンドラーの使用を可能にしました (:issue:`2621`)" -#: ../../whats_new.rst:476 +#: ../../whats_new.rst:695 msgid "Fix issue with :attr:`PartialEmoji.url` reads leading to a failure (:issue:`4015`, :issue:`4016`)" msgstr ":attr:`PartialEmoji.url` での読み込みエラーを修正しました。 (:issue:`4015`, :issue:`4016`)" -#: ../../whats_new.rst:477 +#: ../../whats_new.rst:696 msgid "Allow :meth:`abc.Messageable.history` to take a limit of ``1`` even if ``around`` is passed (:issue:`4019`)" msgstr "``around`` が渡された場合でも、 :meth:`abc.Messageable.history` が上限 ``1`` を取ることができるようにしました。 (:issue:`4019`)" -#: ../../whats_new.rst:478 +#: ../../whats_new.rst:697 msgid "Fix :attr:`Guild.member_count` not updating in certain cases when a member has left the guild (:issue:`4021`)" msgstr "ギルドからメンバーが脱退したとき、特定の場合に :attr:`Guild.member_count` が更新されない問題を修正しました。 (:issue:`4021`)" -#: ../../whats_new.rst:479 +#: ../../whats_new.rst:698 msgid "Fix the type of :attr:`Object.id` not being validated. For backwards compatibility ``str`` is still allowed but is converted to ``int`` (:issue:`4002`)" msgstr ":attr:`Object.id` の型が検証されない問題を修正されました。後方互換性のため ``str`` は使用可能ですが、 ``int`` に変換されます。 (:issue:`4002`)" -#: ../../whats_new.rst:480 +#: ../../whats_new.rst:699 msgid "Fix :meth:`Guild.edit` not allowing editing of notification settings (:issue:`4074`, :issue:`4047`)" msgstr ":meth:`Guild.edit` で通知設定の編集ができない問題を修正しました。 (:issue:`4074`, :issue:`4047`)" -#: ../../whats_new.rst:481 +#: ../../whats_new.rst:700 msgid "Fix crash when the guild widget contains channels that aren't in the payload (:issue:`4114`, :issue:`4115`)" msgstr "ギルドウィジェットの中にペイロードにないチャンネルが含まれている場合にクラッシュする問題を修正しました。 (:issue:`4114`, :issue:`4115`)" -#: ../../whats_new.rst:482 +#: ../../whats_new.rst:701 msgid "Close ffmpeg stdin handling from spawned processes with :class:`FFmpegOpusAudio` and :class:`FFmpegPCMAudio` (:issue:`4036`)" msgstr ":class:`FFmpegOpusAudio` および :class:`FFmpegPCMAudio` を使って生成されたプロセスからの ffmpeg stdin のハンドリングを閉じるようにしました (:issue:`4036`)" -#: ../../whats_new.rst:483 +#: ../../whats_new.rst:702 msgid "Fix :func:`utils.escape_markdown` not escaping masked links (:issue:`4206`, :issue:`4207`)" msgstr ":func:`utils.escape_markdown` がマスクされたリンクをエスケープしない問題を修正しました。 (:issue:`4206`, :issue:`4207`)" -#: ../../whats_new.rst:484 +#: ../../whats_new.rst:703 msgid "Fix reconnect loop due to failed handshake on region change (:issue:`4210`, :issue:`3996`)" msgstr "リージョン変更時のハンドシェイクの失敗による再接続のループを修正しました (:issue:`4210`, :issue:`3996`)" -#: ../../whats_new.rst:485 +#: ../../whats_new.rst:704 msgid "Fix :meth:`Guild.by_category` not returning empty categories (:issue:`4186`)" msgstr "空のカテゴリーを返さない :meth:`Guild.by_category` を修正しました (:issue:`4186`)" -#: ../../whats_new.rst:486 +#: ../../whats_new.rst:705 msgid "Fix certain JPEG images not being identified as JPEG (:issue:`5143`)" msgstr "特定の JPEG 画像が JPEG として認識されないのを修正 (:issue:`5143`)" -#: ../../whats_new.rst:487 +#: ../../whats_new.rst:706 msgid "Fix a crash when an incomplete guild object is used when fetching reaction information (:issue:`5181`)" msgstr "反応情報を取得する際に不完全なギルドオブジェクトを使用するとクラッシュする問題を修正しました (:issue:`5181`)" -#: ../../whats_new.rst:488 +#: ../../whats_new.rst:707 msgid "Fix a timeout issue when fetching members using :meth:`Guild.query_members`" msgstr ":meth:`Guild.query_members` を使用してメンバーを取得する際のタイムアウトの問題を修正しました。" -#: ../../whats_new.rst:489 +#: ../../whats_new.rst:708 msgid "Fix an issue with domain resolution in voice (:issue:`5188`, :issue:`5191`)" msgstr "音声のドメイン解決に関する問題を修正しました (:issue:`5188`, :issue:`5191`)" -#: ../../whats_new.rst:490 +#: ../../whats_new.rst:709 msgid "Fix an issue where :attr:`PartialEmoji.id` could be a string (:issue:`4153`, :issue:`4152`)" msgstr ":attr:`PartialEmoji.id` が文字列である可能性がある問題を修正しました (:issue:`4153`, :issue:`4152`)" -#: ../../whats_new.rst:491 +#: ../../whats_new.rst:710 msgid "Fix regression where :attr:`Member.activities` would not clear." msgstr ":attr:`Member.activities` がクリアされないリグレッションを修正しました。" -#: ../../whats_new.rst:492 +#: ../../whats_new.rst:711 msgid "|commands| A :exc:`TypeError` is now raised when :obj:`typing.Optional` is used within :data:`commands.Greedy <.ext.commands.Greedy>` (:issue:`2253`, :issue:`5068`)" msgstr "|commands| :data:`commands.Greedy <.ext.commands.Greedy>` 内で :obj:`typing.Optional` を使用すると :exc:`TypeError` が発生します (:issue:`2253`, :issue:`5068`)." -#: ../../whats_new.rst:493 +#: ../../whats_new.rst:712 msgid "|commands| :meth:`Bot.walk_commands <.ext.commands.Bot.walk_commands>` no longer yields duplicate commands due to aliases (:issue:`2591`)" msgstr "|commands| :meth:`Bot.walk_commands <.ext.commands.Bot.walk_commands>` はエイリアスにより重複したコマンドを生成しないようになりました (:issue:`2591`)" -#: ../../whats_new.rst:494 +#: ../../whats_new.rst:713 msgid "|commands| Fix regex characters not being escaped in :attr:`HelpCommand.clean_prefix <.ext.commands.HelpCommand.clean_prefix>` (:issue:`4058`, :issue:`4071`)" msgstr "|commands| :attr:`HelpCommand.clean_prefix <.ext.commands.HelpCommand.clean_prefix>` で正規化されていない文字を修正しました (:issue:`4058`, :issue:`4071`)" -#: ../../whats_new.rst:495 +#: ../../whats_new.rst:714 msgid "|commands| Fix :meth:`Bot.get_command <.ext.commands.Bot.get_command>` from raising errors when a name only has whitespace (:issue:`5124`)" msgstr "|commands| 名前に空白文字しかない場合にエラーを発生させないように :meth:`Bot.get_command <.ext.commands.Bot.get_command>` を修正しました (:issue:`5124`)" -#: ../../whats_new.rst:496 +#: ../../whats_new.rst:715 msgid "|commands| Fix issue with :attr:`Context.subcommand_passed <.ext.commands.Context.subcommand_passed>` not functioning as expected (:issue:`5198`)" msgstr "|commands| :attr:`Context.subcommand_passed <.ext.commands.Context.subcommand_passed>` が期待通りに機能しない問題を修正しました (:issue:`5198`)" -#: ../../whats_new.rst:497 +#: ../../whats_new.rst:716 msgid "|tasks| Task objects are no longer stored globally so two class instances can now start two separate tasks (:issue:`2294`)" msgstr "|tasks| Task objects are no longer stored globally so two class instances can start two separate tasks (:issue:`2294`)" -#: ../../whats_new.rst:498 +#: ../../whats_new.rst:717 msgid "|tasks| Allow cancelling the loop within :meth:`before_loop <.ext.tasks.Loop.before_loop>` (:issue:`4082`)" msgstr "|tasks| 内のループをキャンセルできるようにする。:meth:`before_loop <.ext.tasks.Loop.before_loop>` (:issue:`4082`)" -#: ../../whats_new.rst:504 +#: ../../whats_new.rst:723 msgid "The :attr:`Member.roles` cache introduced in v1.3 was reverted due to issues caused (:issue:`4087`, :issue:`4157`)" msgstr "v1.3 で導入された :attr:`Member.roles` キャッシュは、問題が発生したため元に戻されました (:issue:`4087`, :issue:`4157`)" -#: ../../whats_new.rst:505 +#: ../../whats_new.rst:724 msgid ":class:`Webhook` objects are now comparable and hashable (:issue:`4182`)" msgstr ":class:`Webhook` オブジェクトが比較可能になり、ハッシュ化できるようになりました (:issue:`4182`)" -#: ../../whats_new.rst:509 +#: ../../whats_new.rst:728 msgid "Some more API requests got a ``reason`` parameter for audit logs (:issue:`5086`)" msgstr "さらにいくつかの API リクエストで、監査ログ用の ``reason`` パラメータが取得されました (:issue:`5086`)" -#: ../../whats_new.rst:507 +#: ../../whats_new.rst:726 msgid ":meth:`TextChannel.follow`" msgstr ":meth:`TextChannel.follow`" -#: ../../whats_new.rst:508 +#: ../../whats_new.rst:727 msgid ":meth:`Message.pin` and :meth:`Message.unpin`" msgstr ":meth:`Message.pin` と :meth:`Message.unpin`" -#: ../../whats_new.rst:509 +#: ../../whats_new.rst:728 msgid ":meth:`Webhook.delete` and :meth:`Webhook.edit`" msgstr ":meth:`Webhook.delete` と :meth:`Webhook.edit`" -#: ../../whats_new.rst:511 +#: ../../whats_new.rst:730 msgid "For performance reasons ``websockets`` has been dropped in favour of ``aiohttp.ws``." msgstr "パフォーマンス上の理由から、 ``websockets`` は削除され、 ``aiohttp.ws`` が使用されるようになりました。" -#: ../../whats_new.rst:512 +#: ../../whats_new.rst:731 msgid "The blocking logging message now shows the stack trace of where the main thread was blocking" msgstr "ブロッキングのログメッセージは、メインスレッドがブロッキングしていた場所のスタックトレースを表示するようになりました" -#: ../../whats_new.rst:513 +#: ../../whats_new.rst:732 msgid "The domain name was changed from ``discordapp.com`` to ``discord.com`` to prepare for the required domain migration" msgstr "必要なドメイン移行の準備のため、ドメイン名を ``discordapp.com`` から ``discord.com`` に変更しました。" -#: ../../whats_new.rst:514 +#: ../../whats_new.rst:733 msgid "Reduce memory usage when reconnecting due to stale references being held by the message cache (:issue:`5133`)" msgstr "メッセージキャッシュに保持されている古い参照による再接続時のメモリ使用量を削減しました (:issue:`5133`)" -#: ../../whats_new.rst:515 +#: ../../whats_new.rst:734 msgid "Optimize :meth:`abc.GuildChannel.permissions_for` by not creating as many temporary objects (20-32% savings)." msgstr "テンポラリオブジェクトをあまり作成しないように :meth:`abc.GuildChannel.permissions_for` を最適化しました (20-32%の節約)。" -#: ../../whats_new.rst:516 +#: ../../whats_new.rst:735 msgid "|commands| Raise :exc:`~ext.commands.CommandRegistrationError` instead of :exc:`ClientException` when a duplicate error is registered (:issue:`4217`)" msgstr "|commands| 重複するエラーが登録された場合、 :exc:`ClientException` ではなく :exc:`~ext.commands.CommandRegistrationError` を発生するようにしました (:issue:`4217`)" -#: ../../whats_new.rst:517 +#: ../../whats_new.rst:736 msgid "|tasks| No longer handle :exc:`HTTPException` by default in the task reconnect loop (:issue:`5193`)" msgstr "|tasks| タスクの再接続ループにおいて、デフォルトで :exc:`HTTPException` を処理しないようにしました (:issue:`5193`)" -#: ../../whats_new.rst:522 +#: ../../whats_new.rst:741 msgid "v1.3.4" msgstr "v1.3.4" -#: ../../whats_new.rst:527 +#: ../../whats_new.rst:746 msgid "Fix an issue with channel overwrites causing multiple issues including crashes (:issue:`5109`)" msgstr "チャンネルの上書きがクラッシュを含む複数の問題を引き起こす問題を修正しました (:issue:`5109`)" -#: ../../whats_new.rst:532 +#: ../../whats_new.rst:751 msgid "v1.3.3" msgstr "v1.3.3" -#: ../../whats_new.rst:538 +#: ../../whats_new.rst:757 msgid "Change default WS close to 4000 instead of 1000." msgstr "デフォルトのWSクローズを1000から4000に変更。" -#: ../../whats_new.rst:538 +#: ../../whats_new.rst:757 msgid "The previous close code caused sessions to be invalidated at a higher frequency than desired." msgstr "以前のクローズコードは、望ましい頻度よりも高い頻度でセッションが無効化される原因となっていました。" -#: ../../whats_new.rst:540 +#: ../../whats_new.rst:759 msgid "Fix ``None`` appearing in ``Member.activities``. (:issue:`2619`)" msgstr "``Member.activities`` に表示される ``None`` を修正しました。(:issue:`2619`)" -#: ../../whats_new.rst:545 +#: ../../whats_new.rst:764 msgid "v1.3.2" msgstr "v1.3.2" -#: ../../whats_new.rst:547 +#: ../../whats_new.rst:766 msgid "Another minor bug fix release." msgstr "もう一つのマイナーなバグフィックスリリースです。" -#: ../../whats_new.rst:552 +#: ../../whats_new.rst:771 msgid "Higher the wait time during the ``GUILD_CREATE`` stream before ``on_ready`` is fired for :class:`AutoShardedClient`." msgstr ":class:`AutoShardedClient` の ``GUILD_CREATE`` ストリームで ``on_ready`` が発生するまでの待ち時間を長くするようにしました。" -#: ../../whats_new.rst:553 +#: ../../whats_new.rst:772 msgid ":func:`on_voice_state_update` now uses the inner ``member`` payload which should make it more reliable." msgstr ":func:`on_voice_state_update` は内側の ``member`` ペイロードを使用するようになり、より信頼性が高くなりました。" -#: ../../whats_new.rst:554 +#: ../../whats_new.rst:773 msgid "Fix various Cloudflare handling errors (:issue:`2572`, :issue:`2544`)" msgstr "Cloudflare のハンドリングエラーを修正しました (:issue:`2572`, :issue:`2544`)" -#: ../../whats_new.rst:555 +#: ../../whats_new.rst:774 msgid "Fix crashes if :attr:`Message.guild` is :class:`Object` instead of :class:`Guild`." msgstr ":attr:`Message.guild` が :class:`Guild` ではなく :class:`Object` であった場合のクラッシュを修正しました。" -#: ../../whats_new.rst:556 +#: ../../whats_new.rst:775 msgid "Fix :meth:`Webhook.send` returning an empty string instead of ``None`` when ``wait=False``." msgstr ":meth:`Webhook.send` が ``wait=False`` の時に ``None`` ではなく空の文字列を返すように修正しました。" -#: ../../whats_new.rst:557 +#: ../../whats_new.rst:776 msgid "Fix invalid format specifier in webhook state (:issue:`2570`)" msgstr "Webhook の状態における無効なフォーマット指定子を修正 (:issue:`2570`)" -#: ../../whats_new.rst:558 +#: ../../whats_new.rst:777 msgid "|commands| Passing invalid permissions to permission related checks now raises ``TypeError``." msgstr "|commands| パーミッション関連のチェックで無効なパーミッションを渡すと ``TypeError`` が発生するようになりました。" -#: ../../whats_new.rst:563 +#: ../../whats_new.rst:782 msgid "v1.3.1" msgstr "v1.3.1" -#: ../../whats_new.rst:565 +#: ../../whats_new.rst:784 msgid "Minor bug fix release." msgstr "マイナーなバグフィックスリリースです。" -#: ../../whats_new.rst:570 +#: ../../whats_new.rst:789 msgid "Fix fetching invites in guilds that the user is not in." msgstr "ユーザーが参加していないギルドの招待状をフェッチするように修正しました。" -#: ../../whats_new.rst:571 +#: ../../whats_new.rst:790 msgid "Fix the channel returned from :meth:`Client.fetch_channel` raising when sending messages. (:issue:`2531`)" msgstr "メッセージ送信時に :meth:`Client.fetch_channel` から返されるチャンネルを修正しました。(:issue:`2531`)" -#: ../../whats_new.rst:576 +#: ../../whats_new.rst:795 msgid "Fix compatibility warnings when using the Python 3.9 alpha." msgstr "Python 3.9 alpha を使用する際の互換性警告を修正。" -#: ../../whats_new.rst:577 +#: ../../whats_new.rst:796 msgid "Change the unknown event logging from WARNING to DEBUG to reduce noise." msgstr "ノイズを減らすために、不明なイベントのログをWARNINGからDEBUGに変更します。" -#: ../../whats_new.rst:582 +#: ../../whats_new.rst:801 msgid "v1.3.0" msgstr "v1.3.0" -#: ../../whats_new.rst:584 +#: ../../whats_new.rst:803 msgid "This version comes with a lot of bug fixes and new features. It's been in development for a lot longer than was anticipated!" msgstr "このバージョンでは、多くのバグフィックスと新機能が搭載されています。予想以上に長い期間、開発が続けられているのです!" -#: ../../whats_new.rst:589 +#: ../../whats_new.rst:808 msgid "Add :meth:`Guild.fetch_members` to fetch members from the HTTP API. (:issue:`2204`)" msgstr "HTTP API からメンバーを取得するための :meth:`Guild.fetch_members` を追加しました。(:issue:`2204`)" -#: ../../whats_new.rst:590 +#: ../../whats_new.rst:809 msgid "Add :meth:`Guild.fetch_roles` to fetch roles from the HTTP API. (:issue:`2208`)" msgstr "HTTP API からロールをフェッチするために :meth:`Guild.fetch_roles` を追加しました。(:issue:`2208`)" -#: ../../whats_new.rst:591 +#: ../../whats_new.rst:810 msgid "Add support for teams via :class:`Team` when fetching with :meth:`Client.application_info`. (:issue:`2239`)" msgstr ":meth:`Client.application_info` で取得する際に、 :class:`Team` を介してチームをサポートする機能を追加しました。(:issue:`2239`)" -#: ../../whats_new.rst:592 +#: ../../whats_new.rst:811 msgid "Add support for suppressing embeds via :meth:`Message.edit`" msgstr ":meth:`Message.edit` による埋め込みの抑制をサポートするようにしました。" -#: ../../whats_new.rst:593 +#: ../../whats_new.rst:812 msgid "Add support for guild subscriptions. See the :class:`Client` documentation for more details." msgstr "ギルドサブスクリプションのサポートを追加しました。詳細は :class:`Client` のドキュメントを参照してください。" -#: ../../whats_new.rst:594 +#: ../../whats_new.rst:813 msgid "Add :attr:`VoiceChannel.voice_states` to get voice states without relying on member cache." msgstr "メンバーキャッシュに依存せずに音声の状態を取得するために、 :attr:`VoiceChannel.voice_states` を追加しました。" -#: ../../whats_new.rst:595 +#: ../../whats_new.rst:814 msgid "Add :meth:`Guild.query_members` to request members from the gateway." msgstr "ゲートウェイにメンバーを要求するために :meth:`Guild.query_members` を追加しました。" -#: ../../whats_new.rst:596 +#: ../../whats_new.rst:815 msgid "Add :class:`FFmpegOpusAudio` and other voice improvements. (:issue:`2258`)" msgstr ":class:`FFmpegOpusAudio` を追加し、その他の音声の改良を行いました。(:issue:`2258`)" -#: ../../whats_new.rst:597 +#: ../../whats_new.rst:816 msgid "Add :attr:`RawMessageUpdateEvent.channel_id` for retrieving channel IDs during raw message updates. (:issue:`2301`)" msgstr "Rawメッセージの更新時にチャンネルIDを取得するための :attr:`RawMessageUpdateEvent.channel_id` を追加しました。(:issue:`2301`)" -#: ../../whats_new.rst:598 +#: ../../whats_new.rst:817 msgid "Add :attr:`RawReactionActionEvent.event_type` to disambiguate between reaction addition and removal in reaction events." msgstr "リアクションイベントでリアクションが追加されたか除去されたかを明確にする :attr:`RawReactionActionEvent.event_type` を追加しました。" -#: ../../whats_new.rst:599 +#: ../../whats_new.rst:818 msgid "Add :attr:`abc.GuildChannel.permissions_synced` to query whether permissions are synced with the category. (:issue:`2300`, :issue:`2324`)" msgstr "権限がカテゴリと同期されているかを確認する :attr:`abc.GuildChannel.permissions_synced` を追加しました。 (:issue:`2300`, :issue:`2324`)" -#: ../../whats_new.rst:600 +#: ../../whats_new.rst:819 msgid "Add :attr:`MessageType.channel_follow_add` message type for announcement channels being followed. (:issue:`2314`)" msgstr "フォローされているアナウンスチャンネル用の :attr:`MessageType.channel_follow_add` メッセージタイプを追加しました。(:issue:`2314`)" -#: ../../whats_new.rst:601 +#: ../../whats_new.rst:820 msgid "Add :meth:`Message.is_system` to allow for quickly filtering through system messages." msgstr "システムメッセージを素早くフィルタリングできるように :meth:`Message.is_system` を追加しました。" -#: ../../whats_new.rst:602 +#: ../../whats_new.rst:821 msgid "Add :attr:`VoiceState.self_stream` to indicate whether someone is streaming via Go Live. (:issue:`2343`)" msgstr "誰かがGo Live経由でストリーミングしているかどうかを示すための、 :attr:`VoiceState.self_stream` を追加しました。 (:issue:`2343`)" -#: ../../whats_new.rst:603 +#: ../../whats_new.rst:822 msgid "Add :meth:`Emoji.is_usable` to check if the client user can use an emoji. (:issue:`2349`)" msgstr "クライアントユーザーが絵文字を使用できるかどうかを確認できるように、 :meth:`Emoji.is_usable` を追加しました。 (:issue:`2349`)" -#: ../../whats_new.rst:604 +#: ../../whats_new.rst:823 msgid "Add :attr:`VoiceRegion.europe` and :attr:`VoiceRegion.dubai`. (:issue:`2358`, :issue:`2490`)" msgstr ":attr:`VoiceRegion.europe` と :attr:`VoiceRegion.dubai` を追加しました。 (:issue:`2358`, :issue:`2490`)" -#: ../../whats_new.rst:605 +#: ../../whats_new.rst:824 msgid "Add :meth:`TextChannel.follow` to follow a news channel. (:issue:`2367`)" msgstr "ニュースチャンネルをフォローする :meth:`TextChannel.follow` を追加しました。 (:issue:`2367`)" -#: ../../whats_new.rst:606 +#: ../../whats_new.rst:825 msgid "Add :attr:`Permissions.view_guild_insights` permission. (:issue:`2415`)" msgstr ":attr:`Permissions.view_guild_insights` 権限を追加しました。 (:issue:`2415`)" -#: ../../whats_new.rst:608 +#: ../../whats_new.rst:827 msgid "Add support for new audit log types. See :ref:`discord-api-audit-logs` for more information. (:issue:`2427`)" msgstr "新しい監査ログタイプのサポートを追加しました。詳細については :ref:`discord-api-audit-logs` を参照してください。 (:issue:`2427`)" -#: ../../whats_new.rst:608 +#: ../../whats_new.rst:827 msgid "Note that integration support is not finalized." msgstr "インテグレーションのサポートは未確定であることに注意してください。" -#: ../../whats_new.rst:610 +#: ../../whats_new.rst:829 msgid "Add :attr:`Webhook.type` to query the type of webhook (:class:`WebhookType`). (:issue:`2441`)" msgstr "ウェブフック( :class:`WebhookType` )の種類を問い合わせるための :attr:`Webhook.type` を追加しました。 (:issue:`2441`)" -#: ../../whats_new.rst:611 +#: ../../whats_new.rst:830 msgid "Allow bulk editing of channel overwrites through :meth:`abc.GuildChannel.edit`. (:issue:`2198`)" msgstr "チャンネル上書きの一括編集を :meth:`abc.GuildChannel.edit` を通して行えるようにしました。(:issue:`2198`)" -#: ../../whats_new.rst:612 +#: ../../whats_new.rst:831 msgid "Add :class:`Activity.created_at` to see when an activity was started. (:issue:`2446`)" msgstr "アクティビティがいつ開始されたかを確認するために :class:`Activity.created_at` を追加しました。(:issue:`2446`)" -#: ../../whats_new.rst:613 +#: ../../whats_new.rst:832 msgid "Add support for ``xsalsa20_poly1305_lite`` encryption mode for voice. (:issue:`2463`)" msgstr "音声用の ``xsalsa20_poly1305_lite`` 暗号化モードのサポートを追加しました。(:issue:`2463`)" -#: ../../whats_new.rst:614 +#: ../../whats_new.rst:833 msgid "Add :attr:`RawReactionActionEvent.member` to get the member who did the reaction. (:issue:`2443`)" msgstr "リアクションを行ったメンバーを取得するために :attr:`RawReactionActionEvent.member` を追加しました。(:issue:`2443`)" -#: ../../whats_new.rst:615 +#: ../../whats_new.rst:834 msgid "Add support for new YouTube streaming via :attr:`Streaming.platform` and :attr:`Streaming.game`. (:issue:`2445`)" msgstr ":attr:`Streaming.platform` と :attr:`Streaming.game` による新しい YouTube ストリーミングのサポートを追加しました。(:issue:`2445`)" -#: ../../whats_new.rst:616 +#: ../../whats_new.rst:835 msgid "Add :attr:`Guild.discovery_splash_url` to get the discovery splash image asset. (:issue:`2482`)" msgstr "ディスカバリースプラッシュイメージアセットを取得するために :attr:`Guild.discovery_splash_url` を追加しました。(:issue:`2482`)" -#: ../../whats_new.rst:618 +#: ../../whats_new.rst:837 msgid "Add :attr:`Guild.rules_channel` to get the rules channel of public guilds. (:issue:`2482`)" msgstr "パブリック・ギルドのルール・チャンネルを取得するために :attr:`Guild.rules_channel` を追加しました。(:issue:`2482`)" -#: ../../whats_new.rst:618 +#: ../../whats_new.rst:837 msgid "It should be noted that this feature is restricted to those who are either in Server Discovery or planning to be there." msgstr "なお、この機能はサーバーディスカバリーに参加されている方、または参加予定の方に限定しています。" -#: ../../whats_new.rst:620 +#: ../../whats_new.rst:839 msgid "Add support for message flags via :attr:`Message.flags` and :class:`MessageFlags`. (:issue:`2433`)" msgstr ":attr:`Message.flags` と :class:`MessageFlags` によるメッセージフラグのサポートを追加しました。(:issue:`2433`)" -#: ../../whats_new.rst:621 +#: ../../whats_new.rst:840 msgid "Add :attr:`User.system` and :attr:`Profile.system` to know whether a user is an official Discord Trust and Safety account." msgstr "ユーザーがDiscord Trust and Safetyの公式アカウントであるかどうかを知るために、 :attr:`User.system` と :attr:`Profile.system` を追加しました。" -#: ../../whats_new.rst:622 +#: ../../whats_new.rst:841 msgid "Add :attr:`Profile.team_user` to check whether a user is a member of a team." msgstr "ユーザーがチームのメンバーであるかどうかを確認するために :attr:`Profile.team_user` を追加しました。" -#: ../../whats_new.rst:623 +#: ../../whats_new.rst:842 msgid "Add :meth:`Attachment.to_file` to easily convert attachments to :class:`File` for sending." msgstr "添付ファイルを簡単に :class:`File` に変換して送信できるように :meth:`Attachment.to_file` を追加。" -#: ../../whats_new.rst:627 +#: ../../whats_new.rst:846 msgid "Add certain aliases to :class:`Permissions` to match the UI better. (:issue:`2496`)" msgstr "UIにマッチするように、特定のエイリアスを :class:`Permissions` に追加しました。(:issue:`2496`)" -#: ../../whats_new.rst:625 +#: ../../whats_new.rst:844 msgid ":attr:`Permissions.manage_permissions`" msgstr ":attr:`Permissions.manage_permissions`" -#: ../../whats_new.rst:626 +#: ../../whats_new.rst:845 msgid ":attr:`Permissions.view_channel`" msgstr ":attr:`Permissions.view_channel`" -#: ../../whats_new.rst:627 +#: ../../whats_new.rst:846 msgid ":attr:`Permissions.use_external_emojis`" msgstr ":attr:`Permissions.use_external_emojis`" -#: ../../whats_new.rst:629 +#: ../../whats_new.rst:848 msgid "Add support for passing keyword arguments when creating :class:`Permissions`." msgstr ":class:`Permissions` を作成する際に、キーワード引数を渡せるようになりました。" -#: ../../whats_new.rst:631 +#: ../../whats_new.rst:850 msgid "Add support for custom activities via :class:`CustomActivity`. (:issue:`2400`)" msgstr ":class:`CustomActivity` によるカスタムアクティビティーのサポートを追加しました。(:issue:`2400`)" -#: ../../whats_new.rst:631 +#: ../../whats_new.rst:850 msgid "Note that as of now, bots cannot send custom activities yet." msgstr "なお、現在のところ、ボットはまだカスタムアクティビティを送信できません。" -#: ../../whats_new.rst:633 +#: ../../whats_new.rst:852 msgid "Add support for :func:`on_invite_create` and :func:`on_invite_delete` events." msgstr ":func:`on_invite_create` と :func:`on_invite_delete` イベントのサポートを追加しました。" -#: ../../whats_new.rst:636 +#: ../../whats_new.rst:855 msgid "Add support for clearing a specific reaction emoji from a message." msgstr "メッセージから特定のリアクション絵文字を消去する機能を追加しました。" -#: ../../whats_new.rst:635 +#: ../../whats_new.rst:854 msgid ":meth:`Message.clear_reaction` and :meth:`Reaction.clear` methods." msgstr ":meth:`Message.clear_reaction` および :meth:`Reaction.clear` メソッドを使用します。" -#: ../../whats_new.rst:636 +#: ../../whats_new.rst:855 msgid ":func:`on_raw_reaction_clear_emoji` and :func:`on_reaction_clear_emoji` events." msgstr ":func:`on_raw_reaction_clear_emoji` と :func:`on_reaction_clear_emoji` イベントです。" -#: ../../whats_new.rst:638 +#: ../../whats_new.rst:857 msgid "Add :func:`utils.sleep_until` helper to sleep until a specific datetime. (:issue:`2517`, :issue:`2519`)" msgstr "特定の日付までスリープさせる :func:`utils.sleep_until` ヘルパーを追加しました。(:issue:`2517`、:issue:`2519`)" -#: ../../whats_new.rst:639 +#: ../../whats_new.rst:858 msgid "|commands| Add support for teams and :attr:`Bot.owner_ids <.ext.commands.Bot.owner_ids>` to have multiple bot owners. (:issue:`2239`)" msgstr "|commands| チームと :attr:`Bot.owner_ids <.ext.commands.Bot.owner_ids>` が複数のボットオーナーを持つためのサポートを追加しました。(:issue:`2239`)" -#: ../../whats_new.rst:640 +#: ../../whats_new.rst:859 msgid "|commands| Add new :attr:`BucketType.role <.ext.commands.BucketType.role>` bucket type. (:issue:`2201`)" msgstr "|commands| 新しい :attr:`BucketType.role <.ext.commands.BucketType.role>` のバケットタイプを追加しました。(:issue:`2201`)です。" -#: ../../whats_new.rst:641 +#: ../../whats_new.rst:860 msgid "|commands| Expose :attr:`Command.cog <.ext.commands.Command.cog>` property publicly. (:issue:`2360`)" msgstr "|commands| :attr:`Command.cog <.ext.commands.Command.cog>` のプロパティを公開します。(:issue:`2360`)" -#: ../../whats_new.rst:642 +#: ../../whats_new.rst:861 msgid "|commands| Add non-decorator interface for adding checks to commands via :meth:`Command.add_check <.ext.commands.Command.add_check>` and :meth:`Command.remove_check <.ext.commands.Command.remove_check>`. (:issue:`2411`)" msgstr "|commands| :meth:`Command.add_check <.ext.commands.Command.add_check>` および :meth:`Command.remove_check <.ext.commands.Command.remove_check>` によりコマンドにチェックを追加する非デコレーターインターフェイスを追加しました。(:issue:`2411`)" -#: ../../whats_new.rst:643 +#: ../../whats_new.rst:862 msgid "|commands| Add :func:`has_guild_permissions <.ext.commands.has_guild_permissions>` check. (:issue:`2460`)" msgstr "|commands| :func:`has_guild_permissions <.ext.commands.has_guild_permissions>` のチェックを追加しました。(:issue:`2460`)" -#: ../../whats_new.rst:644 +#: ../../whats_new.rst:863 msgid "|commands| Add :func:`bot_has_guild_permissions <.ext.commands.bot_has_guild_permissions>` check. (:issue:`2460`)" msgstr "|commands| :func:`has_guild_permissions <.ext.commands.bot_has_guild_permissions>` のチェックを追加しました。(:issue:`2460`)" -#: ../../whats_new.rst:645 +#: ../../whats_new.rst:864 msgid "|commands| Add ``predicate`` attribute to checks decorated with :func:`~.ext.commands.check`." msgstr "|commands| :func:`~.ext.commands.check` で装飾されたチェックに ``predicate`` 属性を追加しました。" -#: ../../whats_new.rst:646 +#: ../../whats_new.rst:865 msgid "|commands| Add :func:`~.ext.commands.check_any` check to logical OR multiple checks." msgstr "|commands| :func:`~.ext.commands.check_any` チェックを論理的 OR 複数のチェックに追加しました。" -#: ../../whats_new.rst:647 +#: ../../whats_new.rst:866 msgid "|commands| Add :func:`~.ext.commands.max_concurrency` to allow only a certain amount of users to use a command concurrently before waiting or erroring." msgstr "|commands| 待ち時間やエラーになる前に、ある一定のユーザーだけがコマンドを同時に使用できるようにするための :func:`~.ext.commands.max_concurrency` を追加しました。" -#: ../../whats_new.rst:648 +#: ../../whats_new.rst:867 msgid "|commands| Add support for calling a :class:`~.ext.commands.Command` as a regular function." msgstr "|commands| :class:`~.ext.commands.Command` を通常の関数として呼び出すためのサポートを追加しました。" -#: ../../whats_new.rst:649 +#: ../../whats_new.rst:868 msgid "|tasks| :meth:`Loop.add_exception_type <.ext.tasks.Loop.add_exception_type>` now allows multiple exceptions to be set. (:issue:`2333`)" msgstr "|tasks| :meth:`Loop.add_exception_type <.ext.tasks.Loop.add_exception_type>` が、複数の例外を設定できるようになりました。(:issue:`2333`)" -#: ../../whats_new.rst:650 +#: ../../whats_new.rst:869 msgid "|tasks| Add :attr:`Loop.next_iteration <.ext.tasks.Loop.next_iteration>` property. (:issue:`2305`)" msgstr "|tasks| Add :attr:`Loop.next_iteration <.ext.tasks.Loop.next_iteration>` プロパティを追加しました。(:issue:`2305`)" -#: ../../whats_new.rst:655 +#: ../../whats_new.rst:874 msgid "Fix issue with permission resolution sometimes failing for guilds with no owner." msgstr "所有者がいないギルドで権限解決に失敗することがある問題を修正しました。" -#: ../../whats_new.rst:656 +#: ../../whats_new.rst:875 msgid "Tokens are now stripped upon use. (:issue:`2135`)" msgstr "トークンは、使用時に剥奪されるようになりました。(:issue:`2135`)" -#: ../../whats_new.rst:657 +#: ../../whats_new.rst:876 msgid "Passing in a ``name`` is no longer required for :meth:`Emoji.edit`. (:issue:`2368`)" msgstr ":meth:`Emoji.edit` に ``name`` を渡す必要はなくなりました。(:issue:`2368`)" -#: ../../whats_new.rst:658 +#: ../../whats_new.rst:877 msgid "Fix issue with webhooks not re-raising after retries have run out. (:issue:`2272`, :issue:`2380`)" msgstr "Webhooks がリトライを使い切った後に再レイズしない問題を修正しました。(:issue:`2272`, :issue:`2380`)" -#: ../../whats_new.rst:659 +#: ../../whats_new.rst:878 msgid "Fix mismatch in URL handling in :func:`utils.escape_markdown`. (:issue:`2420`)" msgstr ":func:`utils.escape_markdown` のURLハンドリングにおけるミスマッチを修正しました。(:issue:`2420`)" -#: ../../whats_new.rst:660 +#: ../../whats_new.rst:879 msgid "Fix issue with ports being read in little endian when they should be big endian in voice connections. (:issue:`2470`)" msgstr "音声接続において、ビッグエンディアンであるべきポートがリトルエンディアンで読み込まれる問題を修正しました。(:issue:`2470`)" -#: ../../whats_new.rst:661 +#: ../../whats_new.rst:880 msgid "Fix :meth:`Member.mentioned_in` not taking into consideration the message's guild." msgstr "メッセージのギルドが考慮されない :meth:`Member.mentioned_in` を修正しました。" -#: ../../whats_new.rst:662 +#: ../../whats_new.rst:881 msgid "Fix bug with moving channels when there are gaps in positions due to channel deletion and creation." msgstr "チャンネルの削除と作成によりポジションにギャップがある場合、チャンネルを移動する不具合を修正。" -#: ../../whats_new.rst:663 +#: ../../whats_new.rst:882 msgid "Fix :func:`on_shard_ready` not triggering when ``fetch_offline_members`` is disabled. (:issue:`2504`)" msgstr "``fetch_offline_members`` が無効の場合、 :func:`on_shard_ready` が発火されない問題を修正しました。(:issue:`2504`)" -#: ../../whats_new.rst:664 +#: ../../whats_new.rst:883 msgid "Fix issue with large sharded bots taking too long to actually dispatch :func:`on_ready`." msgstr "シャードを使用している大きなBotが :func:`on_ready` を実際に発火するのに長い時間を掛けていた問題を修正しました。" -#: ../../whats_new.rst:665 +#: ../../whats_new.rst:884 msgid "Fix issue with fetching group DM based invites in :meth:`Client.fetch_invite`." msgstr ":meth:`Client.fetch_invite` でグループDMベースの招待を取得する際の問題を修正しました。" -#: ../../whats_new.rst:666 +#: ../../whats_new.rst:885 msgid "Fix out of order files being sent in webhooks when there are 10 files." msgstr "10つのファイルをWebhookで送信する際、ファイルの順序が狂う問題を修正しました。" -#: ../../whats_new.rst:667 +#: ../../whats_new.rst:886 msgid "|commands| Extensions that fail internally due to ImportError will no longer raise :exc:`~.ext.commands.ExtensionNotFound`. (:issue:`2244`, :issue:`2275`, :issue:`2291`)" msgstr "|commands| ImportErrorによって内部的に失敗する拡張機能は、 :exc:`~.ext.commands.ExtensionNotFound` を発生させなくなりました。(:issue:`2244`, :issue:`2275`, :issue:`2291`)" -#: ../../whats_new.rst:668 +#: ../../whats_new.rst:887 msgid "|commands| Updating the :attr:`Paginator.suffix <.ext.commands.Paginator.suffix>` will not cause out of date calculations. (:issue:`2251`)" msgstr "|commands| :attr:`Paginator.suffix <.ext.commands.Paginator.suffix>` を更新しても、計算が古くならないようにしました。(:issue:`2251`)" -#: ../../whats_new.rst:669 +#: ../../whats_new.rst:888 msgid "|commands| Allow converters from custom extension packages. (:issue:`2369`, :issue:`2374`)" msgstr "|commands| カスタム拡張パッケージからのコンバータを許可します。(:issue:`2369`, :issue:`2374`) のようになります。" -#: ../../whats_new.rst:670 +#: ../../whats_new.rst:889 msgid "|commands| Fix issue with paginator prefix being ``None`` causing empty pages. (:issue:`2471`)" msgstr "|commands| paginator のプレフィックスが ``None`` であるために空のページが発生する問題を修正しました。(:issue:`2471`)" -#: ../../whats_new.rst:671 +#: ../../whats_new.rst:890 msgid "|commands| :class:`~.commands.Greedy` now ignores parsing errors rather than propagating them." msgstr "|commands| :class:`~.commands.Greedy` はパージングエラーを伝播するのではなく、無視するようになりました。" -#: ../../whats_new.rst:672 +#: ../../whats_new.rst:891 msgid "|commands| :meth:`Command.can_run <.ext.commands.Command.can_run>` now checks whether a command is disabled." msgstr "|commands| :meth:`Command.can_run <.ext.commands.Command.can_run>` がコマンドが無効かどうかをチェックするようになりました。" -#: ../../whats_new.rst:673 +#: ../../whats_new.rst:892 msgid "|commands| :attr:`HelpCommand.clean_prefix <.ext.commands.HelpCommand.clean_prefix>` now takes into consideration nickname mentions. (:issue:`2489`)" msgstr "|commands| :attr:`HelpCommand.clean_prefix <.ext.commands.HelpCommand.clean_prefix>` がニックネームのメンションを考慮するようになりました。 (:issue:`2489`)" -#: ../../whats_new.rst:674 +#: ../../whats_new.rst:893 msgid "|commands| :meth:`Context.send_help <.ext.commands.Context.send_help>` now properly propagates to the :meth:`HelpCommand.on_help_command_error <.ext.commands.HelpCommand.on_help_command_error>` handler." msgstr "|commands| :meth:`Context.send_help <.ext.commands.Context.send_help>` が :meth:`HelpCommand.on_help_command_error <.ext.commands.HelpCommand.on_help_command_error>` ハンドラに正しく伝播するようになりました。" -#: ../../whats_new.rst:679 +#: ../../whats_new.rst:898 msgid "The library now fully supports Python 3.8 without warnings." msgstr "ライブラリは警告なしに Python 3.8 を完全にサポートするようになりました。" -#: ../../whats_new.rst:680 +#: ../../whats_new.rst:899 msgid "Bump the dependency of ``websockets`` to 8.0 for those who can use it. (:issue:`2453`)" msgstr "依存ライブラリ ``websockets`` のバージョンを 8.0 に上げました。(:issue:`2453`)" -#: ../../whats_new.rst:681 +#: ../../whats_new.rst:900 msgid "Due to Discord providing :class:`Member` data in mentions, users will now be upgraded to :class:`Member` more often if mentioned." msgstr "Discordがメンションで :class:`Member` データを提供するようになったため、メンションされたユーザーが :class:`Member` により多くの機会でアップグレードされるようになりました。" -#: ../../whats_new.rst:682 +#: ../../whats_new.rst:901 msgid ":func:`utils.escape_markdown` now properly escapes new quote markdown." msgstr ":func:`utils.escape_markdown` が新しい引用マークダウンを正しくエスケープするようになりました。" -#: ../../whats_new.rst:683 +#: ../../whats_new.rst:902 msgid "The message cache can now be disabled by passing ``None`` to ``max_messages`` in :class:`Client`." msgstr "メッセージキャッシュを :class:`Client` の ``max_messages`` に ``None`` を渡すことで無効にできるようになりました。" -#: ../../whats_new.rst:684 +#: ../../whats_new.rst:903 msgid "The default message cache size has changed from 5000 to 1000 to accommodate small bots." msgstr "デフォルトのメッセージキャッシュサイズは、小さなボットに対応するために5000から1000に変更されました。" -#: ../../whats_new.rst:685 +#: ../../whats_new.rst:904 msgid "Lower memory usage by only creating certain objects as needed in :class:`Role`." msgstr ":class:`Role` にて、必要な場合のみ特定のオブジェクトを作成することによりメモリ使用量を削減しました。" -#: ../../whats_new.rst:686 +#: ../../whats_new.rst:905 msgid "There is now a sleep of 5 seconds before re-IDENTIFYing during a reconnect to prevent long loops of session invalidation." msgstr "セッションの無効化の長いループを防ぐために、再接続中に再度IDENTIFYする前に5秒間待つようになりました。" -#: ../../whats_new.rst:688 +#: ../../whats_new.rst:907 msgid "The rate limiting code now uses millisecond precision to have more granular rate limit handling." msgstr "レート制限コードは、より細かいレート制限処理を行うためにミリ秒の精度を使用するようになりました。" -#: ../../whats_new.rst:688 +#: ../../whats_new.rst:907 msgid "Along with that, the rate limiting code now uses Discord's response to wait. If you need to use the system clock again for whatever reason, consider passing ``assume_synced_clock`` in :class:`Client`." msgstr "それに伴い、レート制限コードはDiscordのレスポンスを使用して待つようになりました。 何らかの理由でシステムクロックを使用する必要がある場合は、 :class:`Client` で ``assume_synced_clock`` を渡すことを検討してください。" -#: ../../whats_new.rst:690 +#: ../../whats_new.rst:909 msgid "The performance of :attr:`Guild.default_role` has been improved from O(N) to O(1). (:issue:`2375`)" msgstr ":attr:`Guild.default_role` のパフォーマンスが O(N) から O(1) に改善されました。 (:issue:`2375`)" -#: ../../whats_new.rst:691 +#: ../../whats_new.rst:910 msgid "The performance of :attr:`Member.roles` has improved due to usage of caching to avoid surprising performance traps." msgstr "予期しないパフォーマンストラップを避けるために、キャッシュを使用して :attr:`Member.roles` のパフォーマンスを改善しました。" -#: ../../whats_new.rst:692 +#: ../../whats_new.rst:911 msgid "The GC is manually triggered during things that cause large deallocations (such as guild removal) to prevent memory fragmentation." msgstr "メモリの断片化を防ぐため、大規模なメモリの割り当て解除 (ギルドの除去など) が引き起こされた後に手動でガベージコレクションを行うようになりました。" -#: ../../whats_new.rst:693 +#: ../../whats_new.rst:912 msgid "There have been many changes to the documentation for fixes both for usability, correctness, and to fix some linter errors. Thanks to everyone who contributed to those." msgstr "ユーザビリティや正確性を向上させ、リンターエラーを修正するため、ドキュメントに多くの変更がありました。 貢献したすべての人に感謝します。" -#: ../../whats_new.rst:694 +#: ../../whats_new.rst:913 msgid "The loading of the opus module has been delayed which would make the result of :func:`opus.is_loaded` somewhat surprising." msgstr "opus モジュールの読み込みを遅延させるようにしました。このため :func:`opus.is_loaded` の結果が予想しないものになるかもしれません。" -#: ../../whats_new.rst:695 +#: ../../whats_new.rst:914 msgid "|commands| Usernames prefixed with @ inside DMs will properly convert using the :class:`User` converter. (:issue:`2498`)" msgstr "|commands| DM内の@で始まるユーザー名が、 :class:`User` コンバータを使用したとき正しく変換されるようになりました。 (:issue:`2498`)" -#: ../../whats_new.rst:696 +#: ../../whats_new.rst:915 msgid "|tasks| The task sleeping time will now take into consideration the amount of time the task body has taken before sleeping. (:issue:`2516`)" msgstr "|tasks| タスクの待ち時間が、タスク本体が実行するのにかかった時間を考慮に入れるようになりました。 (:issue:`2516`)" -#: ../../whats_new.rst:701 +#: ../../whats_new.rst:920 msgid "v1.2.5" msgstr "v1.2.5" -#: ../../whats_new.rst:706 +#: ../../whats_new.rst:925 msgid "Fix a bug that caused crashes due to missing ``animated`` field in Emoji structures in reactions." msgstr "絵文字構造の ``animated`` フィールドが存在しないとしてクラッシュするバグを修正しました。" -#: ../../whats_new.rst:711 +#: ../../whats_new.rst:930 msgid "v1.2.4" msgstr "v1.2.4" -#: ../../whats_new.rst:716 +#: ../../whats_new.rst:935 msgid "Fix a regression when :attr:`Message.channel` would be ``None``." msgstr ":attr:`Message.channel` が ``None`` になるリグレッションを修正しました。" -#: ../../whats_new.rst:717 +#: ../../whats_new.rst:936 msgid "Fix a regression where :attr:`Message.edited_at` would not update during edits." msgstr ":attr:`Message.edited_at` が編集中に更新されないリグレッションを修正しました。" -#: ../../whats_new.rst:718 +#: ../../whats_new.rst:937 msgid "Fix a crash that would trigger during message updates (:issue:`2265`, :issue:`2287`)." msgstr "メッセージの更新中に引き起こされるクラッシュを修正しました。(:issue:`2265`, :issue:`2287`)" -#: ../../whats_new.rst:719 +#: ../../whats_new.rst:938 msgid "Fix a bug when :meth:`VoiceChannel.connect` would not return (:issue:`2274`, :issue:`2372`, :issue:`2373`, :issue:`2377`)." msgstr ":meth:`VoiceChannel.connect` が応答しないバグを修正しました。(:issue:`2274`、 :issue:`2372`、 :issue:`2373`、 :issue:`2377`)" -#: ../../whats_new.rst:720 +#: ../../whats_new.rst:939 msgid "Fix a crash relating to token-less webhooks (:issue:`2364`)." msgstr "トークンのないWebhookに関するクラッシュを修正しました。(:issue:`2364`)" -#: ../../whats_new.rst:721 +#: ../../whats_new.rst:940 msgid "Fix issue where :attr:`Guild.premium_subscription_count` would be ``None`` due to a Discord bug. (:issue:`2331`, :issue:`2376`)." msgstr "Discord バグにより :attr:`Guild.premium_subscription_count` が ``None`` になる問題を修正しました。(:issue:`2331`, :issue:`2376`)" -#: ../../whats_new.rst:726 +#: ../../whats_new.rst:945 msgid "v1.2.3" msgstr "v1.2.3" -#: ../../whats_new.rst:731 +#: ../../whats_new.rst:950 msgid "Fix an AttributeError when accessing :attr:`Member.premium_since` in :func:`on_member_update`. (:issue:`2213`)" msgstr ":func:`on_member_update` で :attr:`Member.premium_since` にアクセスした際の AttributeError を修正しました。 (:issue:`2213`)" -#: ../../whats_new.rst:732 +#: ../../whats_new.rst:951 msgid "Handle :exc:`asyncio.CancelledError` in :meth:`abc.Messageable.typing` context manager. (:issue:`2218`)" msgstr ":meth:`abc.Messageable.typing` コンテキストマネージャでの :exc:`asyncio.CanceledError` を処理するようにしました。 (:issue:`2218`)" -#: ../../whats_new.rst:733 +#: ../../whats_new.rst:952 msgid "Raise the max encoder bitrate to 512kbps to account for nitro boosting. (:issue:`2232`)" msgstr "ニトロブーストを考慮し、最大エンコーダビットレートを512kbpsに引き上げ。 (:issue:`2232`)" -#: ../../whats_new.rst:734 +#: ../../whats_new.rst:953 msgid "Properly propagate exceptions in :meth:`Client.run`. (:issue:`2237`)" msgstr ":meth:`Client.run` にて例外を適切に伝播するようにしました。(:issue:`2237`)" -#: ../../whats_new.rst:735 +#: ../../whats_new.rst:954 msgid "|commands| Ensure cooldowns are properly copied when used in cog level ``command_attrs``." msgstr "|commands| コグレベル ``command_attrs`` で使用されるクールダウンが正しくコピーされるようにしました。" -#: ../../whats_new.rst:740 +#: ../../whats_new.rst:959 msgid "v1.2.2" msgstr "v1.2.2" -#: ../../whats_new.rst:745 +#: ../../whats_new.rst:964 msgid "Audit log related attribute access have been fixed to not error out when they shouldn't have." msgstr "監査ログ関連の属性アクセスは、本来すべきでないときにエラーを起こさないよう修正されました。" -#: ../../whats_new.rst:750 +#: ../../whats_new.rst:969 msgid "v1.2.1" msgstr "v1.2.1" -#: ../../whats_new.rst:755 +#: ../../whats_new.rst:974 msgid ":attr:`User.avatar_url` and related attributes no longer raise an error." msgstr ":attr:`User.avatar_url` と関連する属性がエラーを引き起こさないように修正しました。" -#: ../../whats_new.rst:756 +#: ../../whats_new.rst:975 msgid "More compatibility shims with the ``enum.Enum`` code." msgstr "``enum.Enum`` コードの互換性が向上しました。" -#: ../../whats_new.rst:761 +#: ../../whats_new.rst:980 msgid "v1.2.0" msgstr "v1.2.0" -#: ../../whats_new.rst:763 +#: ../../whats_new.rst:982 msgid "This update mainly brings performance improvements and various nitro boosting attributes (referred to in the API as \"premium guilds\")." msgstr "今回のアップデートでは、主にパフォーマンスの向上と、さまざまなニトロブースト属性(APIでは「プレミアムギルド」と呼ばれます) が追加されました。" -#: ../../whats_new.rst:768 +#: ../../whats_new.rst:987 msgid "Add :attr:`Guild.premium_tier` to query the guild's current nitro boost level." msgstr ":attr:`Guild.premium_tier` で、ギルドの現在のニトロブーストレベルが取得できます。" -#: ../../whats_new.rst:769 +#: ../../whats_new.rst:988 msgid "Add :attr:`Guild.emoji_limit`, :attr:`Guild.bitrate_limit`, :attr:`Guild.filesize_limit` to query the new limits of a guild when taking into consideration boosting." msgstr "ブーストを考慮してギルドの新しい制限を取得する :attr:`Guild.emoji_limit` 、 :attr:`Guild.bitrate_limit` 、 :attr:`Guild.filesize_limit` を追加しました。" -#: ../../whats_new.rst:770 +#: ../../whats_new.rst:989 msgid "Add :attr:`Guild.premium_subscription_count` to query how many members are boosting a guild." msgstr "ギルドをブーストしているメンバー数を取得する :attr:`Guild.premium_subscription_count` を追加しました。" -#: ../../whats_new.rst:771 +#: ../../whats_new.rst:990 msgid "Add :attr:`Member.premium_since` to query since when a member has boosted a guild." msgstr "メンバーがギルドをブーストし始めた日時を取得する :attr:`Member.premium_since` を追加しました。" -#: ../../whats_new.rst:772 +#: ../../whats_new.rst:991 msgid "Add :attr:`Guild.premium_subscribers` to query all the members currently boosting the guild." msgstr "現在ギルドをブーストしているメンバーをすべて取得する :attr:`Guild.premium_subscribers` を追加しました。" -#: ../../whats_new.rst:773 +#: ../../whats_new.rst:992 msgid "Add :attr:`Guild.system_channel_flags` to query the settings for a guild's :attr:`Guild.system_channel`." msgstr "ギルドの :attr:`Guild.system_channel` の設定を取得する :attr:`Guild.system_channel_flags` を追加しました。" -#: ../../whats_new.rst:774 +#: ../../whats_new.rst:993 msgid "This includes a new type named :class:`SystemChannelFlags`" msgstr ":class:`SystemChannelFlags` という新しい型も含まれます。" -#: ../../whats_new.rst:775 +#: ../../whats_new.rst:994 msgid "Add :attr:`Emoji.available` to query if an emoji can be used (within the guild or otherwise)." msgstr "絵文字が(ギルド内またはそれ以外で)利用できるかを確認する :attr:`Emoji.available` を追加しました。" -#: ../../whats_new.rst:776 +#: ../../whats_new.rst:995 msgid "Add support for animated icons in :meth:`Guild.icon_url_as` and :attr:`Guild.icon_url`." msgstr ":meth:`Guild.icon_url_as` と :attr:`Guild.icon_url` にアニメーションアイコンのサポートを追加しました。" -#: ../../whats_new.rst:777 +#: ../../whats_new.rst:996 msgid "Add :meth:`Guild.is_icon_animated`." msgstr ":meth:`Guild.is_icon_animated` を追加しました。" -#: ../../whats_new.rst:778 +#: ../../whats_new.rst:997 msgid "Add support for the various new :class:`MessageType` involving nitro boosting." msgstr "ニトロブーストに関する様々な新しい :class:`MessageType` のサポートを追加しました。" -#: ../../whats_new.rst:779 +#: ../../whats_new.rst:998 msgid "Add :attr:`VoiceRegion.india`. (:issue:`2145`)" msgstr ":attr:`VoiceRegion.india` を追加しました。 (:issue:`2145`)" -#: ../../whats_new.rst:780 +#: ../../whats_new.rst:999 msgid "Add :meth:`Embed.insert_field_at`. (:issue:`2178`)" msgstr ":meth:`Embed.insert_field_at` を追加しました。 (:issue:`2178`)" -#: ../../whats_new.rst:781 +#: ../../whats_new.rst:1000 msgid "Add a ``type`` attribute for all channels to their appropriate :class:`ChannelType`. (:issue:`2185`)" msgstr "すべてのチャンネルに対し、適切な :class:`ChannelType` を返す ``type`` 属性を追加しました。 (:issue:`2185` )" -#: ../../whats_new.rst:782 +#: ../../whats_new.rst:1001 msgid "Add :meth:`Client.fetch_channel` to fetch a channel by ID via HTTP. (:issue:`2169`)" msgstr "HTTP経由でチャンネルをIDにより取得する、 :meth:`Client.fetch_channel` を追加しました。(:issue:`2169`)" -#: ../../whats_new.rst:783 +#: ../../whats_new.rst:1002 msgid "Add :meth:`Guild.fetch_channels` to fetch all channels via HTTP. (:issue:`2169`)" msgstr "HTTP経由でチャンネルをすべて取得する、 :meth:`Guild.fetch_channels` を追加しました。(:issue:`2169`)" -#: ../../whats_new.rst:784 +#: ../../whats_new.rst:1003 msgid "|tasks| Add :meth:`Loop.stop <.ext.tasks.Loop.stop>` to gracefully stop a task rather than cancelling." msgstr "|tasks| タスクをキャンセルするのではなく、現在のタスクが終了後に停止させる :meth:`Loop.stop <.ext.tasks.Loop.stop>` を追加しました。" -#: ../../whats_new.rst:785 +#: ../../whats_new.rst:1004 msgid "|tasks| Add :meth:`Loop.failed <.ext.tasks.Loop.failed>` to query if a task had failed somehow." msgstr "|tasks| タスクが何らかの理由で失敗したかを調べる :meth:`Loop.failed <.ext.tasks.Loop.failed>` を追加しました。" -#: ../../whats_new.rst:786 +#: ../../whats_new.rst:1005 msgid "|tasks| Add :meth:`Loop.change_interval <.ext.tasks.Loop.change_interval>` to change the sleep interval at runtime (:issue:`2158`, :issue:`2162`)" msgstr "|tasks| 実行時に待機時間を変更できる :meth:`Loop.change_interval <.ext.tasks.Loop.change_interval>` を追加しました。(:issue:`2158`, :issue:`2162`)" -#: ../../whats_new.rst:791 +#: ../../whats_new.rst:1010 msgid "Fix internal error when using :meth:`Guild.prune_members`." msgstr ":meth:`Guild.prune_members` を使用した場合の内部エラーを修正しました。" -#: ../../whats_new.rst:792 +#: ../../whats_new.rst:1011 msgid "|commands| Fix :attr:`.Command.invoked_subcommand` being invalid in many cases." msgstr "|commands| 多くの場合において :attr:`.Command.invoked_subcommand` が誤っているのを修正しました。" -#: ../../whats_new.rst:793 +#: ../../whats_new.rst:1012 msgid "|tasks| Reset iteration count when the loop terminates and is restarted." msgstr "|tasks| ループが終了し、再起動されたときに反復回数をリセットするようにしました。" -#: ../../whats_new.rst:794 +#: ../../whats_new.rst:1013 msgid "|tasks| The decorator interface now works as expected when stacking (:issue:`2154`)" msgstr "|tasks| デコレータインターフェースをスタックした時に期待通り動作するようになりました。 (:issue:`2154`)" -#: ../../whats_new.rst:800 +#: ../../whats_new.rst:1019 msgid "Improve performance of all Enum related code significantly." msgstr "列挙型に関連するすべてのコードのパフォーマンスを大幅に向上させました。" -#: ../../whats_new.rst:800 +#: ../../whats_new.rst:1019 msgid "This was done by replacing the ``enum.Enum`` code with an API compatible one." msgstr "これは、 ``enum.Enum`` コードを API 互換のコードに置き換えることによって行われました。" -#: ../../whats_new.rst:801 +#: ../../whats_new.rst:1020 msgid "This should not be a breaking change for most users due to duck-typing." msgstr "ダックタイピングを使用しているため、ほとんどのユーザーにとっては破壊的変更ではありません。" -#: ../../whats_new.rst:802 +#: ../../whats_new.rst:1021 msgid "Improve performance of message creation by about 1.5x." msgstr "メッセージ作成のパフォーマンスを約1.5倍向上させました。" -#: ../../whats_new.rst:803 +#: ../../whats_new.rst:1022 msgid "Improve performance of message editing by about 1.5-4x depending on payload size." msgstr "メッセージ編集のパフォーマンスが約1.5~4倍向上しました。(内容のサイズに依存します)" -#: ../../whats_new.rst:804 +#: ../../whats_new.rst:1023 msgid "Improve performance of attribute access on :class:`Member` about by 2x." msgstr ":class:`Member` の属性へのアクセスのパフォーマンスが2倍向上しました。" -#: ../../whats_new.rst:805 +#: ../../whats_new.rst:1024 msgid "Improve performance of :func:`utils.get` by around 4-6x depending on usage." msgstr ":func:`utils.get` のパフォーマンスを、使用状況に応じて約 4-6倍 向上させました。" -#: ../../whats_new.rst:806 +#: ../../whats_new.rst:1025 msgid "Improve performance of event parsing lookup by around 2.5x." msgstr "イベント解析中のルックアップのパフォーマンスを約2.5倍向上させました。" -#: ../../whats_new.rst:807 +#: ../../whats_new.rst:1026 msgid "Keyword arguments in :meth:`Client.start` and :meth:`Client.run` are now validated (:issue:`953`, :issue:`2170`)" msgstr ":meth:`Client.start` と :meth:`Client.run` のキーワード引数を検証するようにしました。 (:issue:`953`, :issue:`2170`)" -#: ../../whats_new.rst:808 +#: ../../whats_new.rst:1027 msgid "The Discord error code is now shown in the exception message for :exc:`HTTPException`." msgstr ":exc:`HTTPException` の例外メッセージにDiscordのエラーコードが表示されるようになりました。" -#: ../../whats_new.rst:809 +#: ../../whats_new.rst:1028 msgid "Internal tasks launched by the library will now have their own custom ``__repr__``." msgstr "ライブラリによって実行された内部タスクに独自のカスタム ``__repr__`` を追加しました。" -#: ../../whats_new.rst:810 +#: ../../whats_new.rst:1029 msgid "All public facing types should now have a proper and more detailed ``__repr__``." msgstr "すべての公開された型に、適切でより詳細な ``__repr__`` を追加しました。" -#: ../../whats_new.rst:811 +#: ../../whats_new.rst:1030 msgid "|tasks| Errors are now logged via the standard :mod:`py:logging` module." msgstr "|tasks| 標準の :mod:`py:logging` モジュールを介してエラーが記録されるようになりました。" -#: ../../whats_new.rst:816 +#: ../../whats_new.rst:1035 msgid "v1.1.1" msgstr "v1.1.1" -#: ../../whats_new.rst:821 +#: ../../whats_new.rst:1040 msgid "Webhooks do not overwrite data on retrying their HTTP requests (:issue:`2140`)" msgstr "WebhookがHTTPリクエストを再試行する時にデータを上書きしないようにしました。 (:issue:`2140`)" -#: ../../whats_new.rst:826 +#: ../../whats_new.rst:1045 msgid "Add back signal handling to :meth:`Client.run` due to issues some users had with proper cleanup." msgstr "一部のユーザーが適切なクリーンアップを行うときに問題が生じていたため、 :meth:`Client.run` にシグナル処理を再度追加しました。" -#: ../../whats_new.rst:831 +#: ../../whats_new.rst:1050 msgid "v1.1.0" msgstr "v1.1.0" -#: ../../whats_new.rst:836 +#: ../../whats_new.rst:1055 msgid "**There is a new extension dedicated to making background tasks easier.**" msgstr "**バックグラウンドタスクを簡単にするための新しい拡張機能が追加されました。**" -#: ../../whats_new.rst:837 +#: ../../whats_new.rst:1056 msgid "You can check the documentation here: :ref:`ext_tasks_api`." msgstr "使い方の説明は、 :ref:`ext_tasks_api` で確認できます。" -#: ../../whats_new.rst:838 +#: ../../whats_new.rst:1057 msgid "Add :attr:`Permissions.stream` permission. (:issue:`2077`)" msgstr ":attr:`Permissions.stream` 権限を追加しました。 (:issue:`2077`)" -#: ../../whats_new.rst:839 +#: ../../whats_new.rst:1058 msgid "Add equality comparison and hash support to :class:`Asset`" msgstr ":class:`Asset` に等価比較とハッシュサポートを追加しました。" -#: ../../whats_new.rst:840 +#: ../../whats_new.rst:1059 msgid "Add ``compute_prune_members`` parameter to :meth:`Guild.prune_members` (:issue:`2085`)" msgstr ":meth:`Guild.prune_members` に ``compute_prune_members`` パラメータを追加しました。 (:issue:`2085`)" -#: ../../whats_new.rst:841 +#: ../../whats_new.rst:1060 msgid "Add :attr:`Client.cached_messages` attribute to fetch the message cache (:issue:`2086`)" msgstr "メッセージキャッシュを取得する :attr:`Client.cached_messages` 属性を追加しました。 (:issue:`2086`)" -#: ../../whats_new.rst:842 +#: ../../whats_new.rst:1061 msgid "Add :meth:`abc.GuildChannel.clone` to clone a guild channel. (:issue:`2093`)" msgstr "ギルドのチャンネルをコピーする :meth:`abc.GuildChannel.clone` メソッドが追加されました。( :issue:`2093` )" -#: ../../whats_new.rst:843 +#: ../../whats_new.rst:1062 msgid "Add ``delay`` keyword-only argument to :meth:`Message.delete` (:issue:`2094`)" msgstr ":meth:`Message.delete` にキーワード限定引数 ``delay`` が追加されました。( :issue:`2094` )" -#: ../../whats_new.rst:844 +#: ../../whats_new.rst:1063 msgid "Add support for ``<:name:id>`` when adding reactions (:issue:`2095`)" msgstr "``<:name:id>`` のフォーマットでリアクションを追加できるようになりました。( :issue:`2095` )" -#: ../../whats_new.rst:845 +#: ../../whats_new.rst:1064 msgid "Add :meth:`Asset.read` to fetch the bytes content of an asset (:issue:`2107`)" msgstr "アセットを ``bytes`` オブジェクトとして取得する :meth:`Asset.read` メソッドが追加されました( :issue:`2107` )" -#: ../../whats_new.rst:846 +#: ../../whats_new.rst:1065 msgid "Add :meth:`Attachment.read` to fetch the bytes content of an attachment (:issue:`2118`)" msgstr "添付ファイルを ``bytes`` オブジェクトとして取得する :meth:`Attachment.read` メソッドが追加されました( :issue:`2118` )" -#: ../../whats_new.rst:847 +#: ../../whats_new.rst:1066 msgid "Add support for voice kicking by passing ``None`` to :meth:`Member.move_to`." msgstr ":meth:`Member.move_to` に ``None`` を渡すことでボイスチャンネルから強制切断できるようになりました。" -#: ../../whats_new.rst:850 -#: ../../whats_new.rst:871 -#: ../../whats_new.rst:890 +#: ../../whats_new.rst:1069 +#: ../../whats_new.rst:1090 +#: ../../whats_new.rst:1109 msgid "``discord.ext.commands``" msgstr "``discord.ext.commands``" -#: ../../whats_new.rst:852 +#: ../../whats_new.rst:1071 msgid "Add new :func:`~.commands.dm_only` check." msgstr ":func:`~.commands.dm_only` チェックが追加されました。" -#: ../../whats_new.rst:853 +#: ../../whats_new.rst:1072 msgid "Support callable converters in :data:`~.commands.Greedy`" msgstr "呼び出し可能オブジェクトのコンバーターを :data:`~.commands.Greedy` で使えるようになりました。" -#: ../../whats_new.rst:854 +#: ../../whats_new.rst:1073 msgid "Add new :class:`~.commands.MessageConverter`." msgstr ":class:`~.commands.MessageConverter` が追加されました。" -#: ../../whats_new.rst:855 +#: ../../whats_new.rst:1074 msgid "This allows you to use :class:`Message` as a type hint in functions." msgstr "これにより、 :class:`Message` を関数の型ヒントで使えるようになりました。" -#: ../../whats_new.rst:856 +#: ../../whats_new.rst:1075 msgid "Allow passing ``cls`` in the :func:`~.commands.group` decorator (:issue:`2061`)" msgstr ":func:`~.commands.group` に ``cls`` を渡せるようになりました( :issue:`2061` )" -#: ../../whats_new.rst:857 +#: ../../whats_new.rst:1076 msgid "Add :attr:`.Command.parents` to fetch the parents of a command (:issue:`2104`)" msgstr "親コマンドを取得する :attr:`.Command.parents` が追加されました。( :issue:`2104` )" -#: ../../whats_new.rst:863 +#: ../../whats_new.rst:1082 msgid "Fix :exc:`AttributeError` when using ``__repr__`` on :class:`Widget`." msgstr ":class:`Widget` の ``__repr__`` で :exc:`AttributeError` が発生するバグを修正しました。" -#: ../../whats_new.rst:864 +#: ../../whats_new.rst:1083 msgid "Fix issue with :attr:`abc.GuildChannel.overwrites` returning ``None`` for keys." msgstr ":attr:`abc.GuildChannel.overwrites` のキーが ``None`` になるバグを修正しました。" -#: ../../whats_new.rst:865 +#: ../../whats_new.rst:1084 msgid "Remove incorrect legacy NSFW checks in e.g. :meth:`TextChannel.is_nsfw`." msgstr ":meth:`TextChannel.is_nsfw` 等でのNSFWのチェックを修正しました。" -#: ../../whats_new.rst:866 +#: ../../whats_new.rst:1085 msgid "Fix :exc:`UnboundLocalError` when :class:`RequestsWebhookAdapter` raises an error." msgstr ":class:`RequestsWebhookAdapter` でエラーが発生したときの :exc:`UnboundLocalError` を修正しました。" -#: ../../whats_new.rst:867 +#: ../../whats_new.rst:1086 msgid "Fix bug where updating your own user did not update your member instances." msgstr "ボットのユーザーをアップデートしてもメンバーオブジェクトが更新されないバグを修正しました。" -#: ../../whats_new.rst:868 +#: ../../whats_new.rst:1087 msgid "Tighten constraints of ``__eq__`` in :class:`Spotify` objects (:issue:`2113`, :issue:`2117`)" msgstr ":class:`Spotify` の ``__eq__`` の条件を厳しくしました。( :issue:`2113`, :issue:`2117` )" -#: ../../whats_new.rst:873 +#: ../../whats_new.rst:1092 msgid "Fix lambda converters in a non-module context (e.g. ``eval``)." msgstr "モジュール以外での無名コンバーターを修正しました。(例: ``eval`` )" -#: ../../whats_new.rst:874 +#: ../../whats_new.rst:1093 msgid "Use message creation time for reference time when computing cooldowns." msgstr "クールダウンの計算にメッセージの作成時間を使用するようになりました。" -#: ../../whats_new.rst:875 +#: ../../whats_new.rst:1094 msgid "This prevents cooldowns from triggering during e.g. a RESUME session." msgstr "これにより、RESUME中でのクールダウンの挙動が修正されました。" -#: ../../whats_new.rst:876 +#: ../../whats_new.rst:1095 msgid "Fix the default :func:`on_command_error` to work with new-style cogs (:issue:`2094`)" msgstr "新しいスタイルのコグのため、 :func:`on_command_error` のデフォルトの挙動を修正しました。( :issue:`2094` )" -#: ../../whats_new.rst:877 +#: ../../whats_new.rst:1096 msgid "DM channels are now recognised as NSFW in :func:`~.commands.is_nsfw` check." msgstr "DMチャンネルが :func:`~.commands.is_nsfw` に認識されるようになりました。" -#: ../../whats_new.rst:878 +#: ../../whats_new.rst:1097 msgid "Fix race condition with help commands (:issue:`2123`)" msgstr "ヘルプコマンドの競合状態を修正しました。 (:issue:`2123`)" -#: ../../whats_new.rst:879 +#: ../../whats_new.rst:1098 msgid "Fix cog descriptions not showing in :class:`~.commands.MinimalHelpCommand` (:issue:`2139`)" msgstr ":class:`~.commands.MinimalHelpCommand` にコグの説明が表示されるようになりました。( :issue:`2139` )" -#: ../../whats_new.rst:884 +#: ../../whats_new.rst:1103 msgid "Improve the performance of internal enum creation in the library by about 5x." msgstr "ライブラリ内での列挙型の作成が約5倍早くなりました。" -#: ../../whats_new.rst:885 +#: ../../whats_new.rst:1104 msgid "Make the output of ``python -m discord --version`` a bit more useful." msgstr "``python -m discord --version`` の出力を改善しました。" -#: ../../whats_new.rst:886 +#: ../../whats_new.rst:1105 msgid "The loop cleanup facility has been rewritten again." msgstr "ループのクリーンアップがまた書き直されました。" -#: ../../whats_new.rst:887 +#: ../../whats_new.rst:1106 msgid "The signal handling in :meth:`Client.run` has been removed." msgstr ":meth:`Client.run` でのシグナル制御が削除されました。" -#: ../../whats_new.rst:892 +#: ../../whats_new.rst:1111 msgid "Custom exception classes are now used for all default checks in the library (:issue:`2101`)" msgstr "ライブラリ内の全てのチェックがカスタム例外クラスを使うようになりました( :issue:`2101` )" -#: ../../whats_new.rst:898 +#: ../../whats_new.rst:1117 msgid "v1.0.1" msgstr "v1.0.1" -#: ../../whats_new.rst:903 +#: ../../whats_new.rst:1122 msgid "Fix issue with speaking state being cast to ``int`` when it was invalid." msgstr "スピーキング状態が無効なときに ``int`` にキャストした場合に発生する問題を修正しました。" -#: ../../whats_new.rst:904 +#: ../../whats_new.rst:1123 msgid "Fix some issues with loop cleanup that some users experienced on Linux machines." msgstr "一部のユーザーがLinuxマシンで遭遇したループクリーンアップに関する問題を修正しました。" -#: ../../whats_new.rst:905 +#: ../../whats_new.rst:1124 msgid "Fix voice handshake race condition (:issue:`2056`, :issue:`2063`)" msgstr "ボイスハンドシェイクの競合状態を修正しました。 (:issue:`2056`, :issue:`2063`)" -#: ../../whats_new.rst:910 +#: ../../whats_new.rst:1129 msgid "v1.0.0" msgstr "v1.0.0" -#: ../../whats_new.rst:912 +#: ../../whats_new.rst:1131 msgid "The changeset for this version are too big to be listed here, for more information please see :ref:`the migrating page `." msgstr "このバージョンの変更は大きすぎるため、この場所に収まりきりません。詳細については :ref:`移行についてのページ ` を参照してください。" -#: ../../whats_new.rst:919 +#: ../../whats_new.rst:1138 msgid "v0.16.6" msgstr "v0.16.6" -#: ../../whats_new.rst:924 +#: ../../whats_new.rst:1143 msgid "Fix issue with :meth:`Client.create_server` that made it stop working." msgstr ":meth:`Client.create_server` によって動作が停止する問題を修正しました。" -#: ../../whats_new.rst:925 +#: ../../whats_new.rst:1144 msgid "Fix main thread being blocked upon calling ``StreamPlayer.stop``." msgstr "``StreamPlayer.stop`` の呼び出し時にメインスレッドがブロックされるのを修正しました。" -#: ../../whats_new.rst:926 +#: ../../whats_new.rst:1145 msgid "Handle HEARTBEAT_ACK and resume gracefully when it occurs." msgstr "HEARTBEAT_ACKを処理し、正常に再開します。" -#: ../../whats_new.rst:927 +#: ../../whats_new.rst:1146 msgid "Fix race condition when pre-emptively rate limiting that caused releasing an already released lock." msgstr "既に開放されているロックを解放しようとする原因になっていた先制的なレート制限を行っている時の競合状態を修正しました。" -#: ../../whats_new.rst:928 +#: ../../whats_new.rst:1147 msgid "Fix invalid state errors when immediately cancelling a coroutine." msgstr "コルーチンを直ちにキャンセルするときに無効な状態になるエラーを修正しました。" -#: ../../whats_new.rst:933 +#: ../../whats_new.rst:1152 msgid "v0.16.1" msgstr "v0.16.1" -#: ../../whats_new.rst:935 +#: ../../whats_new.rst:1154 msgid "This release is just a bug fix release with some better rate limit implementation." msgstr "このリリースはバグ修正であり、いくつかのレート制限の実装が改善されています。" -#: ../../whats_new.rst:940 +#: ../../whats_new.rst:1159 msgid "Servers are now properly chunked for user bots." msgstr "ユーザーボットがサーバーを適切にチャンクするようにしました。" -#: ../../whats_new.rst:941 +#: ../../whats_new.rst:1160 msgid "The CDN URL is now used instead of the API URL for assets." msgstr "アセットのAPI URLの代わりにCDN URLが使用されるようになりました。" -#: ../../whats_new.rst:942 +#: ../../whats_new.rst:1161 msgid "Rate limit implementation now tries to use header information if possible." msgstr "レート制限の実装が可能な場合ヘッダ情報を利用するようにしました。" -#: ../../whats_new.rst:943 +#: ../../whats_new.rst:1162 msgid "Event loop is now properly propagated (:issue:`420`)" msgstr "イベントループが正しく伝播するようにしました。 (:issue:`420`)" -#: ../../whats_new.rst:944 +#: ../../whats_new.rst:1163 msgid "Allow falsey values in :meth:`Client.send_message` and :meth:`Client.send_file`." msgstr ":meth:`Client.send_message` と :meth:`Client.send_file` でFalseに変換される値を利用できるようにしました。" -#: ../../whats_new.rst:949 +#: ../../whats_new.rst:1168 msgid "v0.16.0" msgstr "v0.16.0" -#: ../../whats_new.rst:954 +#: ../../whats_new.rst:1173 msgid "Add :attr:`Channel.overwrites` to get all the permission overwrites of a channel." msgstr "チャンネルの権限上書きをすべて取得する :attr:`Channel.overwrites` を追加しました。" -#: ../../whats_new.rst:955 +#: ../../whats_new.rst:1174 msgid "Add :attr:`Server.features` to get information about partnered servers." msgstr "パートナーサーバーの情報を得ることのできる :attr:`Server.features` を追加しました。" -#: ../../whats_new.rst:960 +#: ../../whats_new.rst:1179 msgid "Timeout when waiting for offline members while triggering :func:`on_ready`." msgstr ":func:`on_ready` を実行中にオフラインメンバーを待っているとき、タイムアウトするようにしました。" -#: ../../whats_new.rst:962 +#: ../../whats_new.rst:1181 msgid "The fact that we did not timeout caused a gigantic memory leak in the library that caused thousands of duplicate :class:`Member` instances causing big memory spikes." msgstr "以前はタイムアウトしなかったため、ライブラリで数千もの :class:`Member` インスタンスが作成されメモリ使用量が大幅に上昇する大規模なメモリリークが発生していました。" -#: ../../whats_new.rst:965 +#: ../../whats_new.rst:1184 msgid "Discard null sequences in the gateway." msgstr "ゲートウェイでヌル値のシーケンスを破棄するようにしました。" -#: ../../whats_new.rst:967 +#: ../../whats_new.rst:1186 msgid "The fact these were not discarded meant that :func:`on_ready` kept being called instead of :func:`on_resumed`. Since this has been corrected, in most cases :func:`on_ready` will be called once or twice with :func:`on_resumed` being called much more often." msgstr "以前は破棄されていなかったため、 :func:`on_ready` が :func:`on_resumed` の代わりに呼び出されることがありました。これが修正されたため、多くの場合では :func:`on_ready` は一、二回呼び出されるだけで、 :func:`on_resumed` がより頻繁に呼び出されるようになります。" -#: ../../whats_new.rst:974 +#: ../../whats_new.rst:1193 msgid "v0.15.1" msgstr "v0.15.1" -#: ../../whats_new.rst:976 +#: ../../whats_new.rst:1195 msgid "Fix crash on duplicate or out of order reactions." msgstr "重複したり、順番になっていないリアクションによるクラッシュを修正しました。" -#: ../../whats_new.rst:981 +#: ../../whats_new.rst:1200 msgid "v0.15.0" msgstr "v0.15.0" -#: ../../whats_new.rst:986 +#: ../../whats_new.rst:1205 msgid "Rich Embeds for messages are now supported." msgstr "メッセージのリッチな埋め込みをサポートするようにしました。" -#: ../../whats_new.rst:988 +#: ../../whats_new.rst:1207 msgid "To do so, create your own :class:`Embed` and pass the instance to the ``embed`` keyword argument to :meth:`Client.send_message` or :meth:`Client.edit_message`." msgstr "このためには、自分の :class:`Embed` を作成してインスタンスを :meth:`Client.send_message` や :meth:`Client.edit_message` の ``embed`` キーワード引数に渡してください。" -#: ../../whats_new.rst:989 +#: ../../whats_new.rst:1208 msgid "Add :meth:`Client.clear_reactions` to remove all reactions from a message." msgstr "メッセージからすべてリアクションを除去する :meth:`Client.clear_reactions` を追加しました。" -#: ../../whats_new.rst:990 +#: ../../whats_new.rst:1209 msgid "Add support for MESSAGE_REACTION_REMOVE_ALL event, under :func:`on_reaction_clear`." msgstr ":func:`on_reaction_clear` の下にMESSAGE_REMOVE_ALL イベントのサポートを追加しました。" -#: ../../whats_new.rst:991 +#: ../../whats_new.rst:1210 msgid "Add :meth:`Permissions.update` and :meth:`PermissionOverwrite.update` for bulk permission updates." msgstr "一括して権限を更新する、 :meth:`Permissions.update` と :meth:`PermissionOverwrite.update` を追加しました。" -#: ../../whats_new.rst:993 +#: ../../whats_new.rst:1212 msgid "This allows you to use e.g. ``p.update(read_messages=True, send_messages=False)`` in a single line." msgstr "これにより、例えば ``p.update(read_messages=True, send_messages=False)`` のように一行で使用できます。" -#: ../../whats_new.rst:994 +#: ../../whats_new.rst:1213 msgid "Add :meth:`PermissionOverwrite.is_empty` to check if the overwrite is empty (i.e. has no overwrites set explicitly as true or false)." msgstr "権限上書きが空か(すなわち、明示的にtrueまたはfalseに設定されている上書きが存在しないか)を確認する :meth:`PermissionOverwrite.is_empty` を追加しました。" -#: ../../whats_new.rst:996 +#: ../../whats_new.rst:1215 msgid "For the command extension, the following changed:" msgstr "コマンド拡張の場合、以下のことが変更されます。" -#: ../../whats_new.rst:998 +#: ../../whats_new.rst:1217 msgid "``Context`` is no longer slotted to facilitate setting dynamic attributes." msgstr "``Context`` への動的属性の設定を容易にするためにスロット制限を除去しました。" -#: ../../whats_new.rst:1003 +#: ../../whats_new.rst:1222 msgid "v0.14.3" msgstr "v0.14.3" -#: ../../whats_new.rst:1008 +#: ../../whats_new.rst:1227 msgid "Fix crash when dealing with MESSAGE_REACTION_REMOVE" msgstr "MESSAGE_REACTION_REMOVEを扱う際のクラッシュを修正しました" -#: ../../whats_new.rst:1009 +#: ../../whats_new.rst:1228 msgid "Fix incorrect buckets for reactions." msgstr "リアクションに誤ったバケットが適用されていたのを修正しました。" -#: ../../whats_new.rst:1014 +#: ../../whats_new.rst:1233 msgid "v0.14.2" msgstr "v0.14.2" -#: ../../whats_new.rst:1020 +#: ../../whats_new.rst:1239 msgid ":meth:`Client.wait_for_reaction` now returns a namedtuple with ``reaction`` and ``user`` attributes." msgstr ":meth:`Client.wait_for_reaction` が ``reaction`` と ``user`` 属性を持つ名前付きタプルを返すようになりました。" -#: ../../whats_new.rst:1020 +#: ../../whats_new.rst:1239 msgid "This is for better support in the case that ``None`` is returned since tuple unpacking can lead to issues." msgstr "これは、タプルを展開すると問題につながる可能性がある、 ``None`` が返された場合のより良いサポートのためです。" -#: ../../whats_new.rst:1025 +#: ../../whats_new.rst:1244 msgid "Fix bug that disallowed ``None`` to be passed for ``emoji`` parameter in :meth:`Client.wait_for_reaction`." msgstr ":meth:`Client.wait_for_reaction` の ``emoji`` パラメータに ``None`` を渡すことを許可しないバグを修正しました。" -#: ../../whats_new.rst:1030 +#: ../../whats_new.rst:1249 msgid "v0.14.1" msgstr "v0.14.1" -#: ../../whats_new.rst:1033 +#: ../../whats_new.rst:1252 msgid "Bug fixes" msgstr "バグ修正" -#: ../../whats_new.rst:1036 +#: ../../whats_new.rst:1255 msgid "Fix bug with ``Reaction`` not being visible at import." msgstr "インポート時に ``Reaction`` が表示されないバグを修正しました。" -#: ../../whats_new.rst:1036 +#: ../../whats_new.rst:1255 msgid "This was also breaking the documentation." msgstr "これは、ドキュメントにも影響を与えていました。" -#: ../../whats_new.rst:1041 +#: ../../whats_new.rst:1260 msgid "v0.14.0" msgstr "v0.14.0" -#: ../../whats_new.rst:1043 +#: ../../whats_new.rst:1262 msgid "This update adds new API features and a couple of bug fixes." msgstr "このアップデートには、新しいAPI機能といくつかのバグ修正が含まれています。" -#: ../../whats_new.rst:1048 +#: ../../whats_new.rst:1267 msgid "Add support for Manage Webhooks permission under :attr:`Permissions.manage_webhooks`" msgstr ":attr:`Permissions.manage_webhooks` の下にWebhookの管理の権限のサポートを追加しました。" -#: ../../whats_new.rst:1049 +#: ../../whats_new.rst:1268 msgid "Add support for ``around`` argument in 3.5+ :meth:`Client.logs_from`." msgstr "3.5+ :meth:`Client.logs_from` で ``around`` 引数のサポートを追加しました。" -#: ../../whats_new.rst:1057 +#: ../../whats_new.rst:1276 msgid "Add support for reactions." msgstr "リアクションのサポートを追加します。" -#: ../../whats_new.rst:1051 +#: ../../whats_new.rst:1270 msgid ":meth:`Client.add_reaction` to add a reactions" msgstr "リアクションを追加する :meth:`Client.add_reaction`" -#: ../../whats_new.rst:1052 +#: ../../whats_new.rst:1271 msgid ":meth:`Client.remove_reaction` to remove a reaction." msgstr "リアクションを除去する :meth:`Client.remove_reaction`" -#: ../../whats_new.rst:1053 +#: ../../whats_new.rst:1272 msgid ":meth:`Client.get_reaction_users` to get the users that reacted to a message." msgstr "メッセージにリアクションしたユーザーを取得する :meth:`Client.get_reaction_users`" -#: ../../whats_new.rst:1054 +#: ../../whats_new.rst:1273 msgid ":attr:`Permissions.add_reactions` permission bit support." msgstr ":attr:`Permissions.add_reactions` パーミッションビットのサポート。" -#: ../../whats_new.rst:1055 +#: ../../whats_new.rst:1274 msgid "Two new events, :func:`on_reaction_add` and :func:`on_reaction_remove`." msgstr "2つの新しいイベント、 :func:`on_reaction_add` と :func:`on_reaction_remove` 。" -#: ../../whats_new.rst:1056 +#: ../../whats_new.rst:1275 msgid ":attr:`Message.reactions` to get reactions from a message." msgstr "メッセージからリアクションを取得する :attr:`Message.reactions`" -#: ../../whats_new.rst:1057 +#: ../../whats_new.rst:1276 msgid ":meth:`Client.wait_for_reaction` to wait for a reaction from a user." msgstr "ユーザーからのリアクションを待つ :meth:`Client.wait_for_reaction`" -#: ../../whats_new.rst:1062 +#: ../../whats_new.rst:1281 msgid "Fix bug with Paginator still allowing lines that are too long." msgstr "Paginatorが長すぎる行をいまだ許可していたバグを修正しました。" -#: ../../whats_new.rst:1063 +#: ../../whats_new.rst:1282 msgid "Fix the :attr:`Permissions.manage_emojis` bit being incorrect." msgstr ":attr:`Permissions.manage_emojis` ビットが正しくないバグを修正しました。" -#: ../../whats_new.rst:1068 +#: ../../whats_new.rst:1287 msgid "v0.13.0" msgstr "v0.13.0" -#: ../../whats_new.rst:1070 +#: ../../whats_new.rst:1289 msgid "This is a backwards compatible update with new features." msgstr "これは、新しい機能を備えた後方互換性のあるアップデートです。" -#: ../../whats_new.rst:1075 +#: ../../whats_new.rst:1294 msgid "Add the ability to manage emojis." msgstr "絵文字を管理する機能を追加しました。" -#: ../../whats_new.rst:1077 +#: ../../whats_new.rst:1296 msgid ":meth:`Client.create_custom_emoji` to create new emoji." msgstr "新しい絵文字を作成する :meth:`Client.create_custom_emoji` 。" -#: ../../whats_new.rst:1078 +#: ../../whats_new.rst:1297 msgid ":meth:`Client.edit_custom_emoji` to edit an old emoji." msgstr "既存の絵文字を編集する :meth:`Client.edit_custom_emoji` 。" -#: ../../whats_new.rst:1079 +#: ../../whats_new.rst:1298 msgid ":meth:`Client.delete_custom_emoji` to delete a custom emoji." msgstr "カスタム絵文字を削除する :meth:`Client.delete_custom_emoji` 。" -#: ../../whats_new.rst:1080 +#: ../../whats_new.rst:1299 msgid "Add new :attr:`Permissions.manage_emojis` toggle." msgstr "新しい :attr:`Permissions.manage_emoji` トグルを追加しました。" -#: ../../whats_new.rst:1082 +#: ../../whats_new.rst:1301 msgid "This applies for :class:`PermissionOverwrite` as well." msgstr "これは :class:`PermissionOverwrite` にも適用されます。" -#: ../../whats_new.rst:1083 +#: ../../whats_new.rst:1302 msgid "Add new statuses for :class:`Status`." msgstr ":class:`Status` に新しいステータスを追加しました。" -#: ../../whats_new.rst:1085 +#: ../../whats_new.rst:1304 msgid ":attr:`Status.dnd` (aliased with :attr:`Status.do_not_disturb`\\) for Do Not Disturb." msgstr "取り込み中を示す :attr:`Status.dnd` (エイリアス :attr:`Status.do_not_interrup` )" -#: ../../whats_new.rst:1086 +#: ../../whats_new.rst:1305 msgid ":attr:`Status.invisible` for setting your status to invisible (please see the docs for a caveat)." msgstr "ステータスを非表示に設定するための :attr:`Status.invisible` (ドキュメントの注意事項を参照してください)。" -#: ../../whats_new.rst:1087 +#: ../../whats_new.rst:1306 msgid "Deprecate :meth:`Client.change_status`" msgstr ":meth:`Client.change_status` を非推奨にしました。" -#: ../../whats_new.rst:1089 +#: ../../whats_new.rst:1308 msgid "Use :meth:`Client.change_presence` instead for better more up to date functionality." msgstr "より良い最新の機能を使用するためには、 :meth:`Client.change_presence` を使用してください。" -#: ../../whats_new.rst:1090 +#: ../../whats_new.rst:1309 msgid "This method is subject for removal in a future API version." msgstr "このメソッドは、将来の API バージョンで削除の対象となります。" -#: ../../whats_new.rst:1091 +#: ../../whats_new.rst:1310 msgid "Add :meth:`Client.change_presence` for changing your status with the new Discord API change." msgstr "新しい Discord API でステータスを変更するための :meth:`Client.change_presence` を追加しました。" -#: ../../whats_new.rst:1093 +#: ../../whats_new.rst:1312 msgid "This is the only method that allows changing your status to invisible or do not disturb." msgstr "これは、ステータスを非表示や取り込み中に変更できる唯一の方法です。" -#: ../../whats_new.rst:1098 +#: ../../whats_new.rst:1317 msgid "Paginator pages do not exceed their max_size anymore (:issue:`340`)" msgstr "ページネータのページがmax_sizeを超えないようにしました。 (:issue:`340`)" -#: ../../whats_new.rst:1099 +#: ../../whats_new.rst:1318 msgid "Do Not Disturb users no longer show up offline due to the new :class:`Status` changes." msgstr "取り込み中ユーザーは新しい :class:`Status` の変更によりこれ以降オフラインとして表示されないようになりました。" -#: ../../whats_new.rst:1104 +#: ../../whats_new.rst:1323 msgid "v0.12.0" msgstr "v0.12.0" -#: ../../whats_new.rst:1106 +#: ../../whats_new.rst:1325 msgid "This is a bug fix update that also comes with new features." msgstr "これは、新機能つきのバグ修正アップデートです。" -#: ../../whats_new.rst:1111 +#: ../../whats_new.rst:1330 msgid "Add custom emoji support." msgstr "カスタム絵文字サポートを追加しました。" -#: ../../whats_new.rst:1113 +#: ../../whats_new.rst:1332 msgid "Adds a new class to represent a custom Emoji named :class:`Emoji`" msgstr ":class:`Emoji` という名前のカスタム絵文字を表す新しいクラスを追加しました。" -#: ../../whats_new.rst:1114 +#: ../../whats_new.rst:1333 msgid "Adds a utility generator function, :meth:`Client.get_all_emojis`." msgstr "ユーティリティジェネレータ関数 :meth:`Client.get_all_emojis` を追加しました。" -#: ../../whats_new.rst:1115 +#: ../../whats_new.rst:1334 msgid "Adds a list of emojis on a server, :attr:`Server.emojis`." msgstr "サーバーの絵文字のリストを取得する :attr:`Server.emojis` を追加しました。" -#: ../../whats_new.rst:1116 +#: ../../whats_new.rst:1335 msgid "Adds a new event, :func:`on_server_emojis_update`." msgstr "新しいイベント :func:`on_server_emojis_update` を追加しました。" -#: ../../whats_new.rst:1117 +#: ../../whats_new.rst:1336 msgid "Add new server regions to :class:`ServerRegion`" msgstr ":class:`ServerRegion` に新しいサーバーリージョンを追加しました。" -#: ../../whats_new.rst:1119 +#: ../../whats_new.rst:1338 msgid ":attr:`ServerRegion.eu_central` and :attr:`ServerRegion.eu_west`." msgstr ":attr:`ServerRegion.eu_central` と :attr:`ServerRegion.eu_west` 。" -#: ../../whats_new.rst:1120 +#: ../../whats_new.rst:1339 msgid "Add support for new pinned system message under :attr:`MessageType.pins_add`." msgstr ":attr:`MessageType.pins_add` にて新しいピン留めのシステムメッセージのサポートを追加しました。" -#: ../../whats_new.rst:1121 +#: ../../whats_new.rst:1340 msgid "Add order comparisons for :class:`Role` to allow it to be compared with regards to hierarchy." msgstr ":class:`Role` への比較を追加し、階層を考慮した比較ができるようにしました。" -#: ../../whats_new.rst:1123 +#: ../../whats_new.rst:1342 msgid "This means that you can now do ``role_a > role_b`` etc to check if ``role_b`` is lower in the hierarchy." msgstr "つまり、 ``role_a > role_b`` などを実行して、階層内で ``role_b`` が低いかどうかを確認できるようになりました。" -#: ../../whats_new.rst:1125 +#: ../../whats_new.rst:1344 msgid "Add :attr:`Server.role_hierarchy` to get the server's role hierarchy." msgstr "サーバーのロール階層を取得する :attr:`Server.role_hierarchy` を追加しました。" -#: ../../whats_new.rst:1126 +#: ../../whats_new.rst:1345 msgid "Add :attr:`Member.server_permissions` to get a member's server permissions without their channel specific overwrites." msgstr "チャンネル固有の上書きなしでメンバーのサーバー権限を取得する :attr:`Member.server_permissions` を追加しました。" -#: ../../whats_new.rst:1127 +#: ../../whats_new.rst:1346 msgid "Add :meth:`Client.get_user_info` to retrieve a user's info from their ID." msgstr "IDからユーザ情報を取得することができる、 :meth:`Client.get_user_info` を追加しました。" -#: ../../whats_new.rst:1128 +#: ../../whats_new.rst:1347 msgid "Add a new ``Player`` property, ``Player.error`` to fetch the error that stopped the player." msgstr "プレイヤーを停止させたエラーを取得するために、新しい ``Player`` プロパティ ``Player.error`` を追加しました。" -#: ../../whats_new.rst:1130 +#: ../../whats_new.rst:1349 msgid "To help with this change, a player's ``after`` function can now take a single parameter denoting the current player." msgstr "この変更とともに、プレイヤーの ``after`` 関数に現在のプレイヤーを示すパラメータを取ることができるようになりました。" -#: ../../whats_new.rst:1131 +#: ../../whats_new.rst:1350 msgid "Add support for server verification levels." msgstr "サーバー認証レベルのサポートを追加しました。" -#: ../../whats_new.rst:1133 +#: ../../whats_new.rst:1352 msgid "Adds a new enum called :class:`VerificationLevel`." msgstr ":class:`VerificationLevel` という新しい列挙型を追加しました。" -#: ../../whats_new.rst:1134 +#: ../../whats_new.rst:1353 msgid "This enum can be used in :meth:`Client.edit_server` under the ``verification_level`` keyword argument." msgstr "この列挙型は、 :meth:`Client.edit_server` の ``verification_level`` キーワード引数で使用できます。" -#: ../../whats_new.rst:1135 +#: ../../whats_new.rst:1354 msgid "Adds a new attribute in the server, :attr:`Server.verification_level`." msgstr "サーバーに :attr:`Server.verification_level` という新しい属性を追加しました。" -#: ../../whats_new.rst:1136 +#: ../../whats_new.rst:1355 msgid "Add :attr:`Server.voice_client` shortcut property for :meth:`Client.voice_client_in`." msgstr ":meth:`Client.voice_client_in` のショートカットプロパティである :attr:`Server.voice_client` を追加しました。" -#: ../../whats_new.rst:1138 +#: ../../whats_new.rst:1357 msgid "This is technically old (was added in v0.10.0) but was undocumented until v0.12.0." msgstr "これは厳密にいえば過去のもの (v0.10.0で追加) ですが、v0.12.0までは文書化されていませんでした。" -#: ../../whats_new.rst:1140 -#: ../../whats_new.rst:1186 +#: ../../whats_new.rst:1359 +#: ../../whats_new.rst:1405 msgid "For the command extension, the following are new:" msgstr "コマンド拡張機能では、以下の新機能が追加されました:" -#: ../../whats_new.rst:1142 +#: ../../whats_new.rst:1361 msgid "Add custom emoji converter." msgstr "カスタム絵文字コンバータを追加しました。" -#: ../../whats_new.rst:1143 +#: ../../whats_new.rst:1362 msgid "All default converters that can take IDs can now convert via ID." msgstr "IDを取ることができるすべてのデフォルトのコンバータが、IDにより変換することができるようにしました。" -#: ../../whats_new.rst:1144 +#: ../../whats_new.rst:1363 msgid "Add coroutine support for ``Bot.command_prefix``." msgstr "``Bot.command_prefix`` にコルーチンサポートを追加しました。" -#: ../../whats_new.rst:1145 +#: ../../whats_new.rst:1364 msgid "Add a method to reset command cooldown." msgstr "コマンドのクールダウンをリセットするメソッドを追加しました。" -#: ../../whats_new.rst:1150 +#: ../../whats_new.rst:1369 msgid "Fix bug that caused the library to not work with the latest ``websockets`` library." msgstr "最新の ``websockets`` ライブラリでライブラリが動作しないバグを修正しました。" -#: ../../whats_new.rst:1151 +#: ../../whats_new.rst:1370 msgid "Fix bug that leaked keep alive threads (:issue:`309`)" msgstr "キープアライブスレッドをリークしていたバグを修正しました。 (:issue:`309`)" -#: ../../whats_new.rst:1152 +#: ../../whats_new.rst:1371 msgid "Fix bug that disallowed :class:`ServerRegion` from being used in :meth:`Client.edit_server`." msgstr ":meth:`Client.edit_server` で :class:`ServerRegion` が使用できないバグを修正しました。" -#: ../../whats_new.rst:1153 +#: ../../whats_new.rst:1372 msgid "Fix bug in :meth:`Channel.permissions_for` that caused permission resolution to happen out of order." msgstr ":meth:`Channel.permissions_for` で権限解決が誤った順序で行われたバグを修正しました。" -#: ../../whats_new.rst:1154 +#: ../../whats_new.rst:1373 msgid "Fix bug in :attr:`Member.top_role` that did not account for same-position roles." msgstr ":attr:`Member.top_role` が同じポジションの役割を考慮しないバグを修正しました。" -#: ../../whats_new.rst:1159 +#: ../../whats_new.rst:1378 msgid "v0.11.0" msgstr "v0.11.0" -#: ../../whats_new.rst:1161 +#: ../../whats_new.rst:1380 msgid "This is a minor bug fix update that comes with a gateway update (v5 -> v6)." msgstr "これはゲートウェイのアップデート (v5 -> v6) を含むマイナーなバグ修正アップデートです。" -#: ../../whats_new.rst:1164 +#: ../../whats_new.rst:1383 msgid "Breaking Changes" msgstr "破壊的変更" -#: ../../whats_new.rst:1166 +#: ../../whats_new.rst:1385 msgid "``Permissions.change_nicknames`` has been renamed to :attr:`Permissions.change_nickname` to match the UI." msgstr "``Permissions.change_nicknames`` は UIに一致するように :attr:`Permissions.change_nickname` に名前が変更されました。" -#: ../../whats_new.rst:1171 +#: ../../whats_new.rst:1390 msgid "Add the ability to prune members via :meth:`Client.prune_members`." msgstr ":meth:`Client.prune_members` でメンバーを一括キックする機能を追加しました。" -#: ../../whats_new.rst:1172 +#: ../../whats_new.rst:1391 msgid "Switch the websocket gateway version to v6 from v5. This allows the library to work with group DMs and 1-on-1 calls." msgstr "WebSocketゲートウェイのバージョンをv5からv6に切り替えました。これにより、ライブラリはグループDMと1-on-1コールで動作するようになります。" -#: ../../whats_new.rst:1173 +#: ../../whats_new.rst:1392 msgid "Add :attr:`AppInfo.owner` attribute." msgstr ":attr:`AppInfo.owner` 属性を追加しました。" -#: ../../whats_new.rst:1174 +#: ../../whats_new.rst:1393 msgid "Add :class:`CallMessage` for group voice call messages." msgstr "グループボイス通話メッセージを示す :class:`CallMessage` を追加しました。" -#: ../../whats_new.rst:1175 +#: ../../whats_new.rst:1394 msgid "Add :class:`GroupCall` for group voice call information." msgstr "グループボイス通話情報を示す :class:`GroupCall` を追加しました。" -#: ../../whats_new.rst:1176 +#: ../../whats_new.rst:1395 msgid "Add :attr:`Message.system_content` to get the system message." msgstr "システムメッセージを取得する :attr:`Message.system_content` を追加しました。" -#: ../../whats_new.rst:1177 +#: ../../whats_new.rst:1396 msgid "Add the remaining VIP servers and the Brazil servers into :class:`ServerRegion` enum." msgstr "残りのVIPサーバーとブラジルサーバーを :class:`ServerRegion` に追加しました。" -#: ../../whats_new.rst:1178 +#: ../../whats_new.rst:1397 msgid "Add ``stderr`` argument to :meth:`VoiceClient.create_ffmpeg_player` to redirect stderr." msgstr ":meth:`VoiceClient.create_ffmpeg_player` に標準エラー出力をリダイレクトするための ``stderr`` 引数を追加しました。" -#: ../../whats_new.rst:1179 +#: ../../whats_new.rst:1398 msgid "The library now handles implicit permission resolution in :meth:`Channel.permissions_for`." msgstr "ライブラリは :meth:`Channel.permissions_for` で暗黙的な権限解決を処理するようになりました。" -#: ../../whats_new.rst:1180 +#: ../../whats_new.rst:1399 msgid "Add :attr:`Server.mfa_level` to query a server's 2FA requirement." msgstr "サーバーの 2FA 要件を取得する :attr:`Server.mfa_level` を追加しました。" -#: ../../whats_new.rst:1181 +#: ../../whats_new.rst:1400 msgid "Add :attr:`Permissions.external_emojis` permission." msgstr ":attr:`Permissions.external_emojis` 権限を追加しました。" -#: ../../whats_new.rst:1182 +#: ../../whats_new.rst:1401 msgid "Add :attr:`Member.voice` attribute that refers to a :class:`VoiceState`." msgstr ":class:`VoiceState` を返す :attr:`Member.voice` 属性を追加しました。" -#: ../../whats_new.rst:1184 +#: ../../whats_new.rst:1403 msgid "For backwards compatibility, the member object will have properties mirroring the old behaviour." msgstr "後方互換性のため、メンバーオブジェクトには古い挙動をミラーリングするプロパティも存在します。" -#: ../../whats_new.rst:1188 +#: ../../whats_new.rst:1407 msgid "Command cooldown system with the ``cooldown`` decorator." msgstr "``cololdown`` デコレータを用いたコマンドクールダウンシステム。" -#: ../../whats_new.rst:1189 +#: ../../whats_new.rst:1408 msgid "``UserInputError`` exception for the hierarchy for user input related errors." msgstr "ユーザー入力関連エラーの親である ``UserInputError`` 例外。" -#: ../../whats_new.rst:1194 +#: ../../whats_new.rst:1413 msgid ":attr:`Client.email` is now saved when using a token for user accounts." msgstr ":attr:`Client.email` がユーザーアカウントにトークンを使用してログインしたとき保存されるようになりました。" -#: ../../whats_new.rst:1195 +#: ../../whats_new.rst:1414 msgid "Fix issue when removing roles out of order." msgstr "順番になってないロールの除去で発生した問題を修正しました。" -#: ../../whats_new.rst:1196 +#: ../../whats_new.rst:1415 msgid "Fix bug where discriminators would not update." msgstr "タグが更新されないバグを修正しました。" -#: ../../whats_new.rst:1197 +#: ../../whats_new.rst:1416 msgid "Handle cases where ``HEARTBEAT`` opcode is received. This caused bots to disconnect seemingly randomly." msgstr "``HEARTBEAT`` のコードを受け取った場合を処理するようにしました。これは、ボットが一見ランダムに切断されるのを引き起こしていました。" -#: ../../whats_new.rst:1199 +#: ../../whats_new.rst:1418 msgid "For the command extension, the following bug fixes apply:" msgstr "コマンド拡張機能では、以下のバグが修正されました:" -#: ../../whats_new.rst:1201 +#: ../../whats_new.rst:1420 msgid "``Bot.check`` decorator is actually a decorator not requiring parentheses." msgstr "``Bot.check`` デコレータが実際に括弧を必要としないようになりました。" -#: ../../whats_new.rst:1202 +#: ../../whats_new.rst:1421 msgid "``Bot.remove_command`` and ``Group.remove_command`` no longer throw if the command doesn't exist." msgstr "``Bot.remove_command`` と ``Group.remove_command`` が、コマンドが存在しない場合に例外を送出しないようにしました。" -#: ../../whats_new.rst:1203 +#: ../../whats_new.rst:1422 msgid "Command names are no longer forced to be ``lower()``." msgstr "コマンド名は強制的に ``lower()`` されなくなりました。" -#: ../../whats_new.rst:1204 +#: ../../whats_new.rst:1423 msgid "Fix a bug where Member and User converters failed to work in private message contexts." msgstr "MemberとUserのコンバータがプライベートメッセージ内で動かなかったバグを修正しました。" -#: ../../whats_new.rst:1205 +#: ../../whats_new.rst:1424 msgid "``HelpFormatter`` now ignores hidden commands when deciding the maximum width." msgstr "``HelpFormatter`` が最大幅を決めるときに隠されたコマンドを無視するようになりました。" -#: ../../whats_new.rst:1210 +#: ../../whats_new.rst:1429 msgid "v0.10.0" msgstr "v0.10.0" -#: ../../whats_new.rst:1212 +#: ../../whats_new.rst:1431 msgid "For breaking changes, see :ref:`migrating-to-async`. The breaking changes listed there will not be enumerated below. Since this version is rather a big departure from v0.9.2, this change log will be non-exhaustive." msgstr "破壊的変更に関しては、 :ref:`migrating-to-async` を参照してください。そのページで列挙された破壊的変更はここでは述べません。このバージョンがv0.9.2よりかなり大きな変更であるため、変更履歴は完全ではありません。" -#: ../../whats_new.rst:1217 +#: ../../whats_new.rst:1436 msgid "The library is now fully ``asyncio`` compatible, allowing you to write non-blocking code a lot more easily." msgstr "ライブラリが完全に ``asyncio`` に対応するようになり、ノンブロッキングコードをより簡単に書けるようになりました。" -#: ../../whats_new.rst:1218 +#: ../../whats_new.rst:1437 msgid "The library now fully handles 429s and unconditionally retries on 502s." msgstr "ライブラリが429を完全に処理し、502で無条件に再試行するようにしました。" -#: ../../whats_new.rst:1219 +#: ../../whats_new.rst:1438 msgid "A new command extension module was added but is currently undocumented. Figuring it out is left as an exercise to the reader." msgstr "新しいコマンド拡張機能モジュールが追加されましたが、現在文書化されていません。詳細は読者が自身で調べることをおすすめします。" -#: ../../whats_new.rst:1220 +#: ../../whats_new.rst:1439 msgid "Two new exception types, :exc:`Forbidden` and :exc:`NotFound` to denote permission errors or 404 errors." msgstr "パーミッションエラーや404エラーを示す2つの新しい例外タイプ、 :exc:`Forbidden` と :exc:`NotFound` が追加されました。" -#: ../../whats_new.rst:1221 +#: ../../whats_new.rst:1440 msgid "Added :meth:`Client.delete_invite` to revoke invites." msgstr "招待を取り消す :meth:`Client.delete_invite` を追加しました。" -#: ../../whats_new.rst:1222 +#: ../../whats_new.rst:1441 msgid "Added support for sending voice. Check :class:`VoiceClient` for more details." msgstr "音声を送信するためのサポートを追加しました。詳細は :class:`VoiceClient` を参照してください。" -#: ../../whats_new.rst:1223 +#: ../../whats_new.rst:1442 msgid "Added :meth:`Client.wait_for_message` coroutine to aid with follow up commands." msgstr "フォローアップコマンドを作りやすいように、コルーチン :meth:`Client.wait_for_message` を追加しました。" -#: ../../whats_new.rst:1224 +#: ../../whats_new.rst:1443 msgid "Added :data:`version_info` named tuple to check version info of the library." msgstr "ライブラリのバージョン情報を確認するための、namedtuple :data:`version_info` を追加しました。" -#: ../../whats_new.rst:1225 +#: ../../whats_new.rst:1444 msgid "Login credentials are now cached to have a faster login experience. You can disable this by passing in ``cache_auth=False`` when constructing a :class:`Client`." msgstr "ログイン情報をキャッシュすることで、より高速にログインできるようになりました。これを無効にするには、 :class:`Client` を作成する際に ``cache_auth=False`` を渡します。" -#: ../../whats_new.rst:1227 +#: ../../whats_new.rst:1446 msgid "New utility function, :func:`discord.utils.get` to simplify retrieval of items based on attributes." msgstr "新しいユーティリティ関数 :func:`discord.utils.get` は、属性に基づいたアイテムの取得を簡素化します。" -#: ../../whats_new.rst:1228 +#: ../../whats_new.rst:1447 msgid "All data classes now support ``!=``, ``==``, ``hash(obj)`` and ``str(obj)``." msgstr "すべてのデータクラスが ``!=``, ``==``, ``hash(obj)``, ``str(obj)`` をサポートするようになりました" -#: ../../whats_new.rst:1229 +#: ../../whats_new.rst:1448 msgid "Added :meth:`Client.get_bans` to get banned members from a server." msgstr "サーバーからBANされたメンバーを取得する :meth:`Client.get_bans` を追加しました。" -#: ../../whats_new.rst:1230 +#: ../../whats_new.rst:1449 msgid "Added :meth:`Client.invites_from` to get currently active invites in a server." msgstr "サーバーで現在アクティブな招待を取得する :meth:`Client.invites_from` を追加しました。" -#: ../../whats_new.rst:1231 +#: ../../whats_new.rst:1450 msgid "Added :attr:`Server.me` attribute to get the :class:`Member` version of :attr:`Client.user`." msgstr ":attr:`Client.user` の :class:`Member` を取得できる :attr:`Server.me` を追加しました。" -#: ../../whats_new.rst:1232 +#: ../../whats_new.rst:1451 msgid "Most data classes now support a ``hash(obj)`` function to allow you to use them in ``set`` or ``dict`` classes or subclasses." msgstr "ほとんどのデータクラスが ``hash(obj)`` 関数をサポートするようになり、 ``set`` や ``dict`` クラス、サブクラスで使用できるようになりました。" -#: ../../whats_new.rst:1233 +#: ../../whats_new.rst:1452 msgid "Add :meth:`Message.clean_content` to get a text version of the content with the user and channel mentioned changed into their names." msgstr "ユーザーとチャンネルのメンションを名前に変更したバージョンのコンテンツを取得する、 :meth:`Message.clean_content` を追加しました。" -#: ../../whats_new.rst:1234 +#: ../../whats_new.rst:1453 msgid "Added a way to remove the messages of the user that just got banned in :meth:`Client.ban`." msgstr ":meth:`Client.ban` でBANされたユーザーのメッセージを削除する方法を追加しました。" -#: ../../whats_new.rst:1235 +#: ../../whats_new.rst:1454 msgid "Added :meth:`Client.wait_until_ready` to facilitate easy creation of tasks that require the client cache to be ready." msgstr "クライアントキャッシュを準備する必要があるタスクを簡単に作成できるように、 :meth:`Client.wait_until_ready` を追加しました。" -#: ../../whats_new.rst:1236 +#: ../../whats_new.rst:1455 msgid "Added :meth:`Client.wait_until_login` to facilitate easy creation of tasks that require the client to be logged in." msgstr "クライアントのログインを必要とするタスクを簡単に作成できるように :meth:`Client.wait_until_login` を追加しました。" -#: ../../whats_new.rst:1237 +#: ../../whats_new.rst:1456 msgid "Add :class:`discord.Game` to represent any game with custom text to send to :meth:`Client.change_status`." msgstr ":class:`Client.change_status` に送信する、カスタムテキストを含む任意のゲームを表す :meth:`discord.Game` を追加しました。" -#: ../../whats_new.rst:1238 +#: ../../whats_new.rst:1457 msgid "Add :attr:`Message.nonce` attribute." msgstr ":attr:`Message.nonce` 属性を追加しました。" -#: ../../whats_new.rst:1239 +#: ../../whats_new.rst:1458 msgid "Add :meth:`Member.permissions_in` as another way of doing :meth:`Channel.permissions_for`." msgstr ":meth:`Channel.permissions_for` の代替として :meth:`Member.permissions_in` を追加しました。" -#: ../../whats_new.rst:1240 +#: ../../whats_new.rst:1459 msgid "Add :meth:`Client.move_member` to move a member to another voice channel." msgstr "メンバーを別のボイスチャンネルに移動するための :meth:`Client.move_member` を追加しました。" -#: ../../whats_new.rst:1241 +#: ../../whats_new.rst:1460 msgid "You can now create a server via :meth:`Client.create_server`." msgstr ":meth:`Client.create_server` を使用してサーバーを作成できるようになりました。" -#: ../../whats_new.rst:1242 +#: ../../whats_new.rst:1461 msgid "Added :meth:`Client.edit_server` to edit existing servers." msgstr "既存のサーバを編集するための :meth:`Client.edit_server` を追加しました。" -#: ../../whats_new.rst:1243 +#: ../../whats_new.rst:1462 msgid "Added :meth:`Client.server_voice_state` to server mute or server deafen a member." msgstr "メンバーをサーバーミュートしたり、サーバースピーカーミュートしたりできる :meth:`Client.server_voice_state` を追加しました。" -#: ../../whats_new.rst:1244 +#: ../../whats_new.rst:1463 msgid "If you are being rate limited, the library will now handle it for you." msgstr "レートリミットの際にライブラリが処理するようになりました。" -#: ../../whats_new.rst:1245 +#: ../../whats_new.rst:1464 msgid "Add :func:`on_member_ban` and :func:`on_member_unban` events that trigger when a member is banned/unbanned." msgstr "メンバーがBANまたはBAN解除されたときに実行される :func:`on_member_ban` と :func:`on_member_unban` イベントを追加しました。" -#: ../../whats_new.rst:1248 +#: ../../whats_new.rst:1467 msgid "Performance Improvements" msgstr "パフォーマンスの改善" -#: ../../whats_new.rst:1250 +#: ../../whats_new.rst:1469 msgid "All data classes now use ``__slots__`` which greatly reduce the memory usage of things kept in cache." msgstr "すべてのデータクラスは ``__slots__`` を使用するようになり、キャッシュに保存されているもののメモリ使用量を大幅に削減しました。" -#: ../../whats_new.rst:1251 +#: ../../whats_new.rst:1470 msgid "Due to the usage of ``asyncio``, the CPU usage of the library has gone down significantly." msgstr "``asyncio`` の使用により、ライブラリの CPU 使用率は大幅に減少しました。" -#: ../../whats_new.rst:1252 +#: ../../whats_new.rst:1471 msgid "A lot of the internal cache lists were changed into dictionaries to change the ``O(n)`` lookup into ``O(1)``." msgstr "多くの内部キャッシュリストが ``O(n)`` 検索を ``O(1)`` に変更するために辞書型に変更されました。" -#: ../../whats_new.rst:1253 +#: ../../whats_new.rst:1472 msgid "Compressed READY is now on by default. This means if you're on a lot of servers (or maybe even a few) you would receive performance improvements by having to download and process less data." msgstr "圧縮されたREADYがデフォルトでオンになりました。 つまり、多くのサーバー(あるいはもしかすると少なめのサーバー) にいる場合、より少ないデータをダウンロードして処理することでパフォーマンスが向上されます。" -#: ../../whats_new.rst:1255 +#: ../../whats_new.rst:1474 msgid "While minor, change regex from ``\\d+`` to ``[0-9]+`` to avoid unnecessary unicode character lookups." msgstr "小規模ながら、不要な Unicode 文字の検索を避けるために正規表現を ``\\d+`` から ``[0-9]+`` に変更しました。" -#: ../../whats_new.rst:1260 +#: ../../whats_new.rst:1479 msgid "Fix bug where guilds being updated did not edit the items in cache." msgstr "ギルドが更新されてもキャッシュ内のアイテムが編集されなかったバグを修正しました。" -#: ../../whats_new.rst:1261 +#: ../../whats_new.rst:1480 msgid "Fix bug where ``member.roles`` were empty upon joining instead of having the ``@everyone`` role." msgstr "``member.roles`` が参加時に ``@everyone`` ロールを有さず空であったバグを修正しました。" -#: ../../whats_new.rst:1262 +#: ../../whats_new.rst:1481 msgid "Fix bug where :meth:`Role.is_everyone` was not being set properly when the role was being edited." msgstr "ロールが編集されたときに :meth:`Role.is_everyone` が正しく設定されていないバグを修正しました。" -#: ../../whats_new.rst:1263 +#: ../../whats_new.rst:1482 msgid ":meth:`Client.logs_from` now handles cases where limit > 100 to sidestep the discord API limitation." msgstr ":meth:`Client.logs_from` が、DiscordのAPI制限を避けるために制限 > 100を超える場合を処理するようになりました。" -#: ../../whats_new.rst:1264 +#: ../../whats_new.rst:1483 msgid "Fix bug where a role being deleted would trigger a ``ValueError``." msgstr "ロールが削除されると、 ``ValueError`` が発生するバグを修正しました。" -#: ../../whats_new.rst:1265 +#: ../../whats_new.rst:1484 msgid "Fix bug where :meth:`Permissions.kick_members` and :meth:`Permissions.ban_members` were flipped." msgstr ":meth:`Permissions.kick_members` と :meth:`Permissions.ban_members` がひっくり返されたバグを修正しました。" -#: ../../whats_new.rst:1266 +#: ../../whats_new.rst:1485 msgid "Mentions are now triggered normally. This was changed due to the way discord handles it internally." msgstr "メンションが正常に発動されるようになりました。これは、Discordの内部処理の方法の変更によるものです。" -#: ../../whats_new.rst:1267 +#: ../../whats_new.rst:1486 msgid "Fix issue when a :class:`Message` would attempt to upgrade a :attr:`Message.server` when the channel is a :class:`Object`." msgstr "チャンネルが :class:`Object` の時に、 :class:`Message` が :attr:`Message.server` をアップグレードしようとする問題を修正しました。" -#: ../../whats_new.rst:1269 +#: ../../whats_new.rst:1488 msgid "Unavailable servers were not being added into cache, this has been corrected." msgstr "利用できないサーバーがキャッシュに追加されない不具合が修正されました。" diff --git a/docs/logging.rst b/docs/logging.rst index 739ca0ebe..5c05ae627 100644 --- a/docs/logging.rst +++ b/docs/logging.rst @@ -43,6 +43,12 @@ Likewise, configuring the log level to ``logging.DEBUG`` is also possible: This is recommended, especially at verbose levels such as ``DEBUG``, as there are a lot of events logged and it would clog the stderr of your program. +If you want the logging configuration the library provides to affect all loggers rather than just the ``discord`` logger, you can pass ``root_logger=True`` inside :meth:`Client.run`: + +.. code-block:: python3 + + client.run(token, log_handler=handler, root_logger=True) + If you want to setup logging using the library provided configuration without using :meth:`Client.run`, you can use :func:`discord.utils.setup_logging`: .. code-block:: python3 diff --git a/docs/migrating.rst b/docs/migrating.rst index 8e31417fd..1a0b52158 100644 --- a/docs/migrating.rst +++ b/docs/migrating.rst @@ -1006,6 +1006,20 @@ Due to a breaking API change by Discord, :meth:`Guild.bans` no longer returns a async for ban in guild.bans(limit=1000): ... +Flag classes now have a custom ``bool()`` implementation +-------------------------------------------------------- + +To allow library users to easily check whether an instance of a flag class has any flags enabled, +using `bool` on them will now only return ``True`` if at least one flag is enabled. + +This means that evaluating instances of the following classes in a bool context (such as ``if obj:``) may no longer return ``True``: + +- :class:`Intents` +- :class:`MemberCacheFlags` +- :class:`MessageFlags` +- :class:`Permissions` +- :class:`PublicUserFlags` +- :class:`SystemChannelFlags` Function Signature Changes ---------------------------- diff --git a/docs/whats_new.rst b/docs/whats_new.rst index 614312c8a..44db8c3d4 100644 --- a/docs/whats_new.rst +++ b/docs/whats_new.rst @@ -11,6 +11,577 @@ Changelog This page keeps a detailed human friendly rendering of what's new and changed in specific versions. +.. _vp2p5p2: + +v2.5.2 +------- + +Bug Fixes +~~~~~~~~~~ + +- Fix a serialization issue when sending embeds (:issue:`10126`) + +.. _vp2p5p1: + +v2.5.1 +------- + +Bug Fixes +~~~~~~~~~~ + +- Fix :attr:`InteractionCallbackResponse.resource` having incorrect state (:issue:`10107`) +- Create :class:`ScheduledEvent` on cache miss for :func:`on_scheduled_event_delete` (:issue:`10113`) +- Add defaults for :class:`Message` creation preventing some crashes (:issue:`10115`) +- Fix :meth:`Attachment.is_spoiler` and :meth:`Attachment.is_voice_message` being incorrect (:issue:`10122`) + + +.. _vp2p5p0: + +v2.5.0 +------- + +New Features +~~~~~~~~~~~~~ + +- Add support for message forwarding (:issue:`9950`) + - Adds :class:`MessageReferenceType` + - Adds :class:`MessageSnapshot` + - Adds ``type`` parameter to :class:`MessageReference`, :meth:`MessageReference.from_message`, and :meth:`PartialMessage.to_reference` + - Add :meth:`PartialMessage.forward` + +- Add SKU subscriptions support (:issue:`9930`) + - Adds new events :func:`on_subscription_create`, :func:`on_subscription_update`, and :func:`on_subscription_delete` + - Add :class:`SubscriptionStatus` enum + - Add :class:`Subscription` model + - Add :meth:`SKU.fetch_subscription` and :meth:`SKU.subscriptions` + +- Add support for application emojis (:issue:`9891`) + - Add :meth:`Client.create_application_emoji` + - Add :meth:`Client.fetch_application_emoji` + - Add :meth:`Client.fetch_application_emojis` + - Add :meth:`Emoji.is_application_owned` + +- Support for Soundboard and VC effects (:issue:`9349`) + - Add :class:`BaseSoundboardSound`, :class:`SoundboardDefaultSound`, and :class:`SoundboardSound` + - Add :class:`VoiceChannelEffect` + - Add :class:`VoiceChannelEffectAnimation` + - Add :class:`VoiceChannelEffectAnimationType` + - Add :class:`VoiceChannelSoundEffect` + - Add :meth:`VoiceChannel.send_sound` + - Add new audit log actions: :attr:`AuditLogAction.soundboard_sound_create`, :attr:`AuditLogAction.soundboard_sound_update`, and :attr:`AuditLogAction.soundboard_sound_delete`. + - Add :attr:`Intents.expressions` and make :attr:`Intents.emojis` and :attr:`Intents.emojis_and_stickers` aliases of that intent. + - Add new events: :func:`on_soundboard_sound_create`, :func:`on_soundboard_sound_update`, :func:`on_soundboard_sound_delete`, and :func:`on_voice_channel_effect`. + - Add methods and properties dealing with soundboards: + - :attr:`Client.soundboard_sounds` + - :attr:`Guild.soundboard_sounds` + - :meth:`Client.get_soundboard_sound` + - :meth:`Guild.get_soundboard_sound` + - :meth:`Client.fetch_soundboard_default_sounds` + - :meth:`Guild.fetch_soundboard_sound` + - :meth:`Guild.fetch_soundboard_sounds` + - :meth:`Guild.create_soundboard_sound` + +- Add support for retrieving interaction responses when sending a response (:issue:`9957`) + - Methods from :class:`InteractionResponse` now return :class:`InteractionCallbackResponse` + - Depending on the interaction response type, :attr:`InteractionCallbackResponse.resource` will be different + +- Add :attr:`PartialWebhookChannel.mention` attribute (:issue:`10101`) +- Add support for sending stateless views for :class:`SyncWebhook` or webhooks with no state (:issue:`10089`) +- Add richer :meth:`Role.move` interface (:issue:`10100`) +- Add support for :class:`EmbedFlags` via :attr:`Embed.flags` (:issue:`10085`) +- Add new flags for :class:`AttachmentFlags` (:issue:`10085`) +- Add :func:`on_raw_presence_update` event that does not depend on cache state (:issue:`10048`) + - This requires setting the ``enable_raw_presences`` keyword argument within :class:`Client`. + +- Add :attr:`ForumChannel.members` property. (:issue:`10034`) +- Add ``exclude_deleted`` parameter to :meth:`Client.entitlements` (:issue:`10027`) +- Add :meth:`Client.fetch_guild_preview` (:issue:`9986`) +- Add :meth:`AutoShardedClient.fetch_session_start_limits` (:issue:`10007`) +- Add :attr:`PartialMessageable.mention` (:issue:`9988`) +- Add command target to :class:`MessageInteractionMetadata` (:issue:`10004`) + - :attr:`MessageInteractionMetadata.target_user` + - :attr:`MessageInteractionMetadata.target_message_id` + - :attr:`MessageInteractionMetadata.target_message` + +- Add :attr:`Message.forward` flag (:issue:`9978`) + +- Add support for purchase notification messages (:issue:`9906`) + - Add new type :attr:`MessageType.purchase_notification` + - Add new models :class:`GuildProductPurchase` and :class:`PurchaseNotification` + - Add :attr:`Message.purchase_notification` + +- Add ``category`` parameter to :meth:`.abc.GuildChannel.clone` (:issue:`9941`) +- Add support for message call (:issue:`9911`) + - Add new models :class:`CallMessage` + - Add :attr:`Message.call` attribute + +- Parse full message for message edit event (:issue:`10035`) + - Adds :attr:`RawMessageUpdateEvent.message` attribute + - Potentially speeds up :func:`on_message_edit` by no longer copying data + +- Add support for retrieving and editing integration type configuration (:issue:`9818`) + - This adds :class:`IntegrationTypeConfig` + - Retrievable via :attr:`AppInfo.guild_integration_config` and :attr:`AppInfo.user_integration_config`. + - Editable via :meth:`AppInfo.edit` + +- Allow passing ``None`` for ``scopes`` parameter in :func:`utils.oauth_url` (:issue:`10078`) +- Add support for :attr:`MessageType.poll_result` messages (:issue:`9905`) +- Add various new :class:`MessageFlags` +- Add :meth:`Member.fetch_voice` (:issue:`9908`) +- Add :attr:`Guild.dm_spam_detected_at` and :meth:`Guild.is_dm_spam_detected` (:issue:`9808`) +- Add :attr:`Guild.raid_detected_at` and :meth:`Guild.is_raid_detected` (:issue:`9808`) +- Add :meth:`Client.fetch_premium_sticker_pack` (:issue:`9909`) +- Add :attr:`AppInfo.approximate_user_install_count` (:issue:`9915`) +- Add :meth:`Guild.fetch_role` (:issue:`9921`) +- Add :attr:`Attachment.title` (:issue:`9904`) +- Add :attr:`Member.guild_banner` and :attr:`Member.display_banner` +- Re-add ``connector`` parameter that was removed during v2.0 (:issue:`9900`) +- |commands| Add :class:`~discord.ext.commands.SoundboardSoundConverter` (:issue:`9973`) + +Bug Fixes +~~~~~~~~~~ + +- Change the default file size limit for :attr:`Guild.filesize_limit` to match new Discord limit of 10 MiB (:issue:`10084`) +- Handle improper 1000 close code closures by Discord + - This fixes an issue causing excessive IDENTIFY in large bots + +- Fix potential performance regression when dealing with cookies in the library owned session (:issue:`9916`) +- Add support for AEAD XChaCha20 Poly1305 encryption mode (:issue:`9953`) + - This allows voice to continue working when the older encryption modes eventually get removed. + - Support for DAVE is still tentative. + +- Fix large performance regression due to polls when creating messages +- Fix cases where :attr:`Member.roles` contains a ``None`` role (:issue:`10093`) +- Update all channel clone implementations to work as expected (:issue:`9935`) +- Fix bug in :meth:`Client.entitlements` only returning 100 entries (:issue:`10051`) +- Fix :meth:`TextChannel.clone` always sending slowmode when not applicable to news channels (:issue:`9967`) +- Fix :attr:`Message.system_content` for :attr:`MessageType.role_subscription_purchase` renewals (:issue:`9955`) +- Fix :attr:`Sticker.url` for GIF stickers (:issue:`9913`) +- Fix :attr:`User.default_avatar` for team users and webhooks (:issue:`9907`) +- Fix potential rounding error in :attr:`Poll.duration` (:issue:`9903`) +- Fix introduced potential TypeError when raising :exc:`app_commands.CommandSyncFailure` +- Fix :attr:`AuditLogEntry.target` causing errors for :attr:`AuditLogAction.message_pin` and :attr:`AuditLogAction.message_unpin` actions (:issue:`10061`). +- Fix incorrect :class:`ui.Select` maximum option check (:issue:`9878`, :issue:`9879`) +- Fix path sanitisation for absolute Windows paths when using ``__main__`` (:issue:`10096`, :issue:`10097`) +- |tasks| Fix race condition when setting timer handle when using uvloop (:issue:`10020`) +- |commands| Fix issue with category cooldowns outside of guild channels (:issue:`9959`) +- |commands| Fix :meth:`Context.defer ` unconditionally deferring +- |commands| Fix callable FlagConverter defaults on hybrid commands not being called (:issue:`10037`) +- |commands| Unwrap :class:`~discord.ext.commands.Parameter` if given as default to :func:`~ext.commands.parameter` (:issue:`9977`) +- |commands| Fix fallback behaviour not being respected when calling replace for :class:`~.ext.commands.Parameter` (:issue:`10076`, :issue:`10077`) +- |commands| Respect ``enabled`` keyword argument for hybrid app commands (:issue:`10001`) + +Miscellaneous +~~~~~~~~~~~~~~ + +- Use a fallback package for ``audioop`` to allow the library to work in Python 3.13 or newer. +- Remove ``aiodns`` from being used on Windows (:issue:`9898`) +- Add zstd gateway compression to ``speed`` extras (:issue:`9947`) + - This can be installed using ``discord.py[speed]`` + +- Add proxy support fetching from the CDN (:issue:`9966`) +- Remove ``/`` from being safe from URI encoding when constructing paths internally +- Sanitize invite argument before calling the invite info endpoint +- Avoid returning in finally in specific places to prevent exception swallowing (:issue:`9981`, :issue:`9984`) +- Enforce and create random nonces when creating messages throughout the library +- Revert IPv6 block in the library (:issue:`9870`) +- Allow passing :class:`Permissions` object to :func:`app_commands.default_permissions` decorator (:issue:`9951`, :issue:`9971`) + + +.. _vp2p4p0: + +v2.4.0 +------- + +New Features +~~~~~~~~~~~~~ + +- Add support for allowed contexts in app commands (:issue:`9760`). + - An "allowed context" is the location where an app command can be used. + - This is an internal change to decorators such as :func:`app_commands.guild_only` and :func:`app_commands.dm_only`. + - Add :func:`app_commands.private_channel_only`. + - Add :func:`app_commands.allowed_contexts`. + - Add :class:`app_commands.AppCommandContext`. + - Add :attr:`app_commands.Command.allowed_contexts`. + - Add :attr:`app_commands.AppCommand.allowed_contexts`. + - Add :attr:`app_commands.ContextMenu.allowed_contexts`. + +- Add support for user-installable apps (:issue:`9760`). + - Add :attr:`app_commands.Command.allowed_installs`. + - Add :attr:`app_commands.AppCommand.allowed_installs`. + - Add :attr:`app_commands.ContextMenu.allowed_installs`. + - Add :func:`app_commands.allowed_installs`. + - Add :func:`app_commands.guild_install`. + - Add :func:`app_commands.user_install`. + - Add :class:`app_commands.AppInstallationType`. + - Add :attr:`Interaction.context`. + - Add :meth:`Interaction.is_guild_integration`. + - Add :meth:`Interaction.is_user_integration`. + +- Add support for Polls (:issue:`9759`). + - Polls can be created using :class:`Poll` and the ``poll`` keyword-only parameter in various message sending methods. + - Add :class:`PollAnswer` and :class:`PollMedia`. + - Add :attr:`Intents.polls`, :attr:`Intents.guild_polls` and :attr:`Intents.dm_polls` intents. + - Add :meth:`Message.end_poll` method to end polls. + - Add new events, :func:`on_poll_vote_add`, :func:`on_poll_vote_remove`, :func:`on_raw_poll_vote_add`, and :func:`on_raw_poll_vote_remove`. + +- Voice handling has been completely rewritten to hopefully fix many bugs (:issue:`9525`, :issue:`9528`, :issue:`9536`, :issue:`9572`, :issue:`9576`, :issue:`9596`, :issue:`9683`, :issue:`9699`, :issue:`9772`, etc.) +- Add :attr:`DMChannel.recipients` to get all recipients of a DM channel (:issue:`9760`). +- Add support for :attr:`RawReactionActionEvent.message_author_id`. +- Add support for :attr:`AuditLogAction.creator_monetization_request_created` and :attr:`AuditLogAction.creator_monetization_terms_accepted`. +- Add support for :class:`AttachmentFlags`, accessed via :attr:`Attachment.flags` (:issue:`9486`). +- Add support for :class:`RoleFlags`, accessed via :attr:`Role.flags` (:issue:`9485`). +- Add support for :attr:`ChannelType.media`, accessed via :meth:`ForumChannel.is_media`. +- Add various new permissions (:issue:`9501`, :issue:`9762`, :issue:`9759`, :issue:`9857`) + - Add :meth:`Permissions.events`. + - Add :attr:`Permissions.create_events`. + - Add :attr:`Permissions.view_creator_monetization_analytics`. + - Add :attr:`Permissions.send_polls` + - Add :attr:`Permissions.create_polls`. + - Add :attr:`Permissions.use_external_apps`. + +- Add shortcut for :attr:`CategoryChannel.forums`. +- Add encoder options to :meth:`VoiceClient.play` (:issue:`9527`). +- Add support for team member roles. + - Add :class:`TeamMemberRole`. + - Add :attr:`TeamMember.role`. + - Updated :attr:`Bot.owner_ids <.ext.commands.Bot.owner_ids>` to account for team roles. Team owners or developers are considered Bot owners. + +- Add optional attribute ``integration_type`` in :attr:`AuditLogEntry.extra` for ``kick`` or ``member_role_update`` actions. +- Add support for "dynamic" :class:`ui.Item` that let you parse state out of a ``custom_id`` using regex. + - In order to use this, you must subclass :class:`ui.DynamicItem`. + - This is an alternative to persistent views. + - Add :meth:`Client.add_dynamic_items`. + - Add :meth:`Client.remove_dynamic_items`. + - Add :meth:`ui.Item.interaction_check`. + - Check the :resource:`dynamic_counter example ` for more information. + +- Add support for reading burst reactions. The API does not support sending them as of currently. + - Add :attr:`Reaction.normal_count`. + - Add :attr:`Reaction.burst_count`. + - Add :attr:`Reaction.me_burst`. + +- Add support for default values on select menus (:issue:`9577`). + - Add :class:`SelectDefaultValue`. + - Add :class:`SelectDefaultValueType`. + - Add a ``default_values`` attribute to each specialised select menu. + +- Add ``scheduled_event`` parameter for :meth:`StageChannel.create_instance` (:issue:`9595`). +- Add support for auto mod members (:issue:`9328`). + - Add ``type`` keyword argument to :class:`AutoModRuleAction`. + - Add :attr:`AutoModTrigger.mention_raid_protection`. + - Add :attr:`AutoModRuleTriggerType.member_profile`. + - Add :attr:`AutoModRuleEventType.member_update`. + - Add :attr:`AutoModRuleActionType.block_member_interactions`. + +- Add support for premium app integrations (:issue:`9453`). + - Add multiple SKU and entitlement related classes, e.g. :class:`SKU`, :class:`Entitlement`, :class:`SKUFlags`. + - Add multiple enums, e.g. :class:`SKUType`, :class:`EntitlementType`, :class:`EntitlementOwnerType`. + - Add :meth:`Client.fetch_skus` and :meth:`Client.fetch_entitlement` to fetch from the API. + - Add :meth:`Client.create_entitlement` to create entitlements. + - Add :attr:`Client.entitlements`. + - Add :attr:`Interaction.entitlement_sku_ids`. + - Add :attr:`Interaction.entitlements`. + - Add :attr:`ButtonStyle.premium` and :attr:`ui.Button.sku_id` to send a button asking the user to buy an SKU (:issue:`9845`). + - Add support for one time purchase (:issue:`9803`). + +- Add support for editing application info (:issue:`9610`). + - Add :attr:`AppInfo.interactions_endpoint_url`. + - Add :attr:`AppInfo.redirect_uris`. + - Add :meth:`AppInfo.edit`. + +- Add support for getting/fetching threads from :class:`Message` (:issue:`9665`). + - Add :attr:`PartialMessage.thread`. + - Add :attr:`Message.thread`. + - Add :meth:`Message.fetch_thread`. + +- Add support for platform and assets to activities (:issue:`9677`). + - Add :attr:`Activity.platform`. + - Add :attr:`Game.platform`. + - Add :attr:`Game.assets`. + +- Add support for suppressing embeds in an interaction response (:issue:`9678`). +- Add support for adding forum thread tags via webhook (:issue:`9680`) and (:issue:`9783`). +- Add support for guild incident message types (:issue:`9686`). +- Add :attr:`Locale.latin_american_spanish` (:issue:`9689`). +- Add support for setting voice channel status (:issue:`9603`). +- Add a shard connect timeout parameter to :class:`AutoShardedClient`. +- Add support for guild incidents (:issue:`9590`). + - Updated :meth:`Guild.edit` with ``invites_disabled_until`` and ``dms_disabled_until`` parameters. + - Add :attr:`Guild.invites_paused_until`. + - Add :attr:`Guild.dms_paused_until`. + - Add :meth:`Guild.invites_paused`. + - Add :meth:`Guild.dms_paused`. + +- Add support for :attr:`abc.User.avatar_decoration` (:issue:`9343`). +- Add support for GIF stickers (:issue:`9737`). +- Add support for updating :class:`ClientUser` banners (:issue:`9752`). +- Add support for bulk banning members via :meth:`Guild.bulk_ban`. +- Add ``reason`` keyword argument to :meth:`Thread.delete` (:issue:`9804`). +- Add :attr:`AppInfo.approximate_guild_count` (:issue:`9811`). +- Add support for :attr:`Message.interaction_metadata` (:issue:`9817`). +- Add support for differing :class:`Invite` types (:issue:`9682`). +- Add support for reaction types to raw and non-raw models (:issue:`9836`). +- |tasks| Add ``name`` parameter to :meth:`~ext.tasks.loop` to name the internal :class:`asyncio.Task`. +- |commands| Add fallback behaviour to :class:`~ext.commands.CurrentGuild`. +- |commands| Add logging for errors that occur during :meth:`~ext.commands.Cog.cog_unload`. +- |commands| Add support for :class:`typing.NewType` and ``type`` keyword type aliases (:issue:`9815`). + - Also supports application commands. + +- |commands| Add support for positional-only flag parameters (:issue:`9805`). +- |commands| Add support for channel URLs in ChannelConverter related classes (:issue:`9799`). + + +Bug Fixes +~~~~~~~~~~ + +- Fix emoji and sticker cache being populated despite turning the intent off. +- Fix outstanding chunk requests when receiving a gateway READY event not being cleared (:issue:`9571`). +- Fix escape behaviour for lists and headers in :meth:`~utils.escape_markdown`. +- Fix alias value for :attr:`Intents.auto_moderation` (:issue:`9524`). +- Fixes and improvements for :class:`FFmpegAudio` and all related subclasses (:issue:`9528`). +- Fix :meth:`Template.source_guild` attempting to resolve from cache (:issue:`9535`). +- Fix :exc:`IndexError` being raised instead of :exc:`ValueError` when calling :meth:`Colour.from_str` with an empty string (:issue:`9540`). +- Fix :meth:`View.from_message` not correctly creating the varying :class:`ui.Select` types (:issue:`9559`). +- Fix logging with autocomplete exceptions, which were previously suppressed. +- Fix possible error in voice cleanup logic (:issue:`9572`). +- Fix possible :exc:`AttributeError` during :meth:`app_commands.CommandTree.sync` when a command is regarded as 'too large'. +- Fix possible :exc:`TypeError` if a :class:`app_commands.Group` did not have a name set (:issue:`9581`). +- Fix possible bad voice state where you move to a voice channel with missing permissions (:issue:`9596`). +- Fix websocket reaching an error state due to received error payload (:issue:`9561`). +- Fix handling of :class:`AuditLogDiff` when relating to auto mod triggers (:issue:`9622`). +- Fix race condition in voice logic relating to disconnect and connect (:issue:`9683`). +- Use the :attr:`Interaction.user` guild as a fallback for :attr:`Interaction.guild` if not available. +- Fix restriction on auto moderation audit log ID range. +- Fix check for maximum number of children per :class:`ui.View`. +- Fix comparison between :class:`Object` classes with a ``type`` set. +- Fix handling of an enum in :meth:`AutoModRule.edit` (:issue:`9798`). +- Fix handling of :meth:`Client.close` within :meth:`Client.__aexit__` (:issue:`9769`). +- Fix channel deletion not evicting related threads from cache (:issue:`9796`). +- Fix bug with cache superfluously incrementing role positions (:issue:`9853`). +- Fix ``exempt_channels`` not being passed along in :meth:`Guild.create_automod_rule` (:issue:`9861`). +- Fix :meth:`abc.GuildChannel.purge` failing on single-message delete mode if the message was deleted (:issue:`9830`, :issue:`9863`). +- |commands| Fix localization support for :class:`~ext.commands.HybridGroup` fallback. +- |commands| Fix nested :class:`~ext.commands.HybridGroup`'s inserting manual app commands. +- |commands| Fix an issue where :class:`~ext.commands.HybridGroup` wrapped instances would be out of sync. +- |commands| Fix :class:`~ext.commands.HelpCommand` defined checks not carrying over during copy (:issue:`9843`). + +Miscellaneous +~~~~~~~~~~~~~~ + +- Additional documentation added for logging capabilities. +- Performance increases of constructing :class:`Permissions` using keyword arguments. +- Improve ``__repr__`` of :class:`SyncWebhook` and :class:`Webhook` (:issue:`9764`). +- Change internal thread names to be consistent (:issue:`9538`). + +.. _vp2p3p2: + +v2.3.2 +------- + +Bug Fixes +~~~~~~~~~~ + +- Fix the ``name`` parameter not being respected when sending a :class:`CustomActivity`. +- Fix :attr:`Intents.emoji` and :attr:`Intents.emojis_and_stickers` having swapped alias values (:issue:`9471`). +- Fix ``NameError`` when using :meth:`abc.GuildChannel.create_invite` (:issue:`9505`). +- Fix crash when disconnecting during the middle of a ``HELLO`` packet when using :class:`AutoShardedClient`. +- Fix voice websocket not being closed before being replaced by a new one (:issue:`9518`). +- |commands| Fix the wrong :meth:`~ext.commands.HelpCommand.on_help_command_error` being called when ejected from a cog. +- |commands| Fix ``=None`` being displayed in :attr:`~ext.commands.Command.signature`. + +.. _vp2p3p1: + +v2.3.1 +------- + +Bug Fixes +~~~~~~~~~~ + +- Fix username lookup in :meth:`Guild.get_member_named` (:issue:`9451`). +- Use cache data first for :attr:`Interaction.channel` instead of API data. + - This bug usually manifested in incomplete channel objects (e.g. no ``overwrites``) because Discord does not provide this data. + +- Fix false positives in :meth:`PartialEmoji.from_str` inappropriately setting ``animated`` to ``True`` (:issue:`9456`, :issue:`9457`). +- Fix certain select types not appearing in :attr:`Message.components` (:issue:`9462`). +- |commands| Change lookup order for :class:`~ext.commands.MemberConverter` and :class:`~ext.commands.UserConverter` to prioritise usernames instead of nicknames. + +.. _vp2p3p0: + +v2.3.0 +-------- + +New Features +~~~~~~~~~~~~~ + +- Add support for the new username system (also known as "pomelo"). + - Add :attr:`User.global_name` to get their global nickname or "display name". + - Update :attr:`User.display_name` and :attr:`Member.display_name` to understand global nicknames. + - Update ``__str__`` for :class:`User` to drop discriminators if the user has been migrated. + - Update :meth:`Guild.get_member_named` to work with migrated users. + - Update :attr:`User.default_avatar` to work with migrated users. + - |commands| Update user and member converters to understand migrated users. + +- Add :attr:`DefaultAvatar.pink` for new pink default avatars. +- Add :meth:`Colour.pink` to get the pink default avatar colour. +- Add support for voice messages (:issue:`9358`) + - Add :attr:`MessageFlags.voice` + - Add :attr:`Attachment.duration` and :attr:`Attachment.waveform` + - Add :meth:`Attachment.is_voice_message` + - This does not support *sending* voice messages because this is currently unsupported by the API. + +- Add support for new :attr:`Interaction.channel` attribute from the API update (:issue:`9339`). +- Add support for :attr:`TextChannel.default_thread_slowmode_delay` (:issue:`9291`). +- Add support for :attr:`ForumChannel.default_sort_order` (:issue:`9290`). +- Add support for ``default_reaction_emoji`` and ``default_forum_layout`` in :meth:`Guild.create_forum` (:issue:`9300`). +- Add support for ``widget_channel``, ``widget_enabled``, and ``mfa_level`` in :meth:`Guild.edit` (:issue:`9302`, :issue:`9303`). +- Add various new :class:`Permissions` and changes (:issue:`9312`, :issue:`9325`, :issue:`9358`, :issue:`9378`) + - Add new :attr:`~Permissions.manage_expressions`, :attr:`~Permissions.use_external_sounds`, :attr:`~Permissions.use_soundboard`, :attr:`~Permissions.send_voice_messages`, :attr:`~Permissions.create_expressions` permissions. + - Change :attr:`Permissions.manage_emojis` to be an alias of :attr:`~Permissions.manage_expressions`. + +- Add various new properties to :class:`PartialAppInfo` and :class:`AppInfo` (:issue:`9298`). +- Add support for ``with_counts`` parameter to :meth:`Client.fetch_guilds` (:issue:`9369`). +- Add new :meth:`Guild.get_emoji` helper (:issue:`9296`). +- Add :attr:`ApplicationFlags.auto_mod_badge` (:issue:`9313`). +- Add :attr:`Guild.max_stage_video_users` and :attr:`Guild.safety_alerts_channel` (:issue:`9318`). +- Add support for ``raid_alerts_disabled`` and ``safety_alerts_channel`` in :meth:`Guild.edit` (:issue:`9318`). +- |commands| Add :attr:`BadLiteralArgument.argument ` to get the failed argument's value (:issue:`9283`). +- |commands| Add :attr:`Context.filesize_limit ` property (:issue:`9416`). +- |commands| Add support for :attr:`Parameter.displayed_name ` (:issue:`9427`). + +Bug Fixes +~~~~~~~~~~ + +- Fix ``FileHandler`` handlers being written ANSI characters when the bot is executed inside PyCharm. + - This has the side effect of removing coloured logs from the PyCharm terminal due an upstream bug involving TTY detection. This issue is tracked under `PY-43798 `_. + +- Fix channel edits with :meth:`Webhook.edit` sending two requests instead of one. +- Fix :attr:`StageChannel.last_message_id` always being ``None`` (:issue:`9422`). +- Fix piped audio input ending prematurely (:issue:`9001`, :issue:`9380`). +- Fix persistent detection for :class:`ui.TextInput` being incorrect if the ``custom_id`` is set later (:issue:`9438`). +- Fix custom attributes not being copied over when inheriting from :class:`app_commands.Group` (:issue:`9383`). +- Fix AutoMod audit log entry error due to empty channel_id (:issue:`9384`). +- Fix handling of ``around`` parameter in :meth:`abc.Messageable.history` (:issue:`9388`). +- Fix occasional :exc:`AttributeError` when accessing the :attr:`ClientUser.mutual_guilds` property (:issue:`9387`). +- Fix :func:`utils.escape_markdown` not escaping the new markdown (:issue:`9361`). +- Fix webhook targets not being converted in audit logs (:issue:`9332`). +- Fix error when not passing ``enabled`` in :meth:`Guild.create_automod_rule` (:issue:`9292`). +- Fix how various parameters are handled in :meth:`Guild.create_scheduled_event` (:issue:`9275`). +- Fix not sending the ``ssrc`` parameter when sending the SPEAKING payload (:issue:`9301`). +- Fix :attr:`Message.guild` being ``None`` sometimes when received via an interaction. +- Fix :attr:`Message.system_content` for :attr:`MessageType.channel_icon_change` (:issue:`9410`). + +Miscellaneous +~~~~~~~~~~~~~~ + +- Update the base :attr:`Guild.filesize_limit` to 25MiB (:issue:`9353`). +- Allow Interaction webhook URLs to be used in :meth:`Webhook.from_url`. +- Set the socket family of internal connector to ``AF_INET`` to prevent IPv6 connections (:issue:`9442`, :issue:`9443`). + +.. _vp2p2p3: + +v2.2.3 +------- + +Bug Fixes +~~~~~~~~~~ + +- Fix crash from Discord sending null ``channel_id`` for automod audit logs. +- Fix ``channel`` edits when using :meth:`Webhook.edit` sending two requests. +- Fix :attr:`AuditLogEntry.target` being ``None`` for invites (:issue:`9336`). +- Fix :exc:`KeyError` when accessing data for :class:`GuildSticker` (:issue:`9324`). + + +.. _vp2p2p2: + +v2.2.2 +------- + +Bug Fixes +~~~~~~~~~~ + +- Fix UDP discovery in voice not using new 74 byte layout which caused voice to break (:issue:`9277`, :issue:`9278`) + +.. _vp2p2p0: + +v2.2.0 +------- + +New Features +~~~~~~~~~~~~ + +- Add support for new :func:`on_audit_log_entry_create` event +- Add support for silent messages via ``silent`` parameter in :meth:`abc.Messageable.send` + - This is queryable via :attr:`MessageFlags.suppress_notifications` + +- Implement :class:`abc.Messageable` for :class:`StageChannel` (:issue:`9248`) +- Add setter for :attr:`discord.ui.ChannelSelect.channel_types` (:issue:`9068`) +- Add support for custom messages in automod via :attr:`AutoModRuleAction.custom_message` (:issue:`9267`) +- Add :meth:`ForumChannel.get_thread` (:issue:`9106`) +- Add :attr:`StageChannel.slowmode_delay` and :attr:`VoiceChannel.slowmode_delay` (:issue:`9111`) +- Add support for editing the slowmode for :class:`StageChannel` and :class:`VoiceChannel` (:issue:`9111`) +- Add :attr:`Locale.indonesian` +- Add ``delete_after`` keyword argument to :meth:`Interaction.edit_message` (:issue:`9415`) +- Add ``delete_after`` keyword argument to :meth:`InteractionMessage.edit` (:issue:`9206`) +- Add support for member flags (:issue:`9204`) + - Accessible via :attr:`Member.flags` and has a type of :class:`MemberFlags` + - Support ``bypass_verification`` within :meth:`Member.edit` + +- Add support for passing a client to :meth:`Webhook.from_url` and :meth:`Webhook.partial` + - This allows them to use views (assuming they are "bot owned" webhooks) + +- Add :meth:`Colour.dark_embed` and :meth:`Colour.light_embed` (:issue:`9219`) +- Add support for many more parameters within :meth:`Guild.create_stage_channel` (:issue:`9245`) +- Add :attr:`AppInfo.role_connections_verification_url` +- Add support for :attr:`ForumChannel.default_layout` +- Add various new :class:`MessageType` values such as ones related to stage channel and role subscriptions +- Add support for role subscription related attributes + - :class:`RoleSubscriptionInfo` within :attr:`Message.role_subscription` + - :attr:`MessageType.role_subscription_purchase` + - :attr:`SystemChannelFlags.role_subscription_purchase_notifications` + - :attr:`SystemChannelFlags.role_subscription_purchase_notification_replies` + - :attr:`RoleTags.subscription_listing_id` + - :meth:`RoleTags.is_available_for_purchase` + +- Add support for checking if a role is a linked role under :meth:`RoleTags.is_guild_connection` +- Add support for GIF sticker type +- Add support for :attr:`Message.application_id` and :attr:`Message.position` +- Add :func:`utils.maybe_coroutine` helper +- Add :attr:`ScheduledEvent.creator_id` attribute +- |commands| Add support for :meth:`~ext.commands.Cog.interaction_check` for :class:`~ext.commands.GroupCog` (:issue:`9189`) + +Bug Fixes +~~~~~~~~~~ + +- Fix views not being removed from message store backing leading to a memory leak when used from an application command context +- Fix async iterators requesting past their bounds when using ``oldest_first`` and ``after`` or ``before`` (:issue:`9093`) +- Fix :meth:`Guild.audit_logs` pagination logic being buggy when using ``after`` (:issue:`9269`) +- Fix :attr:`Message.channel` sometimes being :class:`Object` instead of :class:`PartialMessageable` +- Fix :class:`ui.View` not properly calling ``super().__init_subclass__`` (:issue:`9231`) +- Fix ``available_tags`` and ``default_thread_slowmode_delay`` not being respected in :meth:`Guild.create_forum` +- Fix :class:`AutoModTrigger` ignoring ``allow_list`` with type keyword (:issue:`9107`) +- Fix implicit permission resolution for :class:`Thread` (:issue:`9153`) +- Fix :meth:`AutoModRule.edit` to work with actual snowflake types such as :class:`Object` (:issue:`9159`) +- Fix :meth:`Webhook.send` returning :class:`ForumChannel` for :attr:`WebhookMessage.channel` +- When a lookup for :attr:`AuditLogEntry.target` fails, it will fallback to :class:`Object` with the appropriate :attr:`Object.type` (:issue:`9171`) +- Fix :attr:`AuditLogDiff.type` for integrations returning :class:`ChannelType` instead of :class:`str` (:issue:`9200`) +- Fix :attr:`AuditLogDiff.type` for webhooks returning :class:`ChannelType` instead of :class:`WebhookType` (:issue:`9251`) +- Fix webhooks and interactions not properly closing files after the request has completed +- Fix :exc:`NameError` in audit log target for app commands +- Fix :meth:`ScheduledEvent.edit` requiring some arguments to be passed in when unnecessary (:issue:`9261`, :issue:`9268`) +- |commands| Explicit set a traceback for hybrid command invocations (:issue:`9205`) + +Miscellaneous +~~~~~~~~~~~~~~ + +- Add colour preview for the colours predefined in :class:`Colour` +- Finished views are no longer stored by the library when sending them (:issue:`9235`) +- Force enable colour logging for the default logging handler when run under Docker. +- Add various overloads for :meth:`Client.wait_for` to aid in static analysis (:issue:`9184`) +- :class:`Interaction` can now optionally take a generic parameter, ``ClientT`` to represent the type for :attr:`Interaction.client` +- |commands| Respect :attr:`~ext.commands.Command.ignore_extra` for :class:`~discord.ext.commands.FlagConverter` keyword-only parameters +- |commands| Change :attr:`Paginator.pages ` to not prematurely close (:issue:`9257`) + .. _vp2p1p1: v2.1.1 diff --git a/examples/advanced_startup.py b/examples/advanced_startup.py index c521841df..4a452188d 100644 --- a/examples/advanced_startup.py +++ b/examples/advanced_startup.py @@ -1,3 +1,5 @@ +# This example requires the 'message_content' privileged intent to function, however your own bot might not. + # This example covers advanced startup options and uses some real world examples for why you may need them. import asyncio @@ -88,9 +90,16 @@ async def main(): # 2. We become responsible for starting the bot. exts = ['general', 'mod', 'dice'] - async with CustomBot(commands.when_mentioned, db_pool=pool, web_client=our_client, initial_extensions=exts) as bot: - - await bot.start(os.getenv('TOKEN', '')) + intents = discord.Intents.default() + intents.message_content = True + async with CustomBot( + commands.when_mentioned, + db_pool=pool, + web_client=our_client, + initial_extensions=exts, + intents=intents, + ) as bot: + await bot.start('token') # For most use cases, after defining what needs to run, we can just tell asyncio to run it: diff --git a/examples/views/dynamic_counter.py b/examples/views/dynamic_counter.py new file mode 100644 index 000000000..cfb02ee5d --- /dev/null +++ b/examples/views/dynamic_counter.py @@ -0,0 +1,98 @@ +from __future__ import annotations + +from discord.ext import commands +import discord +import re + +# Complicated use cases for persistent views can be difficult to achieve when dealing +# with state changes or dynamic items. In order to facilitate these complicated use cases, +# the library provides DynamicItem which allows you to define an item backed by a regular +# expression that can parse state out of the custom_id. + +# The following example showcases a dynamic item that implements a counter. +# The `template` class parameter is used to give the library a regular expression to parse +# the custom_id. In this case we're parsing out custom_id in the form of e.g. +# `counter:5:user:80088516616269824` where the first number is the current count and the +# second number is the user ID who owns the button. + +# Note that custom_ids can only be up to 100 characters long. +class DynamicCounter( + discord.ui.DynamicItem[discord.ui.Button], + template=r'counter:(?P[0-9]+):user:(?P[0-9]+)', +): + def __init__(self, user_id: int, count: int = 0) -> None: + self.user_id: int = user_id + self.count: int = count + super().__init__( + discord.ui.Button( + label=f'Total: {count}', + style=self.style, + custom_id=f'counter:{count}:user:{user_id}', + emoji='\N{THUMBS UP SIGN}', + ) + ) + + # We want the style of the button to be dynamic depending on the count. + @property + def style(self) -> discord.ButtonStyle: + if self.count < 10: + return discord.ButtonStyle.grey + if self.count < 15: + return discord.ButtonStyle.red + if self.count < 20: + return discord.ButtonStyle.blurple + return discord.ButtonStyle.green + + # This method actually extracts the information from the custom ID and creates the item. + @classmethod + async def from_custom_id(cls, interaction: discord.Interaction, item: discord.ui.Button, match: re.Match[str], /): + count = int(match['count']) + user_id = int(match['id']) + return cls(user_id, count=count) + + # We want to ensure that our button is only called by the user who created it. + async def interaction_check(self, interaction: discord.Interaction) -> bool: + return interaction.user.id == self.user_id + + async def callback(self, interaction: discord.Interaction) -> None: + # When the button is invoked, we want to increase the count and update the button's + # styling and label. + # In order to actually persist these changes we need to also update the custom_id + # to match the new information. + # Note that the custom ID *must* match the template. + self.count += 1 + self.item.label = f'Total: {self.count}' + self.custom_id = f'counter:{self.count}:user:{self.user_id}' + self.item.style = self.style + # In here, self.view is the view given by the interaction's message. + # It cannot be a custom subclass due to limitations. + await interaction.response.edit_message(view=self.view) + + +class DynamicCounterBot(commands.Bot): + def __init__(self): + intents = discord.Intents.default() + super().__init__(command_prefix=commands.when_mentioned, intents=intents) + + async def setup_hook(self) -> None: + # For dynamic items, we must register the classes instead of the views. + self.add_dynamic_items(DynamicCounter) + + async def on_ready(self): + print(f'Logged in as {self.user} (ID: {self.user.id})') + print('------') + + +bot = DynamicCounterBot() + + +@bot.command() +async def counter(ctx: commands.Context): + """Starts a dynamic counter.""" + + view = discord.ui.View(timeout=None) + view.add_item(DynamicCounter(ctx.author.id)) + await ctx.send('Here is your very own button!', view=view) + + +bot.run('token') diff --git a/examples/views/persistent.py b/examples/views/persistent.py index 5eddfab0b..14b267190 100644 --- a/examples/views/persistent.py +++ b/examples/views/persistent.py @@ -1,7 +1,9 @@ # This example requires the 'message_content' privileged intent to function. +from __future__ import annotations from discord.ext import commands import discord +import re # Define a simple View that persists between bot restarts @@ -29,6 +31,38 @@ class PersistentView(discord.ui.View): await interaction.response.send_message('This is grey.', ephemeral=True) +# More complicated cases might require parsing state out from the custom_id instead. +# For this use case, the library provides a `DynamicItem` to make this easier. +# The same constraints as above apply to this too. +# For this example, the `template` class parameter is used to give the library a regular +# expression to parse the custom_id with. +# These custom IDs will be in the form of e.g. `button:user:80088516616269824`. +class DynamicButton(discord.ui.DynamicItem[discord.ui.Button], template=r'button:user:(?P[0-9]+)'): + def __init__(self, user_id: int) -> None: + super().__init__( + discord.ui.Button( + label='Do Thing', + style=discord.ButtonStyle.blurple, + custom_id=f'button:user:{user_id}', + emoji='\N{THUMBS UP SIGN}', + ) + ) + self.user_id: int = user_id + + # This is called when the button is clicked and the custom_id matches the template. + @classmethod + async def from_custom_id(cls, interaction: discord.Interaction, item: discord.ui.Button, match: re.Match[str], /): + user_id = int(match['id']) + return cls(user_id) + + async def interaction_check(self, interaction: discord.Interaction) -> bool: + # Only allow the user who created the button to interact with it. + return interaction.user.id == self.user_id + + async def callback(self, interaction: discord.Interaction) -> None: + await interaction.response.send_message('This is your very own button!', ephemeral=True) + + class PersistentViewBot(commands.Bot): def __init__(self): intents = discord.Intents.default() @@ -43,6 +77,8 @@ class PersistentViewBot(commands.Bot): # If you have the message_id you can also pass it as a keyword argument, but for this example # we don't have one. self.add_view(PersistentView()) + # For dynamic items, we must register the classes instead of the views. + self.add_dynamic_items(DynamicButton) async def on_ready(self): print(f'Logged in as {self.user} (ID: {self.user.id})') @@ -63,4 +99,13 @@ async def prepare(ctx: commands.Context): await ctx.send("What's your favourite colour?", view=PersistentView()) +@bot.command() +async def dynamic_button(ctx: commands.Context): + """Starts a dynamic button.""" + + view = discord.ui.View(timeout=None) + view.add_item(DynamicButton(ctx.author.id)) + await ctx.send('Here is your very own button!', view=view) + + bot.run('token') diff --git a/pyproject.toml b/pyproject.toml index 559b24b4b..92ccb7381 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,91 @@ [build-system] -requires = ["setuptools", "wheel"] +requires = ["setuptools"] build-backend = "setuptools.build_meta" +[project] +name = "discord.py" +description = "A Python wrapper for the Discord API" +readme = { file = "README.rst", content-type = "text/x-rst" } +license = { file = "LICENSE" } +requires-python = ">=3.8" +authors = [{ name = "Rapptz" }] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "License :: OSI Approved :: MIT License", + "Intended Audience :: Developers", + "Natural Language :: English", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Internet", + "Topic :: Software Development :: Libraries", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Utilities", + "Typing :: Typed", +] +dynamic = ["version", "dependencies"] + +[project.urls] +Documentation = "https://discordpy.readthedocs.io/en/latest/" +"Issue tracker" = "https://github.com/Rapptz/discord.py/issues" + +[tool.setuptools.dynamic] +dependencies = { file = "requirements.txt" } + +[project.optional-dependencies] +voice = ["PyNaCl>=1.5.0,<1.6"] +docs = [ + "sphinx==4.4.0", + "sphinxcontrib_trio==1.1.2", + # TODO: bump these when migrating to a newer Sphinx version + "sphinxcontrib-websupport==1.2.4", + "sphinxcontrib-applehelp==1.0.4", + "sphinxcontrib-devhelp==1.0.2", + "sphinxcontrib-htmlhelp==2.0.1", + "sphinxcontrib-jsmath==1.0.1", + "sphinxcontrib-qthelp==1.0.3", + "sphinxcontrib-serializinghtml==1.1.5", + "typing-extensions>=4.3,<5", + "sphinx-inline-tabs==2023.4.21", + # TODO: Remove this when moving to Sphinx >= 6.6 + "imghdr-lts==1.0.0; python_version>='3.13'", +] +speed = [ + "orjson>=3.5.4", + "aiodns>=1.1; sys_platform != 'win32'", + "Brotli", + "cchardet==2.1.7; python_version < '3.10'", + "zstandard>=0.23.0" +] +test = [ + "coverage[toml]", + "pytest", + "pytest-asyncio", + "pytest-cov", + "pytest-mock", + "typing-extensions>=4.3,<5", + "tzdata; sys_platform == 'win32'", +] +dev = [ + "black==22.6", + "typing_extensions>=4.3,<5", +] + +[tool.setuptools] +packages = [ + "discord", + "discord.types", + "discord.ui", + "discord.webhook", + "discord.app_commands", + "discord.ext.commands", + "discord.ext.tasks", +] +include-package-data = true + [tool.black] line-length = 125 skip-string-normalization = true @@ -16,7 +100,7 @@ omit = [ [tool.coverage.report] exclude_lines = [ "pragma: no cover", - "@overload" + "@overload", ] [tool.isort] diff --git a/requirements.txt b/requirements.txt index 046084ebb..ef2b6c534 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ aiohttp>=3.7.4,<4 +audioop-lts; python_version>='3.13' diff --git a/setup.py b/setup.py index d28fa82ea..e3d6d59f4 100644 --- a/setup.py +++ b/setup.py @@ -1,102 +1,32 @@ from setuptools import setup import re -requirements = [] -with open('requirements.txt') as f: - requirements = f.read().splitlines() -version = '' -with open('discord/__init__.py') as f: - version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', f.read(), re.MULTILINE).group(1) +def derive_version() -> str: + version = '' + with open('discord/__init__.py') as f: + version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', f.read(), re.MULTILINE).group(1) -if not version: - raise RuntimeError('version is not set') + if not version: + raise RuntimeError('version is not set') -if version.endswith(('a', 'b', 'rc')): - # append version identifier based on commit count - try: - import subprocess + if version.endswith(('a', 'b', 'rc')): + # append version identifier based on commit count + try: + import subprocess - p = subprocess.Popen(['git', 'rev-list', '--count', 'HEAD'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - out, err = p.communicate() - if out: - version += out.decode('utf-8').strip() - p = subprocess.Popen(['git', 'rev-parse', '--short', 'HEAD'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - out, err = p.communicate() - if out: - version += '+g' + out.decode('utf-8').strip() - except Exception: - pass + p = subprocess.Popen(['git', 'rev-list', '--count', 'HEAD'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = p.communicate() + if out: + version += out.decode('utf-8').strip() + p = subprocess.Popen(['git', 'rev-parse', '--short', 'HEAD'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = p.communicate() + if out: + version += '+g' + out.decode('utf-8').strip() + except Exception: + pass -readme = '' -with open('README.rst') as f: - readme = f.read() + return version -extras_require = { - 'voice': ['PyNaCl>=1.3.0,<1.6'], - 'docs': [ - 'sphinx==4.4.0', - 'sphinxcontrib_trio==1.1.2', - 'sphinxcontrib-websupport', - 'typing-extensions>=4.3,<5', - ], - 'speed': [ - 'orjson>=3.5.4', - 'aiodns>=1.1', - 'Brotli', - 'cchardet==2.1.7; python_version < "3.10"', - ], - 'test': [ - 'coverage[toml]', - 'pytest', - 'pytest-asyncio', - 'pytest-cov', - 'pytest-mock', - 'typing-extensions>=4.3,<5', - ], -} -packages = [ - 'discord', - 'discord.types', - 'discord.ui', - 'discord.webhook', - 'discord.app_commands', - 'discord.ext.commands', - 'discord.ext.tasks', -] - -setup( - name='discord.py', - author='Rapptz', - url='https://github.com/Rapptz/discord.py', - project_urls={ - 'Documentation': 'https://discordpy.readthedocs.io/en/latest/', - 'Issue tracker': 'https://github.com/Rapptz/discord.py/issues', - }, - version=version, - packages=packages, - license='MIT', - description='A Python wrapper for the Discord API', - long_description=readme, - long_description_content_type='text/x-rst', - include_package_data=True, - install_requires=requirements, - extras_require=extras_require, - python_requires='>=3.8.0', - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'License :: OSI Approved :: MIT License', - 'Intended Audience :: Developers', - 'Natural Language :: English', - 'Operating System :: OS Independent', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Topic :: Internet', - 'Topic :: Software Development :: Libraries', - 'Topic :: Software Development :: Libraries :: Python Modules', - 'Topic :: Utilities', - 'Typing :: Typed', - ], -) +setup(version=derive_version()) diff --git a/tests/test_app_commands_group.py b/tests/test_app_commands_group.py index 756f7c48f..4445cfa98 100644 --- a/tests/test_app_commands_group.py +++ b/tests/test_app_commands_group.py @@ -354,3 +354,156 @@ def test_cog_group_with_subclassed_subclass_group(): assert cog.sub_group.my_command.parent is cog.sub_group assert cog.my_cog_command.parent is cog.sub_group assert cog.my_cog_command.binding is cog + + +def test_cog_group_with_custom_state_issue9383(): + class InnerGroup(app_commands.Group): + def __init__(self): + super().__init__() + self.state: int = 20 + + @app_commands.command() + async def my_command(self, interaction: discord.Interaction) -> None: + ... + + class MyCog(commands.GroupCog): + inner = InnerGroup() + + @app_commands.command() + async def my_regular_command(self, interaction: discord.Interaction) -> None: + ... + + @inner.command() + async def my_inner_command(self, interaction: discord.Interaction) -> None: + ... + + cog = MyCog() + assert cog.inner.state == 20 + assert cog.my_regular_command is not MyCog.my_regular_command + + # Basically the same tests as above... (superset?) + assert MyCog.__cog_app_commands__[0].parent is not cog + assert MyCog.__cog_app_commands__[0].parent is not cog.__cog_app_commands_group__ + assert InnerGroup.__discord_app_commands_group_children__[0].parent is not cog.inner + assert InnerGroup.__discord_app_commands_group_children__[0].parent is not cog.inner + assert cog.inner is not MyCog.inner + assert cog.inner.my_command is not InnerGroup.my_command + assert cog.inner.my_command is not InnerGroup.my_command + assert cog.my_inner_command is not MyCog.my_inner_command + assert not hasattr(cog.inner, 'my_inner_command') + assert cog.__cog_app_commands_group__ is not None + assert cog.__cog_app_commands_group__.parent is None + assert cog.inner.parent is cog.__cog_app_commands_group__ + assert cog.inner.my_command.parent is cog.inner + assert cog.my_inner_command.parent is cog.inner + assert cog.my_inner_command.binding is cog + + +def test_cog_hybrid_group_manual_command(): + class MyCog(commands.Cog): + @commands.hybrid_group() + async def first(self, ctx: commands.Context) -> None: + ... + + @first.command(name='both') + async def second_both(self, ctx: commands.Context) -> None: + ... + + @first.app_command.command(name='second') + async def second_app(self, interaction: discord.Interaction) -> None: + ... + + client = discord.Client(intents=discord.Intents.default()) + tree = app_commands.CommandTree(client) + + cog = MyCog() + tree.add_command(cog.first.app_command) + + assert cog.first is not MyCog.first + assert cog.second_both is not MyCog.second_both + assert cog.second_app is not MyCog.second_app + assert cog.first.parent is None + assert cog.second_both.parent is cog.first + assert cog.second_app.parent is cog.first.app_command + assert cog.second_app.binding is cog + assert tree.get_command('first') is cog.first.app_command + + first = tree.get_command('first') + assert isinstance(first, app_commands.Group) + both = first.get_command('both') + assert isinstance(both, app_commands.Command) + assert both.parent is first + assert both.binding is cog + + second = first.get_command('second') + assert isinstance(second, app_commands.Command) + assert second.parent is first + assert second.binding is cog + + +def test_cog_hybrid_group_manual_nested_command(): + class MyCog(commands.Cog): + @commands.hybrid_group() + async def first(self, ctx: commands.Context) -> None: + pass + + @first.group() + async def second(self, ctx: commands.Context) -> None: + pass + + @second.app_command.command() + async def third(self, interaction: discord.Interaction) -> None: + pass + + client = discord.Client(intents=discord.Intents.default()) + tree = app_commands.CommandTree(client) + + cog = MyCog() + tree.add_command(cog.first.app_command) + + assert cog.first is not MyCog.first + assert cog.second is not MyCog.second + assert cog.third is not MyCog.third + assert cog.first.parent is None + assert cog.second.parent is cog.first + assert cog.third.parent is cog.second.app_command + assert cog.third.binding is cog + + first = tree.get_command('first') + assert isinstance(first, app_commands.Group) + + second = first.get_command('second') + assert isinstance(second, app_commands.Group) + + third = second.get_command('third') + assert isinstance(third, app_commands.Command) + assert third.parent is second + assert third.binding is cog + + +def test_cog_hybrid_group_wrapped_instance(): + class MyCog(commands.Cog): + @commands.hybrid_group(fallback='fallback') + async def first(self, ctx: commands.Context) -> None: + pass + + @first.command() + async def second(self, ctx: commands.Context) -> None: + pass + + @first.group() + async def nested(self, ctx: commands.Context) -> None: + pass + + @nested.app_command.command() + async def child(self, interaction: discord.Interaction) -> None: + pass + + cog = MyCog() + + fallback = cog.first.app_command.get_command('fallback') + assert fallback is not None + assert getattr(fallback, 'wrapped', None) is cog.first + assert fallback.parent is cog.first.app_command + assert cog.second.app_command is not None + assert cog.second.app_command.wrapped is cog.second diff --git a/tests/test_app_commands_invoke.py b/tests/test_app_commands_invoke.py index 35915c19b..6366096f0 100644 --- a/tests/test_app_commands_invoke.py +++ b/tests/test_app_commands_invoke.py @@ -21,6 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ + from __future__ import annotations @@ -90,6 +91,7 @@ class MockCommandInteraction(discord.Interaction): "version": 1, "type": 2, "data": self._get_command_data(command, self._get_command_options(**options)), + "attachment_size_limit": 0, } super().__init__(data=data, state=client._connection) diff --git a/tests/test_colour.py b/tests/test_colour.py index bf0e59713..1515b2cb4 100644 --- a/tests/test_colour.py +++ b/tests/test_colour.py @@ -44,6 +44,7 @@ import pytest ('rgb(20%, 24%, 56%)', 0x333D8F), ('rgb(20%, 23.9%, 56.1%)', 0x333D8F), ('rgb(51, 61, 143)', 0x333D8F), + ('0x#333D8F', 0x333D8F), ], ) def test_from_str(value, expected): @@ -53,6 +54,7 @@ def test_from_str(value, expected): @pytest.mark.parametrize( ('value'), [ + None, 'not valid', '0xYEAH', '#YEAH', @@ -62,8 +64,75 @@ def test_from_str(value, expected): 'rgb(30, -1, 60)', 'invalid(a, b, c)', 'rgb(', + '#1000000', + '#FFFFFFF', + "rgb(101%, 50%, 50%)", + "rgb(50%, -10%, 50%)", + "rgb(50%, 50%, 150%)", + "rgb(256, 100, 100)", ], ) def test_from_str_failures(value): with pytest.raises(ValueError): discord.Colour.from_str(value) + + +@pytest.mark.parametrize( + ('value', 'expected'), + [ + (discord.Colour.default(), 0x000000), + (discord.Colour.teal(), 0x1ABC9C), + (discord.Colour.dark_teal(), 0x11806A), + (discord.Colour.brand_green(), 0x57F287), + (discord.Colour.green(), 0x2ECC71), + (discord.Colour.dark_green(), 0x1F8B4C), + (discord.Colour.blue(), 0x3498DB), + (discord.Colour.dark_blue(), 0x206694), + (discord.Colour.purple(), 0x9B59B6), + (discord.Colour.dark_purple(), 0x71368A), + (discord.Colour.magenta(), 0xE91E63), + (discord.Colour.dark_magenta(), 0xAD1457), + (discord.Colour.gold(), 0xF1C40F), + (discord.Colour.dark_gold(), 0xC27C0E), + (discord.Colour.orange(), 0xE67E22), + (discord.Colour.dark_orange(), 0xA84300), + (discord.Colour.brand_red(), 0xED4245), + (discord.Colour.red(), 0xE74C3C), + (discord.Colour.dark_red(), 0x992D22), + (discord.Colour.lighter_grey(), 0x95A5A6), + (discord.Colour.dark_grey(), 0x607D8B), + (discord.Colour.light_grey(), 0x979C9F), + (discord.Colour.darker_grey(), 0x546E7A), + (discord.Colour.og_blurple(), 0x7289DA), + (discord.Colour.blurple(), 0x5865F2), + (discord.Colour.greyple(), 0x99AAB5), + (discord.Colour.ash_theme(), 0x2E2E34), + (discord.Colour.dark_theme(), 0x1A1A1E), + (discord.Colour.onyx_theme(), 0x070709), + (discord.Colour.light_theme(), 0xFBFBFB), + (discord.Colour.fuchsia(), 0xEB459E), + (discord.Colour.yellow(), 0xFEE75C), + (discord.Colour.ash_embed(), 0x37373E), + (discord.Colour.dark_embed(), 0x242429), + (discord.Colour.onyx_embed(), 0x131416), + (discord.Colour.light_embed(), 0xFFFFFF), + (discord.Colour.pink(), 0xEB459F), + ], +) +def test_static_colours(value, expected): + assert value.value == expected + + +@pytest.mark.parametrize( + ('value', 'property', 'expected'), + [ + (discord.Colour(0x000000), 'r', 0), + (discord.Colour(0xFFFFFF), 'g', 255), + (discord.Colour(0xABCDEF), 'b', 239), + (discord.Colour(0x44243B), 'r', 68), + (discord.Colour(0x333D8F), 'g', 61), + (discord.Colour(0xDBFF00), 'b', 0), + ], +) +def test_colour_properties(value, property, expected): + assert getattr(value, property) == expected diff --git a/tests/test_embed.py b/tests/test_embed.py new file mode 100644 index 000000000..004f73e3b --- /dev/null +++ b/tests/test_embed.py @@ -0,0 +1,280 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-present Rapptz + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +""" + +from __future__ import annotations + +import datetime + +import discord +import pytest + + +@pytest.mark.parametrize( + ('title', 'description', 'colour', 'url'), + [ + ('title', 'description', 0xABCDEF, 'https://example.com'), + ('title', 'description', 0xFF1294, None), + ('title', 'description', discord.Colour(0x333D8F), 'https://example.com'), + ('title', 'description', discord.Colour(0x44243B), None), + ], +) +def test_embed_initialization(title, description, colour, url): + embed = discord.Embed(title=title, description=description, colour=colour, url=url) + assert embed.title == title + assert embed.description == description + assert embed.colour == colour or embed.colour == discord.Colour(colour) + assert embed.url == url + + +@pytest.mark.parametrize( + ('text', 'icon_url'), + [ + ('Hello discord.py', 'https://example.com'), + ('text', None), + (None, 'https://example.com'), + (None, None), + ], +) +def test_embed_set_footer(text, icon_url): + embed = discord.Embed() + embed.set_footer(text=text, icon_url=icon_url) + assert embed.footer.text == text + assert embed.footer.icon_url == icon_url + + +def test_embed_remove_footer(): + embed = discord.Embed() + embed.set_footer(text='Hello discord.py', icon_url='https://example.com') + embed.remove_footer() + assert embed.footer.text is None + assert embed.footer.icon_url is None + + +@pytest.mark.parametrize( + ('name', 'url', 'icon_url'), + [ + ('Rapptz', 'http://example.com', 'http://example.com/icon.png'), + ('NCPlayz', None, 'http://example.com/icon.png'), + ('Jackenmen', 'http://example.com', None), + ], +) +def test_embed_set_author(name, url, icon_url): + embed = discord.Embed() + embed.set_author(name=name, url=url, icon_url=icon_url) + assert embed.author.name == name + assert embed.author.url == url + assert embed.author.icon_url == icon_url + + +def test_embed_remove_author(): + embed = discord.Embed() + embed.set_author(name='Rapptz', url='http://example.com', icon_url='http://example.com/icon.png') + embed.remove_author() + assert embed.author.name is None + assert embed.author.url is None + assert embed.author.icon_url is None + + +@pytest.mark.parametrize( + ('thumbnail'), + [ + ('http://example.com'), + (None), + ], +) +def test_embed_set_thumbnail(thumbnail): + embed = discord.Embed() + embed.set_thumbnail(url=thumbnail) + assert embed.thumbnail.url == thumbnail + + +@pytest.mark.parametrize( + ('image'), + [ + ('http://example.com'), + (None), + ], +) +def test_embed_set_image(image): + embed = discord.Embed() + embed.set_image(url=image) + assert embed.image.url == image + + +@pytest.mark.parametrize( + ('name', 'value', 'inline'), + [ + ('music', 'music value', True), + ('sport', 'sport value', False), + ], +) +def test_embed_add_field(name, value, inline): + embed = discord.Embed() + embed.add_field(name=name, value=value, inline=inline) + assert len(embed.fields) == 1 + assert embed.fields[0].name == name + assert embed.fields[0].value == value + assert embed.fields[0].inline == inline + + +def test_embed_insert_field(): + embed = discord.Embed() + embed.add_field(name='name', value='value', inline=True) + embed.insert_field_at(0, name='name 2', value='value 2', inline=False) + assert embed.fields[0].name == 'name 2' + assert embed.fields[0].value == 'value 2' + assert embed.fields[0].inline is False + + +def test_embed_set_field_at(): + embed = discord.Embed() + embed.add_field(name='name', value='value', inline=True) + embed.set_field_at(0, name='name 2', value='value 2', inline=False) + assert embed.fields[0].name == 'name 2' + assert embed.fields[0].value == 'value 2' + assert embed.fields[0].inline is False + + +def test_embed_set_field_at_failure(): + embed = discord.Embed() + with pytest.raises(IndexError): + embed.set_field_at(0, name='name', value='value', inline=True) + + +def test_embed_clear_fields(): + embed = discord.Embed() + embed.add_field(name="field 1", value="value 1", inline=False) + embed.add_field(name="field 2", value="value 2", inline=False) + embed.add_field(name="field 3", value="value 3", inline=False) + embed.clear_fields() + assert len(embed.fields) == 0 + + +def test_embed_remove_field(): + embed = discord.Embed() + embed.add_field(name='name', value='value', inline=True) + embed.remove_field(0) + assert len(embed.fields) == 0 + + +@pytest.mark.parametrize( + ('title', 'description', 'url'), + [ + ('title 1', 'description 1', 'https://example.com'), + ('title 2', 'description 2', None), + ], +) +def test_embed_copy(title, description, url): + embed = discord.Embed(title=title, description=description, url=url) + embed_copy = embed.copy() + + assert embed == embed_copy + assert embed.title == embed_copy.title + assert embed.description == embed_copy.description + assert embed.url == embed_copy.url + + +@pytest.mark.parametrize( + ('title', 'description'), + [ + ('title 1', 'description 1'), + ('title 2', 'description 2'), + ], +) +def test_embed_len(title, description): + embed = discord.Embed(title=title, description=description) + assert len(embed) == len(title) + len(description) + + +@pytest.mark.parametrize( + ('title', 'description', 'fields', 'footer', 'author'), + [ + ( + 'title 1', + 'description 1', + [('field name 1', 'field value 1'), ('field name 2', 'field value 2')], + 'footer 1', + 'author 1', + ), + ('title 2', 'description 2', [('field name 3', 'field value 3')], 'footer 2', 'author 2'), + ], +) +def test_embed_len_with_options(title, description, fields, footer, author): + embed = discord.Embed(title=title, description=description) + for name, value in fields: + embed.add_field(name=name, value=value) + embed.set_footer(text=footer) + embed.set_author(name=author) + assert len(embed) == len(title) + len(description) + len("".join([name + value for name, value in fields])) + len( + footer + ) + len(author) + + +def test_embed_to_dict(): + timestamp = datetime.datetime.now(datetime.timezone.utc) + embed = discord.Embed(title="Test Title", description="Test Description", timestamp=timestamp) + data = embed.to_dict() + assert data['title'] == "Test Title" + assert data['description'] == "Test Description" + assert data['timestamp'] == timestamp.isoformat() + + +def test_embed_from_dict(): + data = { + 'title': 'Test Title', + 'description': 'Test Description', + 'url': 'http://example.com', + 'color': 0x00FF00, + 'timestamp': '2024-07-03T12:34:56+00:00', + } + embed = discord.Embed.from_dict(data) + assert embed.title == 'Test Title' + assert embed.description == 'Test Description' + assert embed.url == 'http://example.com' + assert embed.colour is not None and embed.colour.value == 0x00FF00 + assert embed.timestamp is not None and embed.timestamp.isoformat() == '2024-07-03T12:34:56+00:00' + + +@pytest.mark.parametrize( + ('value'), + [ + -0.5, + '#FFFFFF', + ], +) +def test_embed_colour_setter_failure(value): + embed = discord.Embed() + with pytest.raises(TypeError): + embed.colour = value + +@pytest.mark.parametrize( + ('title', 'return_val'), + [ + ('test', True), + (None, False) + ] +) +def test_embed_truthiness(title: str, return_val: bool) -> None: + embed = discord.Embed(title=title) + assert bool(embed) is return_val diff --git a/tests/test_files.py b/tests/test_files.py index 6096c3a38..72ff3b7b3 100644 --- a/tests/test_files.py +++ b/tests/test_files.py @@ -27,6 +27,7 @@ from __future__ import annotations from io import BytesIO import discord +import pytest FILE = BytesIO() @@ -127,3 +128,58 @@ def test_file_not_spoiler_with_overriding_name_double_spoiler(): f.filename = 'SPOILER_SPOILER_.gitignore' assert f.filename == 'SPOILER_.gitignore' assert f.spoiler == True + + +def test_file_reset(): + f = discord.File('.gitignore') + + f.reset(seek=True) + assert f.fp.tell() == 0 + + f.reset(seek=False) + assert f.fp.tell() == 0 + + +def test_io_reset(): + f = discord.File(FILE) + + f.reset(seek=True) + assert f.fp.tell() == 0 + + f.reset(seek=False) + assert f.fp.tell() == 0 + + +def test_io_failure(): + class NonSeekableReadable(BytesIO): + def seekable(self): + return False + + def readable(self): + return False + + f = NonSeekableReadable() + + with pytest.raises(ValueError) as excinfo: + discord.File(f) + + assert str(excinfo.value) == f"File buffer {f!r} must be seekable and readable" + + +def test_io_to_dict(): + buffer = BytesIO(b"test content") + file = discord.File(buffer, filename="test.txt", description="test description") + + data = file.to_dict(0) + assert data["id"] == 0 + assert data["filename"] == "test.txt" + assert data["description"] == "test description" + + +def test_file_to_dict(): + f = discord.File('.gitignore', description="test description") + + data = f.to_dict(0) + assert data["id"] == 0 + assert data["filename"] == ".gitignore" + assert data["description"] == "test description" diff --git a/tests/test_permissions_all.py b/tests/test_permissions_all.py new file mode 100644 index 000000000..883dc1b63 --- /dev/null +++ b/tests/test_permissions_all.py @@ -0,0 +1,7 @@ +import discord + +from functools import reduce +from operator import or_ + +def test_permissions_all(): + assert discord.Permissions.all().value == reduce(or_, discord.Permissions.VALID_FLAGS.values()) diff --git a/tests/test_ui_buttons.py b/tests/test_ui_buttons.py new file mode 100644 index 000000000..55c0c7cd8 --- /dev/null +++ b/tests/test_ui_buttons.py @@ -0,0 +1,167 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-present Rapptz + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +""" + +from __future__ import annotations + +import discord +import pytest + + +def test_button_init(): + button = discord.ui.Button( + label="Click me!", + ) + assert button.label == "Click me!" + assert button.style == discord.ButtonStyle.secondary + assert button.disabled == False + assert button.url == None + assert button.emoji == None + assert button.sku_id == None + + +def test_button_with_sku_id(): + button = discord.ui.Button( + label="Click me!", + sku_id=1234567890, + ) + assert button.label == "Click me!" + assert button.style == discord.ButtonStyle.premium + assert button.sku_id == 1234567890 + + +def test_button_with_url(): + button = discord.ui.Button( + label="Click me!", + url="https://example.com", + ) + assert button.label == "Click me!" + assert button.style == discord.ButtonStyle.link + assert button.url == "https://example.com" + + +def test_mix_both_custom_id_and_url(): + with pytest.raises(TypeError): + discord.ui.Button( + label="Click me!", + url="https://example.com", + custom_id="test", + ) + + +def test_mix_both_custom_id_and_sku_id(): + with pytest.raises(TypeError): + discord.ui.Button( + label="Click me!", + sku_id=1234567890, + custom_id="test", + ) + + +def test_mix_both_url_and_sku_id(): + with pytest.raises(TypeError): + discord.ui.Button( + label="Click me!", + url="https://example.com", + sku_id=1234567890, + ) + + +def test_invalid_url(): + button = discord.ui.Button( + label="Click me!", + ) + with pytest.raises(TypeError): + button.url = 1234567890 # type: ignore + + +def test_invalid_custom_id(): + with pytest.raises(TypeError): + discord.ui.Button( + label="Click me!", + custom_id=1234567890, # type: ignore + ) + + button = discord.ui.Button( + label="Click me!", + ) + with pytest.raises(TypeError): + button.custom_id = 1234567890 # type: ignore + + +def test_button_with_partial_emoji(): + button = discord.ui.Button( + label="Click me!", + emoji="👍", + ) + assert button.label == "Click me!" + assert button.emoji is not None and button.emoji.name == "👍" + + +def test_button_with_str_emoji(): + emoji = discord.PartialEmoji(name="👍") + button = discord.ui.Button( + label="Click me!", + emoji=emoji, + ) + assert button.label == "Click me!" + assert button.emoji == emoji + + +def test_button_with_invalid_emoji(): + with pytest.raises(TypeError): + discord.ui.Button( + label="Click me!", + emoji=-0.53, # type: ignore + ) + + button = discord.ui.Button( + label="Click me!", + ) + with pytest.raises(TypeError): + button.emoji = -0.53 # type: ignore + + +def test_button_setter(): + button = discord.ui.Button() + + button.label = "Click me!" + assert button.label == "Click me!" + + button.style = discord.ButtonStyle.primary + assert button.style == discord.ButtonStyle.primary + + button.disabled = True + assert button.disabled == True + + button.url = "https://example.com" + assert button.url == "https://example.com" + + button.emoji = "👍" + assert button.emoji is not None and button.emoji.name == "👍" # type: ignore + + button.custom_id = "test" + assert button.custom_id == "test" + + button.sku_id = 1234567890 + assert button.sku_id == 1234567890 diff --git a/tests/test_ui_modals.py b/tests/test_ui_modals.py new file mode 100644 index 000000000..dd1ac7169 --- /dev/null +++ b/tests/test_ui_modals.py @@ -0,0 +1,102 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-present Rapptz + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +""" + +from __future__ import annotations + +import discord +import pytest + + +@pytest.mark.asyncio +async def test_modal_init(): + modal = discord.ui.Modal( + title="Temp Title", + ) + assert modal.title == "Temp Title" + assert modal.timeout == None + + +@pytest.mark.asyncio +async def test_no_title(): + with pytest.raises(ValueError) as excinfo: + discord.ui.Modal() + + assert str(excinfo.value) == "Modal must have a title" + + +@pytest.mark.asyncio +async def test_to_dict(): + modal = discord.ui.Modal( + title="Temp Title", + ) + data = modal.to_dict() + assert data["custom_id"] is not None + assert data["title"] == "Temp Title" + assert data["components"] == [] + + +@pytest.mark.asyncio +async def test_add_item(): + modal = discord.ui.Modal( + title="Temp Title", + ) + item = discord.ui.TextInput(label="Test") + modal.add_item(item) + + assert modal.children == [item] + + +@pytest.mark.asyncio +async def test_add_item_invalid(): + modal = discord.ui.Modal( + title="Temp Title", + ) + with pytest.raises(TypeError): + modal.add_item("Not an item") # type: ignore + + +@pytest.mark.asyncio +async def test_maximum_items(): + modal = discord.ui.Modal( + title="Temp Title", + ) + max_item_limit = 5 + + for i in range(max_item_limit): + modal.add_item(discord.ui.TextInput(label=f"Test {i}")) + + with pytest.raises(ValueError): + modal.add_item(discord.ui.TextInput(label="Test")) + + +@pytest.mark.asyncio +async def test_modal_setters(): + modal = discord.ui.Modal( + title="Temp Title", + ) + modal.title = "New Title" + assert modal.title == "New Title" + + modal.timeout = 120 + assert modal.timeout == 120 diff --git a/tests/test_ui_selects.py b/tests/test_ui_selects.py new file mode 100644 index 000000000..a9019c3de --- /dev/null +++ b/tests/test_ui_selects.py @@ -0,0 +1,39 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-present Rapptz + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +""" + +from __future__ import annotations + +import discord +import pytest + + +@pytest.mark.asyncio +async def test_add_option(): + select = discord.ui.Select() + + for i in range(1, 25 + 1): + select.add_option(label=str(i), value=str(i)) + + with pytest.raises(ValueError): + select.add_option(label="26", value="26")